Merge branch 'stabilization/2106' of https://github.com/o3de/o3de into cgalvan/FixLCGraphNotOpeningOnEdit

This commit is contained in:
Chris Galvan
2021-07-01 14:07:03 -05:00
66 changed files with 667 additions and 388 deletions
@@ -403,7 +403,12 @@ namespace AZ
{
if (!parentValue->EraseMember(tokens[path.GetTokenCount() - 1].name))
{
return settings.m_reporting(R"(The "remove" operation failed to remove member from object.)",
rapidjson::StringBuffer pathString;
path.Stringify(pathString);
return settings.m_reporting(
AZStd::string::format(
R"(The "remove" operation failed to remove member '%s' from object at path '%s'.)",
tokens[path.GetTokenCount() - 1].name, pathString.GetString()),
ResultCode(Tasks::Merge, Outcomes::Invalid), element);
}
}
@@ -34,6 +34,28 @@ namespace AzPhysics
const CollisionGroup CollisionGroup::All_NoTouchBend = CollisionGroup::All.GetMask() & ~CollisionLayer::TouchBend.GetMask();
#endif
void CollisionGroupScriptConstructor(CollisionGroup* thisPtr, AZ::ScriptDataContext& scriptDataContext)
{
if (int numArgs = scriptDataContext.GetNumArguments();
numArgs != 1)
{
scriptDataContext.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true,
"CollisionGroup() accepts only 1 argument, not %d", numArgs);
return;
}
if (!scriptDataContext.IsString(0))
{
scriptDataContext.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true,
"Argument to CollisionGroup() should be string");
return;
}
AZStd::string groupName;
scriptDataContext.ReadArg(0, groupName);
*thisPtr = CollisionGroup(groupName);
}
void CollisionGroup::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
@@ -50,7 +72,9 @@ namespace AzPhysics
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "AzPhysics")
->Constructor<const AZStd::string>()
->Constructor<const AZStd::string&>()
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::ConstructorOverride, &CollisionGroupScriptConstructor)
;
}
}
@@ -88,6 +88,19 @@ namespace AzPhysics
;
}
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SceneQueryRequest>("SceneQueryRequest")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Property("Collision", BehaviorValueProperty(&SceneQueryRequest::m_collisionGroup))
// Until enum class support for behavior context is done, expose this as an int
->Property("QueryType", [](const SceneQueryRequest& self) { return static_cast<int>(self.m_queryType); },
[](SceneQueryRequest& self, int newQueryType) { self.m_queryType = SceneQuery::QueryType(newQueryType); })
;
}
}
/*static*/ void RayCastRequest::Reflect(AZ::ReflectContext* context)
@@ -123,10 +136,6 @@ namespace AzPhysics
->Property("Distance", BehaviorValueProperty(&RayCastRequest::m_distance))
->Property("Start", BehaviorValueProperty(&RayCastRequest::m_start))
->Property("Direction", BehaviorValueProperty(&RayCastRequest::m_direction))
->Property("Collision", BehaviorValueProperty(&RayCastRequest::m_collisionGroup))
// Until enum class support for behavior context is done, expose this as an int
->Property("QueryType", [](const RayCastRequest& self) { return static_cast<int>(self.m_queryType); },
[](RayCastRequest& self, int newQueryType) { self.m_queryType = SceneQuery::QueryType(newQueryType); })
;
}
}
@@ -73,21 +73,22 @@ namespace Physics
{
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<Physics::CharacterRequestBus>("CharacterControllerRequestBus", "Character Controller")
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::RuntimeOwn)
behaviorContext->EBus<CharacterRequestBus>("CharacterControllerRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Event("GetBasePosition", &Physics::CharacterRequests::GetBasePosition, "Get Base Position")
->Event("SetBasePosition", &Physics::CharacterRequests::SetBasePosition, "Set Base Position")
->Event("GetCenterPosition", &Physics::CharacterRequests::GetCenterPosition, "Get Center Position")
->Event("GetStepHeight", &Physics::CharacterRequests::GetStepHeight, "Get Step Height")
->Event("SetStepHeight", &Physics::CharacterRequests::SetStepHeight, "Set Step Height")
->Event("GetUpDirection", &Physics::CharacterRequests::GetUpDirection, "Get Up Direction")
->Event("GetSlopeLimitDegrees", &Physics::CharacterRequests::GetSlopeLimitDegrees, "Get Slope Limit (Degrees)")
->Event("SetSlopeLimitDegrees", &Physics::CharacterRequests::SetSlopeLimitDegrees, "Set Slope Limit (Degrees)")
->Event("GetMaximumSpeed", &Physics::CharacterRequests::GetMaximumSpeed, "Get Maximum Speed")
->Event("SetMaximumSpeed", &Physics::CharacterRequests::SetMaximumSpeed, "Set Maximum Speed")
->Event("GetVelocity", &Physics::CharacterRequests::GetVelocity, "Get Velocity")
->Event("AddVelocity", &Physics::CharacterRequests::AddVelocity, "Add Velocity")
->Event("GetBasePosition", &CharacterRequests::GetBasePosition, "Get Base Position")
->Event("SetBasePosition", &CharacterRequests::SetBasePosition, "Set Base Position")
->Event("GetCenterPosition", &CharacterRequests::GetCenterPosition, "Get Center Position")
->Event("GetStepHeight", &CharacterRequests::GetStepHeight, "Get Step Height")
->Event("SetStepHeight", &CharacterRequests::SetStepHeight, "Set Step Height")
->Event("GetUpDirection", &CharacterRequests::GetUpDirection, "Get Up Direction")
->Event("GetSlopeLimitDegrees", &CharacterRequests::GetSlopeLimitDegrees, "Get Slope Limit (Degrees)")
->Event("SetSlopeLimitDegrees", &CharacterRequests::SetSlopeLimitDegrees, "Set Slope Limit (Degrees)")
->Event("GetMaximumSpeed", &CharacterRequests::GetMaximumSpeed, "Get Maximum Speed")
->Event("SetMaximumSpeed", &CharacterRequests::SetMaximumSpeed, "Set Maximum Speed")
->Event("GetVelocity", &CharacterRequests::GetVelocity, "Get Velocity")
->Event("AddVelocity", &CharacterRequests::AddVelocity, "Add Velocity")
;
}
}
@@ -280,9 +280,11 @@ namespace AzToolsFramework
}
}
// Destroy the entities *before* clearing the lookup maps so that any lookups triggered during an entity's destructor
// are still valid.
m_entities.clear();
m_instanceToTemplateEntityIdMap.clear();
m_templateToInstanceEntityIdMap.clear();
m_entities.clear();
}
bool Instance::RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias)
@@ -157,7 +157,7 @@ namespace AzToolsFramework
"Prefab - EntityIdMapper: Entity with Id %s has no registered owning instance",
entityId.ToString().c_str());
return AZStd::string::format("Entity_%s", entityId.ToString().c_str());
return {};
}
Instance* owningInstance = &(owningInstanceReference->get());
@@ -70,7 +70,15 @@ namespace AzToolsFramework
"Failed to find an owning instance for the entity with id %llu.", static_cast<AZ::u64>(entityId));
Instance& instance = instanceReference->get();
m_templateId = instance.GetTemplateId();
m_entityAlias = (instance.GetEntityAlias(entityId)).value();
auto aliasReference = instance.GetEntityAlias(entityId);
if (!aliasReference.has_value())
{
AZ_Error(
"Prefab", aliasReference.has_value(), "Failed to find the entity alias for entity %s.", entityId.ToString().c_str());
return;
}
m_entityAlias = aliasReference.value();
//generate undo/redo patches
m_instanceToTemplateInterface->GeneratePatch(m_redoPatch, initialState, endState);
@@ -488,6 +488,13 @@ namespace AzToolsFramework
return worldFromLocal.TransformPoint(CalculateCenterOffset(entityId, pivot));
}
void EditorTransformComponentSelection::SetAllViewportUiVisible(const bool visible)
{
SetViewportUiClusterVisible(m_transformModeClusterId, visible);
SetViewportUiClusterVisible(m_spaceCluster.m_spaceClusterId, visible);
m_viewportUiVisible = visible;
}
void EditorTransformComponentSelection::UpdateSpaceCluster(const ReferenceFrame referenceFrame)
{
auto buttonIdFromFrameFn = [this](const ReferenceFrame referenceFrame)
@@ -1009,6 +1016,7 @@ namespace AzToolsFramework
ToolsApplicationNotificationBus::Handler::BusConnect();
Camera::EditorCameraNotificationBus::Handler::BusConnect();
ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusConnect(entityContextId);
EditorEntityContextNotificationBus::Handler::BusConnect();
EditorEntityVisibilityNotificationBus::Router::BusRouterConnect();
EditorEntityLockComponentNotificationBus::Router::BusRouterConnect();
EditorManipulatorCommandUndoRedoRequestBus::Handler::BusConnect(entityContextId);
@@ -1037,6 +1045,7 @@ namespace AzToolsFramework
EditorManipulatorCommandUndoRedoRequestBus::Handler::BusDisconnect();
EditorEntityLockComponentNotificationBus::Router::BusRouterDisconnect();
EditorEntityVisibilityNotificationBus::Router::BusRouterDisconnect();
EditorEntityContextNotificationBus::Handler::BusDisconnect();
ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusDisconnect();
Camera::EditorCameraNotificationBus::Handler::BusDisconnect();
ToolsApplicationNotificationBus::Handler::BusDisconnect();
@@ -2387,9 +2396,7 @@ namespace AzToolsFramework
/*ID_VIEWPORTUI_VISIBLE=*/50040, "Toggle Viewport UI", "Hide/Show Viewport UI",
[this]()
{
SetViewportUiClusterVisible(m_transformModeClusterId, !m_viewportUiVisible);
SetViewportUiClusterVisible(m_spaceCluster.m_spaceClusterId, !m_viewportUiVisible);
m_viewportUiVisible = !m_viewportUiVisible;
SetAllViewportUiVisible(!m_viewportUiVisible);
});
EditorMenuRequestBus::Broadcast(&EditorMenuRequests::RestoreEditMenuToDefault);
@@ -3560,7 +3567,7 @@ namespace AzToolsFramework
void EditorTransformComponentSelection::EnteredComponentMode([[maybe_unused]] const AZStd::vector<AZ::Uuid>& componentModeTypes)
{
SetViewportUiClusterVisible(m_transformModeClusterId, false);
SetAllViewportUiVisible(false);
EditorEntityLockComponentNotificationBus::Router::BusRouterDisconnect();
EditorEntityVisibilityNotificationBus::Router::BusRouterDisconnect();
@@ -3569,7 +3576,7 @@ namespace AzToolsFramework
void EditorTransformComponentSelection::LeftComponentMode([[maybe_unused]] const AZStd::vector<AZ::Uuid>& componentModeTypes)
{
SetViewportUiClusterVisible(m_transformModeClusterId, true);
SetAllViewportUiVisible(true);
ToolsApplicationNotificationBus::Handler::BusConnect();
EditorEntityVisibilityNotificationBus::Router::BusRouterConnect();
@@ -3625,6 +3632,16 @@ namespace AzToolsFramework
ETCS::SetEntityLocalRotation(entityId, localRotation, m_transformChangedInternally);
}
void EditorTransformComponentSelection::OnStartPlayInEditor()
{
SetAllViewportUiVisible(false);
}
void EditorTransformComponentSelection::OnStopPlayInEditor()
{
SetAllViewportUiVisible(true);
}
namespace ETCS
{
// little raii wrapper to switch a value from true to false and back
@@ -128,6 +128,7 @@ namespace AzToolsFramework
, private ToolsApplicationNotificationBus::Handler
, private Camera::EditorCameraNotificationBus::Handler
, private ComponentModeFramework::EditorComponentModeNotificationBus::Handler
, private EditorEntityContextNotificationBus::Handler
, private EditorEntityVisibilityNotificationBus::Router
, private EditorEntityLockComponentNotificationBus::Router
, private EditorManipulatorCommandUndoRedoRequestBus::Handler
@@ -264,6 +265,10 @@ namespace AzToolsFramework
void EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
void LeftComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
// EditorEntityContextNotificationBus overrides ...
void OnStartPlayInEditor() override;
void OnStopPlayInEditor() override;
// Helpers to safely interact with the TransformBus (requests).
void SetEntityWorldTranslation(AZ::EntityId entityId, const AZ::Vector3& worldTranslation);
void SetEntityLocalTranslation(AZ::EntityId entityId, const AZ::Vector3& localTranslation);
@@ -271,9 +276,12 @@ namespace AzToolsFramework
void SetEntityLocalScale(AZ::EntityId entityId, float localScale);
void SetEntityLocalRotation(AZ::EntityId entityId, const AZ::Vector3& localRotation);
// Responsible for keeping the space cluster in sync with the current reference frame.
//! Responsible for keeping the space cluster in sync with the current reference frame.
void UpdateSpaceCluster(ReferenceFrame referenceFrame);
//! Hides/Shows all viewportUi toolbars.
void SetAllViewportUiVisible(bool visible);
AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any).
AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set.
@@ -10,6 +10,7 @@
#include <PythonBindingsInterface.h>
#include <NewProjectSettingsScreen.h>
#include <ScreenHeaderWidget.h>
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemCatalogScreen.h>
#include <QDialogButtonBox>
@@ -46,6 +47,40 @@ namespace O3DE::ProjectManager
m_stack->addWidget(m_gemCatalogScreen);
vLayout->addWidget(m_stack);
// When there are multiple project templates present, we re-gather the gems when changing the selected the project template.
connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex)
{
const GemModel* gemModel = m_gemCatalogScreen->GetGemModel();
const QVector<QModelIndex> toBeAdded = gemModel->GatherGemsToBeAdded();
const QVector<QModelIndex> toBeRemoved = gemModel->GatherGemsToBeRemoved();
if (!toBeAdded.isEmpty() || !toBeRemoved.isEmpty())
{
// In case the user enabled or disabled any gem and the current selection does not match the default from the
// // project template anymore, we need to ask the user if they want to proceed as their modifications will be lost.
const QString title = tr("Modifications will be lost");
const QString text = tr("You selected a new project template after modifying the enabled gems.\n\n"
"All modifications will be lost and the default from the new project template will be used.\n\n"
"Do you want to proceed?");
if (QMessageBox::warning(this, title, text, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
{
// The users wants to proceed. Reinitialize based on the newly selected project template.
ReinitGemCatalogForSelectedTemplate();
}
else
{
// Roll-back to the previously selected project template and
// block signals so that we don't end up in this same callback again.
m_newProjectSettingsScreen->SelectProjectTemplate(oldIndex, /*blockSignals=*/true);
}
}
else
{
// In case the user did not enable or disable any gem and the currently enabled gems matches the previously selected
// ones from the project template, we can just reinitialize based on the newly selected project template.
ReinitGemCatalogForSelectedTemplate();
}
});
QDialogButtonBox* buttons = new QDialogButtonBox();
buttons->setObjectName("footer");
vLayout->addWidget(buttons);
@@ -81,10 +116,8 @@ namespace O3DE::ProjectManager
currentScreen->NotifyCurrentScreen();
}
// Gather the gems from the project template. When we will have multiple project templates, we need to re-gather them
// on changing the template and let the user know that any further changes on top of the template will be lost.
QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true);
// Gather the enabled gems from the default project template when starting the create new project workflow.
ReinitGemCatalogForSelectedTemplate();
}
void CreateProjectCtrl::HandleBackButton()
@@ -223,4 +256,9 @@ namespace O3DE::ProjectManager
}
}
void CreateProjectCtrl::ReinitGemCatalogForSelectedTemplate()
{
const QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true);
}
} // namespace O3DE::ProjectManager
@@ -40,6 +40,7 @@ namespace O3DE::ProjectManager
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
void OnChangeScreenRequest(ProjectManagerScreen screen);
void HandleSecondaryButton();
void ReinitGemCatalogForSelectedTemplate();
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
private:
@@ -21,4 +21,9 @@ namespace O3DE::ProjectManager
connect(browseButton, &QPushButton::pressed, this, &FormBrowseEditWidget::HandleBrowseButton);
m_frameLayout->addWidget(browseButton);
}
FormBrowseEditWidget::FormBrowseEditWidget(const QString& labelText, QWidget* parent)
: FormBrowseEditWidget(labelText, "", parent)
{
}
} // namespace O3DE::ProjectManager
@@ -20,6 +20,7 @@ namespace O3DE::ProjectManager
public:
explicit FormBrowseEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr);
explicit FormBrowseEditWidget(const QString& labelText = "", QWidget* parent = nullptr);
~FormBrowseEditWidget() = default;
protected slots:
@@ -15,8 +15,9 @@
namespace O3DE::ProjectManager
{
FormFolderBrowseEditWidget::FormFolderBrowseEditWidget(const QString& labelText, const QString& valueText, QWidget* parent)
: FormBrowseEditWidget(labelText, valueText, parent)
: FormBrowseEditWidget(labelText, parent)
{
setText(valueText);
}
void FormFolderBrowseEditWidget::HandleBrowseButton()
@@ -30,8 +31,14 @@ namespace O3DE::ProjectManager
QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath));
if (!directory.isEmpty())
{
m_lineEdit->setText(directory);
setText(directory);
}
}
void FormFolderBrowseEditWidget::setText(const QString& text)
{
QString path = QDir::toNativeSeparators(text);
FormBrowseEditWidget::setText(path);
}
} // namespace O3DE::ProjectManager
@@ -22,6 +22,8 @@ namespace O3DE::ProjectManager
explicit FormFolderBrowseEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr);
~FormFolderBrowseEditWidget() = default;
void setText(const QString& text) override;
protected:
void HandleBrowseButton() override;
};
@@ -123,4 +123,14 @@ namespace O3DE::ProjectManager
child->style()->polish(child);
}
}
void FormLineEditWidget::setText(const QString& text)
{
m_lineEdit->setText(text);
}
void FormLineEditWidget::mousePressEvent([[maybe_unused]] QMouseEvent* event)
{
m_lineEdit->setFocus();
}
} // namespace O3DE::ProjectManager
@@ -15,6 +15,7 @@ QT_FORWARD_DECLARE_CLASS(QLineEdit)
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QFrame)
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
QT_FORWARD_DECLARE_CLASS(QMouseEvent)
namespace AzQtComponents
{
@@ -39,6 +40,8 @@ namespace O3DE::ProjectManager
//! Returns a pointer to the underlying LineEdit.
QLineEdit* lineEdit() const;
virtual void setText(const QString& text);
protected:
QLabel* m_errorLabel = nullptr;
QFrame* m_frame = nullptr;
@@ -51,6 +54,8 @@ namespace O3DE::ProjectManager
void onFocusOut();
private:
void mousePressEvent(QMouseEvent* event) override;
void refreshStyle();
};
} // namespace O3DE::ProjectManager
@@ -30,6 +30,8 @@ namespace O3DE::ProjectManager
void ReinitForProject(const QString& projectPath, bool isNewProject);
bool EnableDisableGemsForProject(const QString& projectPath);
GemModel* GetGemModel() const { return m_gemModel; }
private:
void FillModel(const QString& projectPath, bool isNewProject);
@@ -81,8 +81,14 @@ namespace O3DE::ProjectManager
{
if (button && button->property(k_templateIndexProperty).isValid())
{
int projectIndex = button->property(k_templateIndexProperty).toInt();
UpdateTemplateDetails(m_templates.at(projectIndex));
int projectTemplateIndex = button->property(k_templateIndexProperty).toInt();
if (m_selectedTemplateIndex != projectTemplateIndex)
{
const int oldIndex = m_selectedTemplateIndex;
m_selectedTemplateIndex = projectTemplateIndex;
UpdateTemplateDetails(m_templates.at(m_selectedTemplateIndex));
emit OnTemplateSelectionChanged(/*oldIndex=*/oldIndex, /*newIndex=*/m_selectedTemplateIndex);
}
}
});
@@ -115,7 +121,8 @@ namespace O3DE::ProjectManager
flowLayout->addWidget(templateButton);
}
m_projectTemplateButtonGroup->buttons().first()->setChecked(true);
// Select the first project template (default selection).
SelectProjectTemplate(0, /*blockSignals=*/true);
}
containerLayout->addWidget(templatesScrollArea);
}
@@ -159,8 +166,9 @@ namespace O3DE::ProjectManager
QString NewProjectSettingsScreen::GetProjectTemplatePath()
{
const int templateIndex = m_projectTemplateButtonGroup->checkedButton()->property(k_templateIndexProperty).toInt();
return m_templates.at(templateIndex).m_path;
AZ_Assert(m_selectedTemplateIndex == m_projectTemplateButtonGroup->checkedButton()->property(k_templateIndexProperty).toInt(),
"Selected template index not in sync with the currently checked project template button.");
return m_templates.at(m_selectedTemplateIndex).m_path;
}
QFrame* NewProjectSettingsScreen::CreateTemplateDetails(int margin)
@@ -216,4 +224,27 @@ namespace O3DE::ProjectManager
m_templateSummary->setText(templateInfo.m_summary);
m_templateIncludedGems->Update(templateInfo.m_includedGems);
}
void NewProjectSettingsScreen::SelectProjectTemplate(int index, bool blockSignals)
{
const QList<QAbstractButton*> buttons = m_projectTemplateButtonGroup->buttons();
if (index >= buttons.size())
{
return;
}
if (blockSignals)
{
m_projectTemplateButtonGroup->blockSignals(true);
}
QAbstractButton* button = buttons.at(index);
button->setChecked(true);
m_selectedTemplateIndex = button->property(k_templateIndexProperty).toInt();
if (blockSignals)
{
m_projectTemplateButtonGroup->blockSignals(false);
}
}
} // namespace O3DE::ProjectManager
@@ -22,6 +22,8 @@ namespace O3DE::ProjectManager
class NewProjectSettingsScreen
: public ProjectSettingsScreen
{
Q_OBJECT
public:
explicit NewProjectSettingsScreen(QWidget* parent = nullptr);
~NewProjectSettingsScreen() = default;
@@ -31,6 +33,11 @@ namespace O3DE::ProjectManager
void NotifyCurrentScreen() override;
void SelectProjectTemplate(int index, bool blockSignals = false);
signals:
void OnTemplateSelectionChanged(int oldIndex, int newIndex);
private:
QString GetDefaultProjectPath();
QFrame* CreateTemplateDetails(int margin);
@@ -41,6 +48,7 @@ namespace O3DE::ProjectManager
QLabel* m_templateSummary;
TagContainerWidget* m_templateIncludedGems;
QVector<ProjectTemplateInfo> m_templates;
int m_selectedTemplateIndex = -1;
inline constexpr static int s_spacerSize = 20;
inline constexpr static int s_templateDetailsContentMargin = 20;
@@ -92,8 +92,7 @@ namespace O3DE::ProjectManager
QLabel* introLabel = new QLabel(this);
introLabel->setObjectName("introLabel");
introLabel->setText(tr("Welcome to O3DE! Start something new by creating a project. Not sure what to create? \nExplore what's "
"available by downloading our sample project."));
introLabel->setText(tr("Welcome to O3DE! Start something new by creating a project."));
layout->addWidget(introLabel);
QHBoxLayout* buttonLayout = new QHBoxLayout();
@@ -36,63 +36,49 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpBoneImporter, SceneCore::LoadingComponent>()->Version(1);
serializeContext->Class<AssImpBoneImporter, SceneCore::LoadingComponent>()->Version(2);
}
}
void EnumBonesInNode(
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, const aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
void MakeBoneMap(const aiScene* scene, AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
/* From AssImp Documentation
a) Create a map or a similar container to store which nodes are necessary for the skeleton. Pre-initialise it for all nodes with a "no".
b) For each bone in the mesh:
b1) Find the corresponding node in the scene's hierarchy by comparing their names.
b2) Mark this node as "yes" in the necessityMap.
b3) Mark all of its parents the same way until you 1) find the mesh's node or 2) the parent of the mesh's node.
c) Recursively iterate over the node hierarchy
c1) If the node is marked as necessary, copy it into the skeleton and check its children
c2) If the node is marked as not necessary, skip it and do not iterate over its children.
*/
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> nodesWithNoMesh;
for (unsigned meshIndex = 0; meshIndex < node->mNumMeshes; ++meshIndex)
queue.push(scene->mRootNode);
while (!queue.empty())
{
const aiMesh* mesh = scene->mMeshes[node->mMeshes[meshIndex]];
const aiNode* currentNode = queue.front();
queue.pop();
for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
if (currentNode->mNumMeshes == 0)
{
nodesWithNoMesh.emplace(currentNode->mName.C_Str());
}
for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex)
{
queue.push(currentNode->mChildren[childIndex]);
}
}
for (unsigned int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
const aiNode* boneNode = scene->mRootNode->FindNode(bone->mName);
const aiNode* boneParent = boneNode->mParent;
mainBoneList[bone->mName.C_Str()] = boneNode;
boneLookup[bone->mName.C_Str()] = bone;
while (boneParent && boneParent != node && boneParent != node->mParent && boneParent != scene->mRootNode)
if (nodesWithNoMesh.contains(bone->mName.C_Str()))
{
mainBoneList[boneParent->mName.C_Str()] = boneParent;
boneParent = boneParent->mParent;
boneLookup.emplace(bone->mName.C_Str(), bone);
}
}
}
}
void EnumChildren(
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, const aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
EnumBonesInNode(scene, node, mainBoneList, boneLookup);
for (unsigned childIndex = 0; childIndex < node->mNumChildren; ++childIndex)
{
const aiNode* child = node->mChildren[childIndex];
EnumChildren(scene, child, mainBoneList, boneLookup);
}
}
aiMatrix4x4 CalculateWorldTransform(const aiNode* currentNode)
{
aiMatrix4x4 transform = {};
@@ -122,14 +108,10 @@ namespace AZ
bool isBone = false;
{
AZStd::unordered_map<AZStd::string, const aiNode*> mainBoneList;
AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
EnumChildren(scene, scene->mRootNode, mainBoneList, boneLookup);
MakeBoneMap(scene, boneLookup);
if (mainBoneList.find(currentNode->mName.C_Str()) != mainBoneList.end())
{
isBone = true;
}
isBone = boneLookup.contains(currentNode->mName.C_Str());
// If we have an animation, the bones will be listed in there
if (!isBone)
@@ -42,9 +42,29 @@ namespace AZ
}
}
void GetAllBones(
const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneLookup)
void GetAllBones(const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneLookup)
{
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> nodesWithNoMesh;
queue.push(scene->mRootNode);
while (!queue.empty())
{
const aiNode* currentNode = queue.front();
queue.pop();
if (currentNode->mNumMeshes == 0)
{
nodesWithNoMesh.emplace(currentNode->mName.C_Str());
}
for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex)
{
queue.push(currentNode->mChildren[childIndex]);
}
}
for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
@@ -53,7 +73,10 @@ namespace AZ
{
const aiBone* bone = mesh->mBones[boneIndex];
boneLookup.emplace(bone->mName.C_Str(), bone);
if (nodesWithNoMesh.contains(bone->mName.C_Str()))
{
boneLookup.emplace(bone->mName.C_Str(), bone);
}
}
}
}
@@ -208,13 +208,6 @@ namespace AZ
bool SliceConverter::ConvertSliceToPrefab(
AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity)
{
/* Given a root slice entity, we convert it to a prefab by doing the following:
* - Locate the SliceComponent
* - Take all the entities directly located on the slice, and put them into a prefab
* - Fix up any top-level entities to have the prefab container entity as their parent
* - If there are any nested slice instances, convert the nested slices to prefabs, then convert the instances.
*/
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
// Find the slice from the root entity.
@@ -233,23 +226,47 @@ namespace AZ
sliceComponent->RemoveAllEntities(deleteEntities, removeEmptyInstances);
AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size());
// Create the Prefab with the entities from the slice.
// Create an empty Prefab as the start of our conversion.
// The entities are added in a separate step so that we can give them deterministic entity aliases that match their entity Ids
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> sourceInstance(
prefabSystemComponentInterface->CreatePrefab({}, {}, outputPath));
// Add entities into our prefab.
// In slice->prefab conversions, there's a chicken-and-egg problem that occurs with entity references, so we're initially
// going to add empty dummy entities with the right IDs and aliases.
// The problem is that we can have entities in this root list that have references to nested slice instance entities that we
// haven't created yet, and we will have nested slice entities that need to reference these entities as parents.
// If we create these entities as fully-formed first, they will fail to serialize correctly when adding each nested instance,
// due to the references not pointing to valid entities yet. And if we *wait* to create these and build the nested instances
// first, they'll fail to serialize correctly due to referencing these as parents.
// So our solution is that we'll initially create these entities as empty placeholders with no references, *then* we'll build
// up the nested instances, *then* we'll finish building these entities out.
// prefabPlaceholderEntities will hold onto pointers to the entities we're building up in the prefab. The prefab will own
// the lifetime of them, but we'll use the references here for convenient access.
AZStd::vector<AZ::Entity*> prefabPlaceholderEntities;
// entityAliases will hold onto the alias we want to use for each of those entities. We'll need to use the same alias when
// we replace the entities at the end.
AZStd::vector<AZStd::string> entityAliases;
for (auto& entity : sliceEntities)
{
sourceInstance->AddEntity(*entity, AZStd::string::format("Entity_%s", entity->GetId().ToString().c_str()));
auto id = entity->GetId();
prefabPlaceholderEntities.emplace_back(aznew AZ::Entity(id));
entityAliases.emplace_back(AZStd::string::format("Entity_%s", id.ToString().c_str()));
sourceInstance->AddEntity(*(prefabPlaceholderEntities.back()), entityAliases.back());
// Save off a mapping of the original slice entity IDs to the new prefab template entity aliases.
// We'll need this mapping for fixing up all the entity references in this slice as well as any nested instances.
auto result = m_aliasIdMapper.emplace(id, SliceEntityMappingInfo(sourceInstance->GetTemplateId(), entityAliases.back()));
if (!result.second)
{
AZ_Printf("Convert-Slice", " Duplicate entity alias -> entity id entries found, conversion may not be successful.\n");
}
}
// Dispatch events here, because prefab creation might trigger asset loads in rare circumstances.
AZ::Data::AssetManager::Instance().DispatchEvents();
// Fix up the container entity to have the proper components and fix up the slice entities to have the proper hierarchy
// with the container as the top-most parent.
AzToolsFramework::Prefab::EntityOptionalReference container = sourceInstance->GetContainerEntity();
FixPrefabEntities(container->get(), sliceEntities);
// Keep track of the template Id we created, we're going to remove it at the end of slice file conversion to make sure
// the data doesn't stick around between file conversions.
auto templateId = sourceInstance->GetTemplateId();
@@ -260,26 +277,8 @@ namespace AZ
}
m_createdTemplateIds.emplace(templateId);
// Save off a mapping of the original slice entity IDs to the new prefab template entity aliases.
// When converting nested slices, this mapping will be needed to fix up the parent entity hierarchy correctly.
auto entityAliases = sourceInstance->GetEntityAliases();
for (auto& alias : entityAliases)
{
auto id = sourceInstance->GetEntityId(alias);
auto result = m_aliasIdMapper.emplace(id, SliceEntityMappingInfo(templateId, alias));
if (!result.second)
{
AZ_Printf("Convert-Slice", " Duplicate entity alias -> entity id entries found, conversion may not be successful.\n");
}
}
// Save off a mapping of the slice's metadata entity ID as well, even though we never converted the entity itself.
// This will help us better detect entity ID mapping errors for nested slice instances.
AZ::Entity* metadataEntity = sliceComponent->GetMetadataEntity();
constexpr bool isMetadataEntity = true;
m_aliasIdMapper.emplace(metadataEntity->GetId(), SliceEntityMappingInfo(templateId, "MetadataEntity", isMetadataEntity));
// Update the prefab template with the fixed-up data in our prefab instance.
// Save off the the first version of this prefab template with our empty placeholder entities.
// As it saves off, the entities will all change IDs during serialization / propagation, but the aliases will remain the same.
AzToolsFramework::Prefab::PrefabDom prefabDom;
bool storeResult = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefabDom);
if (storeResult == false)
@@ -288,10 +287,20 @@ namespace AZ
return false;
}
prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, prefabDom);
AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
// Dispatch events here, because prefab serialization might trigger asset loads in rare circumstances.
AZ::Data::AssetManager::Instance().DispatchEvents();
// Save off a mapping of the slice's metadata entity ID as well, even though we never converted the entity itself.
// This will help us better detect entity ID mapping errors for nested slice instances.
AZ::Entity* metadataEntity = sliceComponent->GetMetadataEntity();
constexpr bool isMetadataEntity = true;
m_aliasIdMapper.emplace(metadataEntity->GetId(), SliceEntityMappingInfo(templateId, "MetadataEntity", isMetadataEntity));
// Also save off a mapping of the prefab's container entity ID.
m_aliasIdMapper.emplace(sourceInstance->GetContainerEntityId(), SliceEntityMappingInfo(templateId, "ContainerEntity"));
// If this slice has nested slices, we need to loop through those, convert them to prefabs as well, and
// set up the new nesting relationships correctly.
const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices();
@@ -305,6 +314,51 @@ namespace AZ
}
}
// *After* converting the nested slices, remove our placeholder entities and replace them with the correct ones.
// The placeholder entity IDs will have changed from what we originally created, so we need to make sure our replacement
// entities have the same IDs and aliases as the placeholders so that any instance references that have already been fixed
// up continue to reference the correct entities here.
for (size_t curEntityIdx = 0; curEntityIdx < sliceEntities.size(); curEntityIdx++)
{
auto& sliceEntity = sliceEntities[curEntityIdx];
auto& prefabEntity = prefabPlaceholderEntities[curEntityIdx];
sliceEntity->SetId(prefabEntity->GetId());
}
// Remove and delete our placeholder entities.
// (By using an empty callback on DetachEntities, the unique_ptr will auto-delete the placeholder entities)
sourceInstance->DetachEntities([](AZStd::unique_ptr<AZ::Entity>){});
prefabPlaceholderEntities.clear();
for (size_t curEntityIdx = 0; curEntityIdx < sliceEntities.size(); curEntityIdx++)
{
sourceInstance->AddEntity(*(sliceEntities[curEntityIdx]), entityAliases[curEntityIdx]);
}
// Fix up the container entity to have the proper components and fix up the slice entities to have the proper hierarchy
// with the container as the top-most parent.
AzToolsFramework::Prefab::EntityOptionalReference container = sourceInstance->GetContainerEntity();
FixPrefabEntities(container->get(), sliceEntities);
// Also save off a mapping of the prefab's container entity ID.
m_aliasIdMapper.emplace(sourceInstance->GetContainerEntityId(), SliceEntityMappingInfo(templateId, "ContainerEntity"));
// Remap all of the entity references that exist in these top-level slice entities.
SliceComponent::InstantiatedContainer instantiatedEntities(false);
instantiatedEntities.m_entities = sliceEntities;
RemapIdReferences(m_aliasIdMapper, sourceInstance.get(), sourceInstance.get(), &instantiatedEntities, serializeContext);
// Finally, store the completed slice->prefab conversion back into the template.
storeResult = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefabDom);
if (storeResult == false)
{
AZ_Printf("Convert-Slice", " Failed to convert prefab instance data to a PrefabDom.\n");
return false;
}
prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, prefabDom);
AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
// Dispatch events here, because prefab serialization might trigger asset loads in rare circumstances.
AZ::Data::AssetManager::Instance().DispatchEvents();
if (isDryRun)
{
PrintPrefab(templateId);
@@ -417,7 +471,7 @@ namespace AZ
auto instances = slice.GetInstances();
AZ_Printf(
"Convert-Slice", " Attaching %zu instances of nested slice '%s'.\n", instances.size(),
"Convert-Slice", "Attaching %zu instances of nested slice '%s'.\n", instances.size(),
nestedPrefabPath.Native().c_str());
// Before processing any further, save off all the known entity IDs from all the instances and how they map back to
@@ -435,14 +489,20 @@ namespace AZ
}
// Now that we have all the entity ID mappings, convert all the instances.
size_t curInstance = 0;
for (auto& instance : instances)
{
AZ_Printf("Convert-Slice", " Converting instance %zu.\n", curInstance++);
bool instanceConvertResult = ConvertSliceInstance(instance, sliceAsset, nestedTemplate, sourceInstance);
if (!instanceConvertResult)
{
return false;
}
}
AZ_Printf(
"Convert-Slice", "Finished attaching %zu instances of nested slice '%s'.\n", instances.size(),
nestedPrefabPath.Native().c_str());
}
return true;
@@ -507,6 +567,10 @@ namespace AZ
return false;
}
// Save off a mapping of the new nested Instance's container ID
m_aliasIdMapper.emplace(nestedInstance->GetContainerEntityId(),
SliceEntityMappingInfo(nestedInstance->GetTemplateId(), "ContainerEntity"));
// Get the DOM for the unmodified nested instance. This will be used later below for generating the correct patch
// to the top-level template DOM.
AzToolsFramework::Prefab::PrefabDom unmodifiedNestedInstanceDom;
@@ -595,7 +659,7 @@ namespace AZ
auto parentId = transformComponent->GetParentId();
if (parentId.IsValid())
{
// Look to see if the parent ID exists in the same instance (i.e. an entity in the nested slice is a
// Look to see if the parent ID exists in a different instance (i.e. an entity in the nested slice is a
// child of an entity in the containing slice). If this case exists, we need to adjust the parents so that
// the child entity connects to the prefab container, and the *container* is the child of the entity in the
// containing slice. (i.e. go from A->B to A->container->B)
@@ -607,6 +671,7 @@ namespace AZ
{
if (topLevelInstance->GetTemplateId() == parentMappingInfo.m_templateId)
{
// This entity has a parent from the topLevelInstance, so get its parent ID.
parentId = topLevelInstance->GetEntityId(parentMappingInfo.m_entityAlias);
}
else
@@ -630,15 +695,23 @@ namespace AZ
}
// Set the container's parent to this entity's parent, and set this entity's parent to the container
// auto newParentId = topLevelInstance->GetEntityId(parentMappingInfo.m_entityAlias);
SetParentEntity(containerEntity->get(), parentId, false);
onlySetIfInvalid = false;
}
else
{
// If the parent ID is valid and exists inside the same slice instance (i.e. template IDs are equal)
// then it's just a nested entity hierarchy inside the slice and we don't need to adjust anything.
// "onlySetIfInvalid" will still be true, which means we won't change the parent ID below.
}
}
else
{
// If the parent ID is set to something valid, but we can't find it in our ID mapper, something went wrong.
// We'll assert, but don't change the container entity's parent below.
AZ_Assert(false, "Could not find parent entity id: %s", parentId.ToString().c_str());
}
// If the parent ID is valid, but NOT in the top-level instance, then it's just a nested hierarchy inside
// the slice and we don't need to adjust anything. "onlySetIfInvalid" will still be true, which means we
// won't change the parent ID below.
}
SetParentEntity(*entity, containerEntityId, onlySetIfInvalid);
@@ -846,9 +919,10 @@ namespace AZ
{
auto entityEntry = idMapper.find(sourceId);
// Since we've already remapped transform hierarchies to include container entities, it's possible that our entity
// reference is pointing to a container, which means it won't be in our slice mapping table. In that case, just
// return it as-is.
// The id mapping table should include all of our known slice entities, slice metadata entities, and prefab
// container entities. If we can't find the entity reference, it should either be because it's actually invalid
// in the source data or because it's a transform parent id that we've already remapped prior to this point.
// Either way, just keep it as-is and return it.
if (entityEntry == idMapper.end())
{
return sourceId;
@@ -876,6 +950,7 @@ namespace AZ
else
{
AZ_Error("Convert-Slice", false, " Couldn't find source ID %s", sourceId.ToString().c_str());
newId = sourceId;
}
}
else