Re-merging latest dev after rebasing to fix DCO. Re-resolving conflicts
Signed-off-by: Gene Walters <genewalt@amazon.com>
This commit is contained in:
@@ -37,6 +37,10 @@ class TestAutomation(EditorTestSuite):
|
||||
@pytest.mark.test_case_id("C32078115")
|
||||
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
|
||||
|
||||
@pytest.mark.test_case_id("C32078122")
|
||||
class AtomEditorComponents_GridAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module
|
||||
|
||||
@pytest.mark.test_case_id("C32078117")
|
||||
class AtomEditorComponents_LightAdded(EditorSharedTest):
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
class Tests:
|
||||
creation_undo = (
|
||||
"UNDO Entity creation success",
|
||||
"UNDO Entity creation failed")
|
||||
creation_redo = (
|
||||
"REDO Entity creation success",
|
||||
"REDO Entity creation failed")
|
||||
grid_entity_creation = (
|
||||
"Grid Entity successfully created",
|
||||
"Grid Entity failed to be created")
|
||||
grid_component_added = (
|
||||
"Entity has a Grid component",
|
||||
"Entity failed to find Grid component")
|
||||
enter_game_mode = (
|
||||
"Entered game mode",
|
||||
"Failed to enter game mode")
|
||||
exit_game_mode = (
|
||||
"Exited game mode",
|
||||
"Couldn't exit game mode")
|
||||
is_visible = (
|
||||
"Entity is visible",
|
||||
"Entity was not visible")
|
||||
is_hidden = (
|
||||
"Entity is hidden",
|
||||
"Entity was not hidden")
|
||||
entity_deleted = (
|
||||
"Entity deleted",
|
||||
"Entity was not deleted")
|
||||
deletion_undo = (
|
||||
"UNDO deletion success",
|
||||
"UNDO deletion failed")
|
||||
deletion_redo = (
|
||||
"REDO deletion success",
|
||||
"REDO deletion failed")
|
||||
|
||||
|
||||
def AtomEditorComponents_Grid_AddedToEntity():
|
||||
"""
|
||||
Summary:
|
||||
Tests the Grid component can be added to an entity and has the expected functionality.
|
||||
|
||||
Test setup:
|
||||
- Wait for Editor idle loop.
|
||||
- Open the "Base" level.
|
||||
|
||||
Expected Behavior:
|
||||
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
|
||||
Creation and deletion undo/redo should also work.
|
||||
|
||||
Test Steps:
|
||||
1) Create a Grid entity with no components.
|
||||
2) Add a Grid component to Grid entity.
|
||||
3) UNDO the entity creation and component addition.
|
||||
4) REDO the entity creation and component addition.
|
||||
5) Enter/Exit game mode.
|
||||
6) Test IsHidden.
|
||||
7) Test IsVisible.
|
||||
8) Delete Grid entity.
|
||||
9) UNDO deletion.
|
||||
10) REDO deletion.
|
||||
11) Look for errors.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper
|
||||
from Atom.atom_utils.atom_constants import AtomComponentProperties
|
||||
|
||||
with Tracer() as error_tracer:
|
||||
# Test setup begins.
|
||||
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
|
||||
TestHelper.init_idle()
|
||||
TestHelper.open_level("", "Base")
|
||||
|
||||
# Test steps begin.
|
||||
# 1. Create a Grid entity with no components.
|
||||
grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid())
|
||||
Report.critical_result(Tests.grid_entity_creation, grid_entity.exists())
|
||||
|
||||
# 2. Add a Grid component to Grid entity.
|
||||
grid_component = grid_entity.add_component(AtomComponentProperties.grid())
|
||||
Report.critical_result(
|
||||
Tests.grid_component_added,
|
||||
grid_entity.has_component(AtomComponentProperties.grid()))
|
||||
|
||||
# 3. UNDO the entity creation and component addition.
|
||||
# -> UNDO component addition.
|
||||
general.undo()
|
||||
# -> UNDO naming entity.
|
||||
general.undo()
|
||||
# -> UNDO selecting entity.
|
||||
general.undo()
|
||||
# -> UNDO entity creation.
|
||||
general.undo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_undo, not grid_entity.exists())
|
||||
|
||||
# 4. REDO the entity creation and component addition.
|
||||
# -> REDO entity creation.
|
||||
general.redo()
|
||||
# -> REDO selecting entity.
|
||||
general.redo()
|
||||
# -> REDO naming entity.
|
||||
general.redo()
|
||||
# -> REDO component addition.
|
||||
general.redo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_redo, grid_entity.exists())
|
||||
|
||||
# 5. Enter/Exit game mode.
|
||||
TestHelper.enter_game_mode(Tests.enter_game_mode)
|
||||
general.idle_wait_frames(1)
|
||||
TestHelper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
# 6. Test IsHidden.
|
||||
grid_entity.set_visibility_state(False)
|
||||
Report.result(Tests.is_hidden, grid_entity.is_hidden() is True)
|
||||
|
||||
# 7. Test IsVisible.
|
||||
grid_entity.set_visibility_state(True)
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.is_visible, grid_entity.is_visible() is True)
|
||||
|
||||
# 8. Delete Grid entity.
|
||||
grid_entity.delete()
|
||||
Report.result(Tests.entity_deleted, not grid_entity.exists())
|
||||
|
||||
# 9. UNDO deletion.
|
||||
general.undo()
|
||||
Report.result(Tests.deletion_undo, grid_entity.exists())
|
||||
|
||||
# 10. REDO deletion.
|
||||
general.redo()
|
||||
Report.result(Tests.deletion_redo, not grid_entity.exists())
|
||||
|
||||
# 11. Look for errors or asserts.
|
||||
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
|
||||
for error_info in error_tracer.errors:
|
||||
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
|
||||
for assert_info in error_tracer.asserts:
|
||||
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(AtomEditorComponents_Grid_AddedToEntity)
|
||||
@@ -9,7 +9,7 @@
|
||||
#import <AppKit/NSEvent.h>
|
||||
|
||||
#include "EditorDefs.h"
|
||||
#include "QtEditorApplication.h"
|
||||
#include "QtEditorApplication_mac.h"
|
||||
|
||||
// AzFramework
|
||||
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
|
||||
|
||||
@@ -1788,6 +1788,11 @@ AZStd::string SandboxIntegrationManager::GetComponentEditorIcon(const AZ::Uuid&
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
AZStd::string SandboxIntegrationManager::GetComponentTypeEditorIcon(const AZ::Uuid& componentType)
|
||||
{
|
||||
return GetComponentEditorIcon(componentType, nullptr);
|
||||
}
|
||||
|
||||
AZStd::string SandboxIntegrationManager::GetComponentIconPath(const AZ::Uuid& componentType,
|
||||
AZ::Crc32 componentIconAttrib, AZ::Component* component)
|
||||
{
|
||||
|
||||
@@ -233,6 +233,7 @@ private:
|
||||
}
|
||||
|
||||
AZStd::string GetComponentEditorIcon(const AZ::Uuid& componentType, AZ::Component* component) override;
|
||||
AZStd::string GetComponentTypeEditorIcon(const AZ::Uuid& componentType) override;
|
||||
AZStd::string GetComponentIconPath(const AZ::Uuid& componentType, AZ::Crc32 componentIconAttrib, AZ::Component* component) override;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ ComponentDataModel::ComponentDataModel(QObject* parent)
|
||||
if (element.m_elementId == AZ::Edit::ClassElements::EditorData)
|
||||
{
|
||||
AZStd::string iconPath;
|
||||
EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, classData->m_typeId, nullptr);
|
||||
AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, classData->m_typeId);
|
||||
if (!iconPath.empty())
|
||||
{
|
||||
m_componentIcons[classData->m_typeId] = QIcon(iconPath.c_str());
|
||||
|
||||
@@ -408,7 +408,7 @@ CTrackViewNodesCtrl::CTrackViewNodesCtrl(QWidget* hParentWnd, CTrackViewDialog*
|
||||
serializeContext->EnumerateDerived<AZ::Component>([this](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&) -> bool
|
||||
{
|
||||
AZStd::string iconPath;
|
||||
EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, classData->m_typeId, nullptr);
|
||||
AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, classData->m_typeId);
|
||||
if (!iconPath.empty())
|
||||
{
|
||||
m_componentTypeToIconMap[classData->m_typeId] = QIcon(iconPath.c_str());
|
||||
|
||||
@@ -258,10 +258,17 @@ namespace AzFramework
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void XcbNativeWindow::SetWindowTitle(const AZStd::string& title)
|
||||
{
|
||||
// Set the title of both the window and the task bar by using
|
||||
// a buffer to hold the title twice, separated by a null-terminator
|
||||
auto doubleTitleSize = (title.size() + 1) * 2;
|
||||
AZStd::string doubleTitle(doubleTitleSize, '\0');
|
||||
azstrncpy(doubleTitle.data(), doubleTitleSize, title.c_str(), title.size());
|
||||
azstrncpy(&doubleTitle.data()[title.size() + 1], title.size(), title.c_str(), title.size());
|
||||
|
||||
xcb_void_cookie_t xcbCheckResult;
|
||||
xcbCheckResult = xcb_change_property(
|
||||
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, static_cast<uint32_t>(title.size()),
|
||||
title.c_str());
|
||||
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, static_cast<uint32_t>(doubleTitle.size()),
|
||||
doubleTitle.c_str());
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set window title.");
|
||||
}
|
||||
|
||||
|
||||
@@ -826,6 +826,10 @@ namespace AzToolsFramework
|
||||
/// Path will be empty if component should have no icon.
|
||||
virtual AZStd::string GetComponentEditorIcon(const AZ::Uuid& /*componentType*/, AZ::Component* /*component*/) { return AZStd::string(); }
|
||||
|
||||
//! Return path to icon for component type.
|
||||
//! Path will be empty if component type should have no icon.
|
||||
virtual AZStd::string GetComponentTypeEditorIcon(const AZ::Uuid& /*componentType*/) { return AZStd::string(); }
|
||||
|
||||
/**
|
||||
* Return the icon image path based on the component type and where it is used.
|
||||
* \param componentType component type
|
||||
|
||||
+2
@@ -43,6 +43,8 @@ namespace AzToolsFramework
|
||||
};
|
||||
|
||||
//! Provides a bus to notify when the different editor modes are entered/exit.
|
||||
//! @note The editor modes are not discrete states but rather each progression of mode retain the active the parent
|
||||
//! mode that the new mode progressed from.
|
||||
class ViewportEditorModeNotifications : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
|
||||
+27
-2
@@ -9,9 +9,27 @@
|
||||
#include <AzToolsFramework/Application/EditorEntityManager.h>
|
||||
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
static bool AreEntitiesValidForDuplication(const EntityIdList& entityIds)
|
||||
{
|
||||
for (AZ::EntityId entityId : entityIds)
|
||||
{
|
||||
if (GetEntityById(entityId) == nullptr)
|
||||
{
|
||||
AZ_Error(
|
||||
"Entity", false,
|
||||
"Entity with id '%llu' is not found. This can happen when you try to duplicate the entity before it is created. Please "
|
||||
"ensure entities are created before trying to duplicate them.",
|
||||
static_cast<AZ::u64>(entityId));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorEntityManager::Start()
|
||||
{
|
||||
m_prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
|
||||
@@ -62,7 +80,11 @@ namespace AzToolsFramework
|
||||
EntityIdList selectedEntities;
|
||||
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
|
||||
|
||||
m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
|
||||
if (AreEntitiesValidForDuplication(selectedEntities))
|
||||
{
|
||||
m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void EditorEntityManager::DuplicateEntityById(AZ::EntityId entityId)
|
||||
@@ -72,7 +94,10 @@ namespace AzToolsFramework
|
||||
|
||||
void EditorEntityManager::DuplicateEntities(const EntityIdList& entities)
|
||||
{
|
||||
m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
|
||||
if (AreEntitiesValidForDuplication(entities))
|
||||
{
|
||||
m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,5 +34,4 @@ namespace AzToolsFramework
|
||||
private:
|
||||
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace AzToolsFramework
|
||||
, public AZ::BehaviorEBusHandler
|
||||
{
|
||||
AZ_EBUS_BEHAVIOR_BINDER(ToolsApplicationNotificationBusHandler, "{7EB67956-FF86-461A-91E2-7B08279CFACF}", AZ::SystemAllocator,
|
||||
EntityRegistered, EntityDeregistered);
|
||||
EntityRegistered, EntityDeregistered, AfterEntitySelectionChanged);
|
||||
|
||||
void EntityRegistered(AZ::EntityId entityId) override
|
||||
{
|
||||
@@ -187,6 +187,11 @@ namespace AzToolsFramework
|
||||
{
|
||||
Call(FN_EntityDeregistered, entityId);
|
||||
}
|
||||
|
||||
void AfterEntitySelectionChanged(const EntityIdList& newlySelectedEntities, const EntityIdList& newlyDeselectedEntities) override
|
||||
{
|
||||
Call(FN_AfterEntitySelectionChanged, newlySelectedEntities, newlyDeselectedEntities);
|
||||
}
|
||||
};
|
||||
|
||||
struct ViewPaneCallbackBusHandler final
|
||||
@@ -410,6 +415,7 @@ namespace AzToolsFramework
|
||||
->Handler<Internal::ToolsApplicationNotificationBusHandler>()
|
||||
->Event("EntityRegistered", &ToolsApplicationEvents::EntityRegistered)
|
||||
->Event("EntityDeregistered", &ToolsApplicationEvents::EntityDeregistered)
|
||||
->Event("AfterEntitySelectionChanged", &ToolsApplicationEvents::AfterEntitySelectionChanged)
|
||||
;
|
||||
|
||||
behaviorContext->Class<ViewPaneOptions>()
|
||||
@@ -428,6 +434,7 @@ namespace AzToolsFramework
|
||||
->Attribute(AZ::Script::Attributes::Module, "editor")
|
||||
->Event("RegisterCustomViewPane", &EditorRequests::RegisterCustomViewPane)
|
||||
->Event("UnregisterViewPane", &EditorRequests::UnregisterViewPane)
|
||||
->Event("GetComponentTypeEditorIcon", &EditorRequests::GetComponentTypeEditorIcon)
|
||||
;
|
||||
|
||||
behaviorContext->EBus<EditorEventsBus>("EditorEventBus")
|
||||
|
||||
@@ -1110,7 +1110,8 @@ namespace AzToolsFramework
|
||||
// Select the duplicated entities/instances
|
||||
auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances");
|
||||
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
||||
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
|
||||
ToolsApplicationRequestBus::Broadcast(
|
||||
&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
|
||||
}
|
||||
|
||||
return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds));
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
AZStd::string componentIconPath;
|
||||
EBUS_EVENT_RESULT(componentIconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentClass->m_typeId, nullptr);
|
||||
AzToolsFramework::EditorRequestBus::BroadcastResult(componentIconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, componentClass->m_typeId);
|
||||
componentIconTable[componentClass] = QString::fromUtf8(componentIconPath.c_str());
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ namespace AzToolsFramework
|
||||
|
||||
void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event)
|
||||
{
|
||||
m_mousePosition = QPoint();
|
||||
m_mousePosition = QPoint(-1, -1);
|
||||
m_currentHoveredIndex = QModelIndex();
|
||||
update();
|
||||
}
|
||||
@@ -200,7 +200,7 @@ namespace AzToolsFramework
|
||||
const bool isEnabled = (this->model()->flags(index) & Qt::ItemIsEnabled);
|
||||
|
||||
const bool isSelected = selectionModel()->isSelected(index);
|
||||
const bool isHovered = (index == indexAt(m_mousePosition)) && isEnabled;
|
||||
const bool isHovered = (index == indexAt(m_mousePosition).siblingAtColumn(0)) && isEnabled;
|
||||
|
||||
// Paint the branch Selection/Hover Rect
|
||||
PaintBranchSelectionHoverRect(painter, rect, isSelected, isHovered);
|
||||
|
||||
+3
-6
@@ -153,6 +153,9 @@ namespace AzToolsFramework
|
||||
{
|
||||
initEntityOutlinerWidgetResources();
|
||||
|
||||
m_editorEntityUiInterface = AZ::Interface<AzToolsFramework::EditorEntityUiInterface>::Get();
|
||||
AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerWidget requires a EditorEntityUiInterface instance on Initialize.");
|
||||
|
||||
m_gui = new Ui::EntityOutlinerWidgetUI();
|
||||
m_gui->setupUi(this);
|
||||
|
||||
@@ -282,12 +285,6 @@ namespace AzToolsFramework
|
||||
|
||||
m_listModel->Initialize();
|
||||
|
||||
m_editorEntityUiInterface = AZ::Interface<AzToolsFramework::EditorEntityUiInterface>::Get();
|
||||
|
||||
AZ_Assert(
|
||||
m_editorEntityUiInterface != nullptr,
|
||||
"EntityOutlinerWidget requires a EditorEntityUiInterface instance on Initialize.");
|
||||
|
||||
EditorPickModeNotificationBus::Handler::BusConnect(GetEntityContextId());
|
||||
EntityHighlightMessages::Bus::Handler::BusConnect();
|
||||
EntityOutlinerModelNotificationBus::Handler::BusConnect();
|
||||
|
||||
+1
-1
@@ -583,7 +583,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
AZStd::string iconPath;
|
||||
EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentType, const_cast<AZ::Component*>(&componentInstance));
|
||||
AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentEditorIcon, componentType, const_cast<AZ::Component*>(&componentInstance));
|
||||
GetHeader()->SetIcon(QIcon(iconPath.c_str()));
|
||||
|
||||
bool isExpanded = true;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
|
||||
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorPickEntitySelection.h>
|
||||
@@ -187,10 +188,13 @@ namespace UnitTest
|
||||
ASSERT_NE(m_viewportEditorModeTracker, nullptr);
|
||||
m_viewportEditorModes = m_viewportEditorModeTracker->GetViewportEditorModes({AzToolsFramework::GetEntityContextId()});
|
||||
ASSERT_NE(m_viewportEditorModes, nullptr);
|
||||
m_focusModeInterface = AZ::Interface<AzToolsFramework::FocusModeInterface>::Get();
|
||||
ASSERT_NE(m_focusModeInterface, nullptr);
|
||||
}
|
||||
|
||||
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr;
|
||||
const ViewportEditorModesInterface* m_viewportEditorModes = nullptr;
|
||||
AzToolsFramework::FocusModeInterface* m_focusModeInterface = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(ViewportEditorModesTestsFixture, NumberOfEditorModesIsEqualTo4)
|
||||
@@ -522,32 +526,48 @@ namespace UnitTest
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ViewportEditorModeTrackerIntegrationTestFixture, EnteringComponentModeAfterInitialStateHasViewportEditorModesDefaultAndComponentModeActive)
|
||||
ViewportEditorModeTrackerIntegrationTestFixture,
|
||||
EnteringComponentModeAfterInitialStateHasViewportEditorModesDefaultAndComponentModeActive)
|
||||
{
|
||||
// When component mode is entered
|
||||
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
|
||||
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
|
||||
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
|
||||
|
||||
bool inComponentMode = false;
|
||||
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::BroadcastResult(
|
||||
inComponentMode, &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::InComponentMode);
|
||||
|
||||
// Expect to be in component mode
|
||||
EXPECT_TRUE(inComponentMode);
|
||||
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
|
||||
|
||||
// Expect the default and component viewport editor modes to be active
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
|
||||
|
||||
// Do not expect the pick and focus viewport editor modes to be active
|
||||
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
|
||||
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ViewportEditorModeTrackerIntegrationTestFixture,
|
||||
EnteringEditorPickEntitySelectionAfterInitialStateHasOnlyViewportEditorModePickModeActive)
|
||||
ExitingComponentModeAfterEnteringFrominitialStateHasViewportEditorModesDefaultActive)
|
||||
{
|
||||
// When component mode is entered and exited
|
||||
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
|
||||
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
|
||||
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
|
||||
|
||||
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
|
||||
|
||||
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
|
||||
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode);
|
||||
|
||||
// Expect to not be in component mode
|
||||
EXPECT_FALSE(AzToolsFramework::ComponentModeFramework::InComponentMode());
|
||||
|
||||
// Expect only the default viewport editor mode to be active
|
||||
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ViewportEditorModeTrackerIntegrationTestFixture,
|
||||
EnteringEditorPickEntitySelectionAfterInitialStateHasOnlyViewportEditorModePickActive)
|
||||
{
|
||||
// When entering pick mode
|
||||
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
|
||||
@@ -563,6 +583,96 @@ namespace UnitTest
|
||||
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Pick);
|
||||
}
|
||||
|
||||
// FocusMode integration tests will follow (LYN-6995)
|
||||
TEST_F(
|
||||
ViewportEditorModeTrackerIntegrationTestFixture,
|
||||
EnteringEditorDefaultEntitySelectionFromEditorPickEntitySelectionHasOnlyViewportEditorModeDefaultActive)
|
||||
{
|
||||
// When pick mode is entered and exited
|
||||
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
|
||||
EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
|
||||
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
|
||||
});
|
||||
|
||||
EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
|
||||
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
|
||||
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
|
||||
{
|
||||
return AZStd::make_unique<AzToolsFramework::EditorDefaultSelection>(entityDataCache, viewportEditorModeTracker);
|
||||
});
|
||||
|
||||
// Expect only the default viewport editor mode to be active
|
||||
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default);
|
||||
}
|
||||
|
||||
TEST_F(ViewportEditorModeTrackerIntegrationTestFixture, EnteringFocusModeAfterInitialStateHasViewportEditorModeDefaultAndPickActive)
|
||||
{
|
||||
// When entering focus mode
|
||||
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
|
||||
|
||||
// Expect the default and focus viewport editor modes to be active
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
|
||||
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
|
||||
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ViewportEditorModeTrackerIntegrationTestFixture,
|
||||
ExitingFocusModeAfterEnteringFromInitialStateHasOnlyViewportEditorModeDefaultActive)
|
||||
{
|
||||
// When entering and leaving focus mode
|
||||
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
|
||||
m_focusModeInterface->SetFocusRoot(AZ::EntityId());
|
||||
|
||||
// Expect only the default mode to be active
|
||||
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default);
|
||||
}
|
||||
|
||||
TEST_F(ViewportEditorModeTrackerIntegrationTestFixture, EnteringComponentModeFromFocusModeStateHasViewportEditorModeDefaultAndFocusAndComponentActive)
|
||||
{
|
||||
// When entering component mode from focus mode
|
||||
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
|
||||
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
|
||||
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
|
||||
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
|
||||
|
||||
// Expect to be in component mode
|
||||
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
|
||||
|
||||
// Expect the default, focus and component viewport editor modes to be active
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
|
||||
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ViewportEditorModeTrackerIntegrationTestFixture,
|
||||
ExitingComponentModeAfterEnteringFromFocusModeHasViewportEditorModeDefaultAndFocusActive)
|
||||
{
|
||||
// When entering and leaving component mode from focus mode
|
||||
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
|
||||
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
|
||||
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
|
||||
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
|
||||
|
||||
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
|
||||
|
||||
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
|
||||
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode);
|
||||
|
||||
// Expect to not be in component mode
|
||||
EXPECT_FALSE(AzToolsFramework::ComponentModeFramework::InComponentMode());
|
||||
|
||||
// Expect the default and focus viewport editor modes to be active
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
|
||||
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
|
||||
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
|
||||
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -691,6 +691,12 @@ QProgressBar::chunk {
|
||||
|
||||
#gemRepoAddDialogInstructionTitleLabel {
|
||||
font-size:14px;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#gemRepoAddDialogWarningLabel {
|
||||
font-size:12px;
|
||||
font-style:italic;
|
||||
}
|
||||
|
||||
#addGemRepoDialog #formFrame {
|
||||
|
||||
@@ -167,6 +167,10 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
notification += " " + tr("and") + " ";
|
||||
}
|
||||
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
|
||||
{
|
||||
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
|
||||
}
|
||||
}
|
||||
|
||||
if (numChangedDependencies == 1 )
|
||||
@@ -223,6 +227,20 @@ namespace O3DE::ProjectManager
|
||||
m_gemModel->AddGem(gemInfo);
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
|
||||
if (allRepoGemInfosResult.IsSuccess())
|
||||
{
|
||||
const QVector<GemInfo> allRepoGemInfos = allRepoGemInfosResult.GetValue();
|
||||
for (const GemInfo& gemInfo : allRepoGemInfos)
|
||||
{
|
||||
m_gemModel->AddGem(gemInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems from repos.<br><br>Error:<br>%1").arg(allRepoGemInfosResult.GetError().c_str()));
|
||||
}
|
||||
|
||||
m_gemModel->UpdateGemDependencies();
|
||||
m_notificationsEnabled = false;
|
||||
|
||||
@@ -249,14 +267,14 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
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()));
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.<br><br>Error:<br>%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
|
||||
}
|
||||
|
||||
m_notificationsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.<br><br>Error:<br>%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,21 +225,22 @@ namespace O3DE::ProjectManager
|
||||
QVector<QString> elementNames;
|
||||
QVector<int> elementCounts;
|
||||
const int totalGems = m_gemModel->rowCount();
|
||||
const int selectedGemTotal = m_gemModel->TotalAddedGems();
|
||||
const int selectedGemTotal = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true).size();
|
||||
const int unselectedGemTotal = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true).size();
|
||||
const int enabledGemTotal = m_gemModel->TotalAddedGems(/*includeDependencies=*/true);
|
||||
|
||||
elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Unselected));
|
||||
elementCounts.push_back(totalGems - selectedGemTotal);
|
||||
|
||||
elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Selected));
|
||||
elementCounts.push_back(selectedGemTotal);
|
||||
|
||||
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive));
|
||||
elementCounts.push_back(totalGems - enabledGemTotal);
|
||||
elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Unselected));
|
||||
elementCounts.push_back(unselectedGemTotal);
|
||||
|
||||
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Active));
|
||||
elementCounts.push_back(enabledGemTotal);
|
||||
|
||||
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive));
|
||||
elementCounts.push_back(totalGems - enabledGemTotal);
|
||||
|
||||
bool wasCollapsed = false;
|
||||
if (m_statusFilter)
|
||||
{
|
||||
@@ -262,44 +263,51 @@ namespace O3DE::ProjectManager
|
||||
|
||||
const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
|
||||
|
||||
QAbstractButton* unselectedButton = buttons[0];
|
||||
QAbstractButton* selectedButton = buttons[1];
|
||||
unselectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Unselected);
|
||||
selectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Selected);
|
||||
QAbstractButton* selectedButton = buttons[0];
|
||||
QAbstractButton* unselectedButton = buttons[1];
|
||||
selectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Selected);
|
||||
unselectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Unselected);
|
||||
|
||||
auto updateGemSelection = [=]([[maybe_unused]] bool checked)
|
||||
{
|
||||
if (unselectedButton->isChecked() && !selectedButton->isChecked())
|
||||
{
|
||||
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected);
|
||||
}
|
||||
else if (!unselectedButton->isChecked() && selectedButton->isChecked())
|
||||
if (!unselectedButton->isChecked() && selectedButton->isChecked())
|
||||
{
|
||||
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Selected);
|
||||
}
|
||||
else if (unselectedButton->isChecked() && !selectedButton->isChecked())
|
||||
{
|
||||
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::NoFilter);
|
||||
if (unselectedButton->isChecked() && selectedButton->isChecked())
|
||||
{
|
||||
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Both);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::NoFilter);
|
||||
}
|
||||
}
|
||||
};
|
||||
connect(unselectedButton, &QAbstractButton::toggled, this, updateGemSelection);
|
||||
connect(selectedButton, &QAbstractButton::toggled, this, updateGemSelection);
|
||||
|
||||
QAbstractButton* inactiveButton = buttons[2];
|
||||
QAbstractButton* activeButton = buttons[3];
|
||||
inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive);
|
||||
activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active);
|
||||
QAbstractButton* activeButton = buttons[2];
|
||||
QAbstractButton* inactiveButton = buttons[3];
|
||||
activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active);
|
||||
inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive);
|
||||
|
||||
auto updateGemActive = [=]([[maybe_unused]] bool checked)
|
||||
{
|
||||
if (inactiveButton->isChecked() && !activeButton->isChecked())
|
||||
{
|
||||
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive);
|
||||
}
|
||||
else if (!inactiveButton->isChecked() && activeButton->isChecked())
|
||||
if (!inactiveButton->isChecked() && activeButton->isChecked())
|
||||
{
|
||||
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Active);
|
||||
}
|
||||
else if (inactiveButton->isChecked() && !activeButton->isChecked())
|
||||
{
|
||||
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::NoFilter);
|
||||
|
||||
@@ -50,11 +50,26 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
// Gem selected
|
||||
if (m_gemSelectedFilter != GemSelected::NoFilter)
|
||||
// Gem selected
|
||||
if (m_gemSelectedFilter == GemSelected::Selected)
|
||||
{
|
||||
const GemSelected sourceGemStatus = static_cast<GemSelected>(GemModel::IsAdded(sourceIndex));
|
||||
if (m_gemSelectedFilter != sourceGemStatus)
|
||||
if (!GemModel::NeedsToBeAdded(sourceIndex, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Gem unselected
|
||||
else if (m_gemSelectedFilter == GemSelected::Unselected)
|
||||
{
|
||||
if (!GemModel::NeedsToBeRemoved(sourceIndex, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Gem selected or unselected
|
||||
else if (m_gemSelectedFilter == GemSelected::Both)
|
||||
{
|
||||
if (!GemModel::NeedsToBeAdded(sourceIndex, true) && !GemModel::NeedsToBeRemoved(sourceIndex, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
NoFilter = -1,
|
||||
Unselected,
|
||||
Selected
|
||||
Selected,
|
||||
Both
|
||||
};
|
||||
enum class GemActive
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace O3DE::ProjectManager
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
vLayout->setContentsMargins(30, 30, 25, 10);
|
||||
vLayout->setSpacing(0);
|
||||
vLayout->setAlignment(Qt::AlignTop);
|
||||
setLayout(vLayout);
|
||||
|
||||
QLabel* instructionTitleLabel = new QLabel(tr("Enter a valid path to add a new user repository"));
|
||||
@@ -41,9 +42,18 @@ namespace O3DE::ProjectManager
|
||||
vLayout->addWidget(instructionContextLabel);
|
||||
|
||||
m_repoPath = new FormFolderBrowseEditWidget(tr("Repository Path"), "", this);
|
||||
m_repoPath->setFixedWidth(600);
|
||||
m_repoPath->setFixedSize(QSize(600, 100));
|
||||
vLayout->addWidget(m_repoPath);
|
||||
|
||||
vLayout->addSpacing(10);
|
||||
|
||||
QLabel* warningLabel = new QLabel(tr("Online repositories may contain files that could potentially harm your computer,"
|
||||
" please ensure you understand the risks before downloading Gems from third-party sources."));
|
||||
warningLabel->setObjectName("gemRepoAddDialogWarningLabel");
|
||||
warningLabel->setWordWrap(true);
|
||||
warningLabel->setAlignment(Qt::AlignLeft);
|
||||
vLayout->addWidget(warningLabel);
|
||||
|
||||
vLayout->addSpacing(40);
|
||||
|
||||
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Draw refresh button
|
||||
painter->drawPixmap(
|
||||
repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing,
|
||||
repoUpdatedDateRect.left() + s_updatedMaxWidth + s_refreshIconSpacing,
|
||||
contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better
|
||||
m_refreshIcon);
|
||||
|
||||
@@ -150,6 +150,11 @@ namespace O3DE::ProjectManager
|
||||
emit RemoveRepo(modelIndex);
|
||||
return true;
|
||||
}
|
||||
else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_F5)
|
||||
{
|
||||
emit RefreshRepo(modelIndex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress)
|
||||
@@ -160,6 +165,7 @@ namespace O3DE::ProjectManager
|
||||
CalcRects(option, fullRect, itemRect, contentRect);
|
||||
const QRect buttonRect = CalcButtonRect(contentRect);
|
||||
const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect);
|
||||
const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect, buttonRect);
|
||||
|
||||
if (buttonRect.contains(mouseEvent->pos()))
|
||||
{
|
||||
@@ -172,6 +178,11 @@ namespace O3DE::ProjectManager
|
||||
emit RemoveRepo(modelIndex);
|
||||
return true;
|
||||
}
|
||||
else if (refreshButtonRect.contains(mouseEvent->pos()))
|
||||
{
|
||||
emit RefreshRepo(modelIndex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
|
||||
@@ -231,6 +242,13 @@ namespace O3DE::ProjectManager
|
||||
return QRect(topLeft, QSize(s_iconSize, s_iconSize));
|
||||
}
|
||||
|
||||
QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const
|
||||
{
|
||||
const int topLeftX = buttonRect.left() + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing;
|
||||
const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3);
|
||||
return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize));
|
||||
}
|
||||
|
||||
void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
|
||||
{
|
||||
painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
|
||||
|
||||
@@ -68,12 +68,14 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void RemoveRepo(const QModelIndex& modelIndex);
|
||||
void RefreshRepo(const QModelIndex& modelIndex);
|
||||
|
||||
protected:
|
||||
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
|
||||
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
|
||||
QRect CalcButtonRect(const QRect& contentRect) const;
|
||||
QRect CalcDeleteButtonRect(const QRect& contentRect) const;
|
||||
QRect CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const;
|
||||
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
|
||||
void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this);
|
||||
connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo);
|
||||
connect(itemDelegate, &GemRepoItemDelegate::RefreshRepo, this, &GemRepoListView::RefreshRepo);
|
||||
setItemDelegate(itemDelegate);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -28,5 +28,6 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void RemoveRepo(const QModelIndex& modelIndex);
|
||||
void RefreshRepo(const QModelIndex& modelIndex);
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -126,6 +126,36 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemRepoScreen::HandleRefreshAllButton()
|
||||
{
|
||||
bool refreshResult = PythonBindingsInterface::Get()->RefreshAllGemRepos();
|
||||
Reinit();
|
||||
|
||||
if (!refreshResult)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Operation failed"), QString("Some repos failed to refresh."));
|
||||
}
|
||||
}
|
||||
|
||||
void GemRepoScreen::HandleRefreshRepoButton(const QModelIndex& modelIndex)
|
||||
{
|
||||
const QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex);
|
||||
|
||||
AZ::Outcome<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(repoUri);
|
||||
if (refreshResult.IsSuccess())
|
||||
{
|
||||
Reinit();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Operation failed"),
|
||||
QString("Failed to refresh gem repo %1<br>Error:<br>%2")
|
||||
.arg(m_gemRepoModel->GetName(modelIndex), refreshResult.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void GemRepoScreen::FillModel()
|
||||
{
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
|
||||
@@ -237,6 +267,8 @@ namespace O3DE::ProjectManager
|
||||
m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton");
|
||||
topMiddleHLayout->addWidget(m_AllUpdateButton);
|
||||
|
||||
connect(m_AllUpdateButton, &QPushButton::clicked, this, &GemRepoScreen::HandleRefreshAllButton);
|
||||
|
||||
topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
|
||||
|
||||
QPushButton* addRepoButton = new QPushButton(tr("Add Repository"), this);
|
||||
@@ -280,6 +312,7 @@ namespace O3DE::ProjectManager
|
||||
middleVLayout->addWidget(m_gemRepoListView);
|
||||
|
||||
connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton);
|
||||
connect(m_gemRepoListView, &GemRepoListView::RefreshRepo, this, &GemRepoScreen::HandleRefreshRepoButton);
|
||||
|
||||
hLayout->addLayout(middleVLayout);
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace O3DE::ProjectManager
|
||||
public slots:
|
||||
void HandleAddRepoButton();
|
||||
void HandleRemoveRepoButton(const QModelIndex& modelIndex);
|
||||
void HandleRefreshAllButton();
|
||||
void HandleRefreshRepoButton(const QModelIndex& modelIndex);
|
||||
|
||||
private:
|
||||
void FillModel();
|
||||
|
||||
@@ -303,6 +303,7 @@ namespace O3DE::ProjectManager
|
||||
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
|
||||
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
|
||||
m_download = pybind11::module::import("o3de.download");
|
||||
m_repo = pybind11::module::import("o3de.repo");
|
||||
m_pathlib = pybind11::module::import("pathlib");
|
||||
|
||||
// make sure the engine is registered
|
||||
@@ -982,6 +983,46 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RefreshGemRepo(const QString& repoUri)
|
||||
{
|
||||
bool refreshResult = false;
|
||||
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto pyUri = QString_To_Py_String(repoUri);
|
||||
auto pythonRefreshResult = m_repo.attr("refresh_repo")(pyUri);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
refreshResult = !pythonRefreshResult.cast<bool>();
|
||||
});
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (!refreshResult)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Failed to refresh repo.");
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
bool PythonBindings::RefreshAllGemRepos()
|
||||
{
|
||||
bool refreshResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
auto pythonRefreshResult = m_repo.attr("refresh_repos")();
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
refreshResult = !pythonRefreshResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && refreshResult;
|
||||
}
|
||||
|
||||
bool PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
@@ -1160,4 +1201,31 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
m_requestCancelDownload = true;
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
|
||||
{
|
||||
QVector<GemInfo> gemInfos;
|
||||
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")();
|
||||
|
||||
if (pybind11::isinstance<pybind11::set>(gemPaths))
|
||||
{
|
||||
for (auto path : gemPaths)
|
||||
{
|
||||
GemInfo gemInfo = GemInfoFromPath(path, pybind11::none());
|
||||
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded;
|
||||
gemInfos.push_back(gemInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(result.GetError());
|
||||
}
|
||||
|
||||
return AZ::Success(AZStd::move(gemInfos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +59,14 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
|
||||
|
||||
// Gem Repos
|
||||
AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) override;
|
||||
bool RefreshAllGemRepos() override;
|
||||
bool AddGemRepo(const QString& repoUri) override;
|
||||
bool RemoveGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
|
||||
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) override;
|
||||
void CancelDownload() override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() override;
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PythonBindings);
|
||||
@@ -91,6 +94,7 @@ namespace O3DE::ProjectManager
|
||||
pybind11::handle m_disableGemProject;
|
||||
pybind11::handle m_editProjectProperties;
|
||||
pybind11::handle m_download;
|
||||
pybind11::handle m_repo;
|
||||
pybind11::handle m_pathlib;
|
||||
|
||||
bool m_requestCancelDownload = false;
|
||||
|
||||
@@ -176,6 +176,19 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Gem Repos
|
||||
|
||||
/**
|
||||
* Refresh gem repo in the current engine.
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo.
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) = 0;
|
||||
|
||||
/**
|
||||
* Refresh all gem repos in the current engine.
|
||||
* @return true on success, false on failure.
|
||||
*/
|
||||
virtual bool RefreshAllGemRepos() = 0;
|
||||
|
||||
/**
|
||||
* Registers this gem repo with the current engine.
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo.
|
||||
@@ -208,6 +221,12 @@ namespace O3DE::ProjectManager
|
||||
* Cancels the current download.
|
||||
*/
|
||||
virtual void CancelDownload() = 0;
|
||||
|
||||
/**
|
||||
* Gathers all gem infos for all gems registered from repos.
|
||||
* @return A list of gem infos.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() = 0;
|
||||
};
|
||||
|
||||
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
|
||||
|
||||
@@ -133,6 +133,11 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are already on this screen still notify we are on this screen to refresh it
|
||||
newScreen->NotifyCurrentScreen();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
+4
-4
@@ -108,7 +108,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSe
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
@@ -122,7 +122,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithou
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
@@ -140,7 +140,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOu
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
@@ -160,7 +160,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOne
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <CommonFiles/Preprocessor.h>
|
||||
#include <CommonFiles/GlobalBuildOptions.h>
|
||||
@@ -91,19 +92,31 @@ namespace AZ
|
||||
m_shaderAssetBuilder.BusConnect(shaderAssetBuilderDescriptor.m_busId);
|
||||
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderAssetBuilderDescriptor);
|
||||
|
||||
// Register Shader Variant Asset Builder
|
||||
AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilderDescriptor;
|
||||
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
|
||||
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
|
||||
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
|
||||
shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
|
||||
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
|
||||
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
|
||||
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
shaderVariantAssetBuilderDescriptor.m_processJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::ProcessJob, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
// If, either the SettingsRegistry doesn't exist, or the property @EnableShaderVariantAssetBuilderRegistryKey is not found,
|
||||
// the default is to enable the ShaderVariantAssetBuilder.
|
||||
m_enableShaderVariantAssetBuilder = true;
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Get(m_enableShaderVariantAssetBuilder, EnableShaderVariantAssetBuilderRegistryKey);
|
||||
}
|
||||
|
||||
m_shaderVariantAssetBuilder.BusConnect(shaderVariantAssetBuilderDescriptor.m_busId);
|
||||
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilderDescriptor);
|
||||
if (m_enableShaderVariantAssetBuilder)
|
||||
{
|
||||
// Register Shader Variant Asset Builder
|
||||
AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilderDescriptor;
|
||||
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
|
||||
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
|
||||
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
|
||||
shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
|
||||
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
|
||||
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
|
||||
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
shaderVariantAssetBuilderDescriptor.m_processJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::ProcessJob, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
|
||||
m_shaderVariantAssetBuilder.BusConnect(shaderVariantAssetBuilderDescriptor.m_busId);
|
||||
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilderDescriptor);
|
||||
}
|
||||
|
||||
// Register Precompiled Shader Builder
|
||||
AssetBuilderSDK::AssetBuilderDesc precompiledShaderBuilderDescriptor;
|
||||
@@ -121,7 +134,10 @@ namespace AZ
|
||||
void AzslShaderBuilderSystemComponent::Deactivate()
|
||||
{
|
||||
m_shaderAssetBuilder.BusDisconnect();
|
||||
m_shaderVariantAssetBuilder.BusDisconnect();
|
||||
if (m_enableShaderVariantAssetBuilder)
|
||||
{
|
||||
m_shaderVariantAssetBuilder.BusDisconnect();
|
||||
}
|
||||
m_precompiledShaderBuilder.BusDisconnect();
|
||||
|
||||
RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusDisconnect();
|
||||
|
||||
@@ -61,7 +61,17 @@ namespace AZ
|
||||
|
||||
private:
|
||||
ShaderAssetBuilder m_shaderAssetBuilder;
|
||||
|
||||
// The ShaderVariantAssetBuilder can be disabled with this registry key.
|
||||
// By default it is enabled. A user might want to disable it when doing look development
|
||||
// work with shaders or doing lots of iterative changes to shaders. In these cases
|
||||
// GPU performance doesn't matter at all so it is important to not waste time
|
||||
// building ShaderVariantAssets (Other than the Root ShaderVariantAsset, of course.).
|
||||
static constexpr char EnableShaderVariantAssetBuilderRegistryKey[] = "/O3DE/Atom/Shaders/BuildVariants";
|
||||
bool m_enableShaderVariantAssetBuilder = true;
|
||||
|
||||
ShaderVariantAssetBuilder m_shaderVariantAssetBuilder;
|
||||
|
||||
PrecompiledShaderBuilder m_precompiledShaderBuilder;
|
||||
|
||||
/// Contains the ShaderPlatformInterface for all registered RHIs
|
||||
|
||||
@@ -477,8 +477,8 @@ namespace AZ
|
||||
preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end());
|
||||
// Run the preprocessor.
|
||||
PreprocessorData output;
|
||||
PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
|
||||
RHI::ReportErrorMessages(ShaderAssetBuilderName, output.diagnostics);
|
||||
const bool preprocessorSuccess = PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
|
||||
RHI::ReportMessages(ShaderAssetBuilderName, output.diagnostics, !preprocessorSuccess);
|
||||
// Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file.
|
||||
AZStd::string superVariantAzslinStemName = shaderFileName;
|
||||
if (!supervariantInfo.m_name.IsEmpty())
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"O3DE": {
|
||||
"Atom": {
|
||||
"Shaders": {
|
||||
"BuildVariants": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <AzCore/NativeUI/NativeUIRequests.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
@@ -115,7 +116,9 @@ namespace AZ
|
||||
{
|
||||
// GFX TODO - investigate window creation being part of the GameApplication.
|
||||
|
||||
m_nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>("O3DELauncher", AzFramework::WindowGeometry(0, 0, 1920, 1080));
|
||||
auto projectTitle = AZ::Utils::GetProjectName();
|
||||
|
||||
m_nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>(projectTitle.c_str(), AzFramework::WindowGeometry(0, 0, 1920, 1080));
|
||||
AZ_Assert(m_nativeWindow, "Failed to create the game window\n");
|
||||
|
||||
m_nativeWindow->Activate();
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
{
|
||||
"description": "Material Type with properties used to define Enhanced PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model, with advanced features like subsurface scattering, transmission, and anisotropy.",
|
||||
"version": 3,
|
||||
"version": 4,
|
||||
"versionUpdates": [
|
||||
{
|
||||
"toVersion": 4,
|
||||
"actions": [
|
||||
{"op": "rename", "from": "opacity.doubleSided", "to": "general.doubleSided"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"propertyLayout": {
|
||||
"groups": [
|
||||
{
|
||||
@@ -92,6 +100,12 @@
|
||||
],
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
"name": "doubleSided",
|
||||
"displayName": "Double-sided",
|
||||
"description": "Whether to render back-faces or just front-faces.",
|
||||
"type": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "applySpecularAA",
|
||||
"displayName": "Apply Specular AA",
|
||||
@@ -709,12 +723,6 @@
|
||||
"name": "m_opacityFactor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "doubleSided",
|
||||
"displayName": "Double-sided",
|
||||
"description": "Whether to render back-faces or just front-faces.",
|
||||
"type": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "alphaAffectsSpecular",
|
||||
"displayName": "Alpha affects specular",
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
{
|
||||
"description": "Material Type with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.",
|
||||
"version": 3,
|
||||
"version": 4,
|
||||
"versionUpdates": [
|
||||
{
|
||||
"toVersion": 4,
|
||||
"actions": [
|
||||
{"op": "rename", "from": "opacity.doubleSided", "to": "general.doubleSided"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"propertyLayout": {
|
||||
"groups": [
|
||||
{
|
||||
@@ -72,6 +80,12 @@
|
||||
],
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
"name": "doubleSided",
|
||||
"displayName": "Double-sided",
|
||||
"description": "Whether to render back-faces or just front-faces.",
|
||||
"type": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "applySpecularAA",
|
||||
"displayName": "Apply Specular AA",
|
||||
@@ -650,12 +664,6 @@
|
||||
"name": "m_opacityFactor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "doubleSided",
|
||||
"displayName": "Double-sided",
|
||||
"description": "Whether to render back-faces or just front-faces.",
|
||||
"type": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "alphaAffectsSpecular",
|
||||
"displayName": "Alpha affects specular",
|
||||
|
||||
+2
-2
@@ -10,14 +10,14 @@
|
||||
----------------------------------------------------------------------------------------------------
|
||||
|
||||
function GetMaterialPropertyDependencies()
|
||||
return {"opacity.doubleSided"}
|
||||
return {"general.doubleSided"}
|
||||
end
|
||||
|
||||
ForwardPassIndex = 0
|
||||
ForwardPassEdsIndex = 1
|
||||
|
||||
function Process(context)
|
||||
local doubleSided = context:GetMaterialPropertyValue_bool("opacity.doubleSided")
|
||||
local doubleSided = context:GetMaterialPropertyValue_bool("general.doubleSided")
|
||||
local lastShader = context:GetShaderCount() - 1;
|
||||
|
||||
if(doubleSided) then
|
||||
|
||||
@@ -81,7 +81,6 @@ function ProcessEditor(context)
|
||||
context:SetMaterialPropertyVisibility("opacity.textureMap", mainVisibility)
|
||||
context:SetMaterialPropertyVisibility("opacity.textureMapUv", mainVisibility)
|
||||
context:SetMaterialPropertyVisibility("opacity.factor", mainVisibility)
|
||||
context:SetMaterialPropertyVisibility("opacity.doubleSided", mainVisibility)
|
||||
|
||||
if(opacityMode == OpacityMode_Blended or opacityMode == OpacityMode_TintedTransparent) then
|
||||
context:SetMaterialPropertyVisibility("opacity.alphaAffectsSpecular", MaterialPropertyVisibility_Enabled)
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"DrawList" : "forward",
|
||||
|
||||
"CompilerHints" : {
|
||||
"DxcDisableOptimizations" : false,
|
||||
"DxcGenerateDebugInfo" : false
|
||||
"DisableOptimizations" : false,
|
||||
"GenerateDebugInfo" : false
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:57d6744696768f9fb8a5fe5fee9aa36fee1eb87a9dbc1e60d4a35ed3c39d68e6
|
||||
size 810620
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:513f47f6fea5105f603170a8881b7e3b1cd2c4258636d64a6399c725032b500d
|
||||
size 38689
|
||||
@@ -5,12 +5,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
if(LY_MONOLITHIC_GAME) # Do not use OpenImageIO in monolithic game
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(LY_BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
3rdParty::ilmbase
|
||||
)
|
||||
|
||||
@@ -57,12 +57,19 @@ namespace AZ
|
||||
AZStd::string m_azslcAdditionalFreeArguments;
|
||||
// note: if you add new sort of arguments here, don't forget to update HasDifferentAzslcArguments()
|
||||
|
||||
//! DXC
|
||||
bool m_dxcDisableWarnings = false;
|
||||
bool m_dxcWarningAsError = false;
|
||||
bool m_dxcDisableOptimizations = false;
|
||||
bool m_dxcGenerateDebugInfo = false;
|
||||
uint8_t m_dxcOptimizationLevel = LevelUnset;
|
||||
//! Remark: To the user, the following parameters are exposed without the
|
||||
//! "Dxc" prefix because these are common options for the "main" compiler
|
||||
//! for the given RHI. At the moment the only "main" compiler is Dxc, but in
|
||||
//! the future AZSLc may transpile from AZSL to some other proprietary language
|
||||
//! and in that case the "main" compiler won't be DXC
|
||||
bool m_disableWarnings = false;
|
||||
bool m_warningAsError = false;
|
||||
bool m_disableOptimizations = false;
|
||||
bool m_generateDebugInfo = false;
|
||||
uint8_t m_optimizationLevel = LevelUnset;
|
||||
//! "DxcAdditionalFreeArguments" keeps the "Dxc" prefix because these arguments
|
||||
//! are specific to DXC, and it will be relevant only if DXC is the "main" compiler
|
||||
//! for a given RHI, otherwise this parameter won't matter.
|
||||
AZStd::string m_dxcAdditionalFreeArguments;
|
||||
|
||||
//! both
|
||||
|
||||
@@ -83,11 +83,12 @@ namespace AZ
|
||||
const AZStd::string& shaderSourcePathForDebug,
|
||||
const char* toolNameForLog);
|
||||
|
||||
//! Reports error messages to AZ_Error and/or AZ_Warning, given a text blob that potentially contains many lines of errors and warnings.
|
||||
//! @param window Debug window name used for AZ Trace functions
|
||||
//! @param errorMessages String that may contain many lines of errors and warnings
|
||||
//! @param return true if Errors were detected and reported (Warnings don't count)
|
||||
bool ReportErrorMessages(AZStd::string_view window, AZStd::string_view errorMessages);
|
||||
//! Reports messages with AZ_Error or AZ_Warning (See @reportAsErrors).
|
||||
//! @param window Debug window name used for AZ Trace functions.
|
||||
//! @param errorMessages Message string.
|
||||
//! @param reportAsErrors If true, messages are traced with AZ_Error, otherwise AZ_Warning is used.
|
||||
//! @returns true If the input text blob contains at least one line with the "error" string.
|
||||
bool ReportMessages(AZStd::string_view window, AZStd::string_view errorMessages, bool reportAsErrors);
|
||||
|
||||
//! Converts from a RHI::ShaderHardwareStage to an RHI::ShaderStage
|
||||
ShaderStage ToRHIShaderStage(ShaderHardwareStage stageType);
|
||||
|
||||
@@ -33,17 +33,17 @@ namespace AZ
|
||||
RegisterEnumerators<MatrixOrder>(serializeContext);
|
||||
|
||||
serializeContext->Class<ShaderCompilerArguments>()
|
||||
->Version(2)
|
||||
->Version(3)
|
||||
->Field("AzslcWarningLevel", &ShaderCompilerArguments::m_azslcWarningLevel)
|
||||
->Field("AzslcWarningAsError", &ShaderCompilerArguments::m_azslcWarningAsError)
|
||||
->Field("AzslcAdditionalFreeArguments", &ShaderCompilerArguments::m_azslcAdditionalFreeArguments)
|
||||
->Field("DxcDisableWarnings", &ShaderCompilerArguments::m_dxcDisableWarnings)
|
||||
->Field("DxcWarningAsError", &ShaderCompilerArguments::m_dxcWarningAsError)
|
||||
->Field("DxcDisableOptimizations", &ShaderCompilerArguments::m_dxcDisableOptimizations)
|
||||
->Field("DxcGenerateDebugInfo", &ShaderCompilerArguments::m_dxcGenerateDebugInfo)
|
||||
->Field("DxcOptimizationLevel", &ShaderCompilerArguments::m_dxcOptimizationLevel)
|
||||
->Field("DxcAdditionalFreeArguments", &ShaderCompilerArguments::m_dxcAdditionalFreeArguments)
|
||||
->Field("DisableWarnings", &ShaderCompilerArguments::m_disableWarnings)
|
||||
->Field("WarningAsError", &ShaderCompilerArguments::m_warningAsError)
|
||||
->Field("DisableOptimizations", &ShaderCompilerArguments::m_disableOptimizations)
|
||||
->Field("GenerateDebugInfo", &ShaderCompilerArguments::m_generateDebugInfo)
|
||||
->Field("OptimizationLevel", &ShaderCompilerArguments::m_optimizationLevel)
|
||||
->Field("DefaultMatrixOrder", &ShaderCompilerArguments::m_defaultMatrixOrder)
|
||||
->Field("DxcAdditionalFreeArguments", &ShaderCompilerArguments::m_dxcAdditionalFreeArguments)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -62,13 +62,13 @@ namespace AZ
|
||||
}
|
||||
m_azslcWarningAsError = m_azslcWarningAsError || right.m_azslcWarningAsError;
|
||||
m_azslcAdditionalFreeArguments = CommandLineArgumentUtils::MergeCommandLineArguments(m_azslcAdditionalFreeArguments, right.m_azslcAdditionalFreeArguments);
|
||||
m_dxcDisableWarnings = m_dxcDisableWarnings || right.m_dxcDisableWarnings;
|
||||
m_dxcWarningAsError = m_dxcWarningAsError || right.m_dxcWarningAsError;
|
||||
m_dxcDisableOptimizations = m_dxcDisableOptimizations || right.m_dxcDisableOptimizations;
|
||||
m_dxcGenerateDebugInfo = m_dxcGenerateDebugInfo || right.m_dxcGenerateDebugInfo;
|
||||
if (right.m_dxcOptimizationLevel != LevelUnset)
|
||||
m_disableWarnings = m_disableWarnings || right.m_disableWarnings;
|
||||
m_warningAsError = m_warningAsError || right.m_warningAsError;
|
||||
m_disableOptimizations = m_disableOptimizations || right.m_disableOptimizations;
|
||||
m_generateDebugInfo = m_generateDebugInfo || right.m_generateDebugInfo;
|
||||
if (right.m_optimizationLevel != LevelUnset)
|
||||
{
|
||||
m_dxcOptimizationLevel = right.m_dxcOptimizationLevel;
|
||||
m_optimizationLevel = right.m_optimizationLevel;
|
||||
}
|
||||
m_dxcAdditionalFreeArguments = CommandLineArgumentUtils::MergeCommandLineArguments(m_dxcAdditionalFreeArguments, right.m_dxcAdditionalFreeArguments);
|
||||
if (right.m_defaultMatrixOrder != MatrixOrder::Default)
|
||||
@@ -131,21 +131,21 @@ namespace AZ
|
||||
AZStd::string ShaderCompilerArguments::MakeAdditionalDxcCommandLineString() const
|
||||
{
|
||||
AZStd::string arguments;
|
||||
if (m_dxcDisableWarnings)
|
||||
if (m_disableWarnings)
|
||||
{
|
||||
arguments += " -no-warnings";
|
||||
}
|
||||
else if (m_dxcWarningAsError)
|
||||
else if (m_warningAsError)
|
||||
{
|
||||
arguments += " -WX";
|
||||
}
|
||||
if (m_dxcDisableOptimizations)
|
||||
if (m_disableOptimizations)
|
||||
{
|
||||
arguments += " -Od";
|
||||
}
|
||||
else if (m_dxcOptimizationLevel <= 3)
|
||||
else if (m_optimizationLevel <= 3)
|
||||
{
|
||||
arguments = " -O" + AZStd::to_string(m_dxcOptimizationLevel);
|
||||
arguments = " -O" + AZStd::to_string(m_optimizationLevel);
|
||||
}
|
||||
if (m_defaultMatrixOrder == MatrixOrder::Column)
|
||||
{
|
||||
|
||||
@@ -330,7 +330,7 @@ namespace AZ
|
||||
// Pump one last time to make sure the streams have been flushed
|
||||
pumpOuputStreams();
|
||||
|
||||
const bool reportedErrors = ReportErrorMessages(toolNameForLog, errorMessages);
|
||||
const bool reportedErrors = ReportMessages(toolNameForLog, errorMessages, exitCode != 0);
|
||||
|
||||
if (timedOut)
|
||||
{
|
||||
@@ -367,32 +367,20 @@ namespace AZ
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReportErrorMessages([[maybe_unused]] AZStd::string_view window, AZStd::string_view errorMessages)
|
||||
bool ReportMessages([[maybe_unused]] AZStd::string_view window, AZStd::string_view errorMessages, bool reportAsErrors)
|
||||
{
|
||||
// There are more efficient ways to do this, but this approach is simple and gets us moving for now.
|
||||
AZStd::vector<AZStd::string> lines;
|
||||
AzFramework::StringFunc::Tokenize(errorMessages.data(), lines, "\n\r");
|
||||
|
||||
bool foundErrors = false;
|
||||
|
||||
for (auto& line : lines)
|
||||
if (reportAsErrors)
|
||||
{
|
||||
if (AZStd::string::npos != AzFramework::StringFunc::Find(line, "error"))
|
||||
{
|
||||
AZ_Error(window.data(), false, "%s", line.data());
|
||||
foundErrors = true;
|
||||
}
|
||||
else if (AZStd::string::npos != AzFramework::StringFunc::Find(line, "warning"))
|
||||
{
|
||||
AZ_Warning(window.data(), false, "%s", line.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf(window.data(), "%s", line.data());
|
||||
}
|
||||
AZ_Error(window.data(), false, "%.*s", aznumeric_cast<int>(errorMessages.size()), errorMessages.data());
|
||||
}
|
||||
|
||||
return foundErrors;
|
||||
else
|
||||
{
|
||||
// Using AZ_Warning instead of AZ_TracePrintf because this function is commonly
|
||||
// used to report messages from stderr when executing applications. Applications
|
||||
// when ran successfully, only output to stderr for errors or warnings.
|
||||
AZ_Warning(window.data(), false, "%.*s", aznumeric_cast<int>(errorMessages.size()), errorMessages.data());
|
||||
}
|
||||
return AZStd::string::npos != AzFramework::StringFunc::Find(errorMessages, "error");
|
||||
}
|
||||
|
||||
ShaderStage ToRHIShaderStage(ShaderHardwareStage stageType)
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
if (shaderCompilerArguments.m_dxcDisableOptimizations)
|
||||
if (shaderCompilerArguments.m_disableOptimizations)
|
||||
{
|
||||
// When optimizations are disabled (-Od), all resources declared in the source file are available to all stages
|
||||
// (when enabled only the resources which are referenced in a stage are bound to the stage)
|
||||
@@ -195,7 +195,7 @@ namespace AZ
|
||||
|
||||
bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const
|
||||
{
|
||||
return shaderCompilerArguments.m_dxcGenerateDebugInfo;
|
||||
return shaderCompilerArguments.m_generateDebugInfo;
|
||||
}
|
||||
|
||||
const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace AZ
|
||||
|
||||
bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const
|
||||
{
|
||||
return shaderCompilerArguments.m_dxcGenerateDebugInfo;
|
||||
return shaderCompilerArguments.m_generateDebugInfo;
|
||||
}
|
||||
|
||||
const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace AZ
|
||||
|
||||
bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const
|
||||
{
|
||||
return shaderCompilerArguments.m_dxcGenerateDebugInfo;
|
||||
return shaderCompilerArguments.m_generateDebugInfo;
|
||||
}
|
||||
|
||||
const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const
|
||||
|
||||
@@ -729,8 +729,7 @@ namespace AZ
|
||||
|
||||
void CommandList::SetStencilRef(uint8_t stencilRef)
|
||||
{
|
||||
vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_FRONT_BIT, static_cast<uint32_t>(stencilRef));
|
||||
vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_BACK_BIT, static_cast<uint32_t>(stencilRef));
|
||||
vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_FRONT_AND_BACK, aznumeric_cast<uint32_t>(stencilRef));
|
||||
}
|
||||
|
||||
void CommandList::BindPipeline(const PipelineState* pipelineState)
|
||||
|
||||
@@ -93,31 +93,28 @@ namespace AZ
|
||||
|
||||
// Note that the only kind of property update currently supported is rename...
|
||||
|
||||
PropertyGroupMap newPropertyGroups;
|
||||
for (auto& groupPair : m_properties)
|
||||
{
|
||||
PropertyMap& propertyMap = groupPair.second;
|
||||
|
||||
PropertyMap newPropertyMap;
|
||||
|
||||
for (auto& propertyPair : propertyMap)
|
||||
{
|
||||
MaterialPropertyId propertyId{groupPair.first, propertyPair.first};
|
||||
|
||||
if (materialTypeSourceData.ApplyPropertyRenames(propertyId, m_materialTypeVersion))
|
||||
{
|
||||
newPropertyMap[propertyId.GetPropertyName().GetStringView()] = propertyPair.second;
|
||||
changesWereApplied = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
newPropertyMap[propertyPair.first] = propertyPair.second;
|
||||
}
|
||||
|
||||
newPropertyGroups[propertyId.GetGroupName().GetStringView()][propertyId.GetPropertyName().GetStringView()] = propertyPair.second;
|
||||
}
|
||||
|
||||
propertyMap = newPropertyMap;
|
||||
}
|
||||
|
||||
if (changesWereApplied)
|
||||
{
|
||||
m_properties = AZStd::move(newPropertyGroups);
|
||||
|
||||
AZ_Warning("MaterialSourceData", false,
|
||||
"This material is based on version '%u' of '%s', but the material type is now at version '%u'. "
|
||||
"Automatic updates are available. Consider updating the .material source file.",
|
||||
|
||||
@@ -164,16 +164,14 @@ namespace AZ
|
||||
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion) const
|
||||
{
|
||||
auto groupIter = m_propertyLayout.m_properties.find(groupName);
|
||||
if (groupIter == m_propertyLayout.m_properties.end())
|
||||
if (groupIter != m_propertyLayout.m_properties.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const PropertyDefinition& property : groupIter->second)
|
||||
{
|
||||
if (property.m_name == propertyName)
|
||||
for (const PropertyDefinition& property : groupIter->second)
|
||||
{
|
||||
return &property;
|
||||
if (property.m_name == propertyName)
|
||||
{
|
||||
return &property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,16 +183,14 @@ namespace AZ
|
||||
// Do the search again with the new names
|
||||
|
||||
groupIter = m_propertyLayout.m_properties.find(propertyId.GetGroupName().GetStringView());
|
||||
if (groupIter == m_propertyLayout.m_properties.end())
|
||||
if (groupIter != m_propertyLayout.m_properties.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const PropertyDefinition& property : groupIter->second)
|
||||
{
|
||||
if (property.m_name == propertyId.GetPropertyName().GetStringView())
|
||||
for (const PropertyDefinition& property : groupIter->second)
|
||||
{
|
||||
return &property;
|
||||
if (property.m_name == propertyId.GetPropertyName().GetStringView())
|
||||
{
|
||||
return &property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +264,6 @@ namespace AZ
|
||||
{
|
||||
// When rebuilding shaders we may be in a state where the ShaderAsset and root ShaderVariantAsset have been rebuilt and reloaded, but some (or all)
|
||||
// shader variants haven't been built yet. Since we want to use the latest version of the shader code, ignore the old variants and fall back to the newer root variant instead.
|
||||
AZ_Warning("ShaderAsset", false, "ShaderAsset and ShaderVariantAsset are out of sync; defaulting to root shader variant. (This is common while reloading shaders).");
|
||||
return GetRootVariant(supervariantIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,13 @@ namespace UnitTest
|
||||
{"op": "rename", "from": "general.testColorNameB", "to": "general.testColorNameC"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"toVersion": 6,
|
||||
"actions": [
|
||||
{"op": "rename", "from": "oldGroup.MyFloat", "to": "general.MyFloat"},
|
||||
{"op": "rename", "from": "oldGroup.MyIntOldName", "to": "general.MyInt"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"toVersion": 10,
|
||||
"actions": [
|
||||
@@ -751,6 +758,72 @@ namespace UnitTest
|
||||
material.ApplyVersionUpdates();
|
||||
}
|
||||
|
||||
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionUpdate_MovePropertiesToAnotherGroup)
|
||||
{
|
||||
const AZStd::string inputJson = R"(
|
||||
{
|
||||
"materialType": "@exefolder@/Temp/test.materialtype",
|
||||
"materialTypeVersion": 3,
|
||||
"properties": {
|
||||
"oldGroup": {
|
||||
"MyFloat": 1.2,
|
||||
"MyIntOldName": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
MaterialSourceData material;
|
||||
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
|
||||
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
|
||||
|
||||
// Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of
|
||||
// what's actually saved on disk.
|
||||
|
||||
EXPECT_NE(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_NE(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_EQ(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
|
||||
EXPECT_EQ(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
|
||||
|
||||
float myFloat = material.m_properties["oldGroup"]["MyFloat"].m_value.GetValue<float>();
|
||||
EXPECT_EQ(myFloat, 1.2f);
|
||||
|
||||
int32_t myInt = material.m_properties["oldGroup"]["MyIntOldName"].m_value.GetValue<int32_t>();
|
||||
EXPECT_EQ(myInt, 5);
|
||||
|
||||
EXPECT_EQ(3, material.m_materialTypeVersion);
|
||||
|
||||
// Then we force the material data to update to the latest material type version specification
|
||||
ErrorMessageFinder warningFinder; // Note this finds errors and warnings, and we're looking for a warning.
|
||||
warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file");
|
||||
warningFinder.AddExpectedErrorMessage("This material is based on version '3'");
|
||||
warningFinder.AddExpectedErrorMessage("material type is now at version '10'");
|
||||
material.ApplyVersionUpdates();
|
||||
warningFinder.CheckExpectedErrorsFound();
|
||||
|
||||
// Now the material data should match the latest material type.
|
||||
// Look for the property under the latest name in the material type, not the name used in the .material file.
|
||||
|
||||
EXPECT_EQ(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_EQ(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
|
||||
EXPECT_NE(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
|
||||
EXPECT_NE(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
|
||||
|
||||
myFloat = material.m_properties["general"]["MyFloat"].m_value.GetValue<float>();
|
||||
EXPECT_EQ(myFloat, 1.2f);
|
||||
|
||||
myInt = material.m_properties["general"]["MyInt"].m_value.GetValue<int32_t>();
|
||||
EXPECT_EQ(myInt, 5);
|
||||
|
||||
EXPECT_EQ(10, material.m_materialTypeVersion);
|
||||
|
||||
// Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated.
|
||||
warningFinder.Reset();
|
||||
material.ApplyVersionUpdates();
|
||||
}
|
||||
|
||||
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionPartialUpdate)
|
||||
{
|
||||
// This case is similar to Load_MaterialTypeVersionUpdate but we start at a later
|
||||
|
||||
@@ -1346,7 +1346,8 @@ namespace UnitTest
|
||||
{
|
||||
"toVersion": 7,
|
||||
"actions": [
|
||||
{ "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" }
|
||||
{ "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" },
|
||||
{ "op": "rename", "from": "onlyOneProperty.bopA", "to": "otherGroup.bopB" } // This tests a group 'onlyOneProperty' that no longer exists in the material type
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -1370,6 +1371,10 @@ namespace UnitTest
|
||||
{
|
||||
"name": "bazB",
|
||||
"type": "Float"
|
||||
},
|
||||
{
|
||||
"name": "bopB",
|
||||
"type": "Float"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1386,13 +1391,16 @@ namespace UnitTest
|
||||
const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooC");
|
||||
const MaterialTypeSourceData::PropertyDefinition* bar = materialType.FindProperty("general", "barC");
|
||||
const MaterialTypeSourceData::PropertyDefinition* baz = materialType.FindProperty("otherGroup", "bazB");
|
||||
const MaterialTypeSourceData::PropertyDefinition* bop = materialType.FindProperty("otherGroup", "bopB");
|
||||
|
||||
EXPECT_TRUE(foo);
|
||||
EXPECT_TRUE(bar);
|
||||
EXPECT_TRUE(baz);
|
||||
EXPECT_TRUE(bop);
|
||||
EXPECT_EQ(foo->m_name, "fooC");
|
||||
EXPECT_EQ(bar->m_name, "barC");
|
||||
EXPECT_EQ(baz->m_name, "bazB");
|
||||
EXPECT_EQ(bop->m_name, "bopB");
|
||||
|
||||
// Now try doing the property lookup using old versions of the name and make sure the same property can be found
|
||||
|
||||
@@ -1401,12 +1409,15 @@ namespace UnitTest
|
||||
EXPECT_EQ(bar, materialType.FindProperty("general", "barA"));
|
||||
EXPECT_EQ(bar, materialType.FindProperty("general", "barB"));
|
||||
EXPECT_EQ(baz, materialType.FindProperty("general", "bazA"));
|
||||
EXPECT_EQ(bop, materialType.FindProperty("onlyOneProperty", "bopA"));
|
||||
|
||||
EXPECT_EQ(nullptr, materialType.FindProperty("general", "fooX"));
|
||||
EXPECT_EQ(nullptr, materialType.FindProperty("general", "barX"));
|
||||
EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazX"));
|
||||
EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazB"));
|
||||
EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bazA"));
|
||||
EXPECT_EQ(nullptr, materialType.FindProperty("onlyOneProperty", "bopB"));
|
||||
EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bopA"));
|
||||
}
|
||||
|
||||
TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName_Error_UnsupportedVersionUpdate)
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials/Types/StandardPBR.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3,
|
||||
"properties": {
|
||||
"baseColor": {
|
||||
"textureMap": "Textures/Default/checker_uv_basecolor.png"
|
||||
},
|
||||
"general": {
|
||||
"doubleSided": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
|
||||
"CompilerHints" : {
|
||||
"DxcDisableOptimizations" : false
|
||||
"DisableOptimizations" : false
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
|
||||
"CompilerHints" : {
|
||||
"DxcDisableOptimizations" : false
|
||||
"DisableOptimizations" : false
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b2ecc32cd3052f3cb5836c8be7bf5cba54d98f46e6a0eeac95aaef00a123411a
|
||||
size 27340
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:93a7e033d9fb0fcac221647322bde03716643d789390f79078c4fcc37ecfd005
|
||||
size 68327
|
||||
oid sha256:513f47f6fea5105f603170a8881b7e3b1cd2c4258636d64a6399c725032b500d
|
||||
size 38689
|
||||
|
||||
@@ -86,6 +86,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
FILES_CMAKE
|
||||
atomlyintegration_commonfeatures_editor_files.cmake
|
||||
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
|
||||
PLATFORM_INCLUDE_FILES
|
||||
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
if(NOT LY_MONOLITHIC_GAME) # Do not use OpenImageIO in monolithic game
|
||||
set(LY_RUNTIME_DEPENDENCIES
|
||||
3rdParty::OpenImageIO
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
@@ -739,14 +739,32 @@ void ViewportInteraction::MouseWheelEvent(QWheelEvent* ev)
|
||||
|
||||
bool ViewportInteraction::KeyPressEvent(QKeyEvent* ev)
|
||||
{
|
||||
if (ev->key() == Qt::Key_Space)
|
||||
switch (ev->key())
|
||||
{
|
||||
case Qt::Key_Space:
|
||||
if (!ev->isAutoRepeat())
|
||||
{
|
||||
ActivateSpaceBar();
|
||||
}
|
||||
|
||||
return true;
|
||||
case Qt::Key_Up:
|
||||
Nudge(ViewportInteraction::NudgeDirection::Up,
|
||||
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
|
||||
return true;
|
||||
case Qt::Key_Down:
|
||||
Nudge(ViewportInteraction::NudgeDirection::Down,
|
||||
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
|
||||
return true;
|
||||
case Qt::Key_Left:
|
||||
Nudge(ViewportInteraction::NudgeDirection::Left,
|
||||
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
|
||||
return true;
|
||||
case Qt::Key_Right:
|
||||
Nudge(ViewportInteraction::NudgeDirection::Right,
|
||||
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -220,6 +220,7 @@ ViewportWidget::ViewportWidget(EditorWindow* parent)
|
||||
InitUiRenderer();
|
||||
|
||||
SetupShortcuts();
|
||||
installEventFilter(m_editorWindow);
|
||||
|
||||
// Setup a timer for the maximum refresh rate we want.
|
||||
// Refresh is actually triggered by interaction events and by the IdleUpdate. This avoids the UI
|
||||
@@ -258,6 +259,8 @@ ViewportWidget::~ViewportWidget()
|
||||
LyShinePassDataRequestBus::Handler::BusDisconnect();
|
||||
AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
|
||||
|
||||
removeEventFilter(m_editorWindow);
|
||||
|
||||
m_uiRenderer.reset();
|
||||
|
||||
// Notify LyShine that this is no longer a valid UiRenderer.
|
||||
@@ -688,9 +691,9 @@ void ViewportWidget::wheelEvent(QWheelEvent* ev)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool ViewportWidget::event(QEvent* ev)
|
||||
bool ViewportWidget::eventFilter([[maybe_unused]] QObject* watched, QEvent* event)
|
||||
{
|
||||
if (ev->type() == QEvent::ShortcutOverride)
|
||||
if (event->type() == QEvent::ShortcutOverride)
|
||||
{
|
||||
// When a shortcut is matched, Qt's event processing sends out a shortcut override event
|
||||
// to allow other systems to override it. If it's not overridden, then the key events
|
||||
@@ -698,40 +701,48 @@ bool ViewportWidget::event(QEvent* ev)
|
||||
// handler. In our case this causes a problem in preview mode for the Key_Delete event.
|
||||
// So, if we are preview mode avoid treating Key_Delete as a shortcut.
|
||||
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
|
||||
// Override the space bar shortcut so that the key gets handled by the viewport's KeyPress/KeyRelease
|
||||
// events when the viewport has the focus. The space bar is set up as a shortcut in order to give the
|
||||
// viewport the focus and activate the space bar when another widget has the focus. Once the shortcut
|
||||
// is pressed and focus is given to the viewport, the viewport takes over handling the space bar via
|
||||
// the KeyPress/KeyRelease events
|
||||
if (key == Qt::Key_Space)
|
||||
// the KeyPress/KeyRelease events.
|
||||
// Also ignore nudge shortcuts in edit/preview mode so that the KeyPressEvent will be sent.
|
||||
switch (key)
|
||||
{
|
||||
ev->accept();
|
||||
case Qt::Key_Space:
|
||||
case Qt::Key_Up:
|
||||
case Qt::Key_Down:
|
||||
case Qt::Key_Left:
|
||||
case Qt::Key_Right:
|
||||
{
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UiEditorMode editorMode = m_editorWindow->GetEditorMode();
|
||||
if (editorMode == UiEditorMode::Preview)
|
||||
{
|
||||
switch (key)
|
||||
if (key == Qt::Key_Delete)
|
||||
{
|
||||
case Qt::Key_Delete:
|
||||
// Ignore nudge shortcuts in preview mode so that the KeyPressEvent will be sent
|
||||
case Qt::Key_Up:
|
||||
case Qt::Key_Down:
|
||||
case Qt::Key_Left:
|
||||
case Qt::Key_Right:
|
||||
{
|
||||
ev->accept();
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewportWidget::event(QEvent* ev)
|
||||
{
|
||||
bool result = RenderViewportWidget::event(ev);
|
||||
return result;
|
||||
}
|
||||
@@ -742,8 +753,7 @@ void ViewportWidget::keyPressEvent(QKeyEvent* event)
|
||||
if (editorMode == UiEditorMode::Edit)
|
||||
{
|
||||
// in Edit mode just send input to ViewportInteraction
|
||||
bool handled = m_viewportInteraction->KeyPressEvent(event);
|
||||
if (!handled)
|
||||
if (!m_viewportInteraction->KeyPressEvent(event))
|
||||
{
|
||||
RenderViewportWidget::keyPressEvent(event);
|
||||
}
|
||||
@@ -1246,115 +1256,6 @@ void ViewportWidget::SetupShortcuts()
|
||||
{
|
||||
// Actions with shortcuts are created instead of direct shortcuts because the shortcut dispatcher only looks for matching actions
|
||||
|
||||
// Create nudge shortcuts that are active across the entire UI Editor window. Any widgets (such as the spin box widget) that
|
||||
// handle the same keys and want the shortcut to be ignored need to handle that with a shortcut override event.
|
||||
// In preview mode, the nudge shortcuts are ignored via the shortcut override event. KeyPressEvents are sent instead,
|
||||
// and passed along to the canvas
|
||||
|
||||
// Nudge up
|
||||
{
|
||||
QAction* action = new QAction("Up", this);
|
||||
action->setShortcut(QKeySequence(Qt::Key_Up));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Up, ViewportInteraction::NudgeSpeed::Slow);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Nudge up fast
|
||||
{
|
||||
QAction* action = new QAction("Up Fast", this);
|
||||
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Up));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Up, ViewportInteraction::NudgeSpeed::Fast);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Nudge down
|
||||
{
|
||||
QAction* action = new QAction("Down", this);
|
||||
action->setShortcut(QKeySequence(Qt::Key_Down));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Down, ViewportInteraction::NudgeSpeed::Slow);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Nudge down fast
|
||||
{
|
||||
QAction* action = new QAction("Down Fast", this);
|
||||
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Down));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Down, ViewportInteraction::NudgeSpeed::Fast);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Nudge left
|
||||
{
|
||||
QAction* action = new QAction("Left", this);
|
||||
action->setShortcut(QKeySequence(Qt::Key_Left));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Left, ViewportInteraction::NudgeSpeed::Slow);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Nudge left fast
|
||||
{
|
||||
QAction* action = new QAction("Left Fast", this);
|
||||
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Left));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Left, ViewportInteraction::NudgeSpeed::Fast);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Nudge right
|
||||
{
|
||||
QAction* action = new QAction("Right", this);
|
||||
action->setShortcut(QKeySequence(Qt::Key_Right));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Right, ViewportInteraction::NudgeSpeed::Slow);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Nudge right fast
|
||||
{
|
||||
QAction* action = new QAction("Right Fast", this);
|
||||
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Right));
|
||||
QObject::connect(action,
|
||||
&QAction::triggered,
|
||||
[this]()
|
||||
{
|
||||
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Right, ViewportInteraction::NudgeSpeed::Fast);
|
||||
});
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
// Give the viewport focus and activate the space bar
|
||||
{
|
||||
QAction* action = new QAction("Viewport Focus", this);
|
||||
|
||||
@@ -122,12 +122,15 @@ protected:
|
||||
void wheelEvent(QWheelEvent* ev) override;
|
||||
|
||||
//! Prevents shortcuts from interfering with preview mode.
|
||||
bool eventFilter(QObject* watched, QEvent* event) override;
|
||||
|
||||
//! Handle events from Qt.
|
||||
bool event(QEvent* ev) override;
|
||||
|
||||
//! Key press event from Qt
|
||||
//! Key press event from Qt.
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
//! Key release event from Qt
|
||||
//! Key release event from Qt.
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
void focusOutEvent(QFocusEvent* ev) override;
|
||||
|
||||
@@ -17,8 +17,11 @@ namespace Multiplayer
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
|
||||
virtual void SendEditorServerInitPacket(AzNetworking::IConnection* connection) = 0;
|
||||
|
||||
//! Sends a packet that initializes a local server launched from the editor.
|
||||
//! The editor will package the data required for loading the current editor level on the editor-server; data includes entities and asset data.
|
||||
//! @param connection The connection to the editor-server
|
||||
virtual void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) = 0;
|
||||
};
|
||||
using MultiplayerEditorServerRequestBus = AZ::EBus<MultiplayerEditorServerRequests>;
|
||||
} // namespace Multiplayer
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<Include File="Multiplayer/MultiplayerTypes.h" />
|
||||
<Include File="Multiplayer/NetworkTime/INetworkTime.h" />
|
||||
|
||||
<Packet Name="EditorServerReadyForInit" Desc="A packet the local server will send on startup once it's ready for the EditorServerInit packet."/>
|
||||
<Packet Name="EditorServerReadyForLevelData" Desc="A packet the editor-server will send on startup once it's ready to receive all the current level data from the Editor."/>
|
||||
|
||||
<Packet Name="EditorServerInit" Desc="A packet that initializes a local server launched from the editor">
|
||||
<Packet Name="EditorServerLevelData" Desc="A packet that initializes the editor-server with level data from the editor. The packet includes data required for loading the current level on the server (entities and asset data).">
|
||||
<Member Type="bool" Name="lastUpdate" Init="false"/>
|
||||
|
||||
<!--16379 is 16384 (max TCP packet size) - 1 byte (bool lastUpdate) - 4 bytes (serialization overhead for ByteBuffer) -->
|
||||
<Member Type="AzNetworking::ByteBuffer<16379>" Name="assetData"/>
|
||||
</Packet>
|
||||
|
||||
<Packet Name="EditorServerReady" Desc="A response packet the local server should send when ready for traffic"/>
|
||||
<Packet Name="EditorServerReady" Desc="A response packet the editor-server should send after getting the editor's level data when it's ready to begin the actual game-mode network simulation."/>
|
||||
</PacketGroup>
|
||||
|
||||
@@ -37,10 +37,11 @@ namespace Multiplayer
|
||||
{
|
||||
uint16_t editorsv_port = DefaultServerEditorPort;
|
||||
const auto console = AZ::Interface<AZ::IConsole>::Get();
|
||||
|
||||
AZ_Warning(
|
||||
"MultiplayerEditorConnection", console->GetCvarValue("editorsv_port", editorsv_port) == AZ::GetValueResult::Success,
|
||||
"MultiplayerEditorConnection failed! Could not find the editorsv_port cvar; we may not be able to connect to the editor's port!")
|
||||
if (console->GetCvarValue("editorsv_port", editorsv_port) != AZ::GetValueResult::Success)
|
||||
{
|
||||
AZ_Assert( false,
|
||||
"MultiplayerEditorConnection failed! Could not find the editorsv_port cvar; we may not be able to connect to the editor's port! Please update this code to use a valid cvar!")
|
||||
}
|
||||
|
||||
AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.")
|
||||
|
||||
@@ -54,7 +55,7 @@ namespace Multiplayer
|
||||
}
|
||||
else
|
||||
{
|
||||
m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForInit());
|
||||
m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForLevelData());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +64,7 @@ namespace Multiplayer
|
||||
(
|
||||
[[maybe_unused]] AzNetworking::IConnection* connection,
|
||||
[[maybe_unused]] const IPacketHeader& packetHeader,
|
||||
[[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet
|
||||
[[maybe_unused]] MultiplayerEditorPackets::EditorServerLevelData& packet
|
||||
)
|
||||
{
|
||||
// Editor Server Init is intended for non-release targets
|
||||
@@ -90,7 +91,7 @@ namespace Multiplayer
|
||||
AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream<AZ::Data::AssetData>(m_byteStream, nullptr);
|
||||
if (!assetDatum)
|
||||
{
|
||||
AZLOG_ERROR("EditorServerInit packet contains no asset data. Asset: %s", assetHint.c_str());
|
||||
AZLOG_ERROR("EditorServerLevelData packet contains no asset data. Asset: %s", assetHint.c_str())
|
||||
return false;
|
||||
}
|
||||
assetSize = m_byteStream.GetCurPos() - assetSize;
|
||||
@@ -127,8 +128,12 @@ namespace Multiplayer
|
||||
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpNetworkInterfaceName));
|
||||
|
||||
uint16_t sv_port = DefaultServerPort;
|
||||
AZ_Warning("MultiplayerEditorConnection", console->GetCvarValue("sv_port", sv_port) == AZ::GetValueResult::Success,
|
||||
"MultiplayerEditorConnection::HandleRequest for EditorServerInit failed! Could not find the sv_port cvar; we won't be able to listen on the correct port for incoming network messages!")
|
||||
if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success)
|
||||
{
|
||||
AZ_Assert(false,
|
||||
"MultiplayerEditorConnection::HandleRequest for EditorServerLevelData failed! Could not find the sv_port cvar; we won't be able to listen on the correct port for incoming network messages! Please update this code to use a valid cvar!")
|
||||
}
|
||||
|
||||
networkInterface->Listen(sv_port);
|
||||
|
||||
AZLOG_INFO("Editor Server completed asset receive, responding to Editor...");
|
||||
@@ -141,9 +146,9 @@ namespace Multiplayer
|
||||
bool MultiplayerEditorConnection::HandleRequest(
|
||||
[[maybe_unused]] AzNetworking::IConnection* connection,
|
||||
[[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader,
|
||||
[[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForInit& packet)
|
||||
[[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForLevelData& packet)
|
||||
{
|
||||
MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerInitPacket, connection);
|
||||
MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerLevelDataPacket, connection);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -160,15 +165,19 @@ namespace Multiplayer
|
||||
AZ::CVarFixedString editorsv_serveraddr = AZ::CVarFixedString(LocalHost);
|
||||
uint16_t sv_port = DefaultServerEditorPort;
|
||||
|
||||
AZ_Warning(
|
||||
"MultiplayerEditorConnection", console->GetCvarValue("sv_port", sv_port) == AZ::GetValueResult::Success,
|
||||
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the sv_port cvar; we may not be able to "
|
||||
"connect to the correct port for incoming network messages!")
|
||||
if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success)
|
||||
{
|
||||
AZ_Assert(false,
|
||||
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the sv_port cvar; we may not be able to "
|
||||
"connect to the correct port for incoming network messages! Please update this code to use a valid cvar!")
|
||||
}
|
||||
|
||||
AZ_Warning(
|
||||
"MultiplayerEditorConnection", console->GetCvarValue("editorsv_serveraddr", editorsv_serveraddr) == AZ::GetValueResult::Success,
|
||||
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the editorsv_serveraddr cvar; we may not be able to "
|
||||
"connect to the correct port for incoming network messages!")
|
||||
if (console->GetCvarValue("editorsv_serveraddr", editorsv_serveraddr) != AZ::GetValueResult::Success)
|
||||
{
|
||||
AZ_Assert(false,
|
||||
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the editorsv_serveraddr cvar; we may not be able to "
|
||||
"connect to the correct port for incoming network messages! Please update this code to use a valid cvar!")
|
||||
}
|
||||
|
||||
// Connect the Editor to the editor server for Multiplayer simulation
|
||||
AZ::Interface<IMultiplayer>::Get()->Connect(editorsv_serveraddr.c_str(), sv_port);
|
||||
@@ -195,26 +204,5 @@ namespace Multiplayer
|
||||
{
|
||||
return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this);
|
||||
}
|
||||
|
||||
void MultiplayerEditorConnection::OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
void MultiplayerEditorConnection::OnDisconnect([[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint)
|
||||
{
|
||||
const auto console = AZ::Interface<AZ::IConsole>::Get();
|
||||
bool editorsv_launch = false;
|
||||
AZ_Warning(
|
||||
"MultiplayerEditorConnection", console->GetCvarValue("editorsv_launch", editorsv_launch) == AZ::GetValueResult::Success,
|
||||
"MultiplayerEditorConnection::OnDisconnect failed! Could not find the editorsv_launch cvar.")
|
||||
|
||||
if (editorsv_isDedicated && editorsv_launch && m_networkEditorInterface->GetConnectionSet().GetConnectionCount() == 1)
|
||||
{
|
||||
if (m_networkEditorInterface->GetPort() != 0)
|
||||
{
|
||||
m_networkEditorInterface->StopListening();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
#include <Source/AutoGen/MultiplayerEditor.AutoPacketDispatcher.h>
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Console/ILogger.h>
|
||||
#include <AzCore/IO/ByteContainerStream.h>
|
||||
@@ -33,8 +31,8 @@ namespace Multiplayer
|
||||
MultiplayerEditorConnection();
|
||||
~MultiplayerEditorConnection() = default;
|
||||
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReadyForInit& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReadyForLevelData& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerLevelData& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet);
|
||||
|
||||
//! IConnectionListener interface
|
||||
@@ -42,8 +40,8 @@ namespace Multiplayer
|
||||
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
void OnConnect(AzNetworking::IConnection* connection) override;
|
||||
AzNetworking::PacketDispatchResult OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
|
||||
void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override;
|
||||
void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override;
|
||||
void OnPacketLost([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::PacketId packetId) override {}
|
||||
void OnDisconnect([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::DisconnectReason reason, [[maybe_unused]]AzNetworking::TerminationEndpoint endpoint) override {}
|
||||
//! @}
|
||||
|
||||
private:
|
||||
|
||||
@@ -273,7 +273,7 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
// Begin listening for MPEditor packets before we launch the editor-server.
|
||||
// The editor-server will send us (the editor) an "EditorServerReadyForInit" packet to let us know it's ready to receive data.
|
||||
// The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data.
|
||||
INetworkInterface* editorNetworkInterface =
|
||||
AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.");
|
||||
@@ -285,7 +285,7 @@ namespace Multiplayer
|
||||
else
|
||||
{
|
||||
// Editorsv_launch=false, so we're expecting an editor-server already exists.
|
||||
// Connect to the editor-server and then send the EditorServerInit packet.
|
||||
// Connect to the editor-server and then send the EditorServerLevelData packet.
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.")
|
||||
|
||||
@@ -295,14 +295,14 @@ namespace Multiplayer
|
||||
{
|
||||
AZ_Warning(
|
||||
"MultiplayerEditor", false,
|
||||
"Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i)."
|
||||
"Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). "
|
||||
"Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.",
|
||||
remoteAddress.c_str(),
|
||||
static_cast < uint16_t>(editorsv_port))
|
||||
return;
|
||||
}
|
||||
|
||||
SendEditorServerInitPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
|
||||
SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,7 @@ namespace Multiplayer
|
||||
AZ::Interface<IMultiplayer>::Get()->SendReadyForEntityUpdates(true);
|
||||
}
|
||||
|
||||
void MultiplayerEditorSystemComponent::SendEditorServerInitPacket(AzNetworking::IConnection* connection)
|
||||
void MultiplayerEditorSystemComponent::SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection)
|
||||
{
|
||||
const auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
|
||||
if (!prefabEditorEntityOwnershipInterface)
|
||||
@@ -349,13 +349,13 @@ namespace Multiplayer
|
||||
// Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets
|
||||
AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
|
||||
|
||||
// Read the buffer into EditorServerInit packets until we've flushed the whole thing
|
||||
// Read the buffer into EditorServerLevelData packets until we've flushed the whole thing
|
||||
byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
|
||||
|
||||
while (byteStream.GetCurPos() < byteStream.GetLength())
|
||||
{
|
||||
MultiplayerEditorPackets::EditorServerInit editorServerInitPacket;
|
||||
auto& outBuffer = editorServerInitPacket.ModifyAssetData();
|
||||
MultiplayerEditorPackets::EditorServerLevelData editorServerLevelDataPacket;
|
||||
auto& outBuffer = editorServerLevelDataPacket.ModifyAssetData();
|
||||
|
||||
// Size the packet's buffer appropriately
|
||||
size_t readSize = outBuffer.GetCapacity();
|
||||
@@ -371,10 +371,10 @@ namespace Multiplayer
|
||||
// If we've run out of buffer, mark that we're done
|
||||
if (byteStream.GetCurPos() == byteStream.GetLength())
|
||||
{
|
||||
editorServerInitPacket.SetLastUpdate(true);
|
||||
editorServerLevelDataPacket.SetLastUpdate(true);
|
||||
}
|
||||
|
||||
connection->SendReliablePacket(editorServerInitPacket);
|
||||
connection->SendReliablePacket(editorServerLevelDataPacket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Multiplayer
|
||||
|
||||
//! MultiplayerEditorServerRequestBus::Handler
|
||||
//! @{
|
||||
void SendEditorServerInitPacket(AzNetworking::IConnection* connection) override;
|
||||
void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) override;
|
||||
//! @}
|
||||
|
||||
IEditor* m_editor = nullptr;
|
||||
|
||||
@@ -348,19 +348,7 @@ namespace PhysX
|
||||
LmbrCentral::BoxShapeComponentRequestsBus::EventResult(boxDimensions, GetEntityId(),
|
||||
&LmbrCentral::BoxShapeComponentRequests::GetBoxDimensions);
|
||||
|
||||
if (m_shapeType != ShapeType::Box)
|
||||
{
|
||||
m_shapeConfigs.clear();
|
||||
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::BoxShapeConfiguration>(boxDimensions));
|
||||
|
||||
m_shapeType = ShapeType::Box;
|
||||
}
|
||||
else
|
||||
{
|
||||
Physics::BoxShapeConfiguration& configuration =
|
||||
static_cast<Physics::BoxShapeConfiguration&>(*m_shapeConfigs.back());
|
||||
configuration = Physics::BoxShapeConfiguration(boxDimensions);
|
||||
}
|
||||
SetShapeConfig(ShapeType::Box, Physics::BoxShapeConfiguration(boxDimensions));
|
||||
|
||||
m_shapeConfigs.back()->m_scale = scale;
|
||||
m_geometryCache.m_boxDimensions = scale * boxDimensions;
|
||||
@@ -374,19 +362,7 @@ namespace PhysX
|
||||
const Physics::CapsuleShapeConfiguration& capsuleShapeConfig =
|
||||
Utils::ConvertFromLmbrCentralCapsuleConfig(lmbrCentralCapsuleShapeConfig);
|
||||
|
||||
if (m_shapeType != ShapeType::Capsule)
|
||||
{
|
||||
m_shapeConfigs.clear();
|
||||
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::CapsuleShapeConfiguration>(capsuleShapeConfig));
|
||||
|
||||
m_shapeType = ShapeType::Capsule;
|
||||
}
|
||||
else
|
||||
{
|
||||
Physics::CapsuleShapeConfiguration& configuration =
|
||||
static_cast<Physics::CapsuleShapeConfiguration&>(*m_shapeConfigs.back());
|
||||
configuration = capsuleShapeConfig;
|
||||
}
|
||||
SetShapeConfig(ShapeType::Capsule, capsuleShapeConfig);
|
||||
|
||||
m_shapeConfigs.back()->m_scale = scale;
|
||||
const float scalarScale = scale.GetMaxElement();
|
||||
@@ -400,19 +376,7 @@ namespace PhysX
|
||||
LmbrCentral::SphereShapeComponentRequestsBus::EventResult(radius, GetEntityId(),
|
||||
&LmbrCentral::SphereShapeComponentRequests::GetRadius);
|
||||
|
||||
if (m_shapeType != ShapeType::Sphere)
|
||||
{
|
||||
m_shapeConfigs.clear();
|
||||
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::SphereShapeConfiguration>(radius));
|
||||
|
||||
m_shapeType = ShapeType::Sphere;
|
||||
}
|
||||
else
|
||||
{
|
||||
Physics::SphereShapeConfiguration& configuration =
|
||||
static_cast<Physics::SphereShapeConfiguration&>(*m_shapeConfigs.back());
|
||||
configuration = Physics::SphereShapeConfiguration(radius);
|
||||
}
|
||||
SetShapeConfig(ShapeType::Sphere, Physics::SphereShapeConfiguration(radius));
|
||||
|
||||
m_shapeConfigs.back()->m_scale = scale;
|
||||
m_geometryCache.m_radius = scale.GetMaxElement() * radius;
|
||||
@@ -455,19 +419,7 @@ namespace PhysX
|
||||
|
||||
if (shapeConfig.has_value())
|
||||
{
|
||||
if (m_shapeType != ShapeType::Cylinder)
|
||||
{
|
||||
m_shapeConfigs.clear();
|
||||
m_shapeConfigs.push_back(AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(shapeConfig.value()));
|
||||
|
||||
m_shapeType = ShapeType::Cylinder;
|
||||
}
|
||||
else
|
||||
{
|
||||
Physics::CookedMeshShapeConfiguration& configuration =
|
||||
static_cast<Physics::CookedMeshShapeConfiguration&>(*m_shapeConfigs.back());
|
||||
configuration = Physics::CookedMeshShapeConfiguration(shapeConfig.value());
|
||||
}
|
||||
SetShapeConfig(ShapeType::Cylinder, shapeConfig.value());
|
||||
|
||||
CreateStaticEditorCollider();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/Component/NonUniformScaleBus.h>
|
||||
#include <AzFramework/Physics/Shape.h>
|
||||
@@ -90,12 +91,15 @@ namespace PhysX
|
||||
void UpdateBoxConfig(const AZ::Vector3& scale);
|
||||
void UpdateCapsuleConfig(const AZ::Vector3& scale);
|
||||
void UpdateSphereConfig(const AZ::Vector3& scale);
|
||||
void UpdateCylinderConfig(const AZ::Vector3& scale);
|
||||
void UpdatePolygonPrismDecomposition();
|
||||
void UpdatePolygonPrismDecomposition(const AZ::PolygonPrismPtr polygonPrismPtr);
|
||||
|
||||
void RefreshUiProperties();
|
||||
// Helper function to set a specific shape configuration
|
||||
template<typename ConfigType>
|
||||
void SetShapeConfig(ShapeType shapeType, const ConfigType& shapeConfig);
|
||||
|
||||
void UpdateCylinderConfig(const AZ::Vector3& scale);
|
||||
void RefreshUiProperties();
|
||||
|
||||
AZ::u32 OnSubdivisionCountChange();
|
||||
AZ::Crc32 SubdivisionCountVisibility();
|
||||
@@ -154,4 +158,28 @@ namespace PhysX
|
||||
AZ::NonUniformScaleChangedEvent::Handler m_nonUniformScaleChangedHandler; //!< Responds to changes in non-uniform scale.
|
||||
AZ::Vector3 m_currentNonUniformScale = AZ::Vector3::CreateOne(); //!< Caches the current non-uniform scale.
|
||||
};
|
||||
|
||||
template<typename ConfigType>
|
||||
void EditorShapeColliderComponent::SetShapeConfig(ShapeType shapeType, const ConfigType& shapeConfig)
|
||||
{
|
||||
if (m_shapeType != shapeType)
|
||||
{
|
||||
m_shapeConfigs.clear();
|
||||
m_shapeType = shapeType;
|
||||
}
|
||||
|
||||
if (m_shapeConfigs.empty())
|
||||
{
|
||||
m_shapeConfigs.emplace_back(AZStd::make_shared<ConfigType>(shapeConfig));
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(m_shapeConfigs.back()->GetShapeType() == shapeConfig.GetShapeType(),
|
||||
"Expected Physics shape configuration with shape type %d but found one with shape type %d.",
|
||||
static_cast<int>(shapeConfig.GetShapeType()), static_cast<int>(m_shapeConfigs.back()->GetShapeType()));
|
||||
ConfigType& configuration =
|
||||
static_cast<ConfigType&>(*m_shapeConfigs.back());
|
||||
configuration = shapeConfig;
|
||||
}
|
||||
}
|
||||
} // namespace PhysX
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace PhysX
|
||||
const float scaleFactor = (maxHeightBounds <= minHeightBounds) ? 1.0f : AZStd::numeric_limits<int16_t>::max() / halfBounds;
|
||||
const float heightScale{ 1.0f / scaleFactor };
|
||||
|
||||
const uint8_t physxMaximumMaterialIndex = 0x7f;
|
||||
[[maybe_unused]] const uint8_t physxMaximumMaterialIndex = 0x7f;
|
||||
|
||||
// Delete the cached heightfield object if it is there, and create a new one and save in the shape configuration
|
||||
heightfieldConfig.SetCachedNativeHeightfield(nullptr);
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace PhysXEditorTests
|
||||
|
||||
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullHeight_HandledGracefully)
|
||||
{
|
||||
ValidateInvalidEditorShapeColliderComponentParams(0.f, 1.f);
|
||||
ValidateInvalidEditorShapeColliderComponentParams(1.f, 0.f);
|
||||
}
|
||||
|
||||
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullRadiusAndNullHeight_HandledGracefully)
|
||||
@@ -338,6 +338,44 @@ namespace PhysXEditorTests
|
||||
ValidateInvalidEditorShapeColliderComponentParams(0.f, -1.f);
|
||||
}
|
||||
|
||||
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderSwitchingFromNullHeightToValidHeight_HandledGracefully)
|
||||
{
|
||||
// create an editor entity with a shape collider component and a cylinder shape component
|
||||
EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity");
|
||||
editorEntity->CreateComponent<PhysX::EditorShapeColliderComponent>();
|
||||
editorEntity->CreateComponent(LmbrCentral::EditorCylinderShapeComponentTypeId);
|
||||
editorEntity->Activate();
|
||||
|
||||
const float validRadius = 1.0f;
|
||||
const float nullHeight = 0.0f;
|
||||
const float validHeight = 1.0f;
|
||||
|
||||
LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(),
|
||||
&LmbrCentral::CylinderShapeComponentRequests::SetRadius, validRadius);
|
||||
|
||||
{
|
||||
UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid");
|
||||
UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body");
|
||||
|
||||
LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(),
|
||||
&LmbrCentral::CylinderShapeComponentRequests::SetHeight, nullHeight);
|
||||
|
||||
EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 1);
|
||||
EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid");
|
||||
UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body");
|
||||
|
||||
LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(),
|
||||
&LmbrCentral::CylinderShapeComponentRequests::SetHeight, validHeight);
|
||||
|
||||
EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 0);
|
||||
EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithBoxAndRigidBody_CorrectRuntimeComponents)
|
||||
{
|
||||
// create an editor entity with a shape collider component and a box shape component
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Terrain
|
||||
WorldSizeCount,
|
||||
};
|
||||
|
||||
WorldSize m_worldSize;
|
||||
WorldSize m_worldSize = WorldSize::_1024Meters;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-linux
|
||||
ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux TARGETS googletest PACKAGE_HASH 7b7ad330f369450c316a4c4592d17fbb4c14c731c95bd8f37757203e8c2bbc1b)
|
||||
ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-linux TARGETS GoogleBenchmark PACKAGE_HASH 4038878f337fc7e0274f0230f71851b385b2e0327c495fc3dd3d1c18a807928d)
|
||||
ly_associate_package(PACKAGE_NAME unwind-1.2.1-linux TARGETS unwind PACKAGE_HASH 3453265fb056e25432f611a61546a25f60388e315515ad39007b5925dd054a77)
|
||||
ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-linux TARGETS Qt PACKAGE_HASH 76b395897b941a173002845c7219a5f8a799e44b269ffefe8091acc048130f28)
|
||||
ly_associate_package(PACKAGE_NAME qt-5.15.2-rev6-linux TARGETS Qt PACKAGE_HASH a37bd9989f1e8fe57d94b98cbf9bd5c3caaea740e2f314e5162fa77300551531)
|
||||
ly_associate_package(PACKAGE_NAME libpng-1.6.37-rev1-linux TARGETS libpng PACKAGE_HASH 896451999f1de76375599aec4b34ae0573d8d34620d9ab29cc30b8739c265ba6)
|
||||
ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-linux TARGETS libsamplerate PACKAGE_HASH 41643c31bc6b7d037f895f89d8d8d6369e906b92eff42b0fe05ee6a100f06261)
|
||||
ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc)
|
||||
|
||||
@@ -502,7 +502,10 @@ def get_gem_json_data(gem_name: str = None, gem_path: str or pathlib.Path = None
|
||||
if gem_name and not gem_path:
|
||||
gem_path = get_registered(gem_name=gem_name, project_path=project_path)
|
||||
|
||||
return get_json_data('gem', gem_path, validation.valid_o3de_gem_json)
|
||||
if pathlib.Path(gem_path).is_file():
|
||||
return get_json_data_file(gem_path, 'gem', validation.valid_o3de_gem_json)
|
||||
else:
|
||||
return get_json_data('gem', gem_path, validation.valid_o3de_gem_json)
|
||||
|
||||
|
||||
def get_template_json_data(template_name: str = None, template_path: str or pathlib.Path = None,
|
||||
|
||||
+107
-74
@@ -13,7 +13,7 @@ import shutil
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import hashlib
|
||||
|
||||
from datetime import datetime
|
||||
from o3de import manifest, utils, validation
|
||||
|
||||
logger = logging.getLogger()
|
||||
@@ -27,6 +27,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path,
|
||||
return 1
|
||||
cache_folder = manifest.get_o3de_cache_folder()
|
||||
|
||||
repo_data = {}
|
||||
with file_name.open('r') as f:
|
||||
try:
|
||||
repo_data = json.load(f)
|
||||
@@ -34,83 +35,94 @@ def process_add_o3de_repo(file_name: str or pathlib.Path,
|
||||
logger.error(f'{file_name} failed to load: {str(e)}')
|
||||
return 1
|
||||
|
||||
# A repo may not contain all types of object.
|
||||
manifest_download_list = []
|
||||
with file_name.open('w') as f:
|
||||
try:
|
||||
manifest_download_list.append((repo_data['engines'], 'engine.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['projects'], 'project.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['gems'], 'gem.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['templates'], 'template.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['restricted'], 'restricted.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
time_now = datetime.now()
|
||||
# Convert to lower case because AM/PM is capitalized
|
||||
time_str = time_now.strftime('%d/%m/%Y %I:%M%p').lower()
|
||||
repo_data.update({'last_updated': time_str})
|
||||
f.write(json.dumps(repo_data, indent=4) + '\n')
|
||||
except Exception as e:
|
||||
logger.error(f'{file_name} failed to save: {str(e)}')
|
||||
return 1
|
||||
|
||||
for o3de_object_uris, manifest_json in manifest_download_list:
|
||||
# A repo may not contain all types of object.
|
||||
manifest_download_list = []
|
||||
try:
|
||||
manifest_download_list.append((repo_data['engines'], 'engine.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['projects'], 'project.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['gems'], 'gem.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['templates'], 'template.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
manifest_download_list.append((repo_data['restricted'], 'restricted.json'))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for o3de_object_uris, manifest_json in manifest_download_list:
|
||||
for o3de_object_uri in o3de_object_uris:
|
||||
manifest_json_uri = f'{o3de_object_uri}/{manifest_json}'
|
||||
manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode())
|
||||
cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
|
||||
if not cache_file.is_file():
|
||||
parsed_uri = urllib.parse.urlparse(manifest_json_uri)
|
||||
download_file_result = utils.download_file(parsed_uri, cache_file)
|
||||
if download_file_result != 0:
|
||||
return download_file_result
|
||||
|
||||
# Having a repo is also optional
|
||||
repo_list = []
|
||||
try:
|
||||
repo_list.append(repo_data['repos'])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for repo in repo_list:
|
||||
if repo not in repo_set:
|
||||
repo_set.add(repo)
|
||||
for o3de_object_uri in o3de_object_uris:
|
||||
manifest_json_uri = f'{o3de_object_uri}/{manifest_json}'
|
||||
manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode())
|
||||
parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json')
|
||||
manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
|
||||
cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
|
||||
if not cache_file.is_file():
|
||||
parsed_uri = urllib.parse.urlparse(manifest_json_uri)
|
||||
download_file_result = utils.download_file(parsed_uri, cache_file)
|
||||
if download_file_result != 0:
|
||||
return download_file_result
|
||||
if cache_file.is_file():
|
||||
cache_file.unlink()
|
||||
download_file_result = utils.download_file(parsed_uri, cache_file)
|
||||
if download_file_result != 0:
|
||||
return download_file_result
|
||||
|
||||
# Having a repo is also optional
|
||||
repo_list = []
|
||||
try:
|
||||
repo_list.append(repo_data['repos'])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for repo in repo_list:
|
||||
if repo not in repo_set:
|
||||
repo_set.add(repo)
|
||||
for o3de_object_uri in o3de_object_uris:
|
||||
parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json')
|
||||
manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
|
||||
cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
|
||||
if cache_file.is_file():
|
||||
cache_file.unlink()
|
||||
download_file_result = utils.download_file(parsed_uri, cache_file)
|
||||
if download_file_result != 0:
|
||||
return download_file_result
|
||||
|
||||
return process_add_o3de_repo(parsed_uri.geturl(), repo_set)
|
||||
return process_add_o3de_repo(parsed_uri.geturl(), repo_set)
|
||||
return 0
|
||||
|
||||
|
||||
def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list:
|
||||
def get_gem_json_paths_from_cached_repo(repo_uri: str) -> set:
|
||||
url = f'{repo_uri}/repo.json'
|
||||
repo_sha256 = hashlib.sha256(url.encode())
|
||||
cache_folder = manifest.get_o3de_cache_folder()
|
||||
cache_filename = cache_folder / str(repo_sha256.hexdigest() + '.json')
|
||||
|
||||
gem_list = []
|
||||
gem_set = set()
|
||||
|
||||
file_name = pathlib.Path(cache_filename).resolve()
|
||||
if not file_name.is_file():
|
||||
logger.error(f'Could not find cached repo json file for {repo_uri}')
|
||||
return gem_list
|
||||
return gem_set
|
||||
|
||||
with file_name.open('r') as f:
|
||||
try:
|
||||
repo_data = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f'{file_name} failed to load: {str(e)}')
|
||||
return gem_list
|
||||
return gem_set
|
||||
|
||||
# Get list of gems, then add all json paths to the list if they exist in the cache
|
||||
repo_gems = []
|
||||
@@ -125,10 +137,44 @@ def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list:
|
||||
manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode())
|
||||
cache_gem_json_filepath = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
|
||||
if cache_gem_json_filepath.is_file():
|
||||
logger.warn(f'Could not find cached gem json file for {o3de_object_uri} in repo {repo_uri}')
|
||||
gem_list.append(cache_gem_json_filepath)
|
||||
gem_set.add(cache_gem_json_filepath)
|
||||
else:
|
||||
logger.warn(f'Could not find cached gem json file {cache_gem_json_filepath} for {o3de_object_uri} in repo {repo_uri}')
|
||||
|
||||
return gem_list
|
||||
return gem_set
|
||||
|
||||
def get_gem_json_paths_from_all_cached_repos() -> set:
|
||||
json_data = manifest.load_o3de_manifest()
|
||||
gem_set = set()
|
||||
|
||||
for repo_uri in json_data['repos']:
|
||||
gem_set.update(get_gem_json_paths_from_cached_repo(repo_uri))
|
||||
|
||||
return gem_set
|
||||
|
||||
|
||||
def refresh_repo(repo_uri: str,
|
||||
cache_folder: str = None,
|
||||
repo_set: set = None) -> int:
|
||||
if not cache_folder:
|
||||
cache_folder = manifest.get_o3de_cache_folder()
|
||||
if not repo_set:
|
||||
repo_set = set()
|
||||
|
||||
parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json')
|
||||
repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
|
||||
cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json')
|
||||
|
||||
download_file_result = utils.download_file(parsed_uri, cache_file)
|
||||
if download_file_result != 0:
|
||||
return download_file_result
|
||||
|
||||
if not validation.valid_o3de_repo_json(cache_file):
|
||||
logger.error(f'Repo json {repo_uri} is not valid.')
|
||||
cache_file.unlink()
|
||||
return 1
|
||||
|
||||
return process_add_o3de_repo(cache_file, repo_set)
|
||||
|
||||
def refresh_repos() -> int:
|
||||
json_data = manifest.load_o3de_manifest()
|
||||
@@ -147,22 +193,9 @@ def refresh_repos() -> int:
|
||||
if repo_uri not in repo_set:
|
||||
repo_set.add(repo_uri)
|
||||
|
||||
parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json')
|
||||
repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
|
||||
cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json')
|
||||
if not cache_file.is_file():
|
||||
download_file_result = utils.download_file(parsed_uri, cache_file)
|
||||
if download_file_result != 0:
|
||||
return download_file_result
|
||||
|
||||
if not validation.valid_o3de_repo_json(cache_file):
|
||||
logger.error(f'Repo json {repo_uri} is not valid.')
|
||||
cache_file.unlink()
|
||||
return 1
|
||||
|
||||
last_failure = process_add_o3de_repo(cache_file, repo_set)
|
||||
if last_failure:
|
||||
result = last_failure
|
||||
last_failure = refresh_repo(repo_uri, cache_folder, repo_set)
|
||||
if last_failure:
|
||||
result = last_failure
|
||||
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user