Merge branch 'main' into Atom/dmcdiar/ATOM-15517
This commit is contained in:
@@ -13,6 +13,7 @@ AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortLambdasOnASingleLine: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
|
||||
@@ -613,6 +613,7 @@ namespace AzToolsFramework
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> Instance::DetachContainerEntity()
|
||||
{
|
||||
m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId());
|
||||
return AZStd::move(m_containerEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace AzToolsFramework
|
||||
using EntityAliasOptionalReference = AZStd::optional<AZStd::reference_wrapper<EntityAlias>>;
|
||||
using InstanceOptionalReference = AZStd::optional<AZStd::reference_wrapper<Instance>>;
|
||||
using InstanceOptionalConstReference = AZStd::optional<AZStd::reference_wrapper<const Instance>>;
|
||||
|
||||
using InstanceSet = AZStd::unordered_set<Instance*>;
|
||||
using InstanceSetConstReference = AZStd::optional<AZStd::reference_wrapper<const InstanceSet>>;
|
||||
using EntityOptionalReference = AZStd::optional<AZStd::reference_wrapper<AZ::Entity>>;
|
||||
@@ -85,6 +86,7 @@ namespace AzToolsFramework
|
||||
bool AddEntity(AZ::Entity& entity);
|
||||
bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias);
|
||||
AZStd::unique_ptr<AZ::Entity> DetachEntity(const AZ::EntityId& entityId);
|
||||
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
void DetachNestedEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
void RemoveNestedEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
|
||||
|
||||
@@ -182,7 +184,6 @@ namespace AzToolsFramework
|
||||
|
||||
void ClearEntities();
|
||||
|
||||
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
void RemoveEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
|
||||
|
||||
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
|
||||
@@ -191,24 +192,7 @@ namespace AzToolsFramework
|
||||
if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
|
||||
{
|
||||
previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
previousPatch.Accept(writer);
|
||||
QString previousPatchString(buffer.GetString());
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
AZ::EntityId entityId = entity->GetId();
|
||||
AZStd::string oldEntityAlias = oldEntityAliases[entityId];
|
||||
EntityAliasOptionalReference newEntityAlias = instanceToCreate->get().GetEntityAlias(entityId);
|
||||
AZ_Assert(
|
||||
newEntityAlias.has_value(),
|
||||
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
|
||||
static_cast<AZ::u64>(entityId));
|
||||
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
|
||||
}
|
||||
|
||||
previousPatch.Parse(previousPatchString.toUtf8().constData());
|
||||
UpdateLinkPatchesWithNewEntityAliases(previousPatch, oldEntityAliases, instanceToCreate->get());
|
||||
}
|
||||
|
||||
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
|
||||
@@ -243,11 +227,33 @@ namespace AzToolsFramework
|
||||
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
|
||||
AZStd::move(patch));
|
||||
|
||||
// Reset the transform of the container entity so that the new values aren't saved in the new prefab's dom.
|
||||
// The new values were saved in the link, so propagation will apply them correctly.
|
||||
{
|
||||
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
||||
|
||||
PrefabDom containerBeforeReset;
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(containerBeforeReset, *containerEntity);
|
||||
|
||||
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, AZ::EntityId());
|
||||
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
|
||||
|
||||
PrefabDom containerAfterReset;
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity);
|
||||
|
||||
// Update the state of the entity
|
||||
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(containerEntityId)));
|
||||
state->SetParent(undoBatch.GetUndoBatch());
|
||||
state->Capture(containerBeforeReset, containerAfterReset, containerEntityId);
|
||||
|
||||
state->Redo();
|
||||
}
|
||||
|
||||
// This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab.
|
||||
// We are doing this so that the changes in those enities are not queued up twice for propagation.
|
||||
// We are doing this so that the changes in those entities are not queued up twice for propagation.
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
||||
|
||||
|
||||
// Select Container Entity
|
||||
{
|
||||
auto selectionUndo = aznew SelectionCommand({containerEntityId}, "Select Prefab Container Entity");
|
||||
@@ -385,8 +391,8 @@ namespace AzToolsFramework
|
||||
|
||||
CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch));
|
||||
|
||||
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
||||
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
@@ -989,6 +995,123 @@ namespace AzToolsFramework
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
|
||||
{
|
||||
if (!containerEntityId.IsValid())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity."));
|
||||
}
|
||||
|
||||
if (IsLevelInstanceContainerEntity(containerEntityId))
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance."));
|
||||
}
|
||||
|
||||
InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId);
|
||||
if (owningInstance->get().GetContainerEntityId() != containerEntityId)
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity."));
|
||||
}
|
||||
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
||||
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:UndoCapture");
|
||||
|
||||
ScopedUndoBatch undoBatch("Detach Prefab");
|
||||
|
||||
InstanceOptionalReference getParentInstanceResult = owningInstance->get().GetParentInstance();
|
||||
AZ_Assert(getParentInstanceResult.has_value(), "Can't get parent Instance from Instance of given container entity.");
|
||||
|
||||
auto& parentInstance = getParentInstanceResult->get();
|
||||
const auto parentTemplateId = parentInstance.GetTemplateId();
|
||||
|
||||
{
|
||||
auto instancePtr = parentInstance.DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
||||
AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance.");
|
||||
|
||||
RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch());
|
||||
|
||||
Prefab::PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance);
|
||||
|
||||
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
|
||||
oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get());
|
||||
|
||||
auto containerEntityPtr = instancePtr->DetachContainerEntity();
|
||||
auto& containerEntity = *containerEntityPtr.release();
|
||||
auto editorPrefabComponent = containerEntity.FindComponent<EditorPrefabComponent>();
|
||||
containerEntity.Deactivate();
|
||||
const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent);
|
||||
AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed.");
|
||||
delete editorPrefabComponent;
|
||||
containerEntity.Activate();
|
||||
|
||||
const bool containerEntityAdded = parentInstance.AddEntity(containerEntity);
|
||||
AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed.");
|
||||
|
||||
EntityIdList entityIds;
|
||||
entityIds.emplace_back(containerEntity.GetId());
|
||||
|
||||
instancePtr->GetEntities(
|
||||
[&](AZStd::unique_ptr<AZ::Entity>& entityPtr)
|
||||
{
|
||||
oldEntityAliases.emplace(entityPtr->GetId(), instancePtr->GetEntityAlias(entityPtr->GetId())->get());
|
||||
return true;
|
||||
});
|
||||
|
||||
instancePtr->DetachEntities(
|
||||
[&](AZStd::unique_ptr<AZ::Entity> entityPtr)
|
||||
{
|
||||
auto& entity = *entityPtr.release();
|
||||
const bool entityAdded = parentInstance.AddEntity(entity);
|
||||
AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed.");
|
||||
|
||||
entityIds.emplace_back(entity.GetId());
|
||||
});
|
||||
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo");
|
||||
command->RunRedo();
|
||||
}
|
||||
|
||||
const auto instanceTemplateId = instancePtr->GetTemplateId();
|
||||
auto parentContainerEntityId = parentInstance.GetContainerEntityId();
|
||||
instancePtr->GetNestedInstances(
|
||||
[&](AZStd::unique_ptr<Instance>& nestedInstancePtr)
|
||||
{
|
||||
//get previous link patch
|
||||
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstancePtr->GetLinkId());
|
||||
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
|
||||
AZ_Assert(
|
||||
linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.",
|
||||
nestedInstancePtr->GetLinkId());
|
||||
|
||||
PrefabDom linkPatchesCopy;
|
||||
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
|
||||
|
||||
RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch());
|
||||
|
||||
UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance);
|
||||
|
||||
CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(),
|
||||
AZStd::move(linkPatchesCopy), true);
|
||||
});
|
||||
}
|
||||
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities,
|
||||
AZ::Vector3& translation, AZ::Quaternion& rotation)
|
||||
{
|
||||
@@ -1252,5 +1375,30 @@ namespace AzToolsFramework
|
||||
|
||||
stringToReplace.replace(oldAliasPathRef, newAliasPathRef);
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases(
|
||||
PrefabDom& linkPatch,
|
||||
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
|
||||
Instance& newParent)
|
||||
{
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
linkPatch.Accept(writer);
|
||||
QString previousPatchString(buffer.GetString());
|
||||
|
||||
for (const auto& [entityId, oldEntityAlias] : oldEntityAliases)
|
||||
{
|
||||
EntityAliasOptionalReference newEntityAlias = newParent.GetEntityAlias(entityId);
|
||||
AZ_Assert(
|
||||
newEntityAlias.has_value(),
|
||||
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
|
||||
static_cast<AZ::u64>(entityId));
|
||||
|
||||
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
|
||||
}
|
||||
|
||||
linkPatch.Parse(previousPatchString.toUtf8().constData());
|
||||
}
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace AzToolsFramework
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
|
||||
private:
|
||||
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
|
||||
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
|
||||
@@ -132,7 +134,12 @@ namespace AzToolsFramework
|
||||
bool IsCyclicalDependencyFound(
|
||||
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
|
||||
|
||||
void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias);
|
||||
void UpdateLinkPatchesWithNewEntityAliases(
|
||||
PrefabDom& linkPatch,
|
||||
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
|
||||
Instance& newParent);
|
||||
|
||||
static void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias);
|
||||
|
||||
static Instance* GetParentInstance(Instance* instance);
|
||||
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
|
||||
|
||||
@@ -150,6 +150,17 @@ namespace AzToolsFramework
|
||||
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and it's nested instances, adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* @param containerEntityId The container entity id of the instance to detach.
|
||||
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0;
|
||||
};
|
||||
|
||||
} // namespace Prefab
|
||||
|
||||
@@ -652,7 +652,8 @@ namespace AzToolsFramework
|
||||
if (instancesValue->get().FindMember(rapidjson::StringRef(instanceAlias.c_str())) == instancesValue->get().MemberEnd())
|
||||
{
|
||||
instancesValue->get().AddMember(
|
||||
rapidjson::StringRef(instanceAlias.c_str()), PrefabDomValue(), targetTemplateDom.GetAllocator());
|
||||
rapidjson::Value(instanceAlias.c_str(), targetTemplateDom.GetAllocator()), PrefabDomValue(),
|
||||
targetTemplateDom.GetAllocator());
|
||||
}
|
||||
|
||||
Template& sourceTemplate = sourceTemplateRef->get();
|
||||
|
||||
+29
@@ -237,6 +237,24 @@ namespace AzToolsFramework
|
||||
{
|
||||
deleteAction->setDisabled(true);
|
||||
}
|
||||
|
||||
// Detach Prefab
|
||||
if (selectedEntities.size() == 1)
|
||||
{
|
||||
AZ::EntityId selectedEntity = selectedEntities[0];
|
||||
|
||||
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) &&
|
||||
!s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity))
|
||||
{
|
||||
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
|
||||
QObject::connect(
|
||||
detachPrefabAction, &QAction::triggered, detachPrefabAction,
|
||||
[this, selectedEntity]
|
||||
{
|
||||
ContextMenu_DetachPrefab(selectedEntity);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const
|
||||
@@ -392,6 +410,17 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity)
|
||||
{
|
||||
PrefabOperationResult detachPrefabResult =
|
||||
s_prefabPublicInterface->DetachPrefab(containerEntity);
|
||||
|
||||
if (!detachPrefabResult.IsSuccess())
|
||||
{
|
||||
WarnUserOfError("Detach Prefab error", detachPrefabResult.GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace AzToolsFramework
|
||||
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_DeleteSelected();
|
||||
static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity);
|
||||
|
||||
// Prompt and resolve dialogs
|
||||
static bool QueryUserForPrefabSaveLocation(
|
||||
|
||||
+2
-1
@@ -969,7 +969,8 @@ namespace AzToolsFramework
|
||||
{
|
||||
// Build up components to display
|
||||
SharedComponentArray sharedComponentArray;
|
||||
BuildSharedComponentArray(sharedComponentArray, selectionEntityTypeInfo != SelectionEntityTypeInfo::OnlyStandardEntities);
|
||||
BuildSharedComponentArray(sharedComponentArray,
|
||||
!(selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyStandardEntities || selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyPrefabEntities));
|
||||
|
||||
if (sharedComponentArray.size() == 0)
|
||||
{
|
||||
|
||||
+11
-1
@@ -2471,7 +2471,17 @@ namespace AzToolsFramework
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
AddAction(
|
||||
m_actions, { QKeySequence(Qt::Key_U) },
|
||||
/*ID_VIEWPORTUI_VISIBLE=*/50040, "Toggle ViewportUI", "Hide/Unhide Viewport UI",
|
||||
[this]()
|
||||
{
|
||||
SetViewportUiClusterVisible(m_transformModeClusterId, !m_viewportUiVisible);
|
||||
SetViewportUiClusterVisible(m_spaceCluster.m_spaceClusterId, !m_viewportUiVisible);
|
||||
m_viewportUiVisible = !m_viewportUiVisible;
|
||||
});
|
||||
|
||||
EditorMenuRequestBus::Broadcast(&EditorMenuRequests::RestoreEditMenuToDefault);
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -306,6 +306,7 @@ namespace AzToolsFramework
|
||||
AzFramework::ClickDetector m_clickDetector; //!< Detect different types of mouse click.
|
||||
AzFramework::CursorState m_cursorState; //!< Track the mouse position and delta movement each frame.
|
||||
SpaceCluster m_spaceCluster; //!< Related viewport ui state for controlling the current reference space.
|
||||
bool m_viewportUiVisible = true; //!< Used to hide/show the viewport ui elements.
|
||||
};
|
||||
|
||||
//! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by
|
||||
|
||||
@@ -75,14 +75,14 @@
|
||||
<widget class="QSvgWidget" name="m_logo" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>161</width>
|
||||
<height>49</height>
|
||||
<width>175</width>
|
||||
<height>66</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>161</width>
|
||||
<height>49</height>
|
||||
<width>175</width>
|
||||
<height>66</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
|
||||
@@ -177,6 +177,11 @@ ly_add_target(
|
||||
Legacy::EditorLib
|
||||
ProjectManager
|
||||
)
|
||||
set_property(SOURCE
|
||||
CryEdit.cpp
|
||||
APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor"
|
||||
)
|
||||
ly_add_translations(
|
||||
TARGETS Editor
|
||||
PREFIX Translations
|
||||
@@ -186,15 +191,8 @@ ly_add_translations(
|
||||
)
|
||||
ly_add_dependencies(Editor AssetProcessor)
|
||||
|
||||
if(TARGET Editor)
|
||||
set_property(SOURCE
|
||||
CryEdit.cpp
|
||||
APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor"
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to Editor as the target doesn't exist anymore."
|
||||
" Perhaps it has been renamed")
|
||||
if(LY_FIRST_PROJECT_PATH)
|
||||
set_property(TARGET Editor APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"")
|
||||
endif()
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -369,3 +369,4 @@
|
||||
#define ID_TOOLBAR_WIDGET_SPACER_RIGHT 50013
|
||||
#define ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL 50014
|
||||
#define ID_TOOLBAR_WIDGET_LAST 50020
|
||||
#define ID_VIEWPORTUI_VISIBLE 50040
|
||||
|
||||
@@ -42,14 +42,14 @@
|
||||
<widget class="QSvgWidget" name="m_logo" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>161</width>
|
||||
<height>49</height>
|
||||
<width>175</width>
|
||||
<height>66</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>161</width>
|
||||
<height>50</height>
|
||||
<width>175</width>
|
||||
<height>66</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="323px" height="98px" viewBox="0 0 323 98" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Group 12</title>
|
||||
<defs>
|
||||
<polygon id="path-1" points="0 97.741 322.084 97.741 322.084 0 0 0"></polygon>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-12" transform="translate(0.000000, 0.000000)">
|
||||
<path d="M99.7068,20.425 C91.1008,11.686 79.6658,6.841 67.5068,6.782 L67.2838,6.781 C62.9678,6.781 58.7408,7.396 54.6908,8.566 L54.6908,25.339 C58.5158,23.54 62.6988,22.563 67.0218,22.517 C67.1388,22.516 67.2538,22.515 67.3698,22.515 C75.1318,22.515 82.4608,25.519 88.0388,30.996 C93.7828,36.635 96.9708,44.124 97.0168,52.084 C97.0628,60.037 93.9658,67.554 88.2968,73.251 C82.6908,78.884 75.2578,82 67.3558,82.025 L67.2718,82.025 C59.4228,82.025 51.9878,78.959 46.3368,73.393 C40.6918,67.833 37.5578,60.397 37.5108,52.453 C37.4888,48.659 38.1798,44.975 39.5088,41.546 L23.0748,41.546 C19.4908,56.362 23.4348,72.648 34.9328,84.219 C43.5408,92.882 54.9608,97.683 67.0878,97.738 L67.3028,97.738 L67.3058,97.738 C79.3458,97.738 90.7008,93.045 99.2768,84.524 C107.8718,75.984 112.6488,64.62 112.7288,52.524 C112.8088,40.435 108.1838,29.035 99.7068,20.425" id="Fill-1" fill="#FFFFFF"></path>
|
||||
<path d="M175.6326,27.8629 C175.6326,33.3889 173.9586,38.0879 170.6116,41.9599 C167.2646,45.8319 162.5656,48.4939 156.5146,49.9459 L156.5146,50.3089 C163.6536,51.1969 169.0586,53.3629 172.7296,56.8129 C176.3996,60.2619 178.2356,64.9099 178.2356,70.7579 C178.2356,79.2689 175.1496,85.8939 168.9786,90.6319 C162.8076,95.3719 153.9936,97.7409 142.5386,97.7409 C132.9386,97.7409 124.4286,96.1489 117.0076,92.9609 L117.0076,77.0489 C120.4356,78.7839 124.2076,80.1959 128.3216,81.2839 C132.4356,82.3729 136.5096,82.9179 140.5426,82.9179 C146.7146,82.9179 151.2716,81.8699 154.2156,79.7719 C157.1596,77.6749 158.6326,74.3079 158.6326,69.6679 C158.6326,65.5139 156.9386,62.5699 153.5506,60.8349 C150.1626,59.1009 144.7576,58.2329 137.3366,58.2329 L130.6206,58.2329 L130.6206,43.8939 L137.4576,43.8939 C144.3146,43.8939 149.3246,42.9979 152.4916,41.2019 C155.6576,39.4079 157.2406,36.3319 157.2406,31.9759 C157.2406,25.2809 153.0456,21.9329 144.6566,21.9329 C141.7526,21.9329 138.7986,22.4169 135.7936,23.3849 C132.7886,24.3529 129.4506,26.0279 125.7806,28.4069 L117.1306,15.5199 C125.1956,9.7119 134.8166,6.8079 145.9886,6.8079 C155.1446,6.8079 162.3736,8.6639 167.6786,12.3739 C172.9806,16.0869 175.6326,21.2499 175.6326,27.8629" id="Fill-3" fill="#FFFFFF"></path>
|
||||
<path d="M241.8563,51.9425 C241.8563,32.9455 233.4653,23.4465 216.6883,23.4465 L206.7053,23.4465 L206.7053,81.0435 L214.7523,81.0435 C232.8213,81.0435 241.8563,71.3435 241.8563,51.9425 M261.3363,51.4595 C261.3363,66.0195 257.1933,77.1715 248.9043,84.9165 C240.6153,92.6605 228.6463,96.5325 212.9973,96.5325 L187.9503,96.5325 L187.9503,8.0815 L215.7193,8.0815 C230.1593,8.0815 241.3723,11.8925 249.3583,19.5155 C257.3433,27.1365 261.3363,37.7855 261.3363,51.4595" id="Fill-5" fill="#FFFFFF"></path>
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Clip-8"></g>
|
||||
<polygon id="Fill-7" fill="#FFFFFF" mask="url(#mask-2)" points="23.185 30.421 45.046 30.421 45.046 8.56 23.185 8.56"></polygon>
|
||||
<polygon id="Fill-9" fill="#FFFFFF" mask="url(#mask-2)" points="5.251 9.038 14.289 9.038 14.289 0 5.251 0"></polygon>
|
||||
<polygon id="Fill-10" fill="#FFFFFF" mask="url(#mask-2)" points="0 36.195 14.18 36.195 14.18 22.015 0 22.015"></polygon>
|
||||
<polygon id="Fill-11" fill="#FFFFFF" mask="url(#mask-2)" points="322.0838 96.4337 271.0538 96.4337 271.0538 7.8287 322.0838 7.8287 322.0838 23.2227 289.8418 23.2227 289.8418 42.6767 319.8418 42.6767 319.8418 58.0707 289.8418 58.0707 289.8418 80.9187 322.0838 80.9187"></polygon>
|
||||
<svg width="350px" height="133px" viewBox="0 0 350 133" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Artboard</title>
|
||||
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="O3DE-Logo-with-WorkMark-REV-Mono" transform="translate(-0.060000, 0.000000)" fill="#FFFFFF">
|
||||
<g id="Group-3" transform="translate(25.446000, 117.517000)">
|
||||
<path d="M1.833,7.614 C1.833,9.618 2.262,11.139 3.121,12.175 C3.979,13.211 5.226,13.729 6.863,13.729 C8.511,13.729 9.758,13.212 10.599,12.18 C11.441,11.146 11.862,9.625 11.862,7.614 C11.862,5.623 11.443,4.113 10.604,3.083 C9.766,2.055 8.525,1.539 6.883,1.539 C5.234,1.539 3.979,2.057 3.121,3.093 C2.262,4.129 1.833,5.637 1.833,7.614 Z M13.694,7.614 C13.694,9.983 13.092,11.846 11.887,13.202 C10.683,14.559 9.007,15.237 6.863,15.237 C4.67,15.237 2.979,14.571 1.787,13.237 C0.596,11.904 3.55271368e-15,10.023 3.55271368e-15,7.594 C3.55271368e-15,5.184 0.597,3.315 1.792,1.989 C2.986,0.663 4.683,0 6.883,0 C9.022,0 10.692,0.676 11.892,2.025 C13.095,3.375 13.694,5.238 13.694,7.614 L13.694,7.614 Z" id="Fill-1"></path>
|
||||
</g>
|
||||
<path d="M55.659,125.252 L57.216,125.252 C58.75,125.252 59.86,125.005 60.546,124.513 C61.231,124.02 61.574,123.231 61.574,122.144 C61.574,121.165 61.251,120.436 60.606,119.956 C59.961,119.478 58.958,119.239 57.593,119.239 L55.659,119.239 L55.659,125.252 Z M63.366,122.063 C63.366,123.561 62.852,124.714 61.823,125.521 C60.795,126.327 59.324,126.731 57.41,126.731 L55.659,126.731 L55.659,132.551 L53.927,132.551 L53.927,117.749 L57.786,117.749 C61.506,117.749 63.366,119.188 63.366,122.063 L63.366,122.063 Z" id="Fill-4"></path>
|
||||
<polyline id="Fill-5" points="86.247 132.551 77.949 132.551 77.949 117.749 86.247 117.749 86.247 119.279 79.681 119.279 79.681 124.047 85.849 124.047 85.849 125.566 79.681 125.566 79.681 131.013 86.247 131.013 86.247 132.551"></polyline>
|
||||
<g id="Group-9" transform="translate(101.016000, 117.537000)">
|
||||
<path d="M11.628,15.014 L9.651,15.014 L1.517,2.592 L1.436,2.592 C1.544,4.05 1.599,5.387 1.599,6.602 L1.599,15.014 L-1.42108547e-14,15.014 L-1.42108547e-14,0.212 L1.954,0.212 L10.069,12.584 L10.151,12.584 C10.137,12.403 10.106,11.817 10.059,10.828 C10.011,9.839 9.995,9.133 10.008,8.707 L10.008,0.212 L11.628,0.212 L11.628,15.014" id="Fill-6"></path>
|
||||
<path d="M56.235,3.696 C56.235,4.64 55.968,5.413 55.436,6.014 C54.902,6.616 54.147,7.016 53.169,7.219 L53.169,7.3 C54.364,7.449 55.251,7.827 55.828,8.434 C56.404,9.041 56.693,9.837 56.693,10.823 C56.693,12.234 56.201,13.319 55.216,14.079 C54.232,14.837 52.834,15.217 51.022,15.217 C50.234,15.217 49.513,15.159 48.858,15.039 C48.203,14.922 47.567,14.714 46.949,14.417 L46.949,12.818 C47.594,13.135 48.281,13.376 49.011,13.541 C49.741,13.707 50.431,13.789 51.083,13.789 C53.655,13.789 54.942,12.788 54.942,10.783 C54.942,8.987 53.522,8.089 50.685,8.089 L49.219,8.089 L49.219,6.642 L50.707,6.642 C51.866,6.642 52.787,6.387 53.465,5.877 C54.144,5.369 54.483,4.661 54.483,3.756 C54.483,3.034 54.234,2.467 53.735,2.055 C53.236,1.644 52.559,1.438 51.704,1.438 C51.052,1.438 50.438,1.526 49.861,1.702 C49.284,1.877 48.626,2.201 47.886,2.673 L47.031,1.539 C47.642,1.06 48.345,0.683 49.144,0.411 C49.94,0.136 50.78,1.42108547e-14 51.663,1.42108547e-14 C53.109,1.42108547e-14 54.232,0.33 55.033,0.987 C55.834,1.646 56.235,2.548 56.235,3.696" id="Fill-8"></path>
|
||||
</g>
|
||||
<path d="M182.497,125.07 C182.497,123.14 182.009,121.685 181.035,120.706 C180.061,119.727 178.614,119.239 176.693,119.239 L174.178,119.239 L174.178,131.064 L176.286,131.064 C178.349,131.064 179.9,130.56 180.939,129.55 C181.976,128.541 182.497,127.048 182.497,125.07 Z M184.329,125.009 C184.329,127.452 183.662,129.321 182.329,130.613 C180.995,131.906 179.076,132.551 176.571,132.551 L172.447,132.551 L172.447,117.749 L177.008,117.749 C179.323,117.749 181.122,118.388 182.405,119.663 C183.687,120.94 184.329,122.721 184.329,125.009 L184.329,125.009 Z" id="Fill-10"></path>
|
||||
<polyline id="Fill-11" points="228.264 132.551 219.965 132.551 219.965 117.749 228.264 117.749 228.264 119.279 221.697 119.279 221.697 124.047 227.867 124.047 227.867 125.566 221.697 125.566 221.697 131.013 228.264 131.013 228.264 132.551"></polyline>
|
||||
<g id="Group-15" transform="translate(243.032000, 117.537000)">
|
||||
<path d="M11.629,15.014 L9.652,15.014 L1.517,2.592 L1.436,2.592 C1.545,4.05 1.599,5.387 1.599,6.602 L1.599,15.014 L2.84217094e-14,15.014 L2.84217094e-14,0.212 L1.955,0.212 L10.07,12.584 L10.151,12.584 C10.138,12.403 10.107,11.817 10.059,10.828 C10.011,9.839 9.996,9.133 10.008,8.707 L10.008,0.212 L11.629,0.212 L11.629,15.014" id="Fill-12"></path>
|
||||
<path d="M33.736,7.259 L38.796,7.259 L38.796,14.458 C38.008,14.707 37.208,14.897 36.393,15.025 C35.578,15.154 34.634,15.217 33.563,15.217 C31.309,15.217 29.554,14.551 28.299,13.217 C27.044,11.884 26.415,10.017 26.415,7.614 C26.415,6.076 26.726,4.727 27.346,3.569 C27.968,2.413 28.862,1.528 30.03,0.916 C31.197,0.306 32.564,1.42108547e-14 34.133,1.42108547e-14 C35.721,1.42108547e-14 37.201,0.29 38.573,0.872 L37.9,2.39 C36.555,1.823 35.263,1.539 34.021,1.539 C32.209,1.539 30.794,2.076 29.776,3.149 C28.757,4.222 28.248,5.71 28.248,7.614 C28.248,9.612 28.738,11.126 29.719,12.16 C30.7,13.192 32.14,13.709 34.042,13.709 C35.073,13.709 36.081,13.59 37.066,13.354 L37.066,8.798 L33.736,8.798 L33.736,7.259" id="Fill-14"></path>
|
||||
</g>
|
||||
<polygon id="Fill-16" points="296.871 132.551 298.602 132.551 298.602 117.749 296.871 117.749"></polygon>
|
||||
<path d="M325.781,132.551 L323.805,132.551 L315.67,120.129 L315.588,120.129 C315.698,121.587 315.751,122.924 315.751,124.139 L315.751,132.551 L314.153,132.551 L314.153,117.749 L316.108,117.749 L324.223,130.121 L324.304,130.121 C324.29,129.94 324.259,129.354 324.212,128.365 C324.164,127.376 324.148,126.67 324.162,126.244 L324.162,117.749 L325.781,117.749 L325.781,132.551" id="Fill-17"></path>
|
||||
<polyline id="Fill-18" points="349.64 132.551 341.341 132.551 341.341 117.749 349.64 117.749 349.64 119.279 343.073 119.279 343.073 124.047 349.243 124.047 349.243 125.566 343.073 125.566 343.073 131.013 349.64 131.013 349.64 132.551"></polyline>
|
||||
<path d="M187.562,26.549 C187.562,32.293 185.822,37.178 182.341,41.203 C178.861,45.228 173.977,47.995 167.687,49.505 L167.687,49.882 C175.109,50.805 180.727,53.058 184.543,56.643 C188.358,60.228 190.266,65.061 190.266,71.14 C190.266,79.989 187.059,86.875 180.644,91.801 C174.228,96.728 165.067,99.191 153.159,99.191 C143.18,99.191 134.332,97.535 126.618,94.222 L126.618,77.681 C130.181,79.485 134.102,80.951 138.379,82.083 C142.656,83.216 146.891,83.781 151.083,83.781 C157.498,83.781 162.236,82.693 165.297,80.511 C168.358,78.331 169.889,74.831 169.889,70.008 C169.889,65.69 168.128,62.63 164.606,60.825 C161.083,59.023 155.464,58.121 147.75,58.121 L140.769,58.121 L140.769,43.216 L147.876,43.216 C155.004,43.216 160.213,42.283 163.505,40.417 C166.796,38.551 168.442,35.354 168.442,30.825 C168.442,23.865 164.081,20.385 155.36,20.385 C152.341,20.385 149.269,20.888 146.146,21.894 C143.022,22.901 139.552,24.642 135.737,27.115 L126.744,13.718 C135.129,7.68 145.129,4.661 156.744,4.661 C166.262,4.661 173.777,6.591 179.291,10.448 C184.804,14.305 187.562,19.673 187.562,26.549" id="Fill-19"></path>
|
||||
<path d="M261.093,51.538 C261.093,31.236 252.127,21.084 234.196,21.084 L223.527,21.084 L223.527,82.639 L232.126,82.639 C251.437,82.639 261.093,72.273 261.093,51.538 Z M281.913,51.021 C281.913,66.583 277.484,78.5 268.625,86.777 C259.768,95.053 246.976,99.191 230.251,99.191 L203.483,99.191 L203.483,4.661 L233.162,4.661 C248.592,4.661 260.576,8.735 269.111,16.882 C277.646,25.029 281.913,36.408 281.913,51.021 L281.913,51.021 Z" id="Fill-20"></path>
|
||||
<polyline id="Fill-21" points="349.64 99.191 295.198 99.191 295.198 4.661 349.64 4.661 349.64 21.084 315.242 21.084 315.242 41.84 347.247 41.84 347.247 58.262 315.242 58.262 315.242 82.639 349.64 82.639 349.64 99.191"></polyline>
|
||||
<path d="M72.71,4.661 C68.177,4.661 63.799,5.313 59.648,6.505 L59.648,24.983 C63.596,23.066 68.027,21.988 72.71,21.988 C89.245,21.988 102.648,35.392 102.648,51.926 C102.648,68.46 89.245,81.865 72.71,81.865 C56.176,81.865 42.773,68.46 42.773,51.926 C42.773,47.99 43.539,44.237 44.92,40.794 L26.777,40.794 C25.914,44.365 25.446,48.09 25.446,51.926 C25.446,78.03 46.607,99.191 72.71,99.191 C98.814,99.191 119.976,78.03 119.976,51.926 C119.976,25.823 98.814,4.661 72.71,4.661" id="Fill-22"></path>
|
||||
<g id="Group-27">
|
||||
<polyline id="Fill-23" points="53.023 29.655 30.237 34.445 25.447 11.66 48.232 6.869 53.023 29.655"></polyline>
|
||||
<polygon id="Fill-25" points="11.806 9.313 21.12 9.313 21.12 0 11.806 0"></polygon>
|
||||
<polyline id="Fill-26" points="14.761 31.326 0 27.533 3.793 12.773 18.554 16.566 14.761 31.326"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 8.9 KiB |
@@ -125,6 +125,10 @@ ly_add_target(
|
||||
AZ::AssetProcessorBatch.Static
|
||||
)
|
||||
|
||||
if(LY_FIRST_PROJECT_PATH)
|
||||
set_property(TARGET AssetProcessor AssetProcessorBatch APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"")
|
||||
endif()
|
||||
|
||||
# Adds the AssetProcessorBatch target as a C preprocessor define so that it can be used as a Settings Registry
|
||||
# specialization in order to look up the generated .setreg which contains the dependencies
|
||||
# specified for the target.
|
||||
|
||||
@@ -25,7 +25,6 @@ ly_add_target(
|
||||
OUTPUT_NAME o3de
|
||||
NAMESPACE AZ
|
||||
AUTOMOC
|
||||
AUTOUIC
|
||||
AUTORCC
|
||||
FILES_CMAKE
|
||||
project_manager_files.cmake
|
||||
|
||||
@@ -232,6 +232,18 @@ QTabBar::tab:pressed
|
||||
margin-left:30px;
|
||||
}
|
||||
|
||||
#projectSettingsTab::tab-bar {
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
#projectSettingsTabBar::tab {
|
||||
height:50px;
|
||||
}
|
||||
|
||||
#projectSettingsTopFrame {
|
||||
background-color:#1E252F;
|
||||
}
|
||||
|
||||
/************** Projects **************/
|
||||
#firstTimeContent > #titleLabel {
|
||||
font-size:60px;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <NewProjectSettingsScreen.h>
|
||||
#include <ScreenHeaderWidget.h>
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
@@ -42,9 +41,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
m_stack = new QStackedWidget(this);
|
||||
m_stack->setObjectName("body");
|
||||
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding));
|
||||
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
|
||||
m_stack->addWidget(new NewProjectSettingsScreen());
|
||||
m_stack->addWidget(new GemCatalogScreen());
|
||||
m_gemCatalog = new GemCatalogScreen();
|
||||
m_stack->addWidget(m_gemCatalog);
|
||||
vLayout->addWidget(m_stack);
|
||||
|
||||
QDialogButtonBox* backNextButtons = new QDialogButtonBox();
|
||||
@@ -88,6 +88,7 @@ namespace O3DE::ProjectManager
|
||||
emit GotoPreviousScreenRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::HandleNextButton()
|
||||
{
|
||||
ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
|
||||
@@ -106,6 +107,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
m_projectInfo = newProjectScreen->GetProjectInfo();
|
||||
m_projectTemplatePath = newProjectScreen->GetProjectTemplatePath();
|
||||
|
||||
// The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog.
|
||||
m_gemCatalog->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +133,9 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project."));
|
||||
}
|
||||
|
||||
// Enable/disable gems for the newly created project.
|
||||
m_gemCatalog->EnableDisableGemsForProject(m_projectInfo.m_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <ProjectInfo.h>
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
|
||||
@@ -48,6 +49,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QString m_projectTemplatePath;
|
||||
ProjectInfo m_projectInfo;
|
||||
|
||||
GemCatalogScreen* m_gemCatalog = nullptr;
|
||||
};
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemListHeaderWidget.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
//#define USE_TESTGEMDATA
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -29,47 +28,32 @@ namespace O3DE::ProjectManager
|
||||
: ScreenWidget(parent)
|
||||
{
|
||||
m_gemModel = new GemModel(this);
|
||||
GemSortFilterProxyModel* proxyModel = new GemSortFilterProxyModel(m_gemModel, this);
|
||||
m_proxModel = new GemSortFilterProxyModel(m_gemModel, this);
|
||||
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
vLayout->setMargin(0);
|
||||
vLayout->setSpacing(0);
|
||||
setLayout(vLayout);
|
||||
|
||||
GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(proxyModel);
|
||||
GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(m_proxModel);
|
||||
vLayout->addWidget(headerWidget);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setMargin(0);
|
||||
vLayout->addLayout(hLayout);
|
||||
|
||||
m_gemListView = new GemListView(proxyModel, proxyModel->GetSelectionModel(), this);
|
||||
m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this);
|
||||
m_gemInspector = new GemInspector(m_gemModel, this);
|
||||
m_gemInspector->setFixedWidth(320);
|
||||
m_gemInspector->setFixedWidth(240);
|
||||
|
||||
// Start: Temporary gem test data
|
||||
#ifdef USE_TESTGEMDATA
|
||||
QVector<GemInfo> testGemData = GenerateTestData();
|
||||
for (const GemInfo& gemInfo : testGemData)
|
||||
{
|
||||
m_gemModel->AddGem(gemInfo);
|
||||
}
|
||||
#else
|
||||
// End: Temporary gem test data
|
||||
auto result = PythonBindingsInterface::Get()->GetGems();
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
for (auto gemInfo : result.GetValue())
|
||||
{
|
||||
m_gemModel->AddGem(gemInfo);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
QWidget* filterWidget = new QWidget(this);
|
||||
filterWidget->setFixedWidth(240);
|
||||
m_filterWidgetLayout = new QVBoxLayout();
|
||||
m_filterWidgetLayout->setMargin(0);
|
||||
m_filterWidgetLayout->setSpacing(0);
|
||||
filterWidget->setLayout(m_filterWidgetLayout);
|
||||
|
||||
GemFilterWidget* filterWidget = new GemFilterWidget(proxyModel);
|
||||
filterWidget->setFixedWidth(250);
|
||||
|
||||
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(proxyModel);
|
||||
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel);
|
||||
|
||||
QVBoxLayout* middleVLayout = new QVBoxLayout();
|
||||
middleVLayout->setMargin(0);
|
||||
@@ -80,98 +64,111 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addWidget(filterWidget);
|
||||
hLayout->addLayout(middleVLayout);
|
||||
hLayout->addWidget(m_gemInspector);
|
||||
|
||||
proxyModel->InvalidateFilter();
|
||||
}
|
||||
|
||||
QVector<GemInfo> GemCatalogScreen::GenerateTestData()
|
||||
void GemCatalogScreen::ReinitForProject(const QString& projectPath, bool isNewProject)
|
||||
{
|
||||
QVector<GemInfo> result;
|
||||
m_gemModel->clear();
|
||||
FillModel(projectPath, isNewProject);
|
||||
|
||||
GemInfo gem("EMotion FX",
|
||||
"O3DE Foundation",
|
||||
"EMFX is a real-time character animation system. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
(GemInfo::Android | GemInfo::iOS | GemInfo::macOS | GemInfo::Windows | GemInfo::Linux),
|
||||
true);
|
||||
gem.m_directoryLink = "C:/";
|
||||
gem.m_documentationLink = "http://www.amazon.com";
|
||||
gem.m_dependingGemUuids = QStringList({"EMotionFX", "Atom"});
|
||||
gem.m_conflictingGemUuids = QStringList({"Vegetation", "Camera", "ScriptCanvas", "CloudCanvas", "Networking"});
|
||||
gem.m_types = (GemInfo::Code | GemInfo::Asset);
|
||||
gem.m_version = "v1.01";
|
||||
gem.m_lastUpdatedDate = "24th April 2021";
|
||||
gem.m_binarySizeInKB = 40;
|
||||
gem.m_features = QStringList({"Animation", "Assets", "Physics"});
|
||||
gem.m_gemOrigin = GemInfo::O3DEFoundation;
|
||||
result.push_back(gem);
|
||||
if (m_filterWidget)
|
||||
{
|
||||
m_filterWidget->hide();
|
||||
m_filterWidget->deleteLater();
|
||||
}
|
||||
|
||||
gem.m_name = "Atom";
|
||||
gem.m_creator = "O3DE Seattle";
|
||||
gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
|
||||
gem.m_platforms = (GemInfo::Android | GemInfo::Windows | GemInfo::Linux | GemInfo::macOS);
|
||||
gem.m_isAdded = true;
|
||||
gem.m_directoryLink = "C:/";
|
||||
gem.m_documentationLink = "https://aws.amazon.com/gametech/";
|
||||
gem.m_dependingGemUuids = QStringList({"EMotionFX", "Core", "AudioSystem", "Camera", "Particles"});
|
||||
gem.m_conflictingGemUuids = QStringList({"CloudCanvas", "NovaNet"});
|
||||
gem.m_version = "v2.31";
|
||||
gem.m_lastUpdatedDate = "24th November 2020";
|
||||
gem.m_features = QStringList({"Assets", "Rendering", "UI", "VR", "Debug", "Environment"});
|
||||
gem.m_binarySizeInKB = 2087;
|
||||
result.push_back(gem);
|
||||
m_filterWidget = new GemFilterWidget(m_proxModel);
|
||||
m_filterWidgetLayout->addWidget(m_filterWidget);
|
||||
|
||||
gem.m_name = "Physics";
|
||||
gem.m_creator = "O3DE London";
|
||||
gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
gem.m_platforms = (GemInfo::Android | GemInfo::Linux | GemInfo::macOS);
|
||||
gem.m_isAdded = true;
|
||||
gem.m_directoryLink = "C:/";
|
||||
gem.m_documentationLink = "https://aws.amazon.com/gametech/";
|
||||
gem.m_dependingGemUuids = QStringList({"GraphCanvas", "ExpressionEvaluation", "UI Lib", "Multiplayer", "GameStateSamples"});
|
||||
gem.m_conflictingGemUuids = QStringList({"Cloud Canvas", "EMotion FX", "Streaming", "MessagePopup", "Cloth", "Graph Canvas", "Twitch Integration"});
|
||||
gem.m_version = "v1.5.102145";
|
||||
gem.m_lastUpdatedDate = "1st January 2021";
|
||||
gem.m_binarySizeInKB = 2000000;
|
||||
gem.m_features = QStringList({"Physics", "Gameplay", "Debug", "Assets"});
|
||||
result.push_back(gem);
|
||||
m_proxModel->InvalidateFilter();
|
||||
|
||||
result.push_back(O3DE::ProjectManager::GemInfo("Certificate Manager",
|
||||
"O3DE Irvine",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
GemInfo::Windows,
|
||||
false));
|
||||
// Select the first entry after everything got correctly sized
|
||||
QTimer::singleShot(200, [=]{
|
||||
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
|
||||
m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
|
||||
});
|
||||
}
|
||||
|
||||
result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Framework",
|
||||
"O3DE Seattle",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
GemInfo::iOS | GemInfo::Linux,
|
||||
false));
|
||||
void GemCatalogScreen::FillModel(const QString& projectPath, bool isNewProject)
|
||||
{
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult;
|
||||
if (isNewProject)
|
||||
{
|
||||
allGemInfosResult = PythonBindingsInterface::Get()->GetEngineGemInfos();
|
||||
}
|
||||
else
|
||||
{
|
||||
allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
|
||||
}
|
||||
|
||||
result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Core",
|
||||
"O3DE Foundation",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
|
||||
true));
|
||||
if (allGemInfosResult.IsSuccess())
|
||||
{
|
||||
// Add all available gems to the model.
|
||||
const QVector<GemInfo> allGemInfos = allGemInfosResult.GetValue();
|
||||
for (const GemInfo& gemInfo : allGemInfos)
|
||||
{
|
||||
m_gemModel->AddGem(gemInfo);
|
||||
}
|
||||
|
||||
result.push_back(O3DE::ProjectManager::GemInfo("Gestures",
|
||||
"O3DE Foundation",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
|
||||
false));
|
||||
// Gather enabled gems for the given project.
|
||||
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
|
||||
if (enabledGemNamesResult.IsSuccess())
|
||||
{
|
||||
const QVector<AZStd::string> enabledGemNames = enabledGemNamesResult.GetValue();
|
||||
for (const AZStd::string& enabledGemName : enabledGemNames)
|
||||
{
|
||||
const QModelIndex modelIndex = m_gemModel->FindIndexByNameString(enabledGemName.c_str());
|
||||
if (modelIndex.isValid())
|
||||
{
|
||||
GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true);
|
||||
GemModel::SetIsAdded(*m_gemModel, modelIndex, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("ProjectManager::GemCatalog", false,
|
||||
"Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.",
|
||||
enabledGemName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(O3DE::ProjectManager::GemInfo("Effects System",
|
||||
"O3DE Foundation",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
|
||||
true));
|
||||
void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath)
|
||||
{
|
||||
IPythonBindings* pythonBindings = PythonBindingsInterface::Get();
|
||||
QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
|
||||
QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
|
||||
|
||||
result.push_back(O3DE::ProjectManager::GemInfo("Microphone",
|
||||
"O3DE Foundation",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus euismod ligula vitae dui dictum, a sodales dolor luctus. Sed id elit dapibus, finibus neque sed, efficitur mi. Nam facilisis ligula at eleifend pellentesque. Praesent non ex consectetur, blandit tellus in, venenatis lacus. Duis nec neque in urna ullamcorper euismod id eu leo. Nam efficitur dolor sed odio vehicula venenatis. Suspendisse nec est non velit commodo cursus in sit amet dui. Ut bibendum nisl et libero hendrerit dapibus. Vestibulum ultrices ullamcorper urna, placerat porttitor est lobortis in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer a magna ac tellus sollicitudin porttitor. Phasellus lobortis viverra justo id bibendum. Etiam ac pharetra risus. Nulla vitae justo nibh. Nulla viverra leo et molestie interdum. Duis sit amet bibendum nulla, sit amet vehicula augue.",
|
||||
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
|
||||
false));
|
||||
for (const QModelIndex& modelIndex : toBeAdded)
|
||||
{
|
||||
const QString gemPath = GemModel::GetPath(modelIndex);
|
||||
const AZ::Outcome<void, AZStd::string> result = pythonBindings->AddGemToProject(gemPath, projectPath);
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Operation failed",
|
||||
QString("Cannot add gem %1 to project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
for (const QModelIndex& modelIndex : toBeRemoved)
|
||||
{
|
||||
const QString gemPath = GemModel::GetPath(modelIndex);
|
||||
const AZ::Outcome<void, AZStd::string> result = pythonBindings->RemoveGemFromProject(gemPath, projectPath);
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Operation failed",
|
||||
QString("Cannot remove gem %1 from project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
@@ -29,11 +31,17 @@ namespace O3DE::ProjectManager
|
||||
~GemCatalogScreen() = default;
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
|
||||
void ReinitForProject(const QString& projectPath, bool isNewProject);
|
||||
void EnableDisableGemsForProject(const QString& projectPath);
|
||||
|
||||
private:
|
||||
QVector<GemInfo> GenerateTestData();
|
||||
void FillModel(const QString& projectPath, bool isNewProject);
|
||||
|
||||
GemListView* m_gemListView = nullptr;
|
||||
GemInspector* m_gemInspector = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
GemSortFilterProxyModel* m_proxModel = nullptr;
|
||||
QVBoxLayout* m_filterWidgetLayout = nullptr;
|
||||
GemFilterWidget* m_filterWidget = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -79,4 +79,9 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
return (m_platforms & platform);
|
||||
}
|
||||
|
||||
bool GemInfo::operator<(const GemInfo& gemInfo) const
|
||||
{
|
||||
return (m_displayName < gemInfo.m_displayName);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -61,6 +61,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
bool operator<(const GemInfo& gemInfo) const;
|
||||
|
||||
QString m_path;
|
||||
QString m_name = "Unknown Gem Name";
|
||||
QString m_displayName = "Unknown Gem Name";
|
||||
|
||||
@@ -131,6 +131,22 @@ namespace O3DE::ProjectManager
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress)
|
||||
{
|
||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
|
||||
QRect fullRect, itemRect, contentRect;
|
||||
CalcRects(option, fullRect, itemRect, contentRect);
|
||||
const QRect buttonRect = CalcButtonRect(contentRect);
|
||||
|
||||
if (buttonRect.contains(mouseEvent->pos()))
|
||||
{
|
||||
const bool isAdded = GemModel::IsAdded(modelIndex);
|
||||
GemModel::SetIsAdded(*model, modelIndex, !isAdded);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace O3DE::ProjectManager
|
||||
item->setData(aznumeric_cast<int>(gemInfo.m_platforms), RolePlatforms);
|
||||
item->setData(aznumeric_cast<int>(gemInfo.m_types), RoleTypes);
|
||||
item->setData(gemInfo.m_summary, RoleSummary);
|
||||
item->setData(false, RoleWasPreviouslyAdded);
|
||||
item->setData(gemInfo.m_isAdded, RoleIsAdded);
|
||||
item->setData(gemInfo.m_directoryLink, RoleDirectoryLink);
|
||||
item->setData(gemInfo.m_documentationLink, RoleDocLink);
|
||||
@@ -47,6 +48,7 @@ namespace O3DE::ProjectManager
|
||||
item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated);
|
||||
item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize);
|
||||
item->setData(gemInfo.m_features, RoleFeatures);
|
||||
item->setData(gemInfo.m_path, RolePath);
|
||||
|
||||
appendRow(item);
|
||||
|
||||
@@ -89,11 +91,6 @@ namespace O3DE::ProjectManager
|
||||
return modelIndex.data(RoleSummary).toString();
|
||||
}
|
||||
|
||||
bool GemModel::IsAdded(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleIsAdded).toBool();
|
||||
}
|
||||
|
||||
QString GemModel::GetDirectoryLink(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleDirectoryLink).toString();
|
||||
@@ -180,4 +177,62 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
return modelIndex.data(RoleFeatures).toStringList();
|
||||
}
|
||||
|
||||
QString GemModel::GetPath(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RolePath).toString();
|
||||
}
|
||||
|
||||
bool GemModel::IsAdded(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleIsAdded).toBool();
|
||||
}
|
||||
|
||||
void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
|
||||
{
|
||||
model.setData(modelIndex, isAdded, RoleIsAdded);
|
||||
}
|
||||
|
||||
void GemModel::SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded)
|
||||
{
|
||||
model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded);
|
||||
}
|
||||
|
||||
bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex)
|
||||
{
|
||||
return (!modelIndex.data(RoleWasPreviouslyAdded).toBool() && modelIndex.data(RoleIsAdded).toBool());
|
||||
}
|
||||
|
||||
bool GemModel::NeedsToBeRemoved(const QModelIndex& modelIndex)
|
||||
{
|
||||
return (modelIndex.data(RoleWasPreviouslyAdded).toBool() && !modelIndex.data(RoleIsAdded).toBool());
|
||||
}
|
||||
|
||||
QVector<QModelIndex> GemModel::GatherGemsToBeAdded() const
|
||||
{
|
||||
QVector<QModelIndex> result;
|
||||
for (int row = 0; row < rowCount(); ++row)
|
||||
{
|
||||
const QModelIndex modelIndex = index(row, 0);
|
||||
if (NeedsToBeAdded(modelIndex))
|
||||
{
|
||||
result.push_back(modelIndex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<QModelIndex> GemModel::GatherGemsToBeRemoved() const
|
||||
{
|
||||
QVector<QModelIndex> result;
|
||||
for (int row = 0; row < rowCount(); ++row)
|
||||
{
|
||||
const QModelIndex modelIndex = index(row, 0);
|
||||
if (NeedsToBeRemoved(modelIndex))
|
||||
{
|
||||
result.push_back(modelIndex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -46,13 +46,22 @@ namespace O3DE::ProjectManager
|
||||
static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex);
|
||||
static GemInfo::Types GetTypes(const QModelIndex& modelIndex);
|
||||
static QString GetSummary(const QModelIndex& modelIndex);
|
||||
static bool IsAdded(const QModelIndex& modelIndex);
|
||||
static QString GetDirectoryLink(const QModelIndex& modelIndex);
|
||||
static QString GetDocLink(const QModelIndex& modelIndex);
|
||||
static QString GetVersion(const QModelIndex& modelIndex);
|
||||
static QString GetLastUpdated(const QModelIndex& modelIndex);
|
||||
static int GetBinarySizeInKB(const QModelIndex& modelIndex);
|
||||
static QStringList GetFeatures(const QModelIndex& modelIndex);
|
||||
static QString GetPath(const QModelIndex& modelIndex);
|
||||
|
||||
static bool IsAdded(const QModelIndex& modelIndex);
|
||||
static void SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded);
|
||||
static void SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded);
|
||||
static bool NeedsToBeAdded(const QModelIndex& modelIndex);
|
||||
static bool NeedsToBeRemoved(const QModelIndex& modelIndex);
|
||||
|
||||
QVector<QModelIndex> GatherGemsToBeAdded() const;
|
||||
QVector<QModelIndex> GatherGemsToBeRemoved() const;
|
||||
|
||||
private:
|
||||
enum UserRole
|
||||
@@ -62,6 +71,7 @@ namespace O3DE::ProjectManager
|
||||
RoleGemOrigin,
|
||||
RolePlatforms,
|
||||
RoleSummary,
|
||||
RoleWasPreviouslyAdded,
|
||||
RoleIsAdded,
|
||||
RoleDirectoryLink,
|
||||
RoleDocLink,
|
||||
@@ -71,7 +81,8 @@ namespace O3DE::ProjectManager
|
||||
RoleLastUpdated,
|
||||
RoleBinarySize,
|
||||
RoleFeatures,
|
||||
RoleTypes
|
||||
RoleTypes,
|
||||
RolePath
|
||||
};
|
||||
|
||||
QHash<QString, QModelIndex> m_nameToIndexMap;
|
||||
|
||||
@@ -34,83 +34,57 @@ namespace O3DE::ProjectManager
|
||||
constexpr const char* k_pathProperty = "Path";
|
||||
|
||||
NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent)
|
||||
: ScreenWidget(parent)
|
||||
: ProjectSettingsScreen(parent)
|
||||
{
|
||||
QHBoxLayout* hLayout = new QHBoxLayout(this);
|
||||
hLayout->setAlignment(Qt::AlignLeft);
|
||||
hLayout->setContentsMargins(0,0,0,0);
|
||||
const QString defaultName{ "NewProject" };
|
||||
const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName);
|
||||
|
||||
// if we don't provide a parent for this box layout the stylesheet doesn't take
|
||||
// if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
|
||||
QFrame* projectSettingsFrame = new QFrame(this);
|
||||
projectSettingsFrame->setObjectName("projectSettings");
|
||||
QVBoxLayout* vLayout = new QVBoxLayout(this);
|
||||
m_projectName->lineEdit()->setText(defaultName);
|
||||
m_projectPath->lineEdit()->setText(defaultPath);
|
||||
|
||||
// you cannot remove content margins in qss
|
||||
vLayout->setContentsMargins(0,0,0,0);
|
||||
vLayout->setAlignment(Qt::AlignTop);
|
||||
// if we don't use a QFrame we cannot "contain" the widgets inside and move them around
|
||||
// as a group
|
||||
QFrame* projectTemplateWidget = new QFrame(this);
|
||||
projectTemplateWidget->setObjectName("projectTemplate");
|
||||
QVBoxLayout* containerLayout = new QVBoxLayout();
|
||||
containerLayout->setAlignment(Qt::AlignTop);
|
||||
{
|
||||
const QString defaultName{ "NewProject" };
|
||||
const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName);
|
||||
QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template"));
|
||||
projectTemplateLabel->setObjectName("projectTemplateLabel");
|
||||
containerLayout->addWidget(projectTemplateLabel);
|
||||
|
||||
m_projectName = new FormLineEditWidget(tr("Project name"), defaultName, this);
|
||||
connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath);
|
||||
vLayout->addWidget(m_projectName);
|
||||
QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide "
|
||||
"additional functionality and content to the project."));
|
||||
projectTemplateDetailsLabel->setWordWrap(true);
|
||||
projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel");
|
||||
containerLayout->addWidget(projectTemplateDetailsLabel);
|
||||
|
||||
m_projectPath = new FormBrowseEditWidget(tr("Project Location"), defaultPath, this);
|
||||
m_projectPath->lineEdit()->setReadOnly(true);
|
||||
connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath);
|
||||
vLayout->addWidget(m_projectPath);
|
||||
QHBoxLayout* templateLayout = new QHBoxLayout(this);
|
||||
containerLayout->addItem(templateLayout);
|
||||
|
||||
// if we don't use a QFrame we cannot "contain" the widgets inside and move them around
|
||||
// as a group
|
||||
QFrame* projectTemplateWidget = new QFrame(this);
|
||||
projectTemplateWidget->setObjectName("projectTemplate");
|
||||
QVBoxLayout* containerLayout = new QVBoxLayout();
|
||||
containerLayout->setAlignment(Qt::AlignTop);
|
||||
m_projectTemplateButtonGroup = new QButtonGroup(this);
|
||||
m_projectTemplateButtonGroup->setObjectName("templateButtonGroup");
|
||||
auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
|
||||
if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
|
||||
{
|
||||
QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template"));
|
||||
projectTemplateLabel->setObjectName("projectTemplateLabel");
|
||||
containerLayout->addWidget(projectTemplateLabel);
|
||||
|
||||
QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide "
|
||||
"additional functionality and content to the project."));
|
||||
projectTemplateDetailsLabel->setWordWrap(true);
|
||||
projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel");
|
||||
containerLayout->addWidget(projectTemplateDetailsLabel);
|
||||
|
||||
QHBoxLayout* templateLayout = new QHBoxLayout(this);
|
||||
containerLayout->addItem(templateLayout);
|
||||
|
||||
m_projectTemplateButtonGroup = new QButtonGroup(this);
|
||||
m_projectTemplateButtonGroup->setObjectName("templateButtonGroup");
|
||||
auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates();
|
||||
if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty())
|
||||
for (const ProjectTemplateInfo& projectTemplate : templatesResult.GetValue())
|
||||
{
|
||||
for (auto projectTemplate : templatesResult.GetValue())
|
||||
{
|
||||
QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this);
|
||||
radioButton->setProperty(k_pathProperty, projectTemplate.m_path);
|
||||
m_projectTemplateButtonGroup->addButton(radioButton);
|
||||
QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this);
|
||||
radioButton->setProperty(k_pathProperty, projectTemplate.m_path);
|
||||
m_projectTemplateButtonGroup->addButton(radioButton);
|
||||
|
||||
containerLayout->addWidget(radioButton);
|
||||
}
|
||||
|
||||
m_projectTemplateButtonGroup->buttons().first()->setChecked(true);
|
||||
containerLayout->addWidget(radioButton);
|
||||
}
|
||||
}
|
||||
projectTemplateWidget->setLayout(containerLayout);
|
||||
vLayout->addWidget(projectTemplateWidget);
|
||||
}
|
||||
projectSettingsFrame->setLayout(vLayout);
|
||||
|
||||
hLayout->addWidget(projectSettingsFrame);
|
||||
m_projectTemplateButtonGroup->buttons().first()->setChecked(true);
|
||||
}
|
||||
}
|
||||
projectTemplateWidget->setLayout(containerLayout);
|
||||
m_verticalLayout->addWidget(projectTemplateWidget);
|
||||
|
||||
QWidget* projectTemplateDetails = new QWidget(this);
|
||||
projectTemplateDetails->setObjectName("projectTemplateDetails");
|
||||
hLayout->addWidget(projectTemplateDetails);
|
||||
|
||||
this->setLayout(hLayout);
|
||||
m_horizontalLayout->addWidget(projectTemplateDetails);
|
||||
}
|
||||
|
||||
QString NewProjectSettingsScreen::GetDefaultProjectPath()
|
||||
@@ -133,69 +107,13 @@ namespace O3DE::ProjectManager
|
||||
return ProjectManagerScreen::NewProjectSettings;
|
||||
}
|
||||
|
||||
void NewProjectSettingsScreen::ValidateProjectPath()
|
||||
{
|
||||
Validate();
|
||||
}
|
||||
|
||||
void NewProjectSettingsScreen::NotifyCurrentScreen()
|
||||
{
|
||||
Validate();
|
||||
}
|
||||
|
||||
ProjectInfo NewProjectSettingsScreen::GetProjectInfo()
|
||||
{
|
||||
ProjectInfo projectInfo;
|
||||
projectInfo.m_projectName = m_projectName->lineEdit()->text();
|
||||
projectInfo.m_path = m_projectPath->lineEdit()->text();
|
||||
return projectInfo;
|
||||
}
|
||||
|
||||
QString NewProjectSettingsScreen::GetProjectTemplatePath()
|
||||
{
|
||||
return m_projectTemplateButtonGroup->checkedButton()->property(k_pathProperty).toString();
|
||||
}
|
||||
|
||||
bool NewProjectSettingsScreen::Validate()
|
||||
{
|
||||
bool projectPathIsValid = true;
|
||||
if (m_projectPath->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
m_projectPath->setErrorLabelText(tr("Please provide a valid location."));
|
||||
}
|
||||
else
|
||||
{
|
||||
QDir path(m_projectPath->lineEdit()->text());
|
||||
if (path.exists() && !path.isEmpty())
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location."));
|
||||
}
|
||||
}
|
||||
|
||||
bool projectNameIsValid = true;
|
||||
if (m_projectName->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectNameIsValid = false;
|
||||
m_projectName->setErrorLabelText(tr("Please provide a project name."));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this validation should roughly match the utils.validate_identifier which the cli
|
||||
// uses to validate project names
|
||||
QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}");
|
||||
const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text());
|
||||
if (!result)
|
||||
{
|
||||
projectNameIsValid = false;
|
||||
m_projectName->setErrorLabelText(tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m_projectName->setErrorLabelVisible(!projectNameIsValid);
|
||||
m_projectPath->setErrorLabelVisible(!projectPathIsValid);
|
||||
return projectNameIsValid && projectPathIsValid;
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -12,41 +12,28 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <ProjectInfo.h>
|
||||
#include <ProjectSettingsScreen.h>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QButtonGroup)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(FormLineEditWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget)
|
||||
|
||||
class NewProjectSettingsScreen
|
||||
: public ScreenWidget
|
||||
: public ProjectSettingsScreen
|
||||
{
|
||||
public:
|
||||
explicit NewProjectSettingsScreen(QWidget* parent = nullptr);
|
||||
~NewProjectSettingsScreen() = default;
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
|
||||
ProjectInfo GetProjectInfo();
|
||||
QString GetProjectTemplatePath();
|
||||
|
||||
bool Validate();
|
||||
|
||||
void NotifyCurrentScreen() override;
|
||||
|
||||
protected slots:
|
||||
void HandleBrowseButton();
|
||||
void ValidateProjectPath();
|
||||
|
||||
private:
|
||||
QString GetDefaultProjectPath();
|
||||
|
||||
FormLineEditWidget* m_projectName;
|
||||
FormBrowseEditWidget* m_projectPath;
|
||||
QButtonGroup* m_projectTemplateButtonGroup;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
#include <QMenu>
|
||||
#include <QSpacerItem>
|
||||
|
||||
//#define SHOW_ALL_PROJECT_ACTIONS
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
inline constexpr static int s_projectImageWidth = 210;
|
||||
@@ -96,10 +94,6 @@ namespace O3DE::ProjectManager
|
||||
m_removeProjectAction = newProjectMenu->addAction(tr("Remove from O3DE"));
|
||||
m_deleteProjectAction = newProjectMenu->addAction(tr("Delete this Project"));
|
||||
|
||||
#ifdef SHOW_ALL_PROJECT_ACTIONS
|
||||
m_editProjectGemsAction = newProjectMenu->addAction(tr("Cutomize Gems..."));
|
||||
#endif
|
||||
|
||||
QFrame* footer = new QFrame(this);
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -121,10 +115,6 @@ namespace O3DE::ProjectManager
|
||||
connect(m_copyProjectAction, &QAction::triggered, [this]() { emit CopyProject(m_projectInfo.m_path); });
|
||||
connect(m_removeProjectAction, &QAction::triggered, [this]() { emit RemoveProject(m_projectInfo.m_path); });
|
||||
connect(m_deleteProjectAction, &QAction::triggered, [this]() { emit DeleteProject(m_projectInfo.m_path); });
|
||||
|
||||
#ifdef SHOW_ALL_PROJECT_ACTIONS
|
||||
connect(m_editProjectGemsAction, &QAction::triggered, [this]() { emit EditProjectGems(m_projectInfo.m_path); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProjectButton::SetButtonEnabled(bool enabled)
|
||||
|
||||
@@ -62,7 +62,6 @@ namespace O3DE::ProjectManager
|
||||
signals:
|
||||
void OpenProject(const QString& projectName);
|
||||
void EditProject(const QString& projectName);
|
||||
void EditProjectGems(const QString& projectName);
|
||||
void CopyProject(const QString& projectName);
|
||||
void RemoveProject(const QString& projectName);
|
||||
void DeleteProject(const QString& projectName);
|
||||
@@ -73,7 +72,6 @@ namespace O3DE::ProjectManager
|
||||
ProjectInfo m_projectInfo;
|
||||
LabelButton* m_projectImageLabel;
|
||||
QAction* m_editProjectAction;
|
||||
QAction* m_editProjectGemsAction;
|
||||
QAction* m_copyProjectAction;
|
||||
QAction* m_removeProjectAction;
|
||||
QAction* m_deleteProjectAction;
|
||||
|
||||
@@ -25,6 +25,19 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
}
|
||||
|
||||
bool ProjectInfo::operator==(const ProjectInfo& rhs)
|
||||
{
|
||||
return m_path == rhs.m_path
|
||||
&& m_projectName == rhs.m_projectName
|
||||
&& m_imagePath == rhs.m_imagePath
|
||||
&& m_backgroundImagePath == rhs.m_backgroundImagePath;
|
||||
}
|
||||
|
||||
bool ProjectInfo::operator!=(const ProjectInfo& rhs)
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
bool ProjectInfo::IsValid() const
|
||||
{
|
||||
return !m_path.isEmpty() && !m_projectName.isEmpty();
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace O3DE::ProjectManager
|
||||
ProjectInfo() = default;
|
||||
ProjectInfo(const QString& path, const QString& projectName, const QString& displayName,
|
||||
const QString& imagePath, const QString& backgroundImagePath, bool isNew);
|
||||
bool operator==(const ProjectInfo& rhs);
|
||||
bool operator!=(const ProjectInfo& rhs);
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
|
||||
@@ -11,45 +11,131 @@
|
||||
*/
|
||||
|
||||
#include <ProjectSettingsScreen.h>
|
||||
#include <FormBrowseEditWidget.h>
|
||||
#include <FormLineEditWidget.h>
|
||||
#include <PathValidator.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
|
||||
#include <Source/ui_ProjectSettingsScreen.h>
|
||||
#include <QFileDialog>
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
ProjectSettingsScreen::ProjectSettingsScreen(QWidget* parent)
|
||||
: ScreenWidget(parent)
|
||||
, m_ui(new Ui::ProjectSettingsClass())
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_horizontalLayout = new QHBoxLayout(this);
|
||||
m_horizontalLayout->setAlignment(Qt::AlignLeft);
|
||||
m_horizontalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
connect(m_ui->gemsButton, &QPushButton::pressed, this, &ProjectSettingsScreen::HandleGemsButton);
|
||||
// if we don't provide a parent for this box layout the stylesheet doesn't take
|
||||
// if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
|
||||
QFrame* projectSettingsFrame = new QFrame(this);
|
||||
projectSettingsFrame->setObjectName("projectSettings");
|
||||
m_verticalLayout = new QVBoxLayout(this);
|
||||
|
||||
// you cannot remove content margins in qss
|
||||
m_verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_verticalLayout->setAlignment(Qt::AlignTop);
|
||||
|
||||
m_projectName = new FormLineEditWidget(tr("Project name"), "", this);
|
||||
connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::ValidateProjectName);
|
||||
m_verticalLayout->addWidget(m_projectName);
|
||||
|
||||
m_projectPath = new FormBrowseEditWidget(tr("Project Location"), "", this);
|
||||
m_projectPath->lineEdit()->setReadOnly(true);
|
||||
connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::Validate);
|
||||
m_verticalLayout->addWidget(m_projectPath);
|
||||
|
||||
projectSettingsFrame->setLayout(m_verticalLayout);
|
||||
|
||||
m_horizontalLayout->addWidget(projectSettingsFrame);
|
||||
|
||||
setLayout(m_horizontalLayout);
|
||||
}
|
||||
|
||||
ProjectManagerScreen ProjectSettingsScreen::GetScreenEnum()
|
||||
{
|
||||
return ProjectManagerScreen::ProjectSettings;
|
||||
return ProjectManagerScreen::Invalid;
|
||||
}
|
||||
|
||||
QString ProjectSettingsScreen::GetDefaultProjectPath()
|
||||
{
|
||||
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
|
||||
if (engineInfoResult.IsSuccess())
|
||||
{
|
||||
QDir path(QDir::toNativeSeparators(engineInfoResult.GetValue().m_defaultProjectsFolder));
|
||||
if (path.exists())
|
||||
{
|
||||
defaultPath = path.absolutePath();
|
||||
}
|
||||
}
|
||||
return defaultPath;
|
||||
}
|
||||
|
||||
ProjectInfo ProjectSettingsScreen::GetProjectInfo()
|
||||
{
|
||||
// Impl pending next PR
|
||||
return ProjectInfo();
|
||||
ProjectInfo projectInfo;
|
||||
projectInfo.m_projectName = m_projectName->lineEdit()->text();
|
||||
projectInfo.m_path = m_projectPath->lineEdit()->text();
|
||||
return projectInfo;
|
||||
}
|
||||
|
||||
void ProjectSettingsScreen::SetProjectInfo()
|
||||
bool ProjectSettingsScreen::ValidateProjectName()
|
||||
{
|
||||
// Impl pending next PR
|
||||
bool projectNameIsValid = true;
|
||||
if (m_projectName->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectNameIsValid = false;
|
||||
m_projectName->setErrorLabelText(tr("Please provide a project name."));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this validation should roughly match the utils.validate_identifier which the cli
|
||||
// uses to validate project names
|
||||
QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}");
|
||||
const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text());
|
||||
if (!result)
|
||||
{
|
||||
projectNameIsValid = false;
|
||||
m_projectName->setErrorLabelText(
|
||||
tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters"));
|
||||
}
|
||||
}
|
||||
|
||||
m_projectName->setErrorLabelVisible(!projectNameIsValid);
|
||||
return projectNameIsValid;
|
||||
}
|
||||
bool ProjectSettingsScreen::ValidateProjectPath()
|
||||
{
|
||||
bool projectPathIsValid = true;
|
||||
if (m_projectPath->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
m_projectPath->setErrorLabelText(tr("Please provide a valid location."));
|
||||
}
|
||||
else
|
||||
{
|
||||
QDir path(m_projectPath->lineEdit()->text());
|
||||
if (path.exists() && !path.isEmpty())
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location."));
|
||||
}
|
||||
}
|
||||
|
||||
m_projectPath->setErrorLabelVisible(!projectPathIsValid);
|
||||
return projectPathIsValid;
|
||||
}
|
||||
|
||||
bool ProjectSettingsScreen::Validate()
|
||||
{
|
||||
// Impl pending next PR
|
||||
return true;
|
||||
return ValidateProjectName() && ValidateProjectPath();
|
||||
}
|
||||
|
||||
void ProjectSettingsScreen::HandleGemsButton()
|
||||
{
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -12,17 +12,18 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <ProjectInfo.h>
|
||||
#include <ScreenWidget.h>
|
||||
#endif
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ProjectSettingsClass;
|
||||
}
|
||||
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(FormLineEditWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget)
|
||||
|
||||
class ProjectSettingsScreen
|
||||
: public ScreenWidget
|
||||
{
|
||||
@@ -32,15 +33,20 @@ namespace O3DE::ProjectManager
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
|
||||
ProjectInfo GetProjectInfo();
|
||||
void SetProjectInfo();
|
||||
|
||||
bool Validate();
|
||||
|
||||
protected slots:
|
||||
void HandleGemsButton();
|
||||
virtual bool ValidateProjectName();
|
||||
virtual bool ValidateProjectPath();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::ProjectSettingsClass> m_ui;
|
||||
protected:
|
||||
QString GetDefaultProjectPath();
|
||||
|
||||
QHBoxLayout* m_horizontalLayout;
|
||||
QVBoxLayout* m_verticalLayout;
|
||||
FormLineEditWidget* m_projectName;
|
||||
FormBrowseEditWidget* m_projectPath;
|
||||
};
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ProjectSettingsClass</class>
|
||||
<widget class="QWidget" name="ProjectSettingsClass">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>782</width>
|
||||
<height>579</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="projectSettingsButton">
|
||||
<property name="text">
|
||||
<string>Project Settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="gemsButton">
|
||||
<property name="text">
|
||||
<string>Gems</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>761</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Project Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Project Location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_2"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Project Image Location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_3"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Project Background Image Location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_4"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -182,10 +182,6 @@ namespace O3DE::ProjectManager
|
||||
connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
|
||||
connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
|
||||
connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
|
||||
|
||||
#ifdef SHOW_ALL_PROJECT_ACTIONS
|
||||
connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
|
||||
#endif
|
||||
}
|
||||
|
||||
layout->addWidget(projectsScrollArea);
|
||||
@@ -293,14 +289,8 @@ namespace O3DE::ProjectManager
|
||||
void ProjectsScreen::HandleEditProject(const QString& projectPath)
|
||||
{
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
emit ResetScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
}
|
||||
void ProjectsScreen::HandleEditProjectGems(const QString& projectPath)
|
||||
{
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
|
||||
}
|
||||
void ProjectsScreen::HandleCopyProject(const QString& projectPath)
|
||||
{
|
||||
// Open file dialog and choose location for copied project then register copy with O3DE
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace O3DE::ProjectManager
|
||||
void HandleAddProjectButton();
|
||||
void HandleOpenProject(const QString& projectPath);
|
||||
void HandleEditProject(const QString& projectPath);
|
||||
void HandleEditProjectGems(const QString& projectPath);
|
||||
void HandleCopyProject(const QString& projectPath);
|
||||
void HandleRemoveProject(const QString& projectPath);
|
||||
void HandleDeleteProject(const QString& projectPath);
|
||||
|
||||
@@ -283,12 +283,16 @@ namespace O3DE::ProjectManager
|
||||
AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed");
|
||||
|
||||
// import required modules
|
||||
m_cmake = pybind11::module::import("o3de.cmake");
|
||||
m_register = pybind11::module::import("o3de.register");
|
||||
m_manifest = pybind11::module::import("o3de.manifest");
|
||||
m_engineTemplate = pybind11::module::import("o3de.engine_template");
|
||||
m_enableGemProject = pybind11::module::import("o3de.enable_gem");
|
||||
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
|
||||
|
||||
// make sure the engine is registered
|
||||
RegisterThisEngine();
|
||||
|
||||
return result == 0 && !PyErr_Occurred();
|
||||
} catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
@@ -311,7 +315,37 @@ namespace O3DE::ProjectManager
|
||||
return !PyErr_Occurred();
|
||||
}
|
||||
|
||||
bool PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
|
||||
bool PythonBindings::RegisterThisEngine()
|
||||
{
|
||||
bool registrationResult = true; // already registered is considered successful
|
||||
bool pythonResult = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
// check current engine path against all other registered engines
|
||||
// to see if we are already registered
|
||||
auto allEngines = m_manifest.attr("get_engines")();
|
||||
if (pybind11::isinstance<pybind11::list>(allEngines))
|
||||
{
|
||||
for (auto engine : allEngines)
|
||||
{
|
||||
AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"]));
|
||||
if (enginePath.Compare(m_enginePath) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto result = m_register.attr("register")(m_enginePath.c_str());
|
||||
registrationResult = (result.cast<int>() == 0);
|
||||
});
|
||||
|
||||
bool finalResult = (registrationResult && pythonResult);
|
||||
AZ_Assert(finalResult, "Registration of this engine failed!");
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback)
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
|
||||
pybind11::gil_scoped_release release;
|
||||
@@ -320,13 +354,19 @@ namespace O3DE::ProjectManager
|
||||
try
|
||||
{
|
||||
executionCallback();
|
||||
return true;
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
AZ_Warning("PythonBindings", false, "Python exception %s", e.what());
|
||||
return false;
|
||||
return AZ::Failure<AZStd::string>(e.what());
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
bool PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
|
||||
{
|
||||
return ExecuteWithLockErrorHandling(executionCallback).IsSuccess();
|
||||
}
|
||||
|
||||
AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo()
|
||||
@@ -419,7 +459,7 @@ namespace O3DE::ProjectManager
|
||||
return result;
|
||||
}
|
||||
|
||||
AZ::Outcome<GemInfo> PythonBindings::GetGem(const QString& path)
|
||||
AZ::Outcome<GemInfo> PythonBindings::GetGemInfo(const QString& path)
|
||||
{
|
||||
GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString()));
|
||||
if (gemInfo.IsValid())
|
||||
@@ -432,32 +472,79 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<GemInfo>> PythonBindings::GetGems()
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetEngineGemInfos()
|
||||
{
|
||||
QVector<GemInfo> gems;
|
||||
|
||||
bool result = ExecuteWithLock([&] {
|
||||
// external gems
|
||||
for (auto path : m_manifest.attr("get_gems")())
|
||||
auto result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
for (auto path : m_manifest.attr("get_engine_gems")())
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
}
|
||||
|
||||
// gems from the engine
|
||||
for (auto path : m_manifest.attr("get_engine_gems")())
|
||||
std::sort(gems.begin(), gems.end());
|
||||
return AZ::Success(AZStd::move(gems));
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath)
|
||||
{
|
||||
QVector<GemInfo> gems;
|
||||
|
||||
auto result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
});
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
}
|
||||
|
||||
if (!result)
|
||||
std::sort(gems.begin(), gems.end());
|
||||
return AZ::Success(AZStd::move(gems));
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<AZStd::string>, AZStd::string> PythonBindings::GetEnabledGemNames(const QString& projectPath)
|
||||
{
|
||||
// Retrieve the path to the cmake file that lists the enabled gems.
|
||||
pybind11::str enabledGemsFilename;
|
||||
auto result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
const pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")(
|
||||
pybind11::none(), // project_name
|
||||
pyProjectPath); // project_path
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure();
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
}
|
||||
else
|
||||
|
||||
// Retrieve the actual list of names from the cmake file.
|
||||
QVector<AZStd::string> gemNames;
|
||||
result = ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename);
|
||||
for (auto gemName : pyGemNames)
|
||||
{
|
||||
gemNames.push_back(Py_To_String(gemName));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Success(AZStd::move(gems));
|
||||
return AZ::Failure<AZStd::string>(result.GetError().c_str());
|
||||
}
|
||||
|
||||
return AZ::Success(AZStd::move(gemNames));
|
||||
}
|
||||
|
||||
bool PythonBindings::AddProject(const QString& path)
|
||||
@@ -637,38 +724,36 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
bool result = ExecuteWithLock([&] {
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
return ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
|
||||
m_enableGemProject.attr("enable_gem_in_project")(
|
||||
pybind11::none(), // gem_name
|
||||
pyGemPath,
|
||||
pybind11::none(), // project_name
|
||||
pyProjectPath
|
||||
);
|
||||
});
|
||||
|
||||
return result;
|
||||
m_enableGemProject.attr("enable_gem_in_project")(
|
||||
pybind11::none(), // gem name not needed as path is provided
|
||||
pyGemPath,
|
||||
pybind11::none(), // project name not needed as path is provided
|
||||
pyProjectPath
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
bool result = ExecuteWithLock([&] {
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
return ExecuteWithLockErrorHandling([&]
|
||||
{
|
||||
pybind11::str pyGemPath = gemPath.toStdString();
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
|
||||
m_disableGemProject.attr("disable_gem_in_project")(
|
||||
pybind11::none(), // gem_name
|
||||
pyGemPath,
|
||||
pybind11::none(), // project_name
|
||||
pyProjectPath
|
||||
);
|
||||
});
|
||||
|
||||
return result;
|
||||
m_disableGemProject.attr("disable_gem_in_project")(
|
||||
pybind11::none(), // gem name not needed as path is provided
|
||||
pyGemPath,
|
||||
pybind11::none(), // project name not needed as path is provided
|
||||
pyProjectPath
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo)
|
||||
|
||||
@@ -39,8 +39,10 @@ namespace O3DE::ProjectManager
|
||||
bool SetEngineInfo(const EngineInfo& engineInfo) override;
|
||||
|
||||
// Gem
|
||||
AZ::Outcome<GemInfo> GetGem(const QString& path) override;
|
||||
AZ::Outcome<QVector<GemInfo>> GetGems() override;
|
||||
AZ::Outcome<GemInfo> GetGemInfo(const QString& path) override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
|
||||
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
|
||||
|
||||
// Project
|
||||
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
|
||||
@@ -49,8 +51,8 @@ namespace O3DE::ProjectManager
|
||||
bool AddProject(const QString& path) override;
|
||||
bool RemoveProject(const QString& path) override;
|
||||
bool UpdateProject(const ProjectInfo& projectInfo) override;
|
||||
bool AddGemToProject(const QString& gemPath, const QString& projectPath) override;
|
||||
bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
|
||||
AZ::Outcome<void, AZStd::string> AddGemToProject(const QString& gemPath, const QString& projectPath) override;
|
||||
AZ::Outcome<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
|
||||
|
||||
// ProjectTemplate
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() override;
|
||||
@@ -58,16 +60,20 @@ namespace O3DE::ProjectManager
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PythonBindings);
|
||||
|
||||
AZ::Outcome<void, AZStd::string> ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback);
|
||||
bool ExecuteWithLock(AZStd::function<void()> executionCallback);
|
||||
GemInfo GemInfoFromPath(pybind11::handle path);
|
||||
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
|
||||
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path);
|
||||
bool RegisterThisEngine();
|
||||
bool StartPython();
|
||||
bool StopPython();
|
||||
|
||||
|
||||
AZ::IO::FixedMaxPath m_enginePath;
|
||||
pybind11::handle m_engineTemplate;
|
||||
AZStd::recursive_mutex m_lock;
|
||||
pybind11::handle m_cmake;
|
||||
pybind11::handle m_register;
|
||||
pybind11::handle m_manifest;
|
||||
pybind11::handle m_enableGemProject;
|
||||
|
||||
@@ -57,13 +57,27 @@ namespace O3DE::ProjectManager
|
||||
* @param path the absolute path to the Gem
|
||||
* @return an outcome with GemInfo on success
|
||||
*/
|
||||
virtual AZ::Outcome<GemInfo> GetGem(const QString& path) = 0;
|
||||
virtual AZ::Outcome<GemInfo> GetGemInfo(const QString& path) = 0;
|
||||
|
||||
/**
|
||||
* Get info about all known Gems
|
||||
* @return an outcome with GemInfos on success
|
||||
* Get all available gem infos. This concatenates gems registered by the engine and the project.
|
||||
* @param path The absolute path to the project.
|
||||
* @return A list of gem infos.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemInfo>> GetGems() = 0;
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0;
|
||||
|
||||
/**
|
||||
* Get engine gem infos.
|
||||
* @return A list of all registered gem infos.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() = 0;
|
||||
|
||||
/**
|
||||
* Get a list of all enabled gem names for a given project.
|
||||
* @param[in] projectPath Absolute file path to the project.
|
||||
* @return A list of gem names of all the enabled gems for a given project or a error message on failure.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0;
|
||||
|
||||
|
||||
// Projects
|
||||
@@ -114,17 +128,17 @@ namespace O3DE::ProjectManager
|
||||
* Add a gem to a project
|
||||
* @param gemPath the absolute path to the gem
|
||||
* @param projectPath the absolute path to the project
|
||||
* @return true on success, false on failure
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual bool AddGemToProject(const QString& gemPath, const QString& projectPath) = 0;
|
||||
virtual AZ::Outcome<void, AZStd::string> AddGemToProject(const QString& gemPath, const QString& projectPath) = 0;
|
||||
|
||||
/**
|
||||
* Remove gem to a project
|
||||
* @param gemPath the absolute path to the gem
|
||||
* @param projectPath the absolute path to the project
|
||||
* @return true on success, false on failure
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0;
|
||||
virtual AZ::Outcome<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0;
|
||||
|
||||
|
||||
// Project Templates
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace O3DE::ProjectManager
|
||||
GemCatalog,
|
||||
Projects,
|
||||
UpdateProject,
|
||||
ProjectSettings,
|
||||
UpdateProjectSettings,
|
||||
EngineSettings
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
|
||||
{ "GemCatalog", ProjectManagerScreen::GemCatalog},
|
||||
{ "Projects", ProjectManagerScreen::Projects},
|
||||
{ "UpdateProject", ProjectManagerScreen::UpdateProject},
|
||||
{ "ProjectSettings", ProjectManagerScreen::ProjectSettings},
|
||||
{ "UpdateProjectSettings", ProjectManagerScreen::UpdateProjectSettings},
|
||||
{ "EngineSettings", ProjectManagerScreen::EngineSettings}
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <NewProjectSettingsScreen.h>
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <ProjectsScreen.h>
|
||||
#include <ProjectSettingsScreen.h>
|
||||
#include <UpdateProjectSettingsScreen.h>
|
||||
#include <EngineSettingsScreen.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
@@ -42,8 +42,8 @@ namespace O3DE::ProjectManager
|
||||
case (ProjectManagerScreen::UpdateProject):
|
||||
newScreen = new UpdateProjectCtrl(parent);
|
||||
break;
|
||||
case (ProjectManagerScreen::ProjectSettings):
|
||||
newScreen = new ProjectSettingsScreen(parent);
|
||||
case (ProjectManagerScreen::UpdateProjectSettings):
|
||||
newScreen = new UpdateProjectSettingsScreen(parent);
|
||||
break;
|
||||
case (ProjectManagerScreen::EngineSettings):
|
||||
newScreen = new EngineSettingsScreen(parent);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <ScreensCtrl.h>
|
||||
#include <ScreenFactory.h>
|
||||
#include <ScreenWidget.h>
|
||||
#include <UpdateProjectCtrl.h>
|
||||
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
@@ -10,15 +10,20 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <UpdateProjectCtrl.h>
|
||||
#include <ScreensCtrl.h>
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <ProjectSettingsScreen.h>
|
||||
#include <ScreenHeaderWidget.h>
|
||||
#include <ScreensCtrl.h>
|
||||
#include <UpdateProjectCtrl.h>
|
||||
#include <UpdateProjectSettingsScreen.h>
|
||||
#include <ProjectUtils.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -26,31 +31,57 @@ namespace O3DE::ProjectManager
|
||||
: ScreenWidget(parent)
|
||||
{
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
setLayout(vLayout);
|
||||
vLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_screensCtrl = new ScreensCtrl();
|
||||
vLayout->addWidget(m_screensCtrl);
|
||||
m_header = new ScreenHeader(this);
|
||||
m_header->setTitle(tr(""));
|
||||
m_header->setSubTitle(tr("Edit Project Settings:"));
|
||||
connect(m_header->backButton(), &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton);
|
||||
vLayout->addWidget(m_header);
|
||||
|
||||
m_updateSettingsScreen = new UpdateProjectSettingsScreen();
|
||||
m_gemCatalogScreen = new GemCatalogScreen();
|
||||
|
||||
m_stack = new QStackedWidget(this);
|
||||
m_stack->setObjectName("body");
|
||||
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
|
||||
vLayout->addWidget(m_stack);
|
||||
|
||||
QFrame* topBarFrameWidget = new QFrame(this);
|
||||
topBarFrameWidget->setObjectName("projectSettingsTopFrame");
|
||||
QHBoxLayout* topBarHLayout = new QHBoxLayout();
|
||||
topBarHLayout->setContentsMargins(0, 0, 0, 0);
|
||||
topBarFrameWidget->setLayout(topBarHLayout);
|
||||
|
||||
QTabWidget* tabWidget = new QTabWidget();
|
||||
tabWidget->setObjectName("projectSettingsTab");
|
||||
tabWidget->tabBar()->setObjectName("projectSettingsTabBar");
|
||||
tabWidget->addTab(m_updateSettingsScreen, tr("General"));
|
||||
|
||||
QPushButton* gemsButton = new QPushButton(tr("Add More Gems"), this);
|
||||
topBarHLayout->addWidget(gemsButton);
|
||||
tabWidget->setCornerWidget(gemsButton);
|
||||
|
||||
topBarHLayout->addWidget(tabWidget);
|
||||
|
||||
m_stack->addWidget(topBarFrameWidget);
|
||||
m_stack->addWidget(m_gemCatalogScreen);
|
||||
|
||||
QDialogButtonBox* backNextButtons = new QDialogButtonBox();
|
||||
backNextButtons->setObjectName("footer");
|
||||
vLayout->addWidget(backNextButtons);
|
||||
|
||||
m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole);
|
||||
m_backButton->setProperty("secondary", true);
|
||||
m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole);
|
||||
|
||||
connect(m_backButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleBackButton);
|
||||
connect(m_nextButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleNextButton);
|
||||
connect(gemsButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleGemsButton);
|
||||
connect(m_backButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton);
|
||||
connect(m_nextButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleNextButton);
|
||||
connect(reinterpret_cast<ScreensCtrl*>(parent), &ScreensCtrl::NotifyCurrentProject, this, &UpdateProjectCtrl::UpdateCurrentProject);
|
||||
|
||||
m_screensOrder =
|
||||
{
|
||||
ProjectManagerScreen::ProjectSettings,
|
||||
ProjectManagerScreen::GemCatalog
|
||||
};
|
||||
m_screensCtrl->BuildScreens(m_screensOrder);
|
||||
m_screensCtrl->ForceChangeToScreen(ProjectManagerScreen::ProjectSettings, false);
|
||||
|
||||
UpdateNextButtonText();
|
||||
|
||||
Update();
|
||||
setLayout(vLayout);
|
||||
}
|
||||
|
||||
ProjectManagerScreen UpdateProjectCtrl::GetScreenEnum()
|
||||
@@ -58,63 +89,80 @@ namespace O3DE::ProjectManager
|
||||
return ProjectManagerScreen::UpdateProject;
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::NotifyCurrentScreen()
|
||||
{
|
||||
m_stack->setCurrentIndex(ScreenOrder::Settings);
|
||||
Update();
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::HandleGemsButton()
|
||||
{
|
||||
// The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog.
|
||||
m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false);
|
||||
|
||||
m_stack->setCurrentWidget(m_gemCatalogScreen);
|
||||
Update();
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::HandleBackButton()
|
||||
{
|
||||
if (!m_screensCtrl->GotoPreviousScreen())
|
||||
if (m_stack->currentIndex() > 0)
|
||||
{
|
||||
emit GotoPreviousScreenRequest();
|
||||
m_stack->setCurrentIndex(m_stack->currentIndex() - 1);
|
||||
Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateNextButtonText();
|
||||
emit GotoPreviousScreenRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::HandleNextButton()
|
||||
{
|
||||
ScreenWidget* currentScreen = m_screensCtrl->GetCurrentScreen();
|
||||
ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum();
|
||||
auto screenOrderIter = m_screensOrder.begin();
|
||||
for (; screenOrderIter != m_screensOrder.end(); ++screenOrderIter)
|
||||
if (m_stack->currentIndex() == ScreenOrder::Settings)
|
||||
{
|
||||
if (*screenOrderIter == screenEnum)
|
||||
if (m_updateSettingsScreen)
|
||||
{
|
||||
++screenOrderIter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (screenEnum == ProjectManagerScreen::ProjectSettings)
|
||||
{
|
||||
auto projectScreen = reinterpret_cast<ProjectSettingsScreen*>(currentScreen);
|
||||
if (projectScreen)
|
||||
{
|
||||
if (!projectScreen->Validate())
|
||||
if (!m_updateSettingsScreen->Validate())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_projectInfo = projectScreen->GetProjectInfo();
|
||||
ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo();
|
||||
|
||||
// Update project if settings changed
|
||||
if (m_projectInfo != newProjectSettings)
|
||||
{
|
||||
bool result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings);
|
||||
if (!result)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if project path has changed and move it
|
||||
if (newProjectSettings.m_path != m_projectInfo.m_path)
|
||||
{
|
||||
if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_projectInfo = newProjectSettings;
|
||||
}
|
||||
}
|
||||
|
||||
if (screenOrderIter != m_screensOrder.end())
|
||||
if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen)
|
||||
{
|
||||
m_screensCtrl->ChangeToScreen(*screenOrderIter);
|
||||
UpdateNextButtonText();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto result = PythonBindingsInterface::Get()->UpdateProject(m_projectInfo);
|
||||
if (result)
|
||||
{
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project."));
|
||||
}
|
||||
// Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project.
|
||||
m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path);
|
||||
}
|
||||
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::UpdateCurrentProject(const QString& projectPath)
|
||||
@@ -124,16 +172,28 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
m_projectInfo = projectResult.GetValue();
|
||||
}
|
||||
|
||||
Update();
|
||||
UpdateSettingsScreen();
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::UpdateNextButtonText()
|
||||
void UpdateProjectCtrl::Update()
|
||||
{
|
||||
QString nextButtonText = tr("Continue");
|
||||
if (m_screensCtrl->GetCurrentScreen()->GetScreenEnum() == ProjectManagerScreen::GemCatalog)
|
||||
if (m_stack->currentIndex() == ScreenOrder::Gems)
|
||||
{
|
||||
nextButtonText = tr("Update Project");
|
||||
m_header->setSubTitle(QString(tr("Add More Gems to \"%1\"")).arg(m_projectInfo.m_projectName));
|
||||
m_nextButton->setText(tr("Confirm"));
|
||||
}
|
||||
m_nextButton->setText(nextButtonText);
|
||||
else
|
||||
{
|
||||
m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName));
|
||||
m_nextButton->setText(tr("Save"));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::UpdateSettingsScreen()
|
||||
{
|
||||
m_updateSettingsScreen->SetProjectInfo(m_projectInfo);
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -12,40 +12,57 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include "ProjectInfo.h"
|
||||
#include <ProjectInfo.h>
|
||||
#include <ScreenWidget.h>
|
||||
#include <ScreensCtrl.h>
|
||||
#include <QPushButton>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(QTabWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
QT_FORWARD_DECLARE_CLASS(QFrame)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class UpdateProjectCtrl
|
||||
: public ScreenWidget
|
||||
QT_FORWARD_DECLARE_CLASS(ScreenHeader)
|
||||
QT_FORWARD_DECLARE_CLASS(UpdateProjectSettingsScreen)
|
||||
QT_FORWARD_DECLARE_CLASS(GemCatalogScreen)
|
||||
|
||||
class UpdateProjectCtrl : public ScreenWidget
|
||||
{
|
||||
public:
|
||||
explicit UpdateProjectCtrl(QWidget* parent = nullptr);
|
||||
~UpdateProjectCtrl() = default;
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
|
||||
protected:
|
||||
void NotifyCurrentScreen() override;
|
||||
|
||||
protected slots:
|
||||
void HandleBackButton();
|
||||
void HandleNextButton();
|
||||
void HandleGemsButton();
|
||||
void UpdateCurrentProject(const QString& projectPath);
|
||||
|
||||
private:
|
||||
void UpdateNextButtonText();
|
||||
void Update();
|
||||
void UpdateSettingsScreen();
|
||||
|
||||
ScreensCtrl* m_screensCtrl;
|
||||
QPushButton* m_backButton;
|
||||
QPushButton* m_nextButton;
|
||||
enum ScreenOrder
|
||||
{
|
||||
Settings,
|
||||
Gems
|
||||
};
|
||||
|
||||
ScreenHeader* m_header = nullptr;
|
||||
QStackedWidget* m_stack = nullptr;
|
||||
UpdateProjectSettingsScreen* m_updateSettingsScreen = nullptr;
|
||||
GemCatalogScreen* m_gemCatalogScreen = nullptr;
|
||||
|
||||
QPushButton* m_backButton = nullptr;
|
||||
QPushButton* m_nextButton = nullptr;
|
||||
QVector<ProjectManagerScreen> m_screensOrder;
|
||||
|
||||
ProjectInfo m_projectInfo;
|
||||
|
||||
ProjectManagerScreen m_screenEnum;
|
||||
};
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <UpdateProjectSettingsScreen.h>
|
||||
#include <FormBrowseEditWidget.h>
|
||||
#include <FormLineEditWidget.h>
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QDir>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
UpdateProjectSettingsScreen::UpdateProjectSettingsScreen(QWidget* parent)
|
||||
: ProjectSettingsScreen(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ProjectManagerScreen UpdateProjectSettingsScreen::GetScreenEnum()
|
||||
{
|
||||
return ProjectManagerScreen::UpdateProjectSettings;
|
||||
}
|
||||
|
||||
void UpdateProjectSettingsScreen::SetProjectInfo(const ProjectInfo& projectInfo)
|
||||
{
|
||||
m_projectName->lineEdit()->setText(projectInfo.m_projectName);
|
||||
m_projectPath->lineEdit()->setText(projectInfo.m_path);
|
||||
}
|
||||
|
||||
bool UpdateProjectSettingsScreen::ValidateProjectPath()
|
||||
{
|
||||
bool projectPathIsValid = true;
|
||||
if (m_projectPath->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
m_projectPath->setErrorLabelText(tr("Please provide a valid location."));
|
||||
}
|
||||
|
||||
m_projectPath->setErrorLabelVisible(!projectPathIsValid);
|
||||
return projectPathIsValid;
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ProjectSettingsScreen.h>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class UpdateProjectSettingsScreen
|
||||
: public ProjectSettingsScreen
|
||||
{
|
||||
public:
|
||||
explicit UpdateProjectSettingsScreen(QWidget* parent = nullptr);
|
||||
~UpdateProjectSettingsScreen() = default;
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
|
||||
void SetProjectInfo(const ProjectInfo& projectInfo);
|
||||
|
||||
protected:
|
||||
bool ValidateProjectPath() override;
|
||||
};
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -38,6 +38,8 @@ set(FILES
|
||||
Source/ProjectInfo.cpp
|
||||
Source/ProjectUtils.h
|
||||
Source/ProjectUtils.cpp
|
||||
Source/UpdateProjectSettingsScreen.h
|
||||
Source/UpdateProjectSettingsScreen.cpp
|
||||
Source/NewProjectSettingsScreen.h
|
||||
Source/NewProjectSettingsScreen.cpp
|
||||
Source/CreateProjectCtrl.h
|
||||
@@ -48,7 +50,6 @@ set(FILES
|
||||
Source/ProjectsScreen.cpp
|
||||
Source/ProjectSettingsScreen.h
|
||||
Source/ProjectSettingsScreen.cpp
|
||||
Source/ProjectSettingsScreen.ui
|
||||
Source/EngineSettingsScreen.h
|
||||
Source/EngineSettingsScreen.cpp
|
||||
Source/ProjectButtonWidget.h
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL
|
||||
void signal_handler(int signal)
|
||||
void signal_handler([[maybe_unused]] int signal)
|
||||
{
|
||||
AZ_TracePrintf(
|
||||
SceneAPI::Utilities::ErrorWindow,
|
||||
|
||||
@@ -56,9 +56,13 @@ ly_add_target(
|
||||
Gem::HttpRequestor
|
||||
)
|
||||
|
||||
# servers and clients use the above module.
|
||||
# Load the "Gem::AWSClientAuth" module in all types of applications.
|
||||
ly_create_alias(NAME AWSClientAuth.Servers NAMESPACE Gem TARGETS Gem::AWSClientAuth)
|
||||
ly_create_alias(NAME AWSClientAuth.Clients NAMESPACE Gem TARGETS Gem::AWSClientAuth)
|
||||
if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_create_alias(NAME AWSClientAuth.Tools NAMESPACE Gem TARGETS Gem::AWSClientAuth)
|
||||
ly_create_alias(NAME AWSClientAuth.Builders NAMESPACE Gem TARGETS Gem::AWSClientAuth)
|
||||
endif()
|
||||
|
||||
################################################################################
|
||||
# Tests
|
||||
|
||||
@@ -79,14 +79,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
Include/Private
|
||||
COMPILE_DEFINITIONS
|
||||
PRIVATE
|
||||
AWSCORE_EDITOR
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
Gem::AWSCore.Static
|
||||
Gem::AWSCore.Editor.Static
|
||||
RUNTIME_DEPENDENCIES
|
||||
Gem::AWSCore
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AWSCoreModule.h>
|
||||
#include <AzCore/Module/Module.h>
|
||||
|
||||
namespace AWSCore
|
||||
{
|
||||
class AWSCoreEditorModule
|
||||
: public AWSCoreModule
|
||||
:public AZ::Module
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AWSCoreModule);
|
||||
AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AZ::Module);
|
||||
AZ_CLASS_ALLOCATOR(AWSCoreEditorModule, AZ::SystemAllocator, 0);
|
||||
|
||||
AWSCoreEditorModule();
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
namespace AWSCore
|
||||
{
|
||||
AWSCoreEditorModule::AWSCoreEditorModule()
|
||||
: AWSCoreModule()
|
||||
{
|
||||
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
|
||||
m_descriptors.insert(m_descriptors.end(), {
|
||||
@@ -28,10 +27,9 @@ namespace AWSCore
|
||||
*/
|
||||
AZ::ComponentTypeList AWSCoreEditorModule::GetRequiredSystemComponents() const
|
||||
{
|
||||
AZ::ComponentTypeList requiredComponents = AWSCoreModule::GetRequiredSystemComponents();
|
||||
requiredComponents.push_back(azrtti_typeid<AWSCoreEditorSystemComponent>());
|
||||
|
||||
return requiredComponents;
|
||||
return AZ::ComponentTypeList{
|
||||
azrtti_typeid<AWSCoreEditorSystemComponent>()
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ namespace AWSCore
|
||||
|
||||
}
|
||||
|
||||
#if !defined(AWSCORE_EDITOR)
|
||||
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
|
||||
// The first parameter should be GemName_GemIdLower
|
||||
// The second should be the fully qualified name of the class above
|
||||
AZ_DECLARE_MODULE_CLASS(Gem_AWSCore, AWSCore::AWSCoreModule)
|
||||
#endif
|
||||
|
||||
@@ -11,7 +11,5 @@
|
||||
|
||||
set(FILES
|
||||
Include/Private/AWSCoreEditorModule.h
|
||||
Include/Private/AWSCoreModule.h
|
||||
Source/AWSCoreEditorModule.cpp
|
||||
Source/AWSCoreModule.cpp
|
||||
)
|
||||
|
||||
@@ -46,9 +46,13 @@ ly_add_target(
|
||||
Gem::AWSCore
|
||||
)
|
||||
|
||||
# Servers and Clients use the above metrics module
|
||||
# Load the "Gem::AWSMetrics" module in all types of applications.
|
||||
ly_create_alias(NAME AWSMetrics.Servers NAMESPACE Gem TARGETS Gem::AWSMetrics)
|
||||
ly_create_alias(NAME AWSMetrics.Clients NAMESPACE Gem TARGETS Gem::AWSMetrics)
|
||||
if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_create_alias(NAME AWSMetrics.Tools NAMESPACE Gem TARGETS Gem::AWSMetrics)
|
||||
ly_create_alias(NAME AWSMetrics.Builders NAMESPACE Gem TARGETS Gem::AWSMetrics)
|
||||
endif()
|
||||
|
||||
################################################################################
|
||||
# Tests
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
#include <Atom/Feature/Material/MaterialAssignment.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <Material/MaterialAssignmentSerializer.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -22,6 +24,11 @@ namespace AZ
|
||||
{
|
||||
MaterialAssignmentId::Reflect(context);
|
||||
|
||||
if (auto jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
|
||||
{
|
||||
jsonContext->Serializer<JsonMaterialAssignmentSerializer>()->HandlesType<MaterialAssignment>();
|
||||
}
|
||||
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->RegisterGenericType<MaterialAssignmentMap>();
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Material/MaterialAssignmentSerializer.h>
|
||||
#include <Atom/Feature/Material/MaterialAssignment.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonMaterialAssignmentSerializer, AZ::SystemAllocator, 0);
|
||||
|
||||
JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Load(
|
||||
void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult;
|
||||
|
||||
AZ_Assert(
|
||||
azrtti_typeid<AZ::Render::MaterialAssignment>() == outputValueTypeId,
|
||||
"Unable to deserialize MaterialAssignment from json because the provided type is %s.",
|
||||
outputValueTypeId.ToString<AZStd::string>().c_str());
|
||||
|
||||
AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast<AZ::Render::MaterialAssignment*>(outputValue);
|
||||
AZ_Assert(materialAssignment, "Output value for JsonMaterialAssignmentSerializer can't be null.");
|
||||
|
||||
JSR::ResultCode result(JSR::Tasks::ReadField);
|
||||
{
|
||||
result.Combine(ContinueLoadingFromJsonObjectField(
|
||||
&materialAssignment->m_materialAsset, azrtti_typeid<decltype(materialAssignment->m_materialAsset)>(), inputValue,
|
||||
"MaterialAsset", context));
|
||||
}
|
||||
|
||||
if (inputValue.HasMember("PropertyOverrides") && inputValue["PropertyOverrides"].IsObject())
|
||||
{
|
||||
// Attempt to load material property override values for a subset of types
|
||||
for (const auto& inputPropertyPair : inputValue["PropertyOverrides"].GetObject())
|
||||
{
|
||||
const AZ::Name propertyName(inputPropertyPair.name.GetString());
|
||||
if (!propertyName.IsEmpty())
|
||||
{
|
||||
AZStd::any propertyValue;
|
||||
if (LoadAny<bool>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::u8>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::u16>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::u32>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::u64>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::s8>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::s16>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::s32>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::s64>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<float>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<double>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::Vector2>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::Vector3>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::Vector4>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::Color>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZStd::string>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::Data::AssetId>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::Data::Asset<AZ::RPI::ImageAsset>>(propertyValue, inputPropertyPair.value, context, result) ||
|
||||
LoadAny<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(propertyValue, inputPropertyPair.value, context, result))
|
||||
{
|
||||
materialAssignment->m_propertyOverrides[propertyName] = propertyValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context.Report(
|
||||
result,
|
||||
result.GetProcessing() != JSR::Processing::Halted ? "Succesfully loaded MaterialAssignment information."
|
||||
: "Failed to load MaterialAssignment information.");
|
||||
}
|
||||
|
||||
JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Store(
|
||||
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, [[maybe_unused]] const Uuid& valueTypeId,
|
||||
JsonSerializerContext& context)
|
||||
{
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
|
||||
AZ_Assert(
|
||||
azrtti_typeid<AZ::Render::MaterialAssignment>() == valueTypeId,
|
||||
"Unable to Serialize MaterialAssignment because the provided type is %s.", valueTypeId.ToString<AZStd::string>().c_str());
|
||||
|
||||
const AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast<const AZ::Render::MaterialAssignment*>(inputValue);
|
||||
AZ_Assert(materialAssignment, "Input value for JsonMaterialAssignmentSerializer can't be null.");
|
||||
const AZ::Render::MaterialAssignment* defaultMaterialAssignmentInstance =
|
||||
reinterpret_cast<const AZ::Render::MaterialAssignment*>(defaultValue);
|
||||
|
||||
outputValue.SetObject();
|
||||
|
||||
JSR::ResultCode result(JSR::Tasks::WriteValue);
|
||||
{
|
||||
AZ::ScopedContextPath subPathMaterialAsset(context, "m_materialAsset");
|
||||
const AZ::Data::Asset<RPI::MaterialAsset>* materialAsset = &materialAssignment->m_materialAsset;
|
||||
const AZ::Data::Asset<RPI::MaterialAsset>* defaultmaterialAsset =
|
||||
defaultMaterialAssignmentInstance ? &defaultMaterialAssignmentInstance->m_materialAsset : nullptr;
|
||||
|
||||
result.Combine(ContinueStoringToJsonObjectField(
|
||||
outputValue, "MaterialAsset", materialAsset, defaultmaterialAsset,
|
||||
azrtti_typeid<decltype(materialAssignment->m_materialAsset)>(), context));
|
||||
}
|
||||
|
||||
{
|
||||
AZ::ScopedContextPath subPathPropertyOverrides(context, "m_propertyOverrides");
|
||||
if (!materialAssignment->m_propertyOverrides.empty())
|
||||
{
|
||||
rapidjson::Value outputPropertyValueContainer;
|
||||
outputPropertyValueContainer.SetObject();
|
||||
|
||||
// Attempt to extract and store material property override values for a subset of types
|
||||
for (const auto& propertyPair : materialAssignment->m_propertyOverrides)
|
||||
{
|
||||
const AZ::Name& propertyName = propertyPair.first;
|
||||
const AZStd::any& propertyValue = propertyPair.second;
|
||||
if (!propertyName.IsEmpty() && !propertyValue.empty())
|
||||
{
|
||||
rapidjson::Value outputPropertyValue;
|
||||
if (StoreAny<bool>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::u8>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::u16>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::u32>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::u64>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::s8>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::s16>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::s32>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::s64>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<float>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<double>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::Vector2>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::Vector3>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::Vector4>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::Color>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZStd::string>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::Data::AssetId>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::Data::Asset<AZ::RPI::ImageAsset>>(propertyValue, outputPropertyValue, context, result) ||
|
||||
StoreAny<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(
|
||||
propertyValue, outputPropertyValue, context, result))
|
||||
{
|
||||
outputPropertyValueContainer.AddMember(
|
||||
rapidjson::Value::StringRefType(propertyName.GetCStr()), outputPropertyValue,
|
||||
context.GetJsonAllocator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outputPropertyValueContainer.MemberCount() > 0)
|
||||
{
|
||||
outputValue.AddMember("PropertyOverrides", outputPropertyValueContainer, context.GetJsonAllocator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context.Report(
|
||||
result,
|
||||
result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored MaterialAssignment information."
|
||||
: "Failed to store MaterialAssignment information.");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool JsonMaterialAssignmentSerializer::LoadAny(
|
||||
AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context,
|
||||
AZ::JsonSerializationResult::ResultCode& result)
|
||||
{
|
||||
if (inputPropertyValue.IsObject() && inputPropertyValue.HasMember("Value") && inputPropertyValue.HasMember("$type"))
|
||||
{
|
||||
// Requiring explicit type info to differentiate be=tween colors versus vectors and numeric types
|
||||
const AZ::Uuid baseTypeId = azrtti_typeid<T>();
|
||||
AZ::Uuid typeId = AZ::Uuid::CreateNull();
|
||||
result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId));
|
||||
|
||||
if (typeId == azrtti_typeid<T>())
|
||||
{
|
||||
T value = {};
|
||||
result.Combine(ContinueLoadingFromJsonObjectField(&value, azrtti_typeid<T>(), inputPropertyValue, "Value", context));
|
||||
propertyValue = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool JsonMaterialAssignmentSerializer::StoreAny(
|
||||
const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context,
|
||||
AZ::JsonSerializationResult::ResultCode& result)
|
||||
{
|
||||
if (propertyValue.is<T>())
|
||||
{
|
||||
outputPropertyValue.SetObject();
|
||||
|
||||
// Storing explicit type info to differentiate be=tween colors versus vectors and numeric types
|
||||
rapidjson::Value typeValue;
|
||||
result.Combine(StoreTypeId(typeValue, azrtti_typeid<T>(), context));
|
||||
outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator());
|
||||
|
||||
T value = AZStd::any_cast<T>(propertyValue);
|
||||
result.Combine(
|
||||
ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid<T>(), context));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/Name/Name.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
{
|
||||
// Custom JSON serializer for material assignment objects containing AZStd::any property overrides,
|
||||
// which aren't supported by the system
|
||||
class JsonMaterialAssignmentSerializer : public BaseJsonSerializer
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(JsonMaterialAssignmentSerializer, "{3D33653E-4582-483F-91F5-BBCC347C3DF0}", BaseJsonSerializer);
|
||||
AZ_CLASS_ALLOCATOR_DECL;
|
||||
|
||||
JsonSerializationResult::Result Load(
|
||||
void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context) override;
|
||||
|
||||
JsonSerializationResult::Result Store(
|
||||
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId,
|
||||
JsonSerializerContext& context) override;
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
bool LoadAny(
|
||||
AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context,
|
||||
AZ::JsonSerializationResult::ResultCode& result);
|
||||
template<typename T>
|
||||
bool StoreAny(
|
||||
const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context,
|
||||
AZ::JsonSerializationResult::ResultCode& result);
|
||||
};
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
@@ -16,6 +16,8 @@ set(FILES
|
||||
Include/Atom/Feature/Utils/ModelPreset.h
|
||||
Source/Material/MaterialAssignment.cpp
|
||||
Source/Material/MaterialAssignmentId.cpp
|
||||
Source/Material/MaterialAssignmentSerializer.cpp
|
||||
Source/Material/MaterialAssignmentSerializer.h
|
||||
Source/Utils/LightingPreset.cpp
|
||||
Source/Utils/ModelPreset.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
vulkan.h was generated using a code generator from https://github.com/Dav1dde/glad
|
||||
|
||||
/*
|
||||
** Copyright (c) 2014-2020 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -10,3 +10,5 @@
|
||||
#
|
||||
|
||||
add_subdirectory(Code)
|
||||
add_subdirectory(Tools)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace AZ
|
||||
{
|
||||
PrimitiveType = AZ_BIT(0),
|
||||
DepthState = AZ_BIT(1),
|
||||
EnableStencil = AZ_BIT(2),
|
||||
StencilState = AZ_BIT(2),
|
||||
FaceCullMode = AZ_BIT(3),
|
||||
BlendMode = AZ_BIT(4)
|
||||
};
|
||||
@@ -110,8 +110,8 @@ namespace AZ
|
||||
|
||||
//! Set DepthState if DrawStateOptions::DepthState option is enabled
|
||||
void SetDepthState(RHI::DepthState depthState);
|
||||
//! Enable/disable stencil if DrawStateOptions::EnableStencil option is enabled
|
||||
void SetEnableStencil(bool enable);
|
||||
//! Set StencilState if DrawStateOptions::StencilState option is enabled
|
||||
void SetStencilState(RHI::StencilState stencilState);
|
||||
//! Set CullMode if DrawStateOptions::FaceCullMode option is enabled
|
||||
void SetCullMode(RHI::CullMode cullMode);
|
||||
//! Set TargetBlendState for target 0 if DrawStateOptions::BlendMode option is enabled
|
||||
@@ -188,7 +188,7 @@ namespace AZ
|
||||
// states available for change
|
||||
RHI::CullMode m_cullMode;
|
||||
RHI::DepthState m_depthState;
|
||||
bool m_enableStencil;
|
||||
RHI::StencilState m_stencilState;
|
||||
RHI::PrimitiveTopology m_topology;
|
||||
RHI::TargetBlendState m_blendState0;
|
||||
|
||||
|
||||
@@ -35,7 +35,15 @@ namespace AZ
|
||||
ModelKdTree() = default;
|
||||
|
||||
bool Build(const ModelAsset* model);
|
||||
bool RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const;
|
||||
//! Return if a ray intersected the model.
|
||||
//! @param raySrc The starting point of the ray.
|
||||
//! @param rayDir The direction and length of the ray (magnitude is encoded in the direction).
|
||||
//! @param[out] The normalized distance of the intersection (in the range 0.0-1.0) - to calculate the actual
|
||||
//! distance, multiply distanceNormalized by the magnitude of rayDir.
|
||||
//! @param[out] The surface normal of the intersection with the model.
|
||||
//! @return Return true if there was an intersection with the model, false otherwise.
|
||||
bool RayIntersection(
|
||||
const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const;
|
||||
void GetPenetratedBoxes(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector<AZ::Aabb>& outBoxes);
|
||||
|
||||
enum ESplitAxis
|
||||
@@ -53,8 +61,14 @@ namespace AZ
|
||||
private:
|
||||
|
||||
void BuildRecursively(ModelKdTreeNode* pNode, const AZ::Aabb& boundbox, AZStd::vector<ObjectIdTriangleIndices>& indices);
|
||||
bool RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const;
|
||||
void GetPenetratedBoxesRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector<AZ::Aabb>& outBoxes);
|
||||
bool RayIntersectionRecursively(
|
||||
ModelKdTreeNode* pNode,
|
||||
const AZ::Vector3& raySrc,
|
||||
const AZ::Vector3& rayDir,
|
||||
float& distanceNormalized,
|
||||
AZ::Vector3& normal) const;
|
||||
void GetPenetratedBoxesRecursively(
|
||||
ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector<AZ::Aabb>& outBoxes);
|
||||
void ConstructMeshList(const ModelAsset* model, const AZ::Transform& matParent);
|
||||
|
||||
static const int s_MinimumVertexSizeInLeafNode = 3 * 10;
|
||||
|
||||
@@ -30,25 +30,7 @@ namespace AZ
|
||||
constexpr const char* PerContextSrgName = "PerContextSrg";
|
||||
constexpr const char* PerDrawSrgName = "PerDrawSrg";
|
||||
};
|
||||
|
||||
bool CompareTargetBlendState(const RHI::TargetBlendState& firstState, const RHI::TargetBlendState& secondState)
|
||||
{
|
||||
return !(firstState.m_enable != secondState.m_enable
|
||||
|| firstState.m_blendOp != secondState.m_blendOp
|
||||
|| firstState.m_blendDest != secondState.m_blendDest
|
||||
|| firstState.m_blendSource != secondState.m_blendSource
|
||||
|| firstState.m_blendAlphaDest != secondState.m_blendAlphaDest
|
||||
|| firstState.m_blendAlphaOp != secondState.m_blendAlphaOp
|
||||
|| firstState.m_blendAlphaSource != secondState.m_blendAlphaSource);
|
||||
}
|
||||
|
||||
bool CompareDepthState(const RHI::DepthState& firstState, const RHI::DepthState& secondState)
|
||||
{
|
||||
return !(firstState.m_enable != secondState.m_enable
|
||||
|| firstState.m_func != secondState.m_func
|
||||
|| firstState.m_writeMask != secondState.m_writeMask);
|
||||
}
|
||||
|
||||
|
||||
void DynamicDrawContext::MultiStates::UpdateHash(const DrawStateOptions& drawStateOptions)
|
||||
{
|
||||
if (!m_isDirty)
|
||||
@@ -70,9 +52,19 @@ namespace AZ
|
||||
seed = TypeHash64(m_depthState.m_writeMask, seed);
|
||||
}
|
||||
|
||||
if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::EnableStencil))
|
||||
if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::StencilState))
|
||||
{
|
||||
seed = TypeHash64(m_enableStencil, seed);
|
||||
seed = TypeHash64(m_stencilState.m_enable, seed);
|
||||
seed = TypeHash64(m_stencilState.m_readMask, seed);
|
||||
seed = TypeHash64(m_stencilState.m_writeMask, seed);
|
||||
seed = TypeHash64(m_stencilState.m_frontFace.m_failOp, seed);
|
||||
seed = TypeHash64(m_stencilState.m_frontFace.m_depthFailOp, seed);
|
||||
seed = TypeHash64(m_stencilState.m_frontFace.m_passOp, seed);
|
||||
seed = TypeHash64(m_stencilState.m_frontFace.m_func, seed);
|
||||
seed = TypeHash64(m_stencilState.m_backFace.m_failOp, seed);
|
||||
seed = TypeHash64(m_stencilState.m_backFace.m_depthFailOp, seed);
|
||||
seed = TypeHash64(m_stencilState.m_backFace.m_passOp, seed);
|
||||
seed = TypeHash64(m_stencilState.m_backFace.m_func, seed);
|
||||
}
|
||||
|
||||
if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::FaceCullMode))
|
||||
@@ -203,7 +195,7 @@ namespace AZ
|
||||
m_currentStates.m_cullMode = m_pipelineState->ConstDescriptor().m_renderStates.m_rasterState.m_cullMode;
|
||||
m_currentStates.m_topology = m_pipelineState->ConstDescriptor().m_inputStreamLayout.GetTopology();
|
||||
m_currentStates.m_depthState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_depth;
|
||||
m_currentStates.m_enableStencil = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil.m_enable;
|
||||
m_currentStates.m_stencilState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil;
|
||||
m_currentStates.m_blendState0 = m_pipelineState->ConstDescriptor().m_renderStates.m_blendState.m_targets[0];
|
||||
m_currentStates.UpdateHash(m_drawStateOptions);
|
||||
|
||||
@@ -291,7 +283,7 @@ namespace AZ
|
||||
{
|
||||
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::DepthState))
|
||||
{
|
||||
if (!CompareDepthState(m_currentStates.m_depthState, depthState))
|
||||
if (!(m_currentStates.m_depthState == depthState))
|
||||
{
|
||||
m_currentStates.m_depthState = depthState;
|
||||
m_currentStates.m_isDirty = true;
|
||||
@@ -303,19 +295,19 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicDrawContext::SetEnableStencil(bool enable)
|
||||
void DynamicDrawContext::SetStencilState(RHI::StencilState stencilState)
|
||||
{
|
||||
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil))
|
||||
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState))
|
||||
{
|
||||
if (m_currentStates.m_enableStencil != enable)
|
||||
if (!(m_currentStates.m_stencilState == stencilState))
|
||||
{
|
||||
m_currentStates.m_enableStencil = enable;
|
||||
m_currentStates.m_stencilState = stencilState;
|
||||
m_currentStates.m_isDirty = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("RHI", false, "Can't set SetEnableStencil if DrawVariation::EnableStencil wasn't enabled");
|
||||
AZ_Warning("RHI", false, "Can't set SetStencilState if DrawVariation::StencilState wasn't enabled");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -340,7 +332,7 @@ namespace AZ
|
||||
{
|
||||
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::BlendMode))
|
||||
{
|
||||
if (!CompareTargetBlendState(m_currentStates.m_blendState0, blendState))
|
||||
if (!(m_currentStates.m_blendState0 == blendState))
|
||||
{
|
||||
m_currentStates.m_blendState0 = blendState;
|
||||
m_currentStates.m_isDirty = true;
|
||||
@@ -695,9 +687,9 @@ namespace AZ
|
||||
{
|
||||
m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_depth = m_currentStates.m_depthState;
|
||||
}
|
||||
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil))
|
||||
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState))
|
||||
{
|
||||
m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil.m_enable = m_currentStates.m_enableStencil;
|
||||
m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil = m_currentStates.m_stencilState;
|
||||
}
|
||||
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::FaceCullMode))
|
||||
{
|
||||
|
||||
@@ -140,8 +140,9 @@ namespace AZ
|
||||
bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
|
||||
float firstHit;
|
||||
const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, firstHit, distance);
|
||||
float start;
|
||||
float end;
|
||||
const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, start, end);
|
||||
if (Intersect::ISECT_RAY_AABB_NONE != result)
|
||||
{
|
||||
if (ModelAsset* modelAssetPtr = m_modelAsset.Get())
|
||||
@@ -164,7 +165,9 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::RayIntersection(const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const
|
||||
bool Model::RayIntersection(
|
||||
const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir,
|
||||
float& distanceFactor, AZ::Vector3& normal) const
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
|
||||
const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale));
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
#include <AzCore/std/numeric.h>
|
||||
#include <AzCore/std/limits.h>
|
||||
#include <Atom/RPI.Reflect/Model/ModelKdTree.h>
|
||||
#include <AzCore/Math/IntersectSegment.h>
|
||||
|
||||
@@ -191,10 +192,10 @@ namespace AZ
|
||||
|
||||
if (ModelLodAsset* lodAssetPtr = model->GetLodAssets()[0].Get())
|
||||
{
|
||||
AZ_Warning("ModelKdTree", lodAssetPtr->GetMeshes().size() <= std::numeric_limits<AZ::u8>::max() + 1,
|
||||
AZ_Warning("ModelKdTree", lodAssetPtr->GetMeshes().size() <= AZStd::numeric_limits<AZ::u8>::max() + 1,
|
||||
"KdTree generation doesn't support models with greater than 256 meshes. RayIntersection results will be incorrect "
|
||||
"unless the meshes are merged or broken up into multiple models");
|
||||
const size_t size = AZStd::min<size_t>(lodAssetPtr->GetMeshes().size(), std::numeric_limits<AZ::u8>::max() + 1);
|
||||
const size_t size = AZStd::min<size_t>(lodAssetPtr->GetMeshes().size(), AZStd::numeric_limits<AZ::u8>::max() + 1);
|
||||
m_meshes.reserve(size);
|
||||
AZStd::transform(
|
||||
lodAssetPtr->GetMeshes().begin(), AZStd::next(lodAssetPtr->GetMeshes().begin(), size),
|
||||
@@ -204,20 +205,42 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelKdTree::RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const
|
||||
bool ModelKdTree::RayIntersection(
|
||||
const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
|
||||
{
|
||||
return RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, distance, normal);
|
||||
float closestDistanceNormalized = AZStd::numeric_limits<float>::max();
|
||||
if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, closestDistanceNormalized, normal))
|
||||
{
|
||||
distanceNormalized = closestDistanceNormalized;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelKdTree::RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const
|
||||
bool ModelKdTree::RayIntersectionRecursively(
|
||||
ModelKdTreeNode* pNode,
|
||||
const AZ::Vector3& raySrc,
|
||||
const AZ::Vector3& rayDir,
|
||||
float& distanceNormalized,
|
||||
AZ::Vector3& normal) const
|
||||
{
|
||||
using Intersect::IntersectRayAABB2;
|
||||
using Intersect::IntersectSegmentTriangleCCW;
|
||||
using Intersect::ISECT_RAY_AABB_NONE;
|
||||
|
||||
if (!pNode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float start, end;
|
||||
if (AZ::Intersect::IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == Intersect::ISECT_RAY_AABB_NONE)
|
||||
if (IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == ISECT_RAY_AABB_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (start > distanceNormalized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -235,17 +258,13 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::Vector3 intersectionNormal;
|
||||
float hitDistanceNormalized;
|
||||
const float maxDist(FLT_MAX);
|
||||
float nearestDist = maxDist;
|
||||
|
||||
float nearestDistanceNormalized = distanceNormalized;
|
||||
for (AZ::u32 i = 0; i < nVBuffSize; ++i)
|
||||
{
|
||||
const auto& [first, second, third] = pNode->GetVertexIndex(i);
|
||||
const AZ::u32 nObjIndex = pNode->GetObjIndex(i);
|
||||
|
||||
AZStd::array_view<float> positionBuffer = m_meshes[nObjIndex].m_vertexData;
|
||||
const AZStd::array_view<float> positionBuffer = m_meshes[nObjIndex].m_vertexData;
|
||||
|
||||
if (positionBuffer.empty())
|
||||
{
|
||||
@@ -258,25 +277,23 @@ namespace AZ
|
||||
AZ::Vector3{positionBuffer[third * 3 + 0], positionBuffer[third * 3 + 1], positionBuffer[third * 3 + 2]},
|
||||
};
|
||||
|
||||
const AZ::Vector3 rayEnd = raySrc + rayDir * distance;
|
||||
|
||||
if (AZ::Intersect::IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2],
|
||||
intersectionNormal, hitDistanceNormalized) != Intersect::ISECT_RAY_AABB_NONE)
|
||||
float hitDistanceNormalized;
|
||||
AZ::Vector3 intersectionNormal;
|
||||
const AZ::Vector3 rayEnd = raySrc + rayDir;
|
||||
if (IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2],
|
||||
intersectionNormal, hitDistanceNormalized) != ISECT_RAY_AABB_NONE)
|
||||
{
|
||||
float hitDistance = hitDistanceNormalized * distance;
|
||||
|
||||
if (nearestDist > hitDistance)
|
||||
if (nearestDistanceNormalized > hitDistanceNormalized)
|
||||
{
|
||||
normal = intersectionNormal;
|
||||
nearestDistanceNormalized = hitDistanceNormalized;
|
||||
}
|
||||
|
||||
nearestDist = AZStd::GetMin(nearestDist, hitDistance);
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestDist < maxDist)
|
||||
if (nearestDistanceNormalized < distanceNormalized)
|
||||
{
|
||||
distance = AZStd::GetMin(distance, nearestDist);
|
||||
distanceNormalized = nearestDistanceNormalized;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -284,8 +301,8 @@ namespace AZ
|
||||
}
|
||||
|
||||
// running both sides to find the closest intersection
|
||||
const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distance, normal);
|
||||
const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distance, normal);
|
||||
const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distanceNormalized, normal);
|
||||
const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distanceNormalized, normal);
|
||||
|
||||
return bFoundChild0 || bFoundChild1;
|
||||
}
|
||||
@@ -311,5 +328,5 @@ namespace AZ
|
||||
GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes);
|
||||
GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -1074,13 +1074,13 @@ namespace UnitTest
|
||||
}
|
||||
};
|
||||
|
||||
class KdTreeIntersectsFixture
|
||||
class KdTreeIntersectsParameterizedFixture
|
||||
: public ModelTests
|
||||
, public ::testing::WithParamInterface<KdTreeIntersectParams>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(KdTreeIntersectsFixture, KdTreeIntersects)
|
||||
TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects)
|
||||
{
|
||||
TwoSeparatedPlanesMesh mesh;
|
||||
|
||||
@@ -1090,7 +1090,10 @@ namespace UnitTest
|
||||
float distance = AZStd::numeric_limits<float>::max();
|
||||
AZ::Vector3 normal;
|
||||
|
||||
EXPECT_THAT(kdTree.RayIntersection(AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal), testing::Eq(GetParam().expectedShouldIntersect));
|
||||
EXPECT_THAT(
|
||||
kdTree.RayIntersection(
|
||||
AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal),
|
||||
testing::Eq(GetParam().expectedShouldIntersect));
|
||||
EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
|
||||
}
|
||||
|
||||
@@ -1119,5 +1122,63 @@ namespace UnitTest
|
||||
KdTreeIntersectParams{0.778f, 0.111f, 1.0f, 0.5f, true},
|
||||
KdTreeIntersectParams{0.778f, 0.778f, 1.0f, 0.5f, true},
|
||||
};
|
||||
INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsFixture, ::testing::ValuesIn(intersectTestData));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(intersectTestData));
|
||||
|
||||
class KdTreeIntersectsFixture
|
||||
: public ModelTests
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
ModelTests::SetUp();
|
||||
|
||||
m_mesh = AZStd::make_unique<TwoSeparatedPlanesMesh>();
|
||||
m_kdTree = AZStd::make_unique<AZ::RPI::ModelKdTree>();
|
||||
ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get()));
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_kdTree.reset();
|
||||
m_mesh.reset();
|
||||
|
||||
ModelTests::TearDown();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<TwoSeparatedPlanesMesh> m_mesh;
|
||||
AZStd::unique_ptr<AZ::RPI::ModelKdTree> m_kdTree;
|
||||
};
|
||||
|
||||
TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionReturnsNormalizedDistance)
|
||||
{
|
||||
float t = AZStd::numeric_limits<float>::max();
|
||||
AZ::Vector3 normal;
|
||||
|
||||
constexpr float rayLength = 100.0f;
|
||||
EXPECT_THAT(
|
||||
m_kdTree->RayIntersection(
|
||||
AZ::Vector3::CreateZero(), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true));
|
||||
EXPECT_THAT(t, testing::FloatEq(0.005f));
|
||||
}
|
||||
|
||||
TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionHandlesInvalidStartingNormalizedDistance)
|
||||
{
|
||||
float t = -0.5f; // invalid starting distance
|
||||
AZ::Vector3 normal;
|
||||
|
||||
constexpr float rayLength = 10.0f;
|
||||
EXPECT_THAT(
|
||||
m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(0.75f), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true));
|
||||
EXPECT_THAT(t, testing::FloatEq(0.025f));
|
||||
}
|
||||
|
||||
TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionDoesNotScaleRayByStartingDistance)
|
||||
{
|
||||
float t = 10.0f; // starting distance (used to check it is not read from initially by RayIntersection)
|
||||
AZ::Vector3 normal;
|
||||
|
||||
EXPECT_THAT(
|
||||
m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -9,7 +9,15 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
ly_add_external_target(
|
||||
NAME Clang
|
||||
VERSION 6.0.1-az
|
||||
)
|
||||
if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_pip_install_local_package_editable(${CMAKE_CURRENT_LIST_DIR} atom_rpi_tools)
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_add_pytest(
|
||||
NAME RPI::atom_rpi_tools_tests
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/atom_rpi_tools/tests/
|
||||
TIMEOUT 30
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
|
||||
INTRODUCTION
|
||||
------------
|
||||
|
||||
atom_rpi_tools is a Python project that contains a collection of tools
|
||||
developed by the Atom team. The project contains the following tools:
|
||||
|
||||
* Render pipeline merge tool:
|
||||
A library to manipulate .pass asset files and help gems create scripts to update render pipeline
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
|
||||
* Python 3.7.5 (64-bit)
|
||||
|
||||
It is recommended that you completely remove any other versions of Python
|
||||
installed on your system.
|
||||
|
||||
|
||||
INSTALL
|
||||
-----------
|
||||
It is recommended to set up these these tools with Lumberyard's CMake build commands.
|
||||
|
||||
|
||||
UNINSTALLATION
|
||||
--------------
|
||||
|
||||
The preferred way to uninstall the project is:
|
||||
(engine install root)/python/python -m pip uninstall atom_rpi_tools
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
@@ -0,0 +1,210 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
"""
|
||||
|
||||
import sys, os
|
||||
import json
|
||||
import shutil
|
||||
|
||||
class PassTemplate:
|
||||
# This class provide necessary functions for insert pass requests and update connections
|
||||
# which are common functions required for adding features.
|
||||
# It doesn't include the remove/delete furnctions since that's not common case for merging render pipeline
|
||||
def __init__(self, filePath: str):
|
||||
self.initialized = False
|
||||
self.file_path: str = filePath
|
||||
#load the json file
|
||||
json_data = open(filePath, "r")
|
||||
self.file_data = json.load(json_data)
|
||||
|
||||
if 'ClassName' not in self.file_data or 'ClassData' not in self.file_data or self.file_data['ClassName']!='PassAsset' or 'PassTemplate' not in self.file_data['ClassData']:
|
||||
raise KeyError('the json file is not a PassAsset file')
|
||||
return
|
||||
|
||||
if 'PassRequests' in self.file_data['ClassData']['PassTemplate']:
|
||||
self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests']
|
||||
|
||||
if 'Slots' in self.file_data['ClassData']['PassTemplate']:
|
||||
self.slots = self.file_data['ClassData']['PassTemplate']['Slots']
|
||||
|
||||
self.initialized = True
|
||||
print('PassTemplate is loaded from ', filePath)
|
||||
|
||||
def find_pass(self, passName):
|
||||
# return pass's index in PassRequests if a PassRequest with input passName exists
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return -1
|
||||
index = 0
|
||||
for passRequest in self.passRequests:
|
||||
if passRequest['Name'] == passName:
|
||||
return index
|
||||
index += 1
|
||||
return -1
|
||||
|
||||
def get_pass_count(self):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return 0
|
||||
return len(self.passRequests)
|
||||
|
||||
def __validate_pass_request_data(self, passRequest):
|
||||
if ('Name' not in passRequest or 'TemplateName' not in passRequest):
|
||||
raise KeyError('invalid pass request data')
|
||||
|
||||
def __ensure_pass_requests_key(self):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
self.file_data['ClassData']['PassTemplate']['PassRequests'] = []
|
||||
self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests']
|
||||
|
||||
def __ensure_pass_slots_key(self):
|
||||
if not hasattr(self, 'slots'):
|
||||
self.file_data['ClassData']['PassTemplate']['Slots'] = []
|
||||
self.slots = self.file_data['ClassData']['PassTemplate']['Slots']
|
||||
|
||||
def insert_pass_request(self, location, passRequest):
|
||||
self.__validate_pass_request_data(passRequest)
|
||||
|
||||
if (self.find_pass(passRequest['Name']) >= 0):
|
||||
raise ValueError('pass request ', passRequest['Name'], ' is already exist')
|
||||
# insert a passRequest before the specified location
|
||||
self.__ensure_pass_requests_key()
|
||||
self.passRequests.insert(location, passRequest)
|
||||
|
||||
def replace_references_after(self, startPassRequest, oldPass, oldSlot, newPass, newSlot):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return 0
|
||||
# from all pass requests after startPassRequest
|
||||
# replace all attachment references which uses oldPass and oldSlot
|
||||
# with newPass and newSlot
|
||||
started = False
|
||||
replaced_count = 0
|
||||
for request in self.passRequests:
|
||||
if started:
|
||||
if ('Connections' in request):
|
||||
for connection in request['Connections']:
|
||||
if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot:
|
||||
connection['AttachmentRef']['Pass'] = newPass
|
||||
connection['AttachmentRef']['Attachment'] = newSlot
|
||||
replaced_count += 1
|
||||
if request['Name'] == startPassRequest and not started:
|
||||
started = True
|
||||
return replaced_count
|
||||
|
||||
def replace_references_for(self, passRequest, oldPass, oldSlot, newPass, newSlot):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return 0
|
||||
#replace pass reference for the specified passRequest
|
||||
replaced_count = 0
|
||||
for request in self.passRequests:
|
||||
if request['Name'] == passRequest:
|
||||
if ('Connections' in request):
|
||||
for connection in request['Connections']:
|
||||
if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot:
|
||||
connection['AttachmentRef']['Pass'] = newPass
|
||||
connection['AttachmentRef']['Attachment'] = newSlot
|
||||
replaced_count += 1
|
||||
return replaced_count #return when the specified pass request is updated.
|
||||
return replaced_count
|
||||
|
||||
def __validate_slot_data(self, slotData):
|
||||
if ('Name' not in slotData or 'SlotType' not in slotData):
|
||||
raise KeyError('invalid slot data')
|
||||
|
||||
def get_slot_count(self):
|
||||
if not hasattr(self, 'slots'):
|
||||
return 0
|
||||
return len(self.slots)
|
||||
|
||||
def find_slot(self, slotName):
|
||||
# return slot's index in Slots if a PassRequest with input passName exists
|
||||
if not hasattr(self, 'slots'):
|
||||
return -1
|
||||
index = 0
|
||||
for slot in self.slots:
|
||||
if slot['Name'] == slotName:
|
||||
return index
|
||||
index += 1
|
||||
return -1
|
||||
|
||||
def insert_slot(self, location, newSlotData):
|
||||
# insert a new slot at specified location
|
||||
self.__validate_slot_data(newSlotData)
|
||||
# check if the slot already exist
|
||||
if (self.find_slot(newSlotData['Name']) >= 0):
|
||||
raise ValueError('Slot ', newSlotData['Name'], ' is already exist')
|
||||
|
||||
self.__ensure_pass_slots_key()
|
||||
self.slots.insert(location, newSlotData)
|
||||
|
||||
def add_slot(self, newSlotData):
|
||||
# append a new slot to slots
|
||||
self.__validate_slot_data(newSlotData)
|
||||
# check if the slot already exist
|
||||
if (self.find_slot(newSlotData['Name']) >= 0):
|
||||
raise ValueError('Slot ', newSlotData['Name'], ' is already exist')
|
||||
|
||||
self.__ensure_pass_slots_key()
|
||||
self.slots.append(newSlotData)
|
||||
|
||||
def get_pass_request(self, passName):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return
|
||||
# Get the pass request from PassRequests with matching pass name
|
||||
for passRequest in self.passRequests:
|
||||
if passRequest['Name'] == passName:
|
||||
return passRequest
|
||||
|
||||
def save(self):
|
||||
# backup the original file
|
||||
backupFilePath = self.file_path +'.backup'
|
||||
shutil.copyfile(self.file_path, backupFilePath)
|
||||
# save and overwrite file
|
||||
with open(self.file_path, 'w') as json_file:
|
||||
json.dump(self.file_data, json_file, indent = 4)
|
||||
print('File [', self.file_path, '] is updated. Old version is saved in [', backupFilePath, ']')
|
||||
|
||||
|
||||
class PassRequest:
|
||||
|
||||
def __init__(self, passRequest: object):
|
||||
self.pass_request = passRequest
|
||||
if 'Connections' in passRequest:
|
||||
self.connections = passRequest['Connections']
|
||||
|
||||
def __validate_connection(self, connection):
|
||||
if ('LocalSlot' not in connection or 'AttachmentRef' not in connection):
|
||||
raise KeyError('invalid connection data')
|
||||
|
||||
def __ensure_connections_key(self):
|
||||
if not hasattr(self, 'connections'):
|
||||
self.pass_request['Connections'] = []
|
||||
self.connections = self.pass_request['Connections']
|
||||
|
||||
def get_connection_count(self):
|
||||
if not hasattr(self, 'connections'):
|
||||
return 0
|
||||
return len(self.connections)
|
||||
|
||||
def find_connection(self, localSlotName):
|
||||
if not hasattr(self, 'connections'):
|
||||
return -1
|
||||
index = 0
|
||||
for connection in self.connections:
|
||||
if connection['LocalSlot'] == localSlotName:
|
||||
return index
|
||||
index += 1
|
||||
return -1
|
||||
|
||||
def add_connection(self, newConnection):
|
||||
self.__validate_connection(newConnection)
|
||||
if self.find_connection(newConnection['LocalSlot']) >= 0:
|
||||
raise ValueError('connection ', newConnection['LocalSlot'], ' already exists')
|
||||
self.__ensure_connections_key()
|
||||
self.connections.append(newConnection)
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
@@ -0,0 +1,303 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
Unit tests for pass_data.py
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
import shutil
|
||||
import json
|
||||
from atom_rpi_tools.pass_data import PassTemplate
|
||||
from atom_rpi_tools.pass_data import PassRequest
|
||||
|
||||
good_pass_requests_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_requests.json')
|
||||
good_pass_slots_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_slots.json')
|
||||
bad_test_data_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_test_bad.json')
|
||||
|
||||
@pytest.fixture
|
||||
def pass_requests_template(tmpdir):
|
||||
filename = 'pass_requests.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
destFilePath = os.path.join(tmpdir, 'pass_requests.json')
|
||||
shutil.copyfile(source_path, destFilePath)
|
||||
return PassTemplate(destFilePath)
|
||||
|
||||
@pytest.fixture
|
||||
def pass_slots_template(tmpdir):
|
||||
filename = 'pass_slots.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
destFilePath = os.path.join(tmpdir, 'pass_requests.json')
|
||||
shutil.copyfile(source_path, destFilePath)
|
||||
return PassTemplate(destFilePath)
|
||||
|
||||
@pytest.fixture
|
||||
def new_pass_request():
|
||||
pass_request = json.loads('{\"Name\": \"InsertPass\",\"TemplateName\": \"InsertPassTemplate\"}')
|
||||
return pass_request
|
||||
|
||||
@pytest.fixture
|
||||
def new_slot():
|
||||
slot = json.loads('{\"Name\": \"NewSlot\",\"SlotType\": \"Input\"}')
|
||||
return slot
|
||||
|
||||
@pytest.fixture
|
||||
def new_connection():
|
||||
connection = json.loads('{\"LocalSlot\": \"color\", \"AttachmentRef\": { \"Pass\": \"Parent\", \"Attachment\": \"DepthStencil\"}}')
|
||||
return connection
|
||||
|
||||
def test_PassTemplate_Initialize_BadPassTemplateData_ExceptionThrown():
|
||||
with pytest.raises(KeyError):
|
||||
PassTemplate(bad_test_data_file)
|
||||
|
||||
def test_PassTemplate_FindPass_Success(pass_requests_template):
|
||||
assert pass_requests_template.find_pass('OpaquePass') == 0
|
||||
assert pass_requests_template.find_pass('ImGuiPass') == 4
|
||||
assert pass_requests_template.find_pass('NotExistPass') == -1
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_AtBegining_Success(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
template.insert_pass_request(0, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == 0
|
||||
assert template.get_pass_count() == pass_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_pass(new_pass_request['Name'])== 0
|
||||
assert saved_tamplate.get_pass_count() == pass_count+1
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_AtEnd_Success(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
template.insert_pass_request(pass_count, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == pass_count
|
||||
assert template.get_pass_count() == pass_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_pass(new_pass_request['Name']) == pass_count
|
||||
assert saved_tamplate.get_pass_count() == pass_count+1
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
# insert new pass request
|
||||
template.insert_pass_request(0, new_pass_request)
|
||||
pass_count = template.get_pass_count()
|
||||
# exception when insert the same pass again
|
||||
with pytest.raises(ValueError):
|
||||
template.insert_pass_request(2, new_pass_request)
|
||||
# pass count doesn't change
|
||||
assert template.get_pass_count() == pass_count
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_WithBadData_ExceptionThrown(pass_requests_template):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
bad_pass_request = json.loads('{\"name\":\"value\"}')
|
||||
with pytest.raises(KeyError):
|
||||
template.insert_pass_request(2, bad_pass_request)
|
||||
assert template.get_pass_count() == pass_count
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_AtOutOfRange_AppendSuccess(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
template.insert_pass_request(pass_count+2, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == pass_count
|
||||
assert template.get_pass_count() == pass_count+1
|
||||
|
||||
def test_PassTemplate_ReplaceReferencesAfter_Success(pass_requests_template):
|
||||
# replace OpaquePass.DepthStencil with Parent.DepthStencil'
|
||||
refPass = 'OpaquePass'
|
||||
# there are 2 passes after OpaquePass which use OpaquePass.DepthStencil as attachment reference
|
||||
assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 2
|
||||
# after the previous replacement, there it no OpaquePass.DepthStencil reference
|
||||
refPass = 'TransparentPass'
|
||||
assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
# verify changes are saved
|
||||
pass_requests_template.save()
|
||||
saved_tamplate = PassTemplate(pass_requests_template.file_path)
|
||||
assert saved_tamplate.replace_references_after('OpaquePass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
def test_PassTemplate_ReplaceReferencesFor_Success(pass_requests_template):
|
||||
refPass = 'TransparentPass'
|
||||
assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 1
|
||||
refPass = '2DPass'
|
||||
assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
# verify changes are saved
|
||||
pass_requests_template.save()
|
||||
saved_tamplate = PassTemplate(pass_requests_template.file_path)
|
||||
# no reference of OpaquePass.DepthStencil in TransparentPass
|
||||
assert saved_tamplate.replace_references_for('TransparentPass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
def test_PassTemplate_FindSlot_Success(pass_slots_template):
|
||||
assert pass_slots_template.find_slot('Color') == -1
|
||||
assert pass_slots_template.find_slot('DepthStencil') == 0
|
||||
assert pass_slots_template.find_slot('ColorInputOutput') == 1
|
||||
|
||||
def test_PassTemplate_InsertSlot_AtBegining_Success(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
depth_stencil_slot = template.find_slot('DepthStencil')
|
||||
template.insert_slot(0, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == 0
|
||||
assert template.find_slot('DepthStencil') == depth_stencil_slot+1 # DepthStencil moved back by 1
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_slot(new_slot['Name']) == 0
|
||||
assert saved_tamplate.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_InsertSlot_AtEnd_Success(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
depth_stencil_slot = template.find_slot('DepthStencil')
|
||||
template.insert_slot(slot_count, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == slot_count
|
||||
assert template.find_slot('DepthStencil') == depth_stencil_slot
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_slot(new_slot['Name']) == slot_count
|
||||
assert saved_tamplate.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_AddSlot_GoodSlotData_Success(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
depth_stencil_slot = template.find_slot('DepthStencil')
|
||||
template.add_slot(new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == slot_count
|
||||
assert template.find_slot('DepthStencil') == depth_stencil_slot
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_slot(new_slot['Name']) == slot_count
|
||||
assert saved_tamplate.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_InsertSlot_OutOfRange_AppendSuccess(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
template.insert_slot(slot_count+3, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == slot_count
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_AddDuplicateSlot_ExceptionThrown(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
template.add_slot(new_slot)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
template.insert_slot(0, new_slot)
|
||||
with pytest.raises(ValueError):
|
||||
template.add_slot(new_slot)
|
||||
|
||||
def test_PassTemplate_InsertOrAddSlot_WithBadSlotData_ExceptionThrown(pass_slots_template):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
bad_slot = json.loads('{\"slot\": \"xxx\"}')
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
template.insert_slot(0, bad_slot)
|
||||
with pytest.raises(KeyError):
|
||||
template.add_slot(bad_slot)
|
||||
|
||||
def test_PassReqeuest_Initialize_WithExistPassReqeuestFromPassTemplate_Success(pass_requests_template):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
connection_count = request.get_connection_count()
|
||||
assert connection_count == 2
|
||||
|
||||
def test_PassTemplate_GetPassRequest_NotExist_ReturnNull(pass_requests_template):
|
||||
assert not pass_requests_template.get_pass_request('NotExistPass')
|
||||
|
||||
def test_PassReqeuest_AddConnection_WithExistingConnections_Success(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
connection_count = request.get_connection_count()
|
||||
request.add_connection(new_connection)
|
||||
connection_count += 1
|
||||
assert request.get_connection_count() == connection_count
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
saved_request = PassRequest(saved_tamplate.get_pass_request('OpaquePass'))
|
||||
assert saved_request.get_connection_count() == connection_count
|
||||
|
||||
def test_PassReqeuest_AddConnection_WithNoExistingConnections_Success(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('ImGuiPass'))
|
||||
assert request.get_connection_count() == 0
|
||||
request.add_connection(new_connection)
|
||||
assert request.get_connection_count() == 1
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
saved_request = PassRequest(saved_tamplate.get_pass_request('ImGuiPass'))
|
||||
assert saved_request.get_connection_count() == 1
|
||||
|
||||
def test_PassReqeuest_AddConnection_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
request.add_connection(new_connection)
|
||||
with pytest.raises(ValueError):
|
||||
request.add_connection(new_connection)
|
||||
|
||||
def test_PassReqeuest_AddConnect_BadConnectionData_ExceptionThrown(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
bad_connection = json.loads('{\"xxx\": \"xxx\"}')
|
||||
with pytest.raises(KeyError):
|
||||
request.add_connection(bad_connection)
|
||||
|
||||
def test_PassTemplate_InsertSlot_ToEmptyList_Success(pass_requests_template, new_slot):
|
||||
template = pass_requests_template
|
||||
# test insert slot function to pass template which doesn't have any slots
|
||||
slot_count = template.get_slot_count()
|
||||
assert slot_count == 0
|
||||
|
||||
assert template.find_slot(new_slot['Name'])==-1
|
||||
pass_requests_template.insert_slot(0, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == 0
|
||||
assert template.get_slot_count() == 1
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.get_slot_count() == 1
|
||||
|
||||
def test_PassTempalte_InsertPassRequest_ToEmptyList_Success(pass_slots_template, new_pass_request):
|
||||
template = pass_slots_template
|
||||
# test insert pass function to pass template which doesn't have any pass requests
|
||||
pass_count = template.get_pass_count()
|
||||
assert pass_count == 0
|
||||
template.insert_pass_request(0, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == 0
|
||||
assert template.get_pass_count() == 1
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.get_pass_count() == 1
|
||||
|
||||
def test_PassTemplate_Save_Success(pass_requests_template):
|
||||
pass_requests_template.save()
|
||||
saved_tamplate = PassTemplate(pass_requests_template.file_path)
|
||||
assert os.path.exists(pass_requests_template.file_path)
|
||||
assert os.path.exists(pass_requests_template.file_path +'.backup')
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
Unit tests for utils.py
|
||||
"""
|
||||
import pytest
|
||||
import os
|
||||
import atom_rpi_tools.utils as utils
|
||||
|
||||
|
||||
def test_FindOrCopyFile_DestFileNotExist_CopySuccess(tmpdir):
|
||||
# created dir and copied
|
||||
filename = 'pass_requests.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json')
|
||||
assert not os.path.exists(dest_path)
|
||||
utils.find_or_copy_file(dest_path, source_path)
|
||||
assert os.path.exists(dest_path)
|
||||
source_size = os.path.getsize(source_path)
|
||||
dest_size = os.path.getsize(dest_path)
|
||||
assert source_size == dest_size
|
||||
|
||||
def test_FindOrCopyFile_DestFileAlreadyExists_Skip(tmpdir):
|
||||
# copy %cur_dir%/testdata/pass_requests.json to tempdir/testdata/pass_requests.json
|
||||
filename = 'pass_requests.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json')
|
||||
utils.find_or_copy_file(dest_path, source_path)
|
||||
|
||||
# skip if dest_path already exists
|
||||
assert os.path.exists(dest_path)
|
||||
before_size = os.path.getsize(dest_path)
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', 'pass_slots.json')
|
||||
before_source_size = os.path.getsize(source_path)
|
||||
assert before_size != source_path
|
||||
utils.find_or_copy_file(dest_path, source_path)
|
||||
after_size = os.path.getsize(dest_path)
|
||||
assert before_size == after_size
|
||||
|
||||
|
||||
def test_FindOrCopyFile_SourceFileNotExists_ExceptionThrown(tmpdir):
|
||||
# report error if source doesn't exist
|
||||
bad_source_path = 'notexist.dat'
|
||||
dest_path = os.path.join(tmpdir, 'notexist.dat')
|
||||
with pytest.raises(ValueError):
|
||||
utils.find_or_copy_file(dest_path, bad_source_path)
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"Type": "JsonSerialization",
|
||||
"Version": 1,
|
||||
"ClassName": "PassAsset",
|
||||
"ClassData": {
|
||||
"PassTemplate": {
|
||||
"Name": "PipelineTemplate",
|
||||
"PassClass": "ParentPass",
|
||||
"PassRequests": [
|
||||
{
|
||||
"Name": "OpaquePass",
|
||||
"TemplateName": "OpaquePassTemplate",
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "DepthStencil",
|
||||
"AttachmentRef": {
|
||||
"Pass": "Parent",
|
||||
"Attachment": "DepthStencil"
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "Parent",
|
||||
"Attachment": "ColorInputOutput"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TransparentPass",
|
||||
"TemplateName": "TransparentPassTemplate",
|
||||
"Enabled": true,
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "DepthStencil",
|
||||
"AttachmentRef": {
|
||||
"Pass": "OpaquePass",
|
||||
"Attachment": "DepthStencil"
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "OpaquePass",
|
||||
"Attachment": "Color"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PassData": {
|
||||
"$type": "RasterPassData",
|
||||
"DrawListTag": "transparent",
|
||||
"DrawListSortType": "KeyThenReverseDepth",
|
||||
"PipelineViewTag": "MainCamera",
|
||||
"PassSrgAsset": {
|
||||
"FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "AuxGeomPass",
|
||||
"TemplateName": "AuxGeomPassTemplate",
|
||||
"Enabled": true,
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "DepthStencil",
|
||||
"AttachmentRef": {
|
||||
"Pass": "OpaquePass",
|
||||
"Attachment": "DepthStencil"
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "TransparentPass",
|
||||
"Attachment": "ColorInputOutput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PassData": {
|
||||
"$type": "RasterPassData",
|
||||
"DrawListTag": "auxgeom",
|
||||
"PipelineViewTag": "MainCamera"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "2DPass",
|
||||
"TemplateName": "UIPassTemplate",
|
||||
"Enabled": true,
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "TransparentPass",
|
||||
"Attachment": "ColorInputOutput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PassData": {
|
||||
"$type": "RasterPassData",
|
||||
"DrawListTag": "2dpass",
|
||||
"PipelineViewTag": "MainCamera"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "ImGuiPass",
|
||||
"TemplateName": "ImGuiPassTemplate",
|
||||
"PassData": {
|
||||
"$type": "ImGuiPassData",
|
||||
"IsDefaultImGui": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Type": "JsonSerialization",
|
||||
"Version": 1,
|
||||
"ClassName": "PassAsset",
|
||||
"ClassData": {
|
||||
"PassTemplate": {
|
||||
"Name": "PipelineTemplate",
|
||||
"PassClass": "ParentPass",
|
||||
"Slots": [
|
||||
{
|
||||
"Name": "DepthStencil",
|
||||
"SlotType": "InputOutput"
|
||||
},
|
||||
{
|
||||
"Name": "ColorInputOutput",
|
||||
"SlotType": "InputOutput"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Type": "JsonSerialization",
|
||||
"Version": 1,
|
||||
"ClassData": {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
import os.path
|
||||
from os import path
|
||||
import shutil
|
||||
import json
|
||||
|
||||
|
||||
def find_or_copy_file(destFilePath, sourceFilePath):
|
||||
if path.exists(destFilePath):
|
||||
return
|
||||
if not path.exists(sourceFilePath):
|
||||
raise ValueError('find_or_copy_file: source file [', sourceFilePath, '] doesn\'t exist')
|
||||
return
|
||||
|
||||
dstDir = path.dirname(destFilePath)
|
||||
if not path.isdir(dstDir):
|
||||
os.makedirs(dstDir)
|
||||
shutil.copyfile(sourceFilePath, destFilePath)
|
||||
|
||||
def load_json_file(filePath):
|
||||
file_stream = open(filePath, "r")
|
||||
return json.load(file_stream)
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
PYTHON_64 = platform.architecture()[0] == '64bit'
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not PYTHON_64:
|
||||
raise RuntimeError("32-bit Python is not a supported platform.")
|
||||
|
||||
with open(os.path.join(PROJECT_ROOT, 'README.txt')) as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="atom_rpi_tools",
|
||||
version="1.0.0",
|
||||
description='Python interface to Atom RPI tools',
|
||||
long_description=long_description,
|
||||
packages=find_packages(exclude=['tests'])
|
||||
)
|
||||
@@ -29,7 +29,8 @@ ly_add_target(
|
||||
Legacy::CryCommon
|
||||
Gem::Atom_RHI.Reflect
|
||||
Gem::Atom_RPI.Public
|
||||
Gem::Atom_Bootstrap.Headers
|
||||
PUBLIC
|
||||
Gem::Atom_AtomBridge.Static
|
||||
)
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
#include <AzFramework/Scene/SceneSystemInterface.h>
|
||||
|
||||
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h>
|
||||
#include <AtomBridge/PerViewportDynamicDrawInterface.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class FFont;
|
||||
|
||||
static constexpr char AtomFontDynamicDrawContextName[] = "AtomFont";
|
||||
|
||||
|
||||
//! AtomFont is the font system manager.
|
||||
//! AtomFont manages the lifetime of FFont instances, each of which represents an individual font (e.g Courier New Italic)
|
||||
@@ -90,13 +93,6 @@ namespace AZ
|
||||
AzFramework::FontDrawInterface* GetFontDrawInterface(AzFramework::FontId fontId) const override;
|
||||
AzFramework::FontDrawInterface* GetDefaultFontDrawInterface() const override;
|
||||
|
||||
void SceneAboutToBeRemoved(AzFramework::Scene& scene);
|
||||
|
||||
|
||||
// Atom DynamicDraw interface management
|
||||
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> GetOrCreateDynamicDrawForScene(AZ::RPI::Scene* scene);
|
||||
|
||||
|
||||
public:
|
||||
void UnregisterFont(const char* fontName);
|
||||
|
||||
@@ -108,8 +104,6 @@ namespace AZ
|
||||
using FontFamilyMap = AZStd::unordered_map<AZStd::string, AZStd::weak_ptr<FontFamily>>;
|
||||
using FontFamilyReverseLookupMap = AZStd::unordered_map<FontFamily*, FontFamilyMap::iterator>;
|
||||
|
||||
using SceneToDynamicDrawMap = AZStd::unordered_map<AZ::RPI::Scene*, AZ::RPI::Ptr<AZ::RPI::DynamicDrawContext>>;
|
||||
|
||||
private:
|
||||
//! Convenience method for loading fonts
|
||||
IFFont* LoadFont(const char* fontName);
|
||||
@@ -145,9 +139,6 @@ namespace AZ
|
||||
|
||||
int r_persistFontFamilies = 1; //!< Persist fonts for application lifetime to prevent unnecessary work; enabled by default.
|
||||
AZStd::vector<FontFamilyPtr> m_persistedFontFamilies; //!< Stores persisted fonts (if "persist font families" is enabled)
|
||||
|
||||
SceneToDynamicDrawMap m_sceneToDynamicDrawMap;
|
||||
AZStd::shared_mutex m_sceneToDynamicDrawMutex;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -42,11 +42,9 @@
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
|
||||
#include <Atom/RPI.Public/ViewportContextBus.h>
|
||||
#include <Atom/RPI.Public/WindowContext.h>
|
||||
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
||||
|
||||
#include <Atom/Bootstrap/DefaultWindowBus.h>
|
||||
#include <Atom/Bootstrap/BootstrapNotificationBus.h>
|
||||
|
||||
struct ISystem;
|
||||
|
||||
namespace AZ
|
||||
@@ -68,7 +66,6 @@ namespace AZ
|
||||
: public IFFont
|
||||
, public AZStd::intrusive_refcount<AZStd::atomic_uint, FontDeleter>
|
||||
, public AzFramework::FontDrawInterface
|
||||
, private AZ::Render::Bootstrap::NotificationBus::Handler
|
||||
{
|
||||
using ref_count = AZStd::intrusive_refcount<AZStd::atomic_uint, FontDeleter>;
|
||||
friend FontDeleter;
|
||||
@@ -168,8 +165,8 @@ namespace AZ
|
||||
|
||||
struct FontShaderData
|
||||
{
|
||||
AZ::RHI::ShaderInputImageIndex m_imageInputIndex;
|
||||
AZ::RHI::ShaderInputConstantIndex m_viewProjInputIndex;
|
||||
AZ::RHI::ShaderInputNameIndex m_imageInputIndex = "m_texture";
|
||||
AZ::RHI::ShaderInputNameIndex m_viewProjInputIndex = "m_worldToProj";
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -230,7 +227,6 @@ namespace AZ
|
||||
|
||||
private:
|
||||
virtual ~FFont();
|
||||
bool InitFont(AZ::RPI::Scene* renderScene);
|
||||
bool InitTexture();
|
||||
bool InitCache();
|
||||
|
||||
@@ -281,8 +277,6 @@ namespace AZ
|
||||
|
||||
void ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) const;
|
||||
|
||||
void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) override;
|
||||
|
||||
RPI::WindowContextSharedPtr GetDefaultWindowContext() const;
|
||||
RPI::ViewportContextPtr GetDefaultViewportContext() const;
|
||||
|
||||
@@ -303,6 +297,8 @@ namespace AZ
|
||||
string m_name;
|
||||
string m_curPath;
|
||||
|
||||
AZ::Name m_dynamicDrawContextName = AZ::Name(AZ::AtomFontDynamicDrawContextName);
|
||||
|
||||
FontTexture* m_fontTexture = nullptr;
|
||||
|
||||
size_t m_fontBufferSize = 0;
|
||||
@@ -315,13 +311,6 @@ namespace AZ
|
||||
AtomFont* m_atomFont = nullptr;
|
||||
|
||||
bool m_fontTexDirty = false;
|
||||
enum class InitializationState : AZ::u8
|
||||
{
|
||||
Uninitialized,
|
||||
Initializing,
|
||||
Initialized
|
||||
};
|
||||
AZStd::atomic<InitializationState> m_fontInitializationState = InitializationState::Uninitialized;
|
||||
|
||||
FontEffects m_effects;
|
||||
|
||||
@@ -356,6 +345,7 @@ namespace AZ
|
||||
if (font && font->m_atomFont)
|
||||
{
|
||||
font->m_atomFont->UnregisterFont(font->m_name);
|
||||
font->m_atomFont = nullptr;
|
||||
}
|
||||
|
||||
delete font;
|
||||
|
||||
@@ -354,17 +354,26 @@ AZ::AtomFont::AtomFont(ISystem* system)
|
||||
#endif
|
||||
AZ::Interface<AzFramework::FontQueryInterface>::Register(this);
|
||||
|
||||
m_sceneEventHandler = AzFramework::ISceneSystem::SceneEvent::Handler(
|
||||
[this](AzFramework::ISceneSystem::EventType eventType, const AZStd::shared_ptr<AzFramework::Scene>& scene)
|
||||
// register font per viewport dynamic draw context.
|
||||
static const char* shaderFilepath = "Shaders/SimpleTextured.azshader";
|
||||
AZ::AtomBridge::PerViewportDynamicDraw::Get()->RegisterDynamicDrawContext(
|
||||
AZ::Name(AZ::AtomFontDynamicDrawContextName),
|
||||
[](RPI::Ptr<RPI::DynamicDrawContext> drawContext)
|
||||
{
|
||||
if (eventType == AzFramework::ISceneSystem::EventType::ScenePendingRemoval)
|
||||
{
|
||||
SceneAboutToBeRemoved(*scene);
|
||||
}
|
||||
Data::Instance<RPI::Shader> shader = AZ::RPI::LoadShader(shaderFilepath);
|
||||
AZ::RPI::ShaderOptionList shaderOptions;
|
||||
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false")));
|
||||
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true")));
|
||||
drawContext->InitShaderWithVariant(shader, &shaderOptions);
|
||||
drawContext->InitVertexFormat(
|
||||
{
|
||||
{"POSITION", RHI::Format::R32G32B32_FLOAT},
|
||||
{"COLOR", RHI::Format::B8G8R8A8_UNORM},
|
||||
{"TEXCOORD0", RHI::Format::R32G32_FLOAT}
|
||||
});
|
||||
drawContext->EndInit();
|
||||
});
|
||||
auto sceneSystem = AzFramework::SceneSystemInterface::Get();
|
||||
AZ_Assert(sceneSystem, "Font created before the scene system is available.");
|
||||
sceneSystem->ConnectToEvents(m_sceneEventHandler);
|
||||
|
||||
}
|
||||
|
||||
AZ::AtomFont::~AtomFont()
|
||||
@@ -860,52 +869,5 @@ XmlNodeRef AZ::AtomFont::LoadFontFamilyXml(const char* fontFamilyName, string& o
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void AZ::AtomFont::SceneAboutToBeRemoved(AzFramework::Scene& scene)
|
||||
{
|
||||
AZ::RPI::ScenePtr* rpiScene = scene.FindSubsystem<AZ::RPI::ScenePtr>();
|
||||
if (rpiScene)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::shared_mutex> lock(m_sceneToDynamicDrawMutex);
|
||||
if (auto it = m_sceneToDynamicDrawMap.find(rpiScene->get()); it != m_sceneToDynamicDrawMap.end())
|
||||
{
|
||||
m_sceneToDynamicDrawMap.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> AZ::AtomFont::GetOrCreateDynamicDrawForScene(AZ::RPI::Scene* scene)
|
||||
{
|
||||
static const char* shaderFilepath = "Shaders/SimpleTextured.azshader";
|
||||
|
||||
{
|
||||
// shared lock while reading
|
||||
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sceneToDynamicDrawMutex);
|
||||
|
||||
if (auto it = m_sceneToDynamicDrawMap.find(scene); it != m_sceneToDynamicDrawMap.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and initialize DynamicDrawContext for font draw
|
||||
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(scene);
|
||||
|
||||
Data::Instance<RPI::Shader> shader = AZ::RPI::LoadShader(shaderFilepath);
|
||||
AZ::RPI::ShaderOptionList shaderOptions;
|
||||
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false")));
|
||||
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true")));
|
||||
dynamicDraw->InitShaderWithVariant(shader, &shaderOptions);
|
||||
dynamicDraw->InitVertexFormat({{"POSITION", RHI::Format::R32G32B32_FLOAT}, {"COLOR", RHI::Format::B8G8R8A8_UNORM}, {"TEXCOORD0", RHI::Format::R32G32_FLOAT}});
|
||||
dynamicDraw->EndInit();
|
||||
|
||||
// exclusive lock while writing
|
||||
AZStd::lock_guard<AZStd::shared_mutex> lock(m_sceneToDynamicDrawMutex);
|
||||
m_sceneToDynamicDrawMap.insert(AZStd::make_pair(scene, dynamicDraw));
|
||||
|
||||
return dynamicDraw;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -60,14 +60,7 @@ static const size_t MaxVerts = 8 * 1024; // 2048 quads
|
||||
static const size_t MaxIndices = (MaxVerts * 6) / 4; // 6 indices per quad, 6/4 * MaxVerts
|
||||
static const char DrawList2DPassName[] = "2dpass";
|
||||
|
||||
namespace ShaderInputs
|
||||
{
|
||||
static const char TextureIndexName[] = "m_texture";
|
||||
static const char WorldToProjIndexName[] = "m_worldToProj";
|
||||
static const char SamplerIndexName[] = "m_sampler";
|
||||
}
|
||||
|
||||
AZ::FFont::FFont(AtomFont* atomFont, const char* fontName)
|
||||
AZ::FFont::FFont(AZ::AtomFont* atomFont, const char* fontName)
|
||||
: m_name(fontName)
|
||||
, m_atomFont(atomFont)
|
||||
{
|
||||
@@ -78,9 +71,14 @@ AZ::FFont::FFont(AtomFont* atomFont, const char* fontName)
|
||||
FontEffect* effect = AddEffect("default");
|
||||
effect->AddPass();
|
||||
|
||||
AddRef();
|
||||
// Create cpu memory to cache the font draw data before submit
|
||||
m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts];
|
||||
m_indexBuffer = new u16[MaxIndices];
|
||||
|
||||
AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect();
|
||||
m_vertexCount = 0;
|
||||
m_indexCount = 0;
|
||||
|
||||
AddRef();
|
||||
}
|
||||
|
||||
AZ::RPI::ViewportContextPtr AZ::FFont::GetDefaultViewportContext() const
|
||||
@@ -98,55 +96,10 @@ AZ::RPI::WindowContextSharedPtr AZ::FFont::GetDefaultWindowContext() const
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AZ::FFont::InitFont(AZ::RPI::Scene* renderScene)
|
||||
{
|
||||
if (!renderScene)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto initializationState = InitializationState::Uninitialized;
|
||||
// Do an atomic transition to Initializing if we're in the Uninitialized state.
|
||||
// Otherwise, check the current state.
|
||||
// If we're Initialized, there's no more work to be done, return true to indicate we're good to go.
|
||||
// If we're Initializing (on another thread), return false to let the consumer know it's not safe for us to be used yet.
|
||||
if (!m_fontInitializationState.compare_exchange_strong(initializationState, InitializationState::Initializing))
|
||||
{
|
||||
return initializationState == InitializationState::Initialized;
|
||||
}
|
||||
|
||||
// Create and initialize DynamicDrawContext for font draw
|
||||
AZ::RPI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(renderScene);
|
||||
|
||||
// Save draw srg input indices for later use
|
||||
Data::Instance<RPI::ShaderResourceGroup> drawSrg = dynamicDraw->NewDrawSrg();
|
||||
const RHI::ShaderResourceGroupLayout* layout = drawSrg->GetAsset()->GetLayout();
|
||||
|
||||
m_fontShaderData.m_imageInputIndex = layout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::TextureIndexName));
|
||||
AZ_Error("AtomFont::FFont", m_fontShaderData.m_imageInputIndex.IsValid(), "Failed to find shader input constant %s.",
|
||||
ShaderInputs::TextureIndexName);
|
||||
|
||||
m_fontShaderData.m_viewProjInputIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::WorldToProjIndexName));
|
||||
AZ_Error("AtomFont::FFont", m_fontShaderData.m_viewProjInputIndex.IsValid(), "Failed to find shader input constant %s.",
|
||||
ShaderInputs::WorldToProjIndexName);
|
||||
|
||||
// Create cpu memory to cache the font draw data before submit
|
||||
m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts];
|
||||
m_indexBuffer = new u16[MaxIndices];
|
||||
|
||||
m_vertexCount = 0;
|
||||
m_indexCount = 0;
|
||||
|
||||
m_fontInitializationState = InitializationState::Initialized;
|
||||
return true;
|
||||
}
|
||||
|
||||
AZ::FFont::~FFont()
|
||||
{
|
||||
AZ_Assert(m_atomFont == nullptr, "The font should already be unregistered through a call to AZ::FFont::Release()");
|
||||
|
||||
AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect();
|
||||
|
||||
delete[] m_vertexBuffer;
|
||||
delete[] m_indexBuffer;
|
||||
|
||||
@@ -303,7 +256,8 @@ void AZ::FFont::DrawStringUInternal(
|
||||
const TextDrawContext& ctx)
|
||||
{
|
||||
// Lazily ensure we're initialized before attempting to render.
|
||||
if (!viewportContext || !InitFont(viewportContext->GetRenderScene().get()))
|
||||
// Validate that there is a render scene before attempting to init.
|
||||
if (!viewportContext || !viewportContext->GetRenderScene())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -323,12 +277,6 @@ void AZ::FFont::DrawStringUInternal(
|
||||
return;
|
||||
}
|
||||
|
||||
// if the font is about to be deleted then m_atomFont can be nullptr
|
||||
if (!m_atomFont)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool orthoMode = ctx.m_overrideViewProjMatrices;
|
||||
|
||||
const float viewX = viewport.m_minX;
|
||||
@@ -406,14 +354,17 @@ void AZ::FFont::DrawStringUInternal(
|
||||
|
||||
if (numQuads)
|
||||
{
|
||||
auto dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(viewportContext->GetRenderScene().get());
|
||||
//setup per draw srg
|
||||
auto drawSrg = dynamicDraw->NewDrawSrg();
|
||||
drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat);
|
||||
drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontStreamingImage->GetImageView());
|
||||
drawSrg->Compile();
|
||||
AZ::RPI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = AZ::AtomBridge::PerViewportDynamicDraw::Get()->GetDynamicDrawContextForViewport(m_dynamicDrawContextName, viewportContext->GetId());
|
||||
if (dynamicDraw)
|
||||
{
|
||||
//setup per draw srg
|
||||
auto drawSrg = dynamicDraw->NewDrawSrg();
|
||||
drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat);
|
||||
drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontStreamingImage->GetImageView());
|
||||
drawSrg->Compile();
|
||||
|
||||
dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg);
|
||||
dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg);
|
||||
}
|
||||
m_indexCount = 0;
|
||||
m_vertexCount = 0;
|
||||
}
|
||||
@@ -694,12 +645,6 @@ uint32_t AZ::FFont::WriteTextQuadsToBuffers(SVF_P2F_C4B_T2F_F4B* verts, uint16_t
|
||||
return numQuadsWritten;
|
||||
}
|
||||
|
||||
// if the font is about to be deleted then m_atomFont can be nullptr
|
||||
if (!m_atomFont)
|
||||
{
|
||||
return numQuadsWritten;
|
||||
}
|
||||
|
||||
SVF_P2F_C4B_T2F_F4B* vertexData = verts;
|
||||
uint16_t* indexData = indices;
|
||||
size_t vertexOffset = 0;
|
||||
@@ -1523,7 +1468,7 @@ bool AZ::FFont::UpdateTexture()
|
||||
{
|
||||
using namespace AZ;
|
||||
|
||||
if (m_fontInitializationState != InitializationState::Initialized || !m_fontImage)
|
||||
if (!m_fontImage)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1591,7 +1536,7 @@ void AZ::FFont::Prepare(const char* str, bool updateTexture, const AtomFont::Gly
|
||||
const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender;
|
||||
const AtomFont::GlyphSize usedGlyphSize = rerenderGlyphs ? glyphSize : AtomFont::defaultGlyphSize;
|
||||
bool texUpdateNeeded = m_fontTexture->PreCacheString(str, nullptr, m_sizeRatio, usedGlyphSize, m_fontHintParams) == 1 || m_fontTexDirty;
|
||||
if (m_fontInitializationState == InitializationState::Initialized && updateTexture && texUpdateNeeded && m_fontImage)
|
||||
if (updateTexture && texUpdateNeeded && m_fontImage)
|
||||
{
|
||||
UpdateTexture();
|
||||
m_fontTexDirty = false;
|
||||
@@ -1625,12 +1570,6 @@ void AZ::FFont::ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) co
|
||||
y *= height / WindowScaleHeight;
|
||||
}
|
||||
|
||||
|
||||
void AZ::FFont::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene)
|
||||
{
|
||||
InitFont(bootstrapScene);
|
||||
}
|
||||
|
||||
static void SetCommonContextFlags(AZ::TextDrawContext& ctx, const AzFramework::TextDrawParameters& params)
|
||||
{
|
||||
if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Center)
|
||||
|
||||
+1
-3
@@ -78,11 +78,9 @@ namespace AZ
|
||||
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<EditorMaterialComponentSlot>()
|
||||
->Version(4, &EditorMaterialComponentSlot::ConvertVersion)
|
||||
->Version(5, &EditorMaterialComponentSlot::ConvertVersion)
|
||||
->Field("id", &EditorMaterialComponentSlot::m_id)
|
||||
->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset)
|
||||
->Field("propertyOverrides", &EditorMaterialComponentSlot::m_propertyOverrides)
|
||||
->Field("matModUvOverrides", &EditorMaterialComponentSlot::m_matModUvOverrides)
|
||||
;
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
|
||||
@@ -140,9 +140,16 @@ namespace AZ
|
||||
AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne();
|
||||
AZ::NonUniformScaleRequestBus::EventResult(nonUniformScale, GetEntityId(), &AZ::NonUniformScaleRequests::GetScale);
|
||||
|
||||
float t;
|
||||
AZ::Vector3 ignoreNormal;
|
||||
constexpr float rayLength = 1000.0f;
|
||||
if (m_controller.GetModel()->RayIntersection(transform, nonUniformScale, src, dir * rayLength, t, ignoreNormal))
|
||||
{
|
||||
distance = rayLength * t;
|
||||
return true;
|
||||
}
|
||||
|
||||
return m_controller.GetModel()->RayIntersection(transform, nonUniformScale, src, dir, distance, ignoreNormal);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EditorMeshComponent::SupportsEditorRayIntersect()
|
||||
|
||||
@@ -485,16 +485,12 @@ namespace AZ
|
||||
m_transformInterface->GetWorldTM(), m_cachedNonUniformScale, ray.m_startWorldPosition,
|
||||
ray.m_endWorldPosition - ray.m_startWorldPosition, t, normal))
|
||||
{
|
||||
// note: this is a temporary workaround to handle cases where model->RayIntersection
|
||||
// returns negative distances, follow-up ATOM-15673
|
||||
const auto absT = AZStd::abs(t);
|
||||
|
||||
// fill in ray result structure after successful intersection
|
||||
const auto intersectionLine = (ray.m_endWorldPosition - ray.m_startWorldPosition);
|
||||
result.m_uv = AZ::Vector2::CreateZero();
|
||||
result.m_worldPosition = ray.m_startWorldPosition + intersectionLine * absT;
|
||||
result.m_worldPosition = ray.m_startWorldPosition + intersectionLine * t;
|
||||
result.m_worldNormal = normal;
|
||||
result.m_distance = intersectionLine.GetLength() * absT;
|
||||
result.m_distance = intersectionLine.GetLength() * t;
|
||||
result.m_entityAndComponent = m_entityComponentIdPair;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -11,7 +11,6 @@
|
||||
|
||||
set(FILES
|
||||
BuiltInPackages.cmake
|
||||
FindClang.cmake
|
||||
FindOpenGLInterface.cmake
|
||||
FindRadTelemetry.cmake
|
||||
FindVkValidation.cmake
|
||||
|
||||
@@ -15,7 +15,11 @@ ly_set(LY_DEFAULT_INSTALL_COMPONENT Core)
|
||||
|
||||
file(RELATIVE_PATH runtime_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
file(RELATIVE_PATH library_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
||||
set(install_output_folder "${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>")
|
||||
# Anywhere CMAKE_INSTALL_PREFIX is used, it has to be escaped so it is baked into the cmake_install.cmake script instead
|
||||
# of baking the path. This is needed so `cmake --install --prefix <someprefix>` works regardless of the CMAKE_INSTALL_PREFIX
|
||||
# used to generate the solution.
|
||||
# CMAKE_INSTALL_PREFIX is still used when building the INSTALL target
|
||||
set(install_output_folder "\${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>")
|
||||
|
||||
|
||||
#! ly_setup_target: Setup the data needed to re-create the cmake target commands for a single target
|
||||
|
||||
@@ -167,6 +167,9 @@ endfunction()
|
||||
# Add the projects here so the above function is found
|
||||
foreach(project ${LY_PROJECTS})
|
||||
file(REAL_PATH ${project} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
if(NOT LY_FIRST_PROJECT)
|
||||
ly_set(LY_FIRST_PROJECT_PATH ${full_directory_path})
|
||||
endif()
|
||||
string(SHA256 full_directory_hash ${full_directory_path})
|
||||
|
||||
# Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
|
||||
|
||||
Reference in New Issue
Block a user