Merge pull request #7120 from aws-lumberyard-dev/cgalvan/gitflow_220124_o3de_main

Merged `stabilization/2111RTE` to `main`
main 2111.2
Chris Galvan 4 years ago committed by GitHub
commit c1d7294c5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,19 +3,19 @@
"AssetProcessor": {
"Settings": {
"Exclude PythonTest Benchmark Settings Assets": {
"pattern": ".*\\\\/PythonTests\\\\/.*benchmarksettings"
"pattern": "(^|.+/)PythonTests/.*benchmarksettings"
},
"Exclude fbx_tests": {
"pattern": ".*\\\\/fbx_tests\\\\/assets\\\\/.*"
"pattern": "(^|.+/)fbx_tests/assets(/.+)$"
},
"Exclude wwise_bank_dependency_tests": {
"pattern": ".*\\\\/wwise_bank_dependency_tests\\\\/assets\\\\/.*"
"pattern": "(^|.+/)wwise_bank_dependency_tests/assets(/.+)$"
},
"Exclude AssetProcessorTestAssets": {
"pattern": ".*\\\\/asset_processor_tests\\\\/assets\\\\/.*"
"pattern": "(^|.+/)asset_processor_tests/assets(/.+)$"
},
"Exclude Restricted AssetProcessorTestAssets": {
"pattern": ".*\\\\/asset_processor_tests\\\\/restricted\\\\/.*"
"pattern": "(^|.+/)asset_processor_tests/restricted(/.+)$"
}
}
}

@ -229,25 +229,35 @@ void CViewportTitleDlg::SetupHelpersButton()
void CViewportTitleDlg::SetupOverflowMenu()
{
// Setup the overflow menu
QMenu* overFlowMenu = new QMenu(this);
// setup the overflow menu
auto overflowMenu = new QMenu(this);
m_audioMuteAction = new QAction("Mute Audio", overFlowMenu);
m_audioMuteAction = new QAction("Mute Audio", overflowMenu);
connect(m_audioMuteAction, &QAction::triggered, this, &CViewportTitleDlg::OnBnClickedMuteAudio);
overFlowMenu->addAction(m_audioMuteAction);
overflowMenu->addAction(m_audioMuteAction);
m_enableVRAction = new QAction("Enable VR Preview", overFlowMenu);
m_enableVRAction = new QAction("Enable VR Preview", overflowMenu);
connect(m_enableVRAction, &QAction::triggered, this, &CViewportTitleDlg::OnBnClickedEnableVR);
overFlowMenu->addAction(m_enableVRAction);
overflowMenu->addAction(m_enableVRAction);
overFlowMenu->addSeparator();
overflowMenu->addSeparator();
m_enableGridSnappingAction = new QAction("Enable Grid Snapping", overFlowMenu);
m_enableGridSnappingAction = new QAction("Enable Grid Snapping", overflowMenu);
connect(m_enableGridSnappingAction, &QAction::triggered, this, &CViewportTitleDlg::OnGridSnappingToggled);
m_enableGridSnappingAction->setCheckable(true);
overFlowMenu->addAction(m_enableGridSnappingAction);
overflowMenu->addAction(m_enableGridSnappingAction);
m_gridSizeActionWidget = new QWidgetAction(overFlowMenu);
m_enableGridVisualizationAction = new QAction("Show Grid", overflowMenu);
connect(
m_enableGridVisualizationAction, &QAction::triggered,
[]
{
SandboxEditor::SetShowingGrid(!SandboxEditor::ShowingGrid());
});
m_enableGridVisualizationAction->setCheckable(true);
overflowMenu->addAction(m_enableGridVisualizationAction);
m_gridSizeActionWidget = new QWidgetAction(overflowMenu);
m_gridSpinBox = new AzQtComponents::DoubleSpinBox();
m_gridSpinBox->setValue(SandboxEditor::GridSnappingSize());
m_gridSpinBox->setMinimum(1e-2f);
@ -257,31 +267,31 @@ void CViewportTitleDlg::SetupOverflowMenu()
m_gridSpinBox, QOverload<double>::of(&AzQtComponents::DoubleSpinBox::valueChanged), this, &CViewportTitleDlg::OnGridSpinBoxChanged);
m_gridSizeActionWidget->setDefaultWidget(m_gridSpinBox);
overFlowMenu->addAction(m_gridSizeActionWidget);
overflowMenu->addAction(m_gridSizeActionWidget);
overFlowMenu->addSeparator();
overflowMenu->addSeparator();
m_enableAngleSnappingAction = new QAction("Enable Angle Snapping", overFlowMenu);
m_enableAngleSnappingAction = new QAction("Enable Angle Snapping", overflowMenu);
connect(m_enableAngleSnappingAction, &QAction::triggered, this, &CViewportTitleDlg::OnAngleSnappingToggled);
m_enableAngleSnappingAction->setCheckable(true);
overFlowMenu->addAction(m_enableAngleSnappingAction);
overflowMenu->addAction(m_enableAngleSnappingAction);
m_angleSizeActionWidget = new QWidgetAction(overFlowMenu);
m_angleSizeActionWidget = new QWidgetAction(overflowMenu);
m_angleSpinBox = new AzQtComponents::DoubleSpinBox();
m_angleSpinBox->setValue(SandboxEditor::AngleSnappingSize());
m_angleSpinBox->setMinimum(1e-2f);
m_angleSpinBox->setToolTip(tr("Angle Snapping"));
m_angleSpinBox->setToolTip(tr("Angle size"));
QObject::connect(
m_angleSpinBox, QOverload<double>::of(&AzQtComponents::DoubleSpinBox::valueChanged), this,
&CViewportTitleDlg::OnAngleSpinBoxChanged);
m_angleSizeActionWidget->setDefaultWidget(m_angleSpinBox);
overFlowMenu->addAction(m_angleSizeActionWidget);
overflowMenu->addAction(m_angleSizeActionWidget);
m_ui->m_overflowBtn->setMenu(overFlowMenu);
m_ui->m_overflowBtn->setMenu(overflowMenu);
m_ui->m_overflowBtn->setPopupMode(QToolButton::InstantPopup);
connect(overFlowMenu, &QMenu::aboutToShow, this, &CViewportTitleDlg::UpdateOverFlowMenuState);
connect(overflowMenu, &QMenu::aboutToShow, this, &CViewportTitleDlg::UpdateOverFlowMenuState);
UpdateMuteActionText();
}
@ -1004,31 +1014,36 @@ void CViewportTitleDlg::OnAngleSnappingToggled()
MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapAngle)->trigger();
}
void CViewportTitleDlg::OnGridSpinBoxChanged(double value)
void CViewportTitleDlg::OnGridSpinBoxChanged(const double value)
{
SandboxEditor::SetGridSnappingSize(static_cast<float>(value));
SandboxEditor::SetGridSnappingSize(aznumeric_cast<float>(value));
}
void CViewportTitleDlg::OnAngleSpinBoxChanged(double value)
void CViewportTitleDlg::OnAngleSpinBoxChanged(const double value)
{
SandboxEditor::SetAngleSnappingSize(static_cast<float>(value));
SandboxEditor::SetAngleSnappingSize(aznumeric_cast<float>(value));
}
void CViewportTitleDlg::UpdateOverFlowMenuState()
{
bool gridSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapToGrid)->isChecked();
const bool gridSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapToGrid)->isChecked();
{
QSignalBlocker signalBlocker(m_enableGridSnappingAction);
m_enableGridSnappingAction->setChecked(gridSnappingActive);
}
m_gridSizeActionWidget->setEnabled(gridSnappingActive);
bool angleSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapAngle)->isChecked();
const bool angleSnappingActive = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::SnapAngle)->isChecked();
{
QSignalBlocker signalBlocker(m_enableAngleSnappingAction);
m_enableAngleSnappingAction->setChecked(angleSnappingActive);
}
m_angleSizeActionWidget->setEnabled(angleSnappingActive);
{
QSignalBlocker signalBlocker(m_enableGridVisualizationAction);
m_enableGridVisualizationAction->setChecked(SandboxEditor::ShowingGrid());
}
}
namespace

@ -171,6 +171,7 @@ protected:
QAction* m_enableVRAction = nullptr;
QAction* m_enableGridSnappingAction = nullptr;
QAction* m_enableAngleSnappingAction = nullptr;
QAction* m_enableGridVisualizationAction = nullptr;
QComboBox* m_cameraSpeed = nullptr;
AzQtComponents::DoubleSpinBox* m_gridSpinBox = nullptr;
AzQtComponents::DoubleSpinBox* m_angleSpinBox = nullptr;
@ -184,7 +185,7 @@ protected:
namespace AzToolsFramework
{
//! A component to reflect scriptable commands for the Editor
//! A component to reflect scriptable commands for the Editor.
class ViewportTitleDlgPythonFuncsHandler
: public AZ::Component
{

@ -3408,7 +3408,14 @@ LUA_API const Node* lua_getDummyNode()
const BehaviorParameter* arg = method->GetArgument(iArg);
BehaviorClass* argClass = nullptr;
LuaLoadFromStack fromStack = FromLuaStack(context, arg, argClass);
AZ_Assert(fromStack, "Argument %s for Method %s doesn't have support to be converted to Lua!", arg->m_name, method->m_name.c_str());
AZ_Assert(fromStack,
"The argument type: %s for method: %s is not serialized and/or reflected for scripting.\n"
"Make sure %s is added to the SerializeContext and reflected to the BehaviorContext\n"
"For example, verify these two exist and are being called in a Reflect function:\n"
"serializeContext->Class<%s>();\n"
"behaviorContext->Class<%s>();\n"
"%s will not be available for scripting unless these requirements are met."
, arg->m_name, method->m_name.c_str(), arg->m_name, arg->m_name, arg->m_name, method->m_name.c_str());
m_fromLua.push_back(AZStd::make_pair(fromStack, argClass));
}
@ -5066,13 +5073,26 @@ LUA_API const Node* lua_getDummyNode()
// Check all constructors if they have use ScriptDataContext and if so choose this one
if (!customConstructorMethod)
{
int overrideIndex = -1;
AZ::AttributeReader(nullptr, FindAttribute
( Script::Attributes::DefaultConstructorOverrideIndex, behaviorClass->m_attributes)).Read<int>(overrideIndex);
int methodIndex = 0;
for (BehaviorMethod* method : behaviorClass->m_constructors)
{
if (methodIndex == overrideIndex)
{
customConstructorMethod = method;
break;
}
if (method->GetNumArguments() && method->GetArgument(method->GetNumArguments() - 1)->m_typeId == AZ::AzTypeInfo<ScriptDataContext>::Uuid())
{
customConstructorMethod = method;
break;
}
++methodIndex;
}
}

@ -21,6 +21,7 @@ namespace AZ
static constexpr AZ::Crc32 ClassNameOverride = AZ_CRC_CE("ScriptClassNameOverride"); ///< Provide a custom name for script reflection, that doesn't match the behavior Context name
static constexpr AZ::Crc32 MethodOverride = AZ_CRC_CE("ScriptFunctionOverride"); ///< Use a custom function in the attribute instead of the function
static constexpr AZ::Crc32 ConstructorOverride = AZ_CRC_CE("ConstructorOverride"); ///< You can provide a custom constructor to be called when created from Lua script
static constexpr AZ::Crc32 DefaultConstructorOverrideIndex = AZ_CRC_CE("DefaultConstructorOverrideIndex"); ///< Use a different class constructor as the default constructor in Lua
static constexpr AZ::Crc32 EventHandlerCreationFunction = AZ_CRC_CE("EventHandlerCreationFunction"); ///< helps create a handler for any script target so that script functions can be used for AZ::Event signals
static constexpr AZ::Crc32 GenericConstructorOverride = AZ_CRC_CE("GenericConstructorOverride"); ///< You can provide a custom constructor to be called when creating a script
static constexpr AZ::Crc32 ReaderWriterOverride = AZ_CRC_CE("ReaderWriterOverride"); ///< paired with \ref ScriptContext::CustomReaderWriter allows you to customize read/write to Lua VM

@ -133,6 +133,8 @@ namespace AZ
const static AZ::Crc32 AllowClearAsset = AZ_CRC("AllowClearAsset", 0x24827182);
// Show the name of the asset that was produced from the source asset
const static AZ::Crc32 ShowProductAssetFileName = AZ_CRC("ShowProductAssetFileName");
//! Regular expression pattern filter for source files
const static AZ::Crc32 SourceAssetFilterPattern = AZ_CRC_CE("SourceAssetFilterPattern");
//! Component icon attributes
const static AZ::Crc32 Icon = AZ_CRC("Icon", 0x659429db);

@ -204,6 +204,22 @@ namespace AZ
// BaseJsonSerializer
//
JsonSerializationResult::Result BaseJsonSerializer::Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
JsonSerializationResult::ResultCode result(JsonSerializationResult::Tasks::ReadField);
result.Combine(ContinueLoading(outputValue, outputValueTypeId, inputValue, context, ContinuationFlags::IgnoreTypeSerializer));
return context.Report(result, "Ignoring custom serialization during load");
}
JsonSerializationResult::Result BaseJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context)
{
JsonSerializationResult::ResultCode result(JsonSerializationResult::Tasks::WriteValue);
result.Combine(ContinueStoring(outputValue, inputValue, defaultValue, valueTypeId, context, ContinuationFlags::IgnoreTypeSerializer));
return context.Report(result, "Ignoring custom serialization during store");
}
BaseJsonSerializer::OperationFlags BaseJsonSerializer::GetOperationsFlags() const
{
return OperationFlags::None;

@ -180,13 +180,16 @@ namespace AZ
//! Transforms the data from the rapidjson Value to outputValue, if the conversion is possible and supported.
//! The serializer is responsible for casting to the proper type and safely writing to the outputValue memory.
//! \note The default implementation is to load the object ignoring a custom serializers for the type, which allows for custom serializers
//! to modify the object after all default loading has occurred.
virtual JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) = 0;
JsonDeserializerContext& context);
//! Write the input value to a rapidjson value if the default value is not null and doesn't match the input value, otherwise
//! an error is returned and sets the rapidjson value to a null value.
//! \note The default implementation is to store the object ignoring custom serializers.
virtual JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) = 0;
const Uuid& valueTypeId, JsonSerializerContext& context);
//! Returns the operation flags which tells the Json Serialization how this custom json serializer can be used.
virtual OperationFlags GetOperationsFlags() const;

@ -150,7 +150,7 @@ namespace AZ
{
// Not using InsertTypeId here to avoid needing to create the temporary value and swap it in that call.
node.AddMember(rapidjson::StringRef(JsonSerialization::TypeIdFieldIdentifier),
StoreTypeName(classData, context), context.GetJsonAllocator());
StoreTypeName(classData, classData.m_typeId, context), context.GetJsonAllocator());
result = ResultCode(Tasks::WriteValue, Outcomes::Success);
}
return result.Combine(StoreClass(node, object, defaultObject, classData, context));
@ -531,7 +531,7 @@ namespace AZ
return ResolvePointerResult::ContinueProcessing;
}
rapidjson::Value JsonSerializer::StoreTypeName(const SerializeContext::ClassData& classData, JsonSerializerContext& context)
rapidjson::Value JsonSerializer::StoreTypeName(const SerializeContext::ClassData& classData, const Uuid& typeId, JsonSerializerContext& context)
{
rapidjson::Value result;
AZStd::vector<Uuid> ids = context.GetSerializeContext()->FindClassId(Crc32(classData.m_name));
@ -544,7 +544,7 @@ namespace AZ
// Only write the Uuid for the class if there are multiple classes sharing the same name.
// In this case it wouldn't be enough to determine which class needs to be used. The
// class name is still added as a comment for be friendlier for users to read.
AZStd::string fullName = classData.m_typeId.ToString<AZStd::string>();
AZStd::string fullName = typeId.ToString<AZStd::string>();
fullName += ' ';
fullName += classData.m_name;
result.SetString(fullName.c_str(), aznumeric_caster(fullName.size()), context.GetJsonAllocator());
@ -560,7 +560,7 @@ namespace AZ
const SerializeContext::ClassData* data = context.GetSerializeContext()->FindClassData(typeId);
if (data)
{
output = JsonSerializer::StoreTypeName(*data, context);
output = JsonSerializer::StoreTypeName(*data, typeId, context);
return context.Report(Tasks::WriteValue, Outcomes::Success, "Type id successfully stored to json value.");
}
else
@ -580,7 +580,7 @@ namespace AZ
{
rapidjson::Value insertedObject(rapidjson::kObjectType);
insertedObject.AddMember(
rapidjson::StringRef(JsonSerialization::TypeIdFieldIdentifier), StoreTypeName(classData, context),
rapidjson::StringRef(JsonSerialization::TypeIdFieldIdentifier), StoreTypeName(classData, classData.m_typeId, context),
context.GetJsonAllocator());
for (auto& element : output.GetObject())

@ -79,7 +79,7 @@ namespace AZ
const void*& object, const void*& defaultObject, AZStd::any& defaultObjectStorage,
const SerializeContext::ClassData*& elementClassData, const AZ::IRttiHelper& rtti, JsonSerializerContext& context);
static rapidjson::Value StoreTypeName(const SerializeContext::ClassData& classData, JsonSerializerContext& context);
static rapidjson::Value StoreTypeName(const SerializeContext::ClassData& classData, const Uuid& typeId, JsonSerializerContext& context);
static JsonSerializationResult::ResultCode StoreTypeName(rapidjson::Value& output,
const Uuid& typeId, JsonSerializerContext& context);

@ -165,13 +165,12 @@ namespace AZ::Utils
}
Container fileContent;
fileContent.resize(length);
fileContent.resize_no_construct(length);
AZ::IO::SizeType bytesRead = file.Read(length, fileContent.data());
file.Close();
// Resize again just in case bytesRead is less than length for some reason
fileContent.resize(bytesRead);
fileContent.resize_no_construct(bytesRead);
return AZ::Success(AZStd::move(fileContent));
}

@ -10,6 +10,72 @@
#include <Tests/Serialization/Json/JsonSerializationTests.h>
#include <Tests/Serialization/Json/TestCases_Classes.h>
#include <Tests/Serialization/Json/TestCases_Pointers.h>
#include <AzCore/Asset/AssetCommon.h>
namespace AZ
{
template<typename T>
struct SerializeGenericTypeInfo<JsonSerializationTests::TemplatedClass<T>>
{
using ThisType = JsonSerializationTests::TemplatedClass<T>;
class GenericTemplatedClassInfo : public GenericClassInfo
{
public:
GenericTemplatedClassInfo()
: m_classData{ SerializeContext::ClassData::Create<ThisType>(
"TemplatedClass", "{CA4ADF74-66E7-4D16-B4AC-F71278C60EC7}", nullptr, nullptr) }
{
}
SerializeContext::ClassData* GetClassData() override
{
return &m_classData;
}
size_t GetNumTemplatedArguments() override
{
return 1;
}
const Uuid& GetSpecializedTypeId() const override
{
return m_classData.m_typeId;
}
const Uuid& GetGenericTypeId() const override
{
return m_classData.m_typeId;
}
const Uuid& GetTemplatedTypeId(size_t element) override
{
(void)element;
return SerializeGenericTypeInfo<T>::GetClassTypeId();
}
void Reflect(SerializeContext* serializeContext) override
{
if (serializeContext)
{
serializeContext->RegisterGenericClassInfo(
GetSpecializedTypeId(), this, &AZ::AnyTypeInfoConcept<Data::Asset<Data::AssetData>>::CreateAny);
serializeContext->RegisterGenericClassInfo(
azrtti_typeid<ThisType>(), this,
&AZ::AnyTypeInfoConcept<ThisType>::CreateAny);
}
}
SerializeContext::ClassData m_classData;
};
using ClassInfoType = GenericTemplatedClassInfo;
static ClassInfoType* GetGenericInfo()
{
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ThisType>();
}
};
} // namespace AZ
namespace JsonSerializationTests
{
@ -286,4 +352,32 @@ namespace JsonSerializationTests
EXPECT_EQ(Processing::Halted, result.GetProcessing());
EXPECT_EQ(Outcomes::Unknown, result.GetOutcome());
}
TEST_F(JsonSerializationTests, StoreTypeId_TemplatedType_StoresUuidWithName)
{
using namespace AZ;
using namespace AZ::JsonSerializationResult;
m_serializeContext->RegisterGenericType<TemplatedClass<A::Inherited>>();
m_serializeContext->RegisterGenericType<TemplatedClass<BaseClass>>();
Uuid input = azrtti_typeid<TemplatedClass<A::Inherited>>();
ResultCode result = JsonSerialization::StoreTypeId(
*m_jsonDocument, m_jsonDocument->GetAllocator(), input, AZStd::string_view{}, *m_serializationSettings);
EXPECT_EQ(Processing::Completed, result.GetProcessing());
AZStd::string expected =
AZStd::string::format(R"("%s TemplatedClass")", azrtti_typeid<TemplatedClass<A::Inherited>>().ToString<AZStd::string>().c_str());
Expect_DocStrEq(expected.c_str(), false);
input = azrtti_typeid<TemplatedClass<BaseClass>>();
result = JsonSerialization::StoreTypeId(
*m_jsonDocument, m_jsonDocument->GetAllocator(), input, AZStd::string_view{}, *m_serializationSettings);
expected =
AZStd::string::format(R"("%s TemplatedClass")", azrtti_typeid<TemplatedClass<BaseClass>>().ToString<AZStd::string>().c_str());
EXPECT_EQ(Processing::Completed, result.GetProcessing());
Expect_DocStrEq(expected.c_str(), false);
}
} // namespace JsonSerializationTests

@ -9,9 +9,27 @@
#include <AzToolsFramework/Application/EditorEntityManager.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
namespace AzToolsFramework
{
static bool AreEntitiesValidForDuplication(const EntityIdList& entityIds)
{
for (AZ::EntityId entityId : entityIds)
{
if (GetEntityById(entityId) == nullptr)
{
AZ_Error(
"Entity", false,
"Entity with id '%llu' is not found. This can happen when you try to duplicate the entity before it is created. Please "
"ensure entities are created before trying to duplicate them.",
static_cast<AZ::u64>(entityId));
return false;
}
}
return true;
}
void EditorEntityManager::Start()
{
m_prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
@ -62,7 +80,11 @@ namespace AzToolsFramework
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
if (AreEntitiesValidForDuplication(selectedEntities))
{
m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
}
}
void EditorEntityManager::DuplicateEntityById(AZ::EntityId entityId)
@ -72,7 +94,10 @@ namespace AzToolsFramework
void EditorEntityManager::DuplicateEntities(const EntityIdList& entities)
{
m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
if (AreEntitiesValidForDuplication(entities))
{
m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
}
}
}

@ -34,5 +34,4 @@ namespace AzToolsFramework
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
};
}

@ -99,6 +99,18 @@ namespace AzToolsFramework
}
}
if (selection.GetSelectedAssetIds().empty())
{
for (auto& filePath : selection.GetSelectedFilePaths())
{
if (!filePath.empty())
{
selectedAsset = true;
m_ui->m_assetBrowserTreeViewWidget->SelectFileAtPath(filePath);
}
}
}
if (!selectedAsset)
{
m_ui->m_assetBrowserTreeViewWidget->SelectFolder(selection.GetDefaultDirectory());

@ -10,18 +10,24 @@
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/EBusFindAssetTypeByName.h>
#if !defined(Q_MOC_RUN)
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option")
#include <QRegExp>
AZ_POP_DISABLE_WARNING
#endif
namespace AzToolsFramework
{
namespace AssetBrowser
{
namespace
{
FilterConstType ProductsNoFoldersFilter()
FilterConstType EntryTypeNoFoldersFilter(AssetBrowserEntry::AssetEntryType entryType = AssetBrowserEntry::AssetEntryType::Product)
{
EntryTypeFilter* productFilter = new EntryTypeFilter();
productFilter->SetEntryType(AssetBrowserEntry::AssetEntryType::Product);
EntryTypeFilter* entryTypeFilter = new EntryTypeFilter();
entryTypeFilter->SetEntryType(entryType);
// in case entry is a source or folder, it may still contain relevant product
productFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down);
entryTypeFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down);
EntryTypeFilter* foldersFilter = new EntryTypeFilter();
foldersFilter->SetEntryType(AssetBrowserEntry::AssetEntryType::Folder);
@ -30,7 +36,7 @@ namespace AzToolsFramework
noFoldersFilter->SetFilter(FilterConstType(foldersFilter));
CompositeFilter* compFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND);
compFilter->AddFilter(FilterConstType(productFilter));
compFilter->AddFilter(FilterConstType(entryTypeFilter));
compFilter->AddFilter(FilterConstType(noFoldersFilter));
return FilterConstType(compFilter);
@ -79,15 +85,39 @@ namespace AzToolsFramework
void AssetSelectionModel::SetSelectedAssetIds(const AZStd::vector<AZ::Data::AssetId>& selectedAssetIds)
{
m_selectedFilePaths.clear();
m_selectedAssetIds = selectedAssetIds;
}
void AssetSelectionModel::SetSelectedAssetId(const AZ::Data::AssetId& selectedAssetId)
{
m_selectedFilePaths.clear();
m_selectedAssetIds.clear();
m_selectedAssetIds.push_back(selectedAssetId);
}
const AZStd::vector<AZStd::string>& AssetSelectionModel::GetSelectedFilePaths() const
{
return m_selectedFilePaths;
}
void AssetSelectionModel::SetSelectedFilePaths(const AZStd::vector<AZStd::string>& selectedFilePaths)
{
m_selectedAssetIds.clear();
m_selectedFilePaths = selectedFilePaths;
}
void AssetSelectionModel::SetSelectedFilePath(const AZStd::string& selectedFilePath)
{
m_selectedAssetIds.clear();
m_selectedFilePaths.clear();
m_selectedFilePaths.push_back(selectedFilePath);
}
void AssetSelectionModel::SetDefaultDirectory(AZStd::string_view defaultDirectory)
{
m_defaultDirectory = defaultDirectory;
@ -136,7 +166,7 @@ namespace AzToolsFramework
CompositeFilter* compFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND);
compFilter->AddFilter(assetTypeFilterPtr);
compFilter->AddFilter(ProductsNoFoldersFilter());
compFilter->AddFilter(EntryTypeNoFoldersFilter());
selection.SetSelectionFilter(FilterConstType(compFilter));
selection.SetMultiselect(multiselect);
@ -169,7 +199,7 @@ namespace AzToolsFramework
CompositeFilter* compFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND);
compFilter->AddFilter(anyAssetTypeFilterPtr);
compFilter->AddFilter(ProductsNoFoldersFilter());
compFilter->AddFilter(EntryTypeNoFoldersFilter());
selection.SetSelectionFilter(FilterConstType(compFilter));
selection.SetMultiselect(multiselect);
@ -190,7 +220,28 @@ namespace AzToolsFramework
CompositeFilter* compFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND);
compFilter->AddFilter(assetGroupFilterPtr);
compFilter->AddFilter(ProductsNoFoldersFilter());
compFilter->AddFilter(EntryTypeNoFoldersFilter());
selection.SetSelectionFilter(FilterConstType(compFilter));
selection.SetMultiselect(multiselect);
return selection;
}
AssetSelectionModel AssetSelectionModel::SourceAssetTypeSelection(const QString& pattern, bool multiselect)
{
AssetSelectionModel selection;
RegExpFilter* patternFilter = new RegExpFilter();
patternFilter->SetFilterPattern(pattern);
patternFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down);
auto patternFilterPtr = FilterConstType(patternFilter);
selection.SetDisplayFilter(patternFilterPtr);
CompositeFilter* compFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND);
compFilter->AddFilter(patternFilterPtr);
compFilter->AddFilter(EntryTypeNoFoldersFilter(AssetBrowserEntry::AssetEntryType::Source));
selection.SetSelectionFilter(FilterConstType(compFilter));
selection.SetMultiselect(multiselect);
@ -204,7 +255,7 @@ namespace AzToolsFramework
CompositeFilter* compFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::OR);
selection.SetDisplayFilter(FilterConstType(compFilter));
selection.SetSelectionFilter(ProductsNoFoldersFilter());
selection.SetSelectionFilter(EntryTypeNoFoldersFilter());
selection.SetMultiselect(multiselect);
return selection;

@ -44,6 +44,10 @@ namespace AzToolsFramework
void SetSelectedAssetIds(const AZStd::vector<AZ::Data::AssetId>& selectedAssetIds);
void SetSelectedAssetId(const AZ::Data::AssetId& selectedAssetId);
const AZStd::vector<AZStd::string>& GetSelectedFilePaths() const;
void SetSelectedFilePaths(const AZStd::vector<AZStd::string>& selectedFilePaths);
void SetSelectedFilePath(const AZStd::string& selectedFilePath);
void SetDefaultDirectory(AZStd::string_view defaultDirectory);
AZStd::string_view GetDefaultDirectory() const;
@ -60,6 +64,7 @@ namespace AzToolsFramework
static AssetSelectionModel AssetTypeSelection(const char* assetTypeName, bool multiselect = false);
static AssetSelectionModel AssetTypesSelection(const AZStd::vector<AZ::Data::AssetType>& assetTypes, bool multiselect = false);
static AssetSelectionModel AssetGroupSelection(const char* group, bool multiselect = false);
static AssetSelectionModel SourceAssetTypeSelection(const QString& pattern, bool multiselect = false);
static AssetSelectionModel EverythingSelection(bool multiselect = false);
private:
@ -68,8 +73,12 @@ namespace AzToolsFramework
// some entries like folder should always be displayed, but not always selectable, thus 2 separate filters
FilterConstType m_selectionFilter;
FilterConstType m_displayFilter;
//! Selection can be based on asset ids (for products), or file paths (for sources)
//! These are mututally exclusive
AZStd::vector<AZ::Data::AssetId> m_selectedAssetIds;
AZStd::vector<AZStd::string> m_selectedFilePaths;
AZStd::vector<const AssetBrowserEntry*> m_results;
AZStd::string m_defaultDirectory;

@ -251,6 +251,40 @@ namespace AzToolsFramework
return false;
}
//////////////////////////////////////////////////////////////////////////
// RegExpFilter
//////////////////////////////////////////////////////////////////////////
RegExpFilter::RegExpFilter()
: m_filterPattern("")
{
}
void RegExpFilter::SetFilterPattern(const QString& filterPattern)
{
m_filterPattern = filterPattern;
Q_EMIT updatedSignal();
}
QString RegExpFilter::GetNameInternal() const
{
return m_filterPattern;
}
bool RegExpFilter::MatchInternal(const AssetBrowserEntry* entry) const
{
// no filter pattern matches any asset
if (m_filterPattern.isEmpty())
{
return true;
}
// entry's name matches regular expression pattern
QRegExp regExp(m_filterPattern);
regExp.setPatternSyntax(QRegExp::Wildcard);
return regExp.exactMatch(entry->GetDisplayName());
}
//////////////////////////////////////////////////////////////////////////
// AssetTypeFilter
//////////////////////////////////////////////////////////////////////////

@ -115,6 +115,28 @@ namespace AzToolsFramework
QString m_filterString;
};
//////////////////////////////////////////////////////////////////////////
// RegExpFilter
//////////////////////////////////////////////////////////////////////////
//! RegExpFilter filters assets based on a regular expression pattern
class RegExpFilter
: public AssetBrowserEntryFilter
{
Q_OBJECT
public:
RegExpFilter();
~RegExpFilter() override = default;
void SetFilterPattern(const QString& filterPattern);
protected:
QString GetNameInternal() const override;
bool MatchInternal(const AssetBrowserEntry* entry) const override;
private:
QString m_filterPattern;
};
//////////////////////////////////////////////////////////////////////////
// AssetTypeFilter
//////////////////////////////////////////////////////////////////////////

@ -1158,7 +1158,8 @@ namespace AzToolsFramework
// Select the duplicated entities/instances
auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
}
return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds));

@ -860,18 +860,20 @@ namespace AzToolsFramework
return;
}
bool isDuringUndoRedo = false;
EBUS_EVENT_RESULT(isDuringUndoRedo, AzToolsFramework::ToolsApplicationRequests::Bus, IsDuringUndoRedo);
if (!isDuringUndoRedo)
bool suppressTransformChangedEvent = m_suppressTransformChangedEvent;
// temporarily disable calling OnTransformChanged, because CheckApplyCachedWorldTransform is not guaranteed
// to call it when m_cachedWorldTransform is identity. We send it manually later.
m_suppressTransformChangedEvent = false;
// When parent comes online, compute local TM from world TM.
CheckApplyCachedWorldTransform(parentTransform->GetWorldTM());
if (!m_initialized)
{
// When parent comes online, compute local TM from world TM.
CheckApplyCachedWorldTransform(parentTransform->GetWorldTM());
}
else
{
// During undo operations, just apply our local TM.
m_initialized = true;
// If this is the first time this entity is being activated, manually compute OnTransformChanged
// this can occur when either the entity first created or undo/redo command is performed
OnTransformChanged(AZ::Transform::Identity(), parentTransform->GetWorldTM());
}
m_suppressTransformChangedEvent = suppressTransformChangedEvent;
auto& parentChildIds = GetParentTransformComponent()->m_childrenEntityIds;
if (parentChildIds.end() == AZStd::find(parentChildIds.begin(), parentChildIds.end(), GetEntityId()))

@ -242,6 +242,9 @@ namespace AzToolsFramework
// element is used rather than a data element.
bool m_addNonUniformScaleButton = false;
// Used to check whether entity was just created vs manually reactivated. Set true after OnEntityActivated is called the first time.
bool m_initialized = false;
// Deprecated
AZ::InterpolationMode m_interpolatePosition;
AZ::InterpolationMode m_interpolateRotation;

@ -39,7 +39,12 @@ namespace AzToolsFramework
typeFilter->SetAssetType(filterType);
typeFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down);
m_assetBrowserFilterModel->SetFilter(FilterConstType(typeFilter));
SetFilter(FilterConstType(typeFilter));
}
void AssetCompleterModel::SetFilter(FilterConstType filter)
{
m_assetBrowserFilterModel->SetFilter(filter);
RefreshAssetList();
}
@ -120,9 +125,6 @@ namespace AzToolsFramework
int rows = m_assetBrowserFilterModel->rowCount(index);
if (rows == 0)
{
if (index != QModelIndex()) {
AZ_Error("AssetCompleterModel", false, "No children detected in FetchResources()");
}
return;
}
@ -131,7 +133,7 @@ namespace AzToolsFramework
QModelIndex childIndex = m_assetBrowserFilterModel->index(i, 0, index);
AssetBrowserEntry* childEntry = GetAssetEntry(m_assetBrowserFilterModel->mapToSource(childIndex));
if (childEntry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Product)
if (childEntry->GetEntryType() == m_entryType)
{
ProductAssetBrowserEntry* productEntry = static_cast<ProductAssetBrowserEntry*>(childEntry);
AZStd::string assetName;
@ -167,7 +169,6 @@ namespace AzToolsFramework
return m_assets[index.row()].m_displayName;
}
const AZ::Data::AssetId AssetCompleterModel::GetAssetIdFromIndex(const QModelIndex& index)
{
if (!index.isValid())
@ -177,4 +178,19 @@ namespace AzToolsFramework
return m_assets[index.row()].m_assetId;
}
const AZStd::string_view AssetCompleterModel::GetPathFromIndex(const QModelIndex& index)
{
if (!index.isValid())
{
return "";
}
return m_assets[index.row()].m_path;
}
void AssetCompleterModel::SetFetchEntryType(AssetBrowserEntry::AssetEntryType entryType)
{
m_entryType = entryType;
}
}

@ -32,6 +32,7 @@ namespace AzToolsFramework
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void SetFilter(AZ::Data::AssetType filterType);
void SetFilter(FilterConstType filter);
void RefreshAssetList();
void SearchStringHighlight(QString searchString);
@ -39,6 +40,9 @@ namespace AzToolsFramework
const AZStd::string_view GetNameFromIndex(const QModelIndex& index);
const AZ::Data::AssetId GetAssetIdFromIndex(const QModelIndex& index);
const AZStd::string_view GetPathFromIndex(const QModelIndex& index);
void SetFetchEntryType(AssetBrowserEntry::AssetEntryType entryType);
private:
struct AssetItem
@ -57,6 +61,8 @@ namespace AzToolsFramework
AZStd::vector<AssetItem> m_assets;
//! String that will be highlighted in the suggestions
QString m_highlightString;
AssetBrowserEntry::AssetEntryType m_entryType = AssetBrowserEntry::AssetEntryType::Product;
};
}

@ -1288,7 +1288,7 @@ namespace AzToolsFramework
return newCtrl;
}
void AssetPropertyHandlerDefault::ConsumeAttribute(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName)
void AssetPropertyHandlerDefault::ConsumeAttributeInternal(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName)
{
(void)debugName;
@ -1487,6 +1487,11 @@ namespace AzToolsFramework
}
}
void AssetPropertyHandlerDefault::ConsumeAttribute(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName)
{
ConsumeAttributeInternal(GUI, attrib, attrValue, debugName);
}
void AssetPropertyHandlerDefault::WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node)
{
(void)index;
@ -1629,8 +1634,8 @@ namespace AzToolsFramework
void RegisterAssetPropertyHandler()
{
EBUS_EVENT(PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew AssetPropertyHandlerDefault());
EBUS_EVENT(PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew SimpleAssetPropertyHandlerDefault());
PropertyTypeRegistrationMessages::Bus::Broadcast(&PropertyTypeRegistrationMessages::RegisterPropertyType, aznew AssetPropertyHandlerDefault());
PropertyTypeRegistrationMessages::Bus::Broadcast(&PropertyTypeRegistrationMessages::RegisterPropertyType, aznew SimpleAssetPropertyHandlerDefault());
}
}

@ -175,10 +175,11 @@ namespace AzToolsFramework
virtual void SetFolderSelection(const AZStd::string& /* folderPath */) {}
virtual void ClearAssetInternal();
void ConfigureAutocompleter();
virtual void ConfigureAutocompleter();
void RefreshAutocompleter();
void EnableAutocompleter();
void DisableAutocompleter();
const QModelIndex GetSourceIndex(const QModelIndex& index);
void HandleFieldClear();
AZStd::string AddDefaultSuffix(const AZStd::string& filename);
@ -235,20 +236,19 @@ namespace AzToolsFramework
void SetSelectedAssetID(const AZ::Data::AssetId& newID, const AZ::Data::AssetType& newType);
void SetCurrentAssetHint(const AZStd::string& hint);
void SetDefaultAssetID(const AZ::Data::AssetId& defaultID);
void PopupAssetPicker();
virtual void PopupAssetPicker();
void OnClearButtonClicked();
void UpdateAssetDisplay();
void OnLineEditFocus(bool focus);
virtual void OnEditButtonClicked();
void OnThumbnailClicked();
void OnCompletionModelReset();
void OnAutocomplete(const QModelIndex& index);
virtual void OnAutocomplete(const QModelIndex& index);
void OnTextChange(const QString& text);
void OnReturnPressed();
void ShowContextMenu(const QPoint& pos);
private:
const QModelIndex GetSourceIndex(const QModelIndex& index);
void UpdateThumbnail();
};
@ -270,7 +270,8 @@ namespace AzToolsFramework
virtual void UpdateWidgetInternalTabbing(PropertyAssetCtrl* widget) override { widget->UpdateTabOrder(); }
virtual QWidget* CreateGUI(QWidget* pParent) override;
virtual void ConsumeAttribute(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override;
static void ConsumeAttributeInternal(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName);
void ConsumeAttribute(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override;
virtual void WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node) override;
virtual bool ReadValuesIntoGUI(size_t index, PropertyAssetCtrl* GUI, const property_t& instance, InstanceDataNode* node) override;
};

@ -3534,10 +3534,9 @@ namespace AzToolsFramework
debugDisplay.SetLineWidth(1.0f);
const float labelOffset = ed_viewportGizmoAxisLabelOffset;
const float screenScale = GetScreenDisplayScaling(viewportId);
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize;
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize;
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize;
// draw the label of of each axis for the gizmo
const float labelSize = ed_viewportGizmoAxisLabelSize;

@ -11,6 +11,8 @@
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
@ -50,6 +52,15 @@ namespace UnitTest
GetApplication()->RegisterComponentDescriptor(PrefabTestComponent::CreateDescriptor());
GetApplication()->RegisterComponentDescriptor(PrefabTestComponentWithUnReflectedTypeMember::CreateDescriptor());
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
m_undoStack, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetUndoStack);
AZ_Assert(m_undoStack, "Failed to look up undo stack from tools application");
}
void PrefabTestFixture::TearDownEditorFixtureImpl()
{
m_undoStack = nullptr;
}
AZStd::unique_ptr<ToolsTestApplication> PrefabTestFixture::CreateTestApplication()
@ -57,7 +68,20 @@ namespace UnitTest
return AZStd::make_unique<PrefabTestToolsApplication>("PrefabTestApplication");
}
AZ::Entity* PrefabTestFixture::CreateEntity(const char* entityName, const bool shouldActivate)
void PrefabTestFixture::CreateRootPrefab()
{
auto entityOwnershipService = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
ASSERT_TRUE(entityOwnershipService != nullptr);
entityOwnershipService->CreateNewLevelPrefab("UnitTestRoot.prefab", "");
auto rootEntityReference = entityOwnershipService->GetRootPrefabInstance()->get().GetContainerEntity();
ASSERT_TRUE(rootEntityReference.has_value());
auto& rootEntity = rootEntityReference->get();
rootEntity.Deactivate();
rootEntity.CreateComponent<AzToolsFramework::Components::TransformComponent>();
rootEntity.Activate();
}
AZ::Entity* PrefabTestFixture::CreateEntity(AZStd::string entityName, const bool shouldActivate)
{
// Circumvent the EntityContext system and generate a new entity with a transformcomponent
AZ::Entity* newEntity = aznew AZ::Entity(entityName);
@ -71,8 +95,43 @@ namespace UnitTest
return newEntity;
}
AZ::EntityId PrefabTestFixture::CreateEntityUnderRootPrefab(AZStd::string name, AZ::EntityId parentId)
{
auto createResult = m_prefabPublicInterface->CreateEntity(parentId, AZ::Vector3());
AZ_Assert(createResult.IsSuccess(), "Failed to create entity: %s", createResult.GetError().c_str());
AZ::EntityId entityId = createResult.GetValue();
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
entity->Deactivate();
entity->SetName(name);
// Normally, in invalid parent ID should automatically parent us to the root prefab, but currently in the unit test
// environment entities aren't created with a default transform component, so CreateEntity won't correctly parent.
// We get the actual target parent ID here, then create our missing transform component.
if (!parentId.IsValid())
{
auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
parentId = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance()->get().GetContainerEntityId();
}
auto transform = aznew AzToolsFramework::Components::TransformComponent;
transform->SetParent(parentId);
entity->AddComponent(transform);
entity->Activate();
// Update our undo cache entry to include the rename / reparent as one atomic operation.
m_prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_undoStack->GetTop());
m_prefabSystemComponent->OnSystemTick();
return entityId;
}
void PrefabTestFixture::CompareInstances(const AzToolsFramework::Prefab::Instance& instanceA,
const AzToolsFramework::Prefab::Instance& instanceB, bool shouldCompareLinkIds, bool shouldCompareContainerEntities)
const AzToolsFramework::Prefab::Instance& instanceB, bool shouldCompareLinkIds, bool shouldCompareContainerEntities)
{
AzToolsFramework::Prefab::TemplateId templateAId = instanceA.GetTemplateId();
AzToolsFramework::Prefab::TemplateId templateBId = instanceB.GetTemplateId();
@ -125,4 +184,22 @@ namespace UnitTest
EXPECT_EQ(entityInInstance->GetState(), AZ::Entity::State::Active);
}
}
void PrefabTestFixture::ProcessDeferredUpdates()
{
// Force a prefab propagation for updates that are deferred to the next tick.
m_prefabSystemComponent->OnSystemTick();
}
void PrefabTestFixture::Undo()
{
m_undoStack->Undo();
ProcessDeferredUpdates();
}
void PrefabTestFixture::Redo()
{
m_undoStack->Redo();
ProcessDeferredUpdates();
}
}

@ -49,10 +49,13 @@ namespace UnitTest
inline static const char* CarPrefabMockFilePath = "SomePathToCar";
void SetUpEditorFixtureImpl() override;
void TearDownEditorFixtureImpl() override;
AZStd::unique_ptr<ToolsTestApplication> CreateTestApplication() override;
AZ::Entity* CreateEntity(const char* entityName, const bool shouldActivate = true);
void CreateRootPrefab();
AZ::Entity* CreateEntity(AZStd::string entityName, const bool shouldActivate = true);
AZ::EntityId CreateEntityUnderRootPrefab(AZStd::string name, AZ::EntityId parentId = AZ::EntityId());
void CompareInstances(const Instance& instanceA, const Instance& instanceB, bool shouldCompareLinkIds = true,
bool shouldCompareContainerEntities = true);
@ -62,10 +65,20 @@ namespace UnitTest
//! Validates that all entities within a prefab instance are in 'Active' state.
void ValidateInstanceEntitiesActive(Instance& instance);
// Kicks off any updates scheduled for the next tick
virtual void ProcessDeferredUpdates();
// Performs an undo operation and ensures the tick-scheduled updates happen
void Undo();
// Performs a redo operation and ensures the tick-scheduled updates happen
void Redo();
PrefabSystemComponent* m_prefabSystemComponent = nullptr;
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
InstanceUpdateExecutorInterface* m_instanceUpdateExecutorInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
AzToolsFramework::UndoSystem::UndoStack* m_undoStack = nullptr;
};
}

@ -18,7 +18,10 @@
#include <AzFramework/Components/TransformComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <Prefab/PrefabTestFixture.h>
#include <AZTestShared/Math/MathTestHelpers.h>
@ -1063,4 +1066,67 @@ R"DELIMITER(<ObjectStream version="1">
}
}
}
// Fixture provides a root prefab with Transform component and listens for TransformNotificationBus.
class TransformComponentActivationTest
: public PrefabTestFixture
, public TransformNotificationBus::Handler
{
protected:
void SetUpEditorFixtureImpl() override
{
PrefabTestFixture::SetUpEditorFixtureImpl();
CreateRootPrefab();
}
void TearDownEditorFixtureImpl() override
{
BusDisconnect();
PrefabTestFixture::TearDownEditorFixtureImpl();
}
void OnTransformChanged(const Transform& /*local*/, const Transform& /*world*/) override
{
m_transformUpdated = true;
}
void MoveEntity(AZ::EntityId entityId)
{
AzToolsFramework::ScopedUndoBatch undoBatch("Move Entity");
TransformBus::Event(entityId, &TransformInterface::SetWorldTranslation, Vector3(1.f, 0.f, 0.f));
}
bool m_transformUpdated = false;
};
TEST_F(TransformComponentActivationTest, TransformChangedEventIsSentWhenEntityIsActivatedViaUndoRedo)
{
AZ::EntityId entityId = CreateEntityUnderRootPrefab("Entity");
MoveEntity(entityId);
BusConnect(entityId);
// verify that undoing/redoing move operations fires TransformChanged event
Undo();
EXPECT_TRUE(m_transformUpdated);
m_transformUpdated = false;
Redo();
EXPECT_TRUE(m_transformUpdated);
m_transformUpdated = false;
}
TEST_F(TransformComponentActivationTest, TransformChangedEventIsNotSentWhenEntityIsDeactivatedAndActivated)
{
AZ::EntityId entityId = CreateEntityUnderRootPrefab("Entity");
BusConnect(entityId);
// verify that simply activating/deactivating an entity does not fire TransformChanged event
Entity* entity = nullptr;
ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
entity->Deactivate();
entity->Activate();
EXPECT_FALSE(m_transformUpdated);
}
} // namespace UnitTest

@ -37,16 +37,11 @@ namespace UnitTest
m_model->Initialize();
m_modelTester =
AZStd::make_unique<QAbstractItemModelTester>(m_model.get(), QAbstractItemModelTester::FailureReportingMode::Fatal);
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
m_undoStack, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetUndoStack);
AZ_Assert(m_undoStack, "Failed to look up undo stack from tools application");
// Create a new root prefab - the synthetic "NewLevel.prefab" that comes in by default isn't suitable for outliner tests
// because it's created before the EditorEntityModel that our EntityOutlinerListModel subscribes to, and we want to
// recreate it as part of the fixture regardless.
auto entityOwnershipService = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
entityOwnershipService->CreateNewLevelPrefab("UnitTestRoot.prefab", "");
CreateRootPrefab();
}
void TearDownEditorFixtureImpl() override
@ -56,45 +51,7 @@ namespace UnitTest
m_model.reset();
PrefabTestFixture::TearDownEditorFixtureImpl();
}
// Creates an entity with a given name as one undoable operation
// Parents to parentId, or the root prefab container entity if parentId is invalid
AZ::EntityId CreateNamedEntity(AZStd::string name, AZ::EntityId parentId = AZ::EntityId())
{
auto createResult = m_prefabPublicInterface->CreateEntity(parentId, AZ::Vector3());
AZ_Assert(createResult.IsSuccess(), "Failed to create entity: %s", createResult.GetError().c_str());
AZ::EntityId entityId = createResult.GetValue();
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
entity->Deactivate();
entity->SetName(name);
// Normally, in invalid parent ID should automatically parent us to the root prefab, but currently in the unit test
// environment entities aren't created with a default transform component, so CreateEntity won't correctly parent.
// We get the actual target parent ID here, then create our missing transform component.
if (!parentId.IsValid())
{
auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
parentId = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance()->get().GetContainerEntityId();
}
auto transform = aznew AzToolsFramework::Components::TransformComponent;
transform->SetParent(parentId);
entity->AddComponent(transform);
entity->Activate();
// Update our undo cache entry to include the rename / reparent as one atomic operation.
m_prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_undoStack->GetTop());
ProcessDeferredUpdates();
return entityId;
}
// Helper to visualize debug state
void PrintModel()
{
@ -125,32 +82,16 @@ namespace UnitTest
}
// Kicks off any updates scheduled for the next tick
void ProcessDeferredUpdates()
void ProcessDeferredUpdates() override
{
// Force a prefab propagation for updates that are deferred to the next tick.
m_prefabSystemComponent->OnSystemTick();
PrefabTestFixture::ProcessDeferredUpdates();
// Ensure the model process its entity update queue
m_model->ProcessEntityUpdates();
}
// Performs an undo operation and ensures the tick-scheduled updates happen
void Undo()
{
m_undoStack->Undo();
ProcessDeferredUpdates();
}
// Performs a redo operation and ensures the tick-scheduled updates happen
void Redo()
{
m_undoStack->Redo();
ProcessDeferredUpdates();
}
AZStd::unique_ptr<AzToolsFramework::EntityOutlinerListModel> m_model;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTester;
AzToolsFramework::UndoSystem::UndoStack* m_undoStack = nullptr;
};
TEST_F(EntityOutlinerTest, TestCreateFlatHierarchyUndoAndRedoWorks)
@ -159,7 +100,7 @@ namespace UnitTest
for (size_t i = 0; i < entityCount; ++i)
{
CreateNamedEntity(AZStd::string::format("Entity%zu", i));
CreateEntityUnderRootPrefab(AZStd::string::format("Entity%zu", i));
EXPECT_EQ(m_model->rowCount(GetRootIndex()), i + 1);
}
@ -195,7 +136,7 @@ namespace UnitTest
AZ::EntityId parentId;
for (int i = 0; i < depth; i++)
{
parentId = CreateNamedEntity(AZStd::string::format("EntityDepth%i", i), parentId);
parentId = CreateEntityUnderRootPrefab(AZStd::string::format("EntityDepth%i", i), parentId);
EXPECT_EQ(modelDepth(), i + 1);
}

@ -283,7 +283,7 @@ namespace AssetProcessor
ExcludeAssetRecognizer excludeRecogniser;
excludeRecogniser.m_name = "backup";
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/savebackup\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|.+/)savebackup/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
config.AddExcludeRecognizer(excludeRecogniser);
}

@ -123,7 +123,7 @@ namespace AssetProcessor
ExcludeAssetRecognizer excludeRecogniser;
excludeRecogniser.m_name = "backup";
// we are excluding all the files in the folder but not the folder itself
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/subfolder2\\/aaa\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|[^/]+/)aaa/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
m_platformConfig.get()->AddExcludeRecognizer(excludeRecogniser);
m_assetScanner.get()->StartScan();
@ -144,7 +144,7 @@ namespace AssetProcessor
ExcludeAssetRecognizer excludeRecogniser;
excludeRecogniser.m_name = "backup";
// we are excluding the complete folder here
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/subfolder2\\/aaa", AssetBuilderSDK::AssetBuilderPattern::Regex);
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|[^/]+/)aaa", AssetBuilderSDK::AssetBuilderPattern::Regex);
m_platformConfig.get()->AddExcludeRecognizer(excludeRecogniser);
m_assetScanner.get()->StartScan();

@ -413,6 +413,8 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularExcludes)
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
config.AddScanFolder(ScanFolderInfo("blahblah", "Blah ScanFolder", "sf2", true, true), true);
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);

@ -347,7 +347,7 @@ namespace AssetProcessor
ExcludeAssetRecognizer excludeRecogniser;
excludeRecogniser.m_name = "backup";
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/savebackup\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("(^|.+/)savebackup/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
config.AddExcludeRecognizer(excludeRecogniser);
AssetProcessorManager_Test apm(&config); // note, this will 'push' the scan folders in to the db.
@ -1791,13 +1791,8 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE((newfingerprintForPCAfterVersionChange != fingerprintForPC) || (newfingerprintForPCAfterVersionChange != newfingerprintForPC));//Fingerprints should be different
UNIT_TEST_EXPECT_TRUE((newfingerprintForANDROIDAfterVersionChange != fingerprintForANDROID) || (newfingerprintForANDROIDAfterVersionChange != newfingerprintForANDROID));//Fingerprints should be different
//------Test for Files which are excluded
processResults.clear();
absolutePath = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("subfolder3/savebackup/test.txt"));
QMetaObject::invokeMethod(&apm, "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
UNIT_TEST_EXPECT_FALSE(BlockUntil(idling, 3000)); //Processing a file that will be excluded should not cause assetprocessor manager to emit the onBecameIdle signal because its state should not change
UNIT_TEST_EXPECT_TRUE(processResults.size() == 0);
// ------------- Test querying asset status -------------------
{
absolutePath = tempPath.absoluteFilePath("subfolder2/folder/ship.tiff");

@ -1633,13 +1633,18 @@ namespace AssetProcessor
bool AssetProcessor::PlatformConfiguration::IsFileExcluded(QString fileName) const
{
for (const ExcludeAssetRecognizer& excludeRecognizer : m_excludeAssetRecognizers)
QString relPath, scanFolderName;
if (ConvertToRelativePath(fileName, relPath, scanFolderName))
{
if (excludeRecognizer.m_patternMatcher.MatchesPath(fileName.toUtf8().constData()))
for (const ExcludeAssetRecognizer& excludeRecognizer : m_excludeAssetRecognizers)
{
return true;
if (excludeRecognizer.m_patternMatcher.MatchesPath(relPath.toUtf8().constData()))
{
return true;
}
}
}
return false;
}

@ -50,10 +50,10 @@
"order": 6000
},
"Exclude HoldFiles": {
"pattern": ".*\\\\/Levels\\\\/.*_hold\\\\/.*"
"pattern": "(^|.+/)Levels/.*_hold(/.*)?$"
},
"Exclude TempFiles": {
"pattern": ".*\\\\/\\\\$tmp[0-9]*_.*"
"pattern": "(^|.+/)\\\\$tmp[0-9]*_.*"
},
"RC i_caf": {
"glob": "*.i_caf",

@ -74,10 +74,10 @@
"include": "test"
},
"Exclude HoldFiles": {
"pattern": ".*\\\\/Levels\\\\/.*_hold\\\\/.*"
"pattern": "(^|.+/)Levels/.*_hold(/.*)?$"
},
"Exclude TempFiles": {
"pattern": ".*\\\\/\\\\$tmp[0-9]*_.*"
"pattern": "(^|.+/)\\\\$tmp[0-9]*_.*"
},
"RC i_caf": {
"glob": "*.i_caf",

@ -19,6 +19,7 @@ namespace O3DE::ProjectManager
QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix);
return AZ::Success(QStringList{ ProjectCMakeCommand,
"-G", "Visual Studio 16 2019",
"-B", targetBuildPath,
"-S", m_projectInfo.m_path,
QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath),

@ -77,7 +77,7 @@ namespace O3DE::ProjectManager
if (vsWhereFile.exists() && vsWhereFile.isFile())
{
QStringList vsWhereBaseArguments = QStringList{"-version",
"16.9.2",
"[16.9.2,17)",
"-latest",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64"};
@ -106,10 +106,8 @@ namespace O3DE::ProjectManager
}
return AZ::Failure(QObject::tr("Visual Studio 2019 version 16.9.2 or higher not found.<br><br>"
"Visual Studio 2019 is required to build this project."
" Install any edition of <a href='https://visualstudio.microsoft.com/downloads/'>Visual Studio 2019</a>"
" or update to a newer version before proceeding to the next step."
" While installing configure Visual Studio with these <a href='https://o3de.org/docs/welcome-guide/setup/requirements/#visual-studio-configuration'>workloads</a>."));
"A compatible version of Visual Studio is required to build this project.<br>"
"Refer to the <a href='https://o3de.org/docs/welcome-guide/requirements/#microsoft-visual-studio'>Visual Studio requirements</a> for more information."));
}
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath)

@ -21,6 +21,7 @@
#include <QApplication>
#include <QDir>
#include <QMessageBox>
#include <QInputDialog>
namespace O3DE::ProjectManager
{
@ -111,6 +112,11 @@ namespace O3DE::ProjectManager
}
}
if (!RegisterEngine(interactive))
{
return false;
}
const AZ::CommandLine* commandLine = GetCommandLine();
AZ_Assert(commandLine, "Failed to get command line");
@ -165,6 +171,85 @@ namespace O3DE::ProjectManager
return m_entity != nullptr;
}
bool Application::RegisterEngine(bool interactive)
{
// get this engine's info
auto engineInfoOutcome = m_pythonBindings->GetEngineInfo();
if (!engineInfoOutcome)
{
if (interactive)
{
QMessageBox::critical(nullptr,
QObject::tr("Failed to get engine info"),
QObject::tr("A valid engine.json could not be found or loaded. "
"Please verify a valid engine.json file exists in %1")
.arg(GetEngineRoot()));
}
AZ_Error("Project Manager", false, "Failed to get engine info");
return false;
}
EngineInfo engineInfo = engineInfoOutcome.GetValue();
if (engineInfo.m_registered)
{
return true;
}
// check if an engine with this name is already registered and has a valid engine.json
auto existingEngineResult = m_pythonBindings->GetEngineInfo(engineInfo.m_name);
if (existingEngineResult)
{
if (!interactive)
{
AZ_Error("Project Manager", false, "An engine with the name %s is already registered with the path %s",
engineInfo.m_name.toUtf8().constData(), engineInfo.m_path.toUtf8().constData());
return false;
}
// get the updated engine name unless the user wants to cancel
bool okPressed = false;
const EngineInfo& otherEngineInfo = existingEngineResult.GetValue();
engineInfo.m_name = QInputDialog::getText(nullptr,
QObject::tr("Engine '%1' already registered").arg(engineInfo.m_name),
QObject::tr("An engine named '%1' is already registered.<br /><br />"
"<b>Current path</b><br />%2<br/><br />"
"<b>New path</b><br />%3<br /><br />"
"Press 'OK' to force registration, or provide a new engine name below.<br />"
"Alternatively, press `Cancel` to close the Project Manager and resolve the issue manually.")
.arg(engineInfo.m_name, otherEngineInfo.m_path, engineInfo.m_path),
QLineEdit::Normal,
engineInfo.m_name,
&okPressed);
if (!okPressed)
{
// user elected not to change the name or force registration
return false;
}
}
// always force register in case there is an engine registered in o3de_manifest.json, but
// the engine.json is missing or corrupt in which case GetEngineInfo() fails
constexpr bool forceRegistration = true;
auto registerOutcome = m_pythonBindings->SetEngineInfo(engineInfo, forceRegistration);
if (!registerOutcome)
{
if (interactive)
{
ProjectUtils::DisplayDetailedError(QObject::tr("Failed to register engine"), registerOutcome);
}
AZ_Error("Project Manager", false, "Failed to register engine %s : %s",
engineInfo.m_path.toUtf8().constData(), registerOutcome.GetError().first.c_str());
return false;
}
return true;
}
void Application::TearDown()
{
if (m_entity)

@ -34,6 +34,7 @@ namespace O3DE::ProjectManager
private:
bool InitLog(const char* logName);
bool RegisterEngine(bool interactive);
AZStd::unique_ptr<PythonBindings> m_pythonBindings;
QSharedPointer<QCoreApplication> m_app;

@ -25,13 +25,16 @@ namespace O3DE::ProjectManager
QString m_name;
QString m_thirdPartyPath;
// from o3de_manifest.json
QString m_path;
// from o3de_manifest.json
QString m_defaultProjectsFolder;
QString m_defaultGemsFolder;
QString m_defaultTemplatesFolder;
QString m_defaultRestrictedFolder;
bool m_registered = false;
bool IsValid() const;
};
} // namespace O3DE::ProjectManager

@ -11,6 +11,7 @@
#include <FormFolderBrowseEditWidget.h>
#include <PythonBindingsInterface.h>
#include <PathValidator.h>
#include <ProjectUtils.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <QVBoxLayout>
@ -114,10 +115,10 @@ namespace O3DE::ProjectManager
engineInfo.m_defaultGemsFolder = m_defaultGems->lineEdit()->text();
engineInfo.m_defaultTemplatesFolder = m_defaultProjectTemplates->lineEdit()->text();
bool result = PythonBindingsInterface::Get()->SetEngineInfo(engineInfo);
auto result = PythonBindingsInterface::Get()->SetEngineInfo(engineInfo);
if (!result)
{
QMessageBox::critical(this, tr("Engine Settings"), tr("Failed to save engine settings."));
ProjectUtils::DisplayDetailedError(tr("Failed to save engine settings"), result, this);
}
}
else

@ -14,6 +14,7 @@
#include <GemRepo/GemRepoInspector.h>
#include <PythonBindingsInterface.h>
#include <ProjectManagerDefs.h>
#include <ProjectUtils.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
@ -92,8 +93,7 @@ namespace O3DE::ProjectManager
return;
}
AZ::Outcome < void,
AZStd::pair<AZStd::string, AZStd::string>> addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
auto addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
if (addGemRepoResult.IsSuccess())
{
Reinit();
@ -102,20 +102,7 @@ namespace O3DE::ProjectManager
else
{
QString failureMessage = tr("Failed to add gem repo: %1.").arg(repoUri);
if (!addGemRepoResult.GetError().second.empty())
{
QMessageBox addRepoError;
addRepoError.setIcon(QMessageBox::Critical);
addRepoError.setWindowTitle(failureMessage);
addRepoError.setText(addGemRepoResult.GetError().first.c_str());
addRepoError.setDetailedText(addGemRepoResult.GetError().second.c_str());
addRepoError.exec();
}
else
{
QMessageBox::critical(this, failureMessage, addGemRepoResult.GetError().first.c_str());
}
ProjectUtils::DisplayDetailedError(failureMessage, addGemRepoResult, this);
AZ_Error("Project Manager", false, failureMessage.toUtf8());
}
}

@ -659,5 +659,24 @@ namespace O3DE::ProjectManager
return AZ::Success(QString(projectBuildPath.c_str()));
}
void DisplayDetailedError(const QString& title, const AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>>& outcome, QWidget* parent)
{
const AZStd::string& generalError = outcome.GetError().first;
const AZStd::string& detailedError = outcome.GetError().second;
if (!detailedError.empty())
{
QMessageBox errorDialog(parent);
errorDialog.setIcon(QMessageBox::Critical);
errorDialog.setWindowTitle(title);
errorDialog.setText(generalError.c_str());
errorDialog.setDetailedText(detailedError.c_str());
errorDialog.exec();
}
else
{
QMessageBox::critical(parent, title, generalError.c_str());
}
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager

@ -98,5 +98,14 @@ namespace O3DE::ProjectManager
*/
AZ::IO::FixedMaxPath GetEditorExecutablePath(const AZ::IO::PathView& projectPath);
/**
* Display a dialog with general and detailed sections for the given AZ::Outcome
* @param title Dialog title
* @param outcome The AZ::Outcome with general and detailed error messages
* @param parent Optional QWidget parent
*/
void DisplayDetailedError(const QString& title, const AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>>& outcome, QWidget* parent = nullptr);
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager

@ -312,6 +312,7 @@ namespace O3DE::ProjectManager
m_register = pybind11::module::import("o3de.register");
m_manifest = pybind11::module::import("o3de.manifest");
m_engineTemplate = pybind11::module::import("o3de.engine_template");
m_engineProperties = pybind11::module::import("o3de.engine_properties");
m_enableGemProject = pybind11::module::import("o3de.enable_gem");
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
@ -319,9 +320,6 @@ namespace O3DE::ProjectManager
m_repo = pybind11::module::import("o3de.repo");
m_pathlib = pybind11::module::import("pathlib");
// make sure the engine is registered
RegisterThisEngine();
m_pythonStarted = !PyErr_Occurred();
return m_pythonStarted;
}
@ -346,36 +344,6 @@ namespace O3DE::ProjectManager
return !PyErr_Occurred();
}
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));
if (enginePath.Compare(m_enginePath) == 0)
{
return;
}
}
}
auto result = m_register.attr("register")(QString_To_Py_Path(QString(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)
{
if (!Py_IsInitialized())
@ -407,16 +375,22 @@ namespace O3DE::ProjectManager
return ExecuteWithLockErrorHandling(executionCallback).IsSuccess();
}
AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo()
EngineInfo PythonBindings::EngineInfoFromPath(pybind11::handle enginePath)
{
EngineInfo engineInfo;
bool result = ExecuteWithLock([&] {
auto enginePath = m_manifest.attr("get_this_engine_path")();
try
{
auto engineData = m_manifest.attr("get_engine_json_data")(pybind11::none(), enginePath);
if (pybind11::isinstance<pybind11::dict>(engineData))
{
engineInfo.m_version = Py_To_String_Optional(engineData, "O3DEVersion", "0.0.0.0");
engineInfo.m_name = Py_To_String_Optional(engineData, "engine_name", "O3DE");
engineInfo.m_path = Py_To_String(enginePath);
}
auto o3deData = m_manifest.attr("load_o3de_manifest")();
if (pybind11::isinstance<pybind11::dict>(o3deData))
{
engineInfo.m_path = Py_To_String(enginePath);
auto defaultGemsFolder = m_manifest.attr("get_o3de_gems_folder")();
engineInfo.m_defaultGemsFolder = Py_To_String_Optional(o3deData, "default_gems_folder", Py_To_String(defaultGemsFolder));
@ -433,19 +407,36 @@ namespace O3DE::ProjectManager
engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData, "default_third_party_folder", Py_To_String(defaultThirdPartyFolder));
}
auto engineData = m_manifest.attr("get_engine_json_data")(pybind11::none(), enginePath);
if (pybind11::isinstance<pybind11::dict>(engineData))
// check if engine path is registered
auto allEngines = m_manifest.attr("get_engines")();
if (pybind11::isinstance<pybind11::list>(allEngines))
{
try
{
engineInfo.m_version = Py_To_String_Optional(engineData, "O3DEVersion", "0.0.0.0");
engineInfo.m_name = Py_To_String_Optional(engineData, "engine_name", "O3DE");
}
catch ([[maybe_unused]] const std::exception& e)
const AZ::IO::FixedMaxPath enginePathFixed(Py_To_String(enginePath));
for (auto engine : allEngines)
{
AZ_Warning("PythonBindings", false, "Failed to get EngineInfo from %s", Py_To_String(enginePath));
AZ::IO::FixedMaxPath otherEnginePath(Py_To_String(engine));
if (otherEnginePath.Compare(enginePathFixed) == 0)
{
engineInfo.m_registered = true;
break;
}
}
}
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("PythonBindings", false, "Failed to get EngineInfo from %s", Py_To_String(enginePath));
}
return engineInfo;
}
AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo()
{
EngineInfo engineInfo;
bool result = ExecuteWithLock([&] {
auto enginePath = m_manifest.attr("get_this_engine_path")();
engineInfo = EngineInfoFromPath(enginePath);
});
if (!result || !engineInfo.IsValid())
@ -458,10 +449,58 @@ namespace O3DE::ProjectManager
}
}
bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo)
AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo(const QString& engineName)
{
EngineInfo engineInfo;
bool result = ExecuteWithLock([&] {
auto registrationResult = m_register.attr("register")(
auto enginePathResult = m_manifest.attr("get_registered")(QString_To_Py_String(engineName));
// if a valid registered object is not found None is returned
if (!pybind11::isinstance<pybind11::none>(enginePathResult))
{
engineInfo = EngineInfoFromPath(enginePathResult);
// it is possible an engine is registered in o3de_manifest.json but the engine.json is
// missing or corrupt in which case we do not consider it a registered engine
}
});
if (!result || !engineInfo.IsValid())
{
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(engineInfo));
}
}
IPythonBindings::DetailedOutcome PythonBindings::SetEngineInfo(const EngineInfo& engineInfo, bool force)
{
bool registrationSuccess = false;
bool pythonSuccess = ExecuteWithLock([&] {
EngineInfo currentEngine = EngineInfoFromPath(QString_To_Py_Path(engineInfo.m_path));
// be kind to source control and avoid needlessly updating engine.json
if (currentEngine.IsValid() &&
(currentEngine.m_name.compare(engineInfo.m_name) != 0 || currentEngine.m_version.compare(engineInfo.m_version) != 0))
{
auto enginePropsResult = m_engineProperties.attr("edit_engine_props")(
QString_To_Py_Path(engineInfo.m_path),
pybind11::none(), // existing engine_name
QString_To_Py_String(engineInfo.m_name),
QString_To_Py_String(engineInfo.m_version)
);
if (enginePropsResult.cast<int>() != 0)
{
// do not proceed with registration
return;
}
}
auto result = m_register.attr("register")(
QString_To_Py_Path(engineInfo.m_path),
pybind11::none(), // project_path
pybind11::none(), // gem_path
@ -474,16 +513,22 @@ namespace O3DE::ProjectManager
QString_To_Py_Path(engineInfo.m_defaultGemsFolder),
QString_To_Py_Path(engineInfo.m_defaultTemplatesFolder),
pybind11::none(), // default_restricted_folder
QString_To_Py_Path(engineInfo.m_thirdPartyPath)
);
QString_To_Py_Path(engineInfo.m_thirdPartyPath),
pybind11::none(), // external_subdir_engine_path
pybind11::none(), // external_subdir_project_path
false, // remove
force
);
if (registrationResult.cast<int>() != 0)
{
result = false;
}
registrationSuccess = result.cast<int>() == 0;
});
return result;
if (pythonSuccess && registrationSuccess)
{
return AZ::Success();
}
return AZ::Failure<ErrorPair>(GetErrorPair());
}
AZ::Outcome<GemInfo> PythonBindings::GetGemInfo(const QString& path, const QString& projectPath)
@ -1064,7 +1109,7 @@ namespace O3DE::ProjectManager
return result && refreshResult;
}
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> PythonBindings::AddGemRepo(const QString& repoUri)
IPythonBindings::DetailedOutcome PythonBindings::AddGemRepo(const QString& repoUri)
{
bool registrationResult = false;
bool result = ExecuteWithLock(
@ -1080,7 +1125,7 @@ namespace O3DE::ProjectManager
if (!result || !registrationResult)
{
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(GetSimpleDetailedErrorPair());
return AZ::Failure<IPythonBindings::ErrorPair>(GetErrorPair());
}
return AZ::Success();
@ -1170,13 +1215,10 @@ namespace O3DE::ProjectManager
return gemRepoInfo;
}
//#define MOCK_GEM_REPO_INFO true
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> PythonBindings::GetAllGemRepoInfos()
{
QVector<GemRepoInfo> gemRepos;
#ifndef MOCK_GEM_REPO_INFO
auto result = ExecuteWithLockErrorHandling(
[&]
{
@ -1189,18 +1231,6 @@ namespace O3DE::ProjectManager
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
#else
GemRepoInfo mockJohnRepo("JohnCreates", "John Smith", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true);
mockJohnRepo.m_summary = "John's Summary. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitudin dapibus urna";
mockJohnRepo.m_repoUri = "https://github.com/o3de/o3de";
mockJohnRepo.m_additionalInfo = "John's additional info. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitu.";
gemRepos.push_back(mockJohnRepo);
GemRepoInfo mockJaneRepo("JanesGems", "Jane Doe", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false);
mockJaneRepo.m_summary = "Jane's Summary.";
mockJaneRepo.m_repoUri = "https://github.com/o3de/o3de.org";
gemRepos.push_back(mockJaneRepo);
#endif // MOCK_GEM_REPO_INFO
std::sort(gemRepos.begin(), gemRepos.end());
return AZ::Success(AZStd::move(gemRepos));
@ -1261,7 +1291,7 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemInfos));
}
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> PythonBindings::DownloadGem(
IPythonBindings::DetailedOutcome PythonBindings::DownloadGem(
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force)
{
// This process is currently limited to download a single gem at a time.
@ -1290,12 +1320,12 @@ namespace O3DE::ProjectManager
if (!result.IsSuccess())
{
AZStd::pair<AZStd::string, AZStd::string> pythonRunError(result.GetError(), result.GetError());
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(AZStd::move(pythonRunError));
IPythonBindings::ErrorPair pythonRunError(result.GetError(), result.GetError());
return AZ::Failure<IPythonBindings::ErrorPair>(AZStd::move(pythonRunError));
}
else if (!downloadSucceeded)
{
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(GetSimpleDetailedErrorPair());
return AZ::Failure<IPythonBindings::ErrorPair>(GetErrorPair());
}
return AZ::Success();
@ -1322,13 +1352,13 @@ namespace O3DE::ProjectManager
return result && updateAvaliableResult;
}
AZStd::pair<AZStd::string, AZStd::string> PythonBindings::GetSimpleDetailedErrorPair()
IPythonBindings::ErrorPair PythonBindings::GetErrorPair()
{
AZStd::string detailedString = m_pythonErrorStrings.size() == 1
? ""
: AZStd::accumulate(m_pythonErrorStrings.begin(), m_pythonErrorStrings.end(), AZStd::string(""));
return AZStd::pair<AZStd::string, AZStd::string>(m_pythonErrorStrings.front(), detailedString);
return IPythonBindings::ErrorPair(m_pythonErrorStrings.front(), detailedString);
}
void PythonBindings::AddErrorString(AZStd::string errorString)

@ -35,7 +35,8 @@ namespace O3DE::ProjectManager
// Engine
AZ::Outcome<EngineInfo> GetEngineInfo() override;
bool SetEngineInfo(const EngineInfo& engineInfo) override;
AZ::Outcome<EngineInfo> GetEngineInfo(const QString& engineName) override;
DetailedOutcome SetEngineInfo(const EngineInfo& engineInfo, bool force = false) override;
// Gem
AZ::Outcome<GemInfo> GetGemInfo(const QString& path, const QString& projectPath = {}) override;
@ -62,12 +63,12 @@ namespace O3DE::ProjectManager
// Gem Repos
AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) override;
bool RefreshAllGemRepos() override;
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> AddGemRepo(const QString& repoUri) override;
DetailedOutcome AddGemRepo(const QString& repoUri) override;
bool RemoveGemRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForAllRepos() override;
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> DownloadGem(
DetailedOutcome DownloadGem(
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) override;
void CancelDownload() override;
bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override;
@ -80,14 +81,14 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, AZStd::string> ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback);
bool ExecuteWithLock(AZStd::function<void()> executionCallback);
EngineInfo EngineInfoFromPath(pybind11::handle enginePath);
GemInfo GemInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
GemRepoInfo GetGemRepoInfo(pybind11::handle repoUri);
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
AZ::Outcome<void, AZStd::string> GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false);
bool RegisterThisEngine();
bool StopPython();
AZStd::pair<AZStd::string, AZStd::string> GetSimpleDetailedErrorPair();
IPythonBindings::ErrorPair GetErrorPair();
bool m_pythonStarted = false;
@ -96,6 +97,7 @@ namespace O3DE::ProjectManager
AZStd::recursive_mutex m_lock;
pybind11::handle m_engineTemplate;
pybind11::handle m_engineProperties;
pybind11::handle m_cmake;
pybind11::handle m_register;
pybind11::handle m_manifest;

@ -31,6 +31,10 @@ namespace O3DE::ProjectManager
IPythonBindings() = default;
virtual ~IPythonBindings() = default;
//! First string in pair is general error, second is detailed
using ErrorPair = AZStd::pair<AZStd::string, AZStd::string>;
using DetailedOutcome = AZ::Outcome<void, ErrorPair>;
/**
* Get whether Python was started or not. All Python functionality will fail if Python
* failed to start.
@ -49,17 +53,25 @@ namespace O3DE::ProjectManager
// Engine
/**
* Get info about the engine
* Get info about the current engine
* @return an outcome with EngineInfo on success
*/
virtual AZ::Outcome<EngineInfo> GetEngineInfo() = 0;
/**
* Get info about an engine by name
* @param engineName The name of the engine to get info about
* @return an outcome with EngineInfo on success
*/
virtual AZ::Outcome<EngineInfo> GetEngineInfo(const QString& engineName) = 0;
/**
* Set info about the engine
* @param force True to force registration even if an engine with the same name is already registered
* @param engineInfo an EngineInfo object
* @return a detailed error outcome on failure.
*/
virtual bool SetEngineInfo(const EngineInfo& engineInfo) = 0;
virtual DetailedOutcome SetEngineInfo(const EngineInfo& engineInfo, bool force = false) = 0;
// Gems
@ -202,7 +214,7 @@ namespace O3DE::ProjectManager
* @param repoUri the absolute filesystem path or url to the gem repo.
* @return an outcome with a pair of string error and detailed messages on failure.
*/
virtual AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> AddGemRepo(const QString& repoUri) = 0;
virtual DetailedOutcome AddGemRepo(const QString& repoUri) = 0;
/**
* Unregisters this gem repo with the current engine.
@ -237,7 +249,7 @@ namespace O3DE::ProjectManager
* @param force should we forcibly overwrite the old version of the gem.
* @return an outcome with a pair of string error and detailed messages on failure.
*/
virtual AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> DownloadGem(
virtual DetailedOutcome DownloadGem(
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) = 0;
/**

@ -7,7 +7,7 @@
"UUID": "{5D9ECB52-4CD9-4CB8-80E3-10CAE5EFB8A2}",
"Name": "AlbedoWithGenericAlpha",
"RGB_Weight": "CIEXYZ",
"PixelFormat": "ASTC_4x4",
"PixelFormat": "BC3",
"MipMapSetting": {
"MipGenType": "Box"
}

@ -79,6 +79,7 @@ namespace ImageProcessingAtom
};
bool IsASTCFormat(EPixelFormat fmt);
bool IsHDRFormat(EPixelFormat fmt);
} // namespace ImageProcessingAtom
namespace AZ

@ -620,6 +620,7 @@ namespace ImageProcessingAtom
{
PresetName emptyPreset;
//get file mask of this image file
AZStd::string fileMask = GetFileMask(imageFilePath);
@ -636,8 +637,17 @@ namespace ImageProcessingAtom
}
if (outPreset == emptyPreset)
{
outPreset = m_defaultPreset;
{
auto image = IImageObjectPtr(LoadImageFromFile(imageFilePath));
if (image->GetAlphaContent() == EAlphaContent::eAlphaContent_Absent
|| image->GetAlphaContent() == EAlphaContent::eAlphaContent_OnlyWhite)
{
outPreset = m_defaultPreset;
}
else
{
outPreset = m_defaultPresetAlpha;
}
}
return outPreset;

@ -78,7 +78,7 @@ namespace ImageProcessingAtom
return true;
}
astcenc_profile GetAstcProfile(bool isSrgb, EPixelFormat pixelFormat)
astcenc_profile GetAstcProfile(bool isSrgb, bool isHDR)
{
// select profile depends on LDR or HDR, SRGB or Linear
// ASTCENC_PRF_LDR
@ -86,8 +86,6 @@ namespace ImageProcessingAtom
// ASTCENC_PRF_HDR_RGB_LDR_A
// ASTCENC_PRF_HDR
auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
bool isHDR = formatInfo->eSampleType == ESampleType::eSampleType_Half || formatInfo->eSampleType == ESampleType::eSampleType_Float;
astcenc_profile profile;
if (isHDR)
{
@ -170,7 +168,7 @@ namespace ImageProcessingAtom
auto dstFormatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtDst);
const float quality = GetAstcCompressQuality(compressOption->compressQuality);
const astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), fmtSrc);
const astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), srcImage->HasImageFlags(EIF_HDR));
astcenc_config config;
astcenc_error status;
@ -182,10 +180,12 @@ namespace ImageProcessingAtom
// Create a context based on the configuration
astcenc_context* context;
AZ::u32 blockCount = ((srcImage->GetWidth(0)+ dstFormatInfo->blockWidth-1)/dstFormatInfo->blockWidth) * ((srcImage->GetHeight(0) + dstFormatInfo->blockHeight-1)/dstFormatInfo->blockHeight);
AZ::u32 threadCount = AZStd::min(AZStd::thread::hardware_concurrency(), blockCount);
AZ::u32 threadCount = AZStd::min(AZStd::thread::hardware_concurrency()/2, blockCount);
status = astcenc_context_alloc(&config, threadCount, &context);
AZ_Assert( status == ASTCENC_SUCCESS, "ERROR: Codec context alloc failed: %s\n", astcenc_get_error_string(status));
AZ::Job* currentJob = AZ::JobContext::GetGlobalContext()->GetJobManager().GetCurrentJob();
const astcenc_type dataType =GetAstcDataType(fmtSrc);
// Compress the image for each mips
@ -209,29 +209,65 @@ namespace ImageProcessingAtom
dstImage->GetImagePointer(mip, dstMem, dstPitch);
AZ::u32 dataSize = dstImage->GetMipBufSize(mip);
// Create jobs for each compression thread
auto completionJob = aznew AZ::JobCompletion();
for (AZ::u32 threadIdx = 0; threadIdx < threadCount; threadIdx++)
if (threadCount == 1)
{
astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, 0);
if (error != ASTCENC_SUCCESS)
{
status = error;
}
}
else
{
const auto jobLambda = [&status, context, &image, &swizzle, dstMem, dataSize, threadIdx]()
AZ::JobCompletion* completionJob = nullptr;
if (!currentJob)
{
completionJob = aznew AZ::JobCompletion();
}
// Create jobs for each compression thread
for (AZ::u32 threadIdx = 0; threadIdx < threadCount; threadIdx++)
{
const auto jobLambda = [&status, context, &image, &swizzle, dstMem, dataSize, threadIdx]()
{
astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, threadIdx);
if (error != ASTCENC_SUCCESS)
{
status = error;
}
};
AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes
// adds this job as child to current job if there is a current job
// otherwise adds it as a dependent for the complete job
if (currentJob)
{
currentJob->StartAsChild(simulationJob);
}
else
{
simulationJob->SetDependent(completionJob);
simulationJob->Start();
}
astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, threadIdx);
if (error != ASTCENC_SUCCESS)
{
status = error;
}
};
AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes
simulationJob->SetDependent(completionJob);
simulationJob->Start();
}
}
if (currentJob)
{
currentJob->WaitForChildren();
}
if (completionJob)
{
completionJob->StartAndWaitForCompletion();
delete completionJob;
completionJob = nullptr;
if (completionJob)
{
completionJob->StartAndWaitForCompletion();
delete completionJob;
completionJob = nullptr;
}
}
if (status != ASTCENC_SUCCESS)

@ -74,7 +74,7 @@ namespace ImageProcessingAtom
builderDescriptor.m_busId = azrtti_typeid<ImageBuilderWorker>();
builderDescriptor.m_createJobFunction = AZStd::bind(&ImageBuilderWorker::CreateJobs, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_processJobFunction = AZStd::bind(&ImageBuilderWorker::ProcessJob, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_version = 26; // [ATOM-15086]
builderDescriptor.m_version = 27; // [ATOM-16958]
builderDescriptor.m_analysisFingerprint = ImageProcessingAtom::BuilderSettingManager::Instance()->GetAnalysisFingerprint();
m_imageBuilder.BusConnect(builderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDescriptor);
@ -221,18 +221,19 @@ namespace ImageProcessingAtom
m_isShuttingDown = true;
}
PresetName GetImagePreset(const AZStd::string& filepath)
PresetName GetImagePreset(const AZStd::string& imageFileFullPath)
{
// first let preset from asset info
TextureSettings textureSettings;
StringOutcome output = TextureSettings::LoadTextureSetting(filepath, textureSettings);
AZStd::string settingFilePath = imageFileFullPath + TextureSettings::ExtensionName;
TextureSettings::LoadTextureSetting(settingFilePath, textureSettings);
if (!textureSettings.m_preset.IsEmpty())
{
return textureSettings.m_preset;
}
return BuilderSettingManager::Instance()->GetSuggestedPreset(filepath);
return BuilderSettingManager::Instance()->GetSuggestedPreset(imageFileFullPath);
}
void HandlePresetDependency(PresetName presetName, AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencyList)
@ -283,6 +284,10 @@ namespace ImageProcessingAtom
return;
}
// Full path of the image file
AZStd::string fullPath;
AzFramework::StringFunc::Path::Join(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true, true);
// Get the extension of the file
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.c_str(), ext, false);
@ -305,13 +310,12 @@ namespace ImageProcessingAtom
// add source dependency for .assetinfo file
AssetBuilderSDK::SourceFileDependency sourceFileDependency;
sourceFileDependency.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute;
sourceFileDependency.m_sourceFileDependencyPath = request.m_sourceFile;
AZ::StringFunc::Path::ReplaceExtension(sourceFileDependency.m_sourceFileDependencyPath, TextureSettings::ExtensionName);
sourceFileDependency.m_sourceFileDependencyPath = fullPath + TextureSettings::ExtensionName;
response.m_sourceFileDependencyList.push_back(sourceFileDependency);
// add source dependencies for .preset files
// Get the preset for this file
auto presetName = GetImagePreset(request.m_sourceFile);
// Get the preset for this file
auto presetName = GetImagePreset(fullPath.c_str());
HandlePresetDependency(presetName, response.m_sourceFileDependencyList);
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;

@ -8,6 +8,7 @@
#include <ImageLoader/ImageLoaders.h>
#include <Processing/ImageFlags.h>
#include <Atom/ImageProcessing/ImageObject.h>
// warning C4251: class QT_Type needs to have dll-interface to be used by clients of class 'QT_Type'
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option")
@ -26,21 +27,31 @@ namespace ImageProcessingAtom
return nullptr;
}
IImageObject* loadedImage = nullptr;
if (TIFFLoader::IsExtensionSupported(ext.toUtf8()))
{
return TIFFLoader::LoadImageFromTIFF(filename);
loadedImage = TIFFLoader::LoadImageFromTIFF(filename);
}
else if (DdsLoader::IsExtensionSupported(ext.toUtf8()))
{
return DdsLoader::LoadImageFromFile(filename);
loadedImage = DdsLoader::LoadImageFromFile(filename);
}
else if (QtImageLoader::IsExtensionSupported(ext.toUtf8()))
{
return QtImageLoader::LoadImageFromFile(filename);
loadedImage = QtImageLoader::LoadImageFromFile(filename);
}
else if (ExrLoader::IsExtensionSupported(ext.toUtf8()))
{
return ExrLoader::LoadImageFromFile(filename);
loadedImage = ExrLoader::LoadImageFromFile(filename);
}
if (loadedImage)
{
if (IsHDRFormat(loadedImage->GetPixelFormat()))
{
loadedImage->AddImageFlags(EIF_HDR);
}
return loadedImage;
}
AZ_Warning("ImageProcessing", false, "No proper image loader to load file: %s", filename.c_str());

@ -193,7 +193,7 @@ namespace ImageProcessingAtom
}
m_image->Get()->Swizzle(swizzle.c_str());
if (!m_input->m_presetSetting.m_discardAlpha)
if (m_input->m_presetSetting.m_discardAlpha)
{
m_alphaContent = EAlphaContent::eAlphaContent_Absent;
}

@ -19,7 +19,7 @@ namespace ImageProcessingAtom
const static AZ::u32 EIF_Decal = 0x4; // this is usually set through the preset
const static AZ::u32 EIF_Greyscale = 0x8; // hint for the engine (e.g. greyscale light beams can be applied to shadow mask), can be for DXT1 because compression artfacts don't count as color
const static AZ::u32 EIF_SupressEngineReduce = 0x10; // info for the engine: don't reduce texture resolution on this texture
const static AZ::u32 EIF_UNUSED_BIT = 0x40; // Free to use
const static AZ::u32 EIF_HDR = 0x40; // the image contains HDR data
const static AZ::u32 EIF_AttachedAlpha = 0x400; // deprecated: info for the engine: it's a texture with attached alpha channel
const static AZ::u32 EIF_SRGBRead = 0x800; // info for the engine: if gamma corrected rendering is on, this texture requires SRGBRead (it's not stored in linear)
const static AZ::u32 EIF_DontResize = 0x8000; // info for the engine: for dds textures that shouldn't be resized

@ -52,6 +52,24 @@ namespace ImageProcessingAtom
return false;
}
bool IsHDRFormat(EPixelFormat fmt)
{
switch (fmt)
{
case ePixelFormat_BC6UH:
case ePixelFormat_R9G9B9E5:
case ePixelFormat_R32G32B32A32F:
case ePixelFormat_R32G32F:
case ePixelFormat_R32F:
case ePixelFormat_R16G16B16A16F:
case ePixelFormat_R16G16F:
case ePixelFormat_R16F:
return true;
default:
return false;
}
}
PixelFormatInfo::PixelFormatInfo(
uint32_t a_bitsPerPixel,
uint32_t a_Channels,

@ -16,7 +16,9 @@ namespace AZ
AZ_CLASS_ALLOCATOR_IMPL(JsonMaterialAssignmentSerializer, AZ::SystemAllocator, 0);
JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Load(
void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
void* outputValue,
[[maybe_unused]] const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
@ -62,6 +64,7 @@ namespace AZ
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::Data::AssetData>>(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))
{
@ -78,7 +81,10 @@ namespace AZ
}
JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Store(
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, [[maybe_unused]] const Uuid& valueTypeId,
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
[[maybe_unused]] const Uuid& valueTypeId,
JsonSerializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
@ -138,9 +144,9 @@ namespace AZ
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::Data::AssetData>>(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))
StoreAny<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(propertyValue, outputPropertyValue, context, result))
{
outputPropertyValueContainer.AddMember(
rapidjson::Value::StringRefType(propertyName.GetCStr()), outputPropertyValue,
@ -164,7 +170,9 @@ namespace AZ
template<typename T>
bool JsonMaterialAssignmentSerializer::LoadAny(
AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context,
AZStd::any& propertyValue,
const rapidjson::Value& inputPropertyValue,
AZ::JsonDeserializerContext& context,
AZ::JsonSerializationResult::ResultCode& result)
{
if (inputPropertyValue.IsObject() && inputPropertyValue.HasMember("Value") && inputPropertyValue.HasMember("$type"))
@ -187,7 +195,9 @@ namespace AZ
template<typename T>
bool JsonMaterialAssignmentSerializer::StoreAny(
const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context,
const AZStd::any& propertyValue,
rapidjson::Value& outputPropertyValue,
AZ::JsonSerializerContext& context,
AZ::JsonSerializationResult::ResultCode& result)
{
if (propertyValue.is<T>())
@ -199,7 +209,7 @@ namespace AZ
result.Combine(StoreTypeId(typeValue, azrtti_typeid<T>(), context));
outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator());
T value = AZStd::any_cast<T>(propertyValue);
const T& value = AZStd::any_cast<T>(propertyValue);
result.Combine(
ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid<T>(), context));
return true;

@ -25,21 +25,31 @@ namespace AZ
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(
void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
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,
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,
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,
const AZStd::any& propertyValue,
rapidjson::Value& outputPropertyValue,
AZ::JsonSerializerContext& context,
AZ::JsonSerializationResult::ResultCode& result);
};
} // namespace Render

@ -204,23 +204,19 @@ namespace AZ
{
ShaderResourceGroup& group = static_cast<ShaderResourceGroup&>(groupBase);
auto& device = static_cast<Device&>(GetDevice());
group.m_compiledDataIndex = (group.m_compiledDataIndex + 1) % RHI::Limits::Device::FrameCountMax;
if (!groupData.IsAnyResourceTypeUpdated())
{
return RHI::ResultCode::Success;
}
if (m_constantBufferSize &&
groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ConstantDataMask)))
group.m_compiledDataIndex = (group.m_compiledDataIndex + 1) % RHI::Limits::Device::FrameCountMax;
if (m_constantBufferSize)
{
memcpy(group.GetCompiledData().m_cpuConstantAddress, groupData.GetConstantData().data(), groupData.GetConstantData().size());
}
if (m_viewsDescriptorTableSize &&
groupData.IsResourceTypeEnabledForCompilation(
static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ImageViewMask) |
static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::BufferViewMask)))
if (m_viewsDescriptorTableSize)
{
//Lazy initialization for cbv/srv/uav Descriptor Tables
if (!group.m_viewsDescriptorTable.IsValid())
@ -245,17 +241,12 @@ namespace AZ
UpdateViewsDescriptorTable(descriptorTable, groupData);
}
if (m_unboundedArrayCount &&
groupData.IsResourceTypeEnabledForCompilation(
static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ImageViewUnboundedArrayMask) |
static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::BufferViewUnboundedArrayMask)))
if (m_unboundedArrayCount)
{
UpdateUnboundedArrayDescriptorTables(group, groupData);
}
if (m_samplersDescriptorTableSize &&
groupData.IsResourceTypeEnabledForCompilation(
static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::SamplerMask)))
if (m_samplersDescriptorTableSize)
{
const DescriptorTable descriptorTable(
group.m_samplersDescriptorTable.GetOffset() + group.m_compiledDataIndex * m_samplersDescriptorTableSize,

@ -61,10 +61,19 @@ namespace AZ
NSError* error = nil;
id<MTLLibrary> lib = nil;
bool loadFromByteCode = false;
// MacOS Big Sur (11.16.x) has issue loading some shader's byte code when GPUCapture(Metal) is on.
// Only enable it for Monterey (12.x)
if(@available(iOS 14.0, macOS 12.0, *))
{
loadFromByteCode = true;
}
const uint8_t* shaderByteCode = reinterpret_cast<const uint8_t*>(shaderFunction->GetByteCode().data());
const int byteCodeLength = shaderFunction->GetByteCode().size();
if(byteCodeLength > 0 )
if(byteCodeLength > 0 && loadFromByteCode)
{
dispatch_data_t dispatchByteCodeData = dispatch_data_create(shaderByteCode, byteCodeLength, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
lib = [mtlDevice newLibraryWithData:dispatchByteCodeData error:&error];
@ -74,7 +83,7 @@ namespace AZ
//In case byte code was not generated try to create the lib with source code
MTLCompileOptions* compileOptions = [MTLCompileOptions alloc];
compileOptions.fastMathEnabled = YES;
compileOptions.languageVersion = MTLLanguageVersion2_0;
compileOptions.languageVersion = MTLLanguageVersion2_2;
lib = [mtlDevice newLibraryWithSource:source
options:compileOptions
error:&error];

@ -6,7 +6,6 @@
*
*/
#include <AzCore/Debug/EventTrace.h>
#include <RHI/ArgumentBuffer.h>
#include <RHI/Conversions.h>
#include <RHI/Device.h>
@ -62,57 +61,48 @@ namespace AZ
RHI::ResultCode ShaderResourceGroupPool::CompileGroupInternal(RHI::ShaderResourceGroup& groupBase, const RHI::ShaderResourceGroupData& groupData)
{
ShaderResourceGroup& group = static_cast<ShaderResourceGroup&>(groupBase);
group.UpdateCompiledDataIndex();
if (!groupData.IsAnyResourceTypeUpdated())
{
return RHI::ResultCode::Success;
}
group.UpdateCompiledDataIndex();
ArgumentBuffer& argBuffer = *group.m_compiledArgBuffers[group.m_compiledDataIndex];
argBuffer.ClearResourceTracking();
auto constantData = groupData.GetConstantData();
if (!constantData.empty() && groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ConstantDataMask)))
if (!constantData.empty())
{
argBuffer.UpdateConstantBufferViews(groupData.GetConstantData());
}
const RHI::ShaderResourceGroupLayout* layout = groupData.GetLayout();
uint32_t shaderInputIndex = 0;
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ImageViewMask)))
for (const RHI::ShaderInputImageDescriptor& shaderInputImage : layout->GetShaderInputListForImages())
{
for (const RHI::ShaderInputImageDescriptor& shaderInputImage : layout->GetShaderInputListForImages())
{
const RHI::ShaderInputImageIndex imageInputIndex(shaderInputIndex);
AZStd::array_view<RHI::ConstPtr<RHI::ImageView>> imageViews = groupData.GetImageViewArray(imageInputIndex);
argBuffer.UpdateImageViews(shaderInputImage, imageInputIndex, imageViews);
++shaderInputIndex;
}
const RHI::ShaderInputImageIndex imageInputIndex(shaderInputIndex);
AZStd::array_view<RHI::ConstPtr<RHI::ImageView>> imageViews = groupData.GetImageViewArray(imageInputIndex);
argBuffer.UpdateImageViews(shaderInputImage, imageInputIndex, imageViews);
++shaderInputIndex;
}
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::SamplerMask)))
shaderInputIndex = 0;
for (const RHI::ShaderInputSamplerDescriptor& shaderInputSampler : layout->GetShaderInputListForSamplers())
{
shaderInputIndex = 0;
for (const RHI::ShaderInputSamplerDescriptor& shaderInputSampler : layout->GetShaderInputListForSamplers())
{
const RHI::ShaderInputSamplerIndex samplerInputIndex(shaderInputIndex);
AZStd::array_view<RHI::SamplerState> samplerStates = groupData.GetSamplerArray(samplerInputIndex);
argBuffer.UpdateSamplers(shaderInputSampler, samplerInputIndex, samplerStates);
++shaderInputIndex;
}
const RHI::ShaderInputSamplerIndex samplerInputIndex(shaderInputIndex);
AZStd::array_view<RHI::SamplerState> samplerStates = groupData.GetSamplerArray(samplerInputIndex);
argBuffer.UpdateSamplers(shaderInputSampler, samplerInputIndex, samplerStates);
++shaderInputIndex;
}
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::BufferViewMask)))
shaderInputIndex = 0;
for (const RHI::ShaderInputBufferDescriptor& shaderInputBuffer : layout->GetShaderInputListForBuffers())
{
shaderInputIndex = 0;
for (const RHI::ShaderInputBufferDescriptor& shaderInputBuffer : layout->GetShaderInputListForBuffers())
{
const RHI::ShaderInputBufferIndex bufferInputIndex(shaderInputIndex);
AZStd::array_view<RHI::ConstPtr<RHI::BufferView>> bufferViews = groupData.GetBufferViewArray(bufferInputIndex);
argBuffer.UpdateBufferViews(shaderInputBuffer, bufferInputIndex, bufferViews);
++shaderInputIndex;
}
const RHI::ShaderInputBufferIndex bufferInputIndex(shaderInputIndex);
AZStd::array_view<RHI::ConstPtr<RHI::BufferView>> bufferViews = groupData.GetBufferViewArray(bufferInputIndex);
argBuffer.UpdateBufferViews(shaderInputBuffer, bufferInputIndex, bufferViews);
++shaderInputIndex;
}
return RHI::ResultCode::Success;

@ -6,6 +6,7 @@
*
*/
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/fixed_vector.h>
#include <AzCore/std/parallel/lock.h>
#include <RHI/Buffer.h>
#include <RHI/BufferView.h>
@ -688,8 +689,8 @@ namespace AZ
if (interval != InvalidInterval)
{
uint32_t numBuffers = interval.m_max - interval.m_min + 1;
AZStd::vector<VkBuffer> nativeBuffers(numBuffers, VK_NULL_HANDLE);
AZStd::vector<VkDeviceSize> offsets(numBuffers, 0);
AZStd::fixed_vector<VkBuffer, RHI::Limits::Pipeline::StreamCountMax> nativeBuffers(numBuffers, VK_NULL_HANDLE);
AZStd::fixed_vector<VkDeviceSize, RHI::Limits::Pipeline::StreamCountMax> offsets(numBuffers, 0);
for (uint32_t i = 0; i < numBuffers; ++i)
{
const RHI::StreamBufferView& bufferView = streams[i + interval.m_min];

@ -104,13 +104,13 @@ namespace AZ
RHI::ResultCode ShaderResourceGroupPool::CompileGroupInternal(RHI::ShaderResourceGroup& groupBase, const RHI::ShaderResourceGroupData& groupData)
{
auto& group = static_cast<ShaderResourceGroup&>(groupBase);
group.UpdateCompiledDataIndex(m_currentIteration);
if (!groupData.IsAnyResourceTypeUpdated())
{
return RHI::ResultCode::Success;
}
group.UpdateCompiledDataIndex(m_currentIteration);
DescriptorSet& descriptorSet = *group.m_compiledData[group.GetCompileDataIndex()];
const RHI::ShaderResourceGroupLayout* layout = groupData.GetLayout();

@ -33,11 +33,13 @@ namespace AtomToolsFramework
{
if (value.Is<AZ::Data::Asset<AZ::RPI::ImageAsset>>())
{
const AZ::Data::Asset<AZ::RPI::ImageAsset>& imageAsset = value.GetValue<AZ::Data::Asset<AZ::RPI::ImageAsset>>();
return AZStd::any(AZ::Data::Asset<AZ::RPI::StreamingImageAsset>(
imageAsset.GetId(),
azrtti_typeid<AZ::RPI::StreamingImageAsset>(),
imageAsset.GetHint()));
const auto& imageAsset = value.GetValue<AZ::Data::Asset<AZ::RPI::ImageAsset>>();
return AZStd::any(AZ::Data::Asset<AZ::RPI::StreamingImageAsset>(imageAsset.GetId(), azrtti_typeid<AZ::RPI::StreamingImageAsset>(), imageAsset.GetHint()));
}
else if (value.Is<AZ::Data::Instance<AZ::RPI::Image>>())
{
const auto& image = value.GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
return AZStd::any(AZ::Data::Asset<AZ::RPI::StreamingImageAsset>(image->GetAssetId(), azrtti_typeid<AZ::RPI::StreamingImageAsset>()));
}
return AZ::RPI::MaterialPropertyValue::ToAny(value);

@ -6,7 +6,7 @@
// Sample Gems, Block source folders
// ------------------------------------------------------------------------------
"Exclude Work In Progress Folders": {
"pattern": ".*\\\\/.[Ss]rc\\\\/.*"
"pattern": "(^|.+/).[Ss]rc(/.*)?$"
}
}
}

@ -1362,8 +1362,9 @@ namespace AZ::AtomBridge
// if 2d draw need to project pos to screen first
AzFramework::TextDrawParameters params;
AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
const auto dpiScaleFactor = viewportContext->GetDpiScalingFactor();
params.m_drawViewportId = viewportContext->GetId(); // get the viewport ID so default viewport works
params.m_position = AZ::Vector3(x, y, 1.0f);
params.m_position = AZ::Vector3(x * dpiScaleFactor, y * dpiScaleFactor, 1.0f);
params.m_color = m_rendState.m_color;
params.m_scale = AZ::Vector2(size);
params.m_hAlign = center ? AzFramework::TextHorizontalAlignment::Center : AzFramework::TextHorizontalAlignment::Left; //! Horizontal text alignment

@ -104,6 +104,7 @@ namespace AZ
MaterialComponentController::MaterialComponentController(const MaterialComponentConfig& config)
: m_configuration(config)
{
ConvertAssetsForSerialization();
}
void MaterialComponentController::Activate(EntityId entityId)
@ -135,6 +136,7 @@ namespace AZ
void MaterialComponentController::SetConfiguration(const MaterialComponentConfig& config)
{
m_configuration = config;
ConvertAssetsForSerialization();
}
const MaterialComponentConfig& MaterialComponentController::GetConfiguration() const
@ -338,6 +340,7 @@ namespace AZ
// before LoadMaterials() is called [LYN-2249]
auto temp = m_configuration.m_materials;
m_configuration.m_materials = materials;
ConvertAssetsForSerialization();
LoadMaterials();
}
@ -489,6 +492,7 @@ namespace AZ
auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
const bool wasEmpty = materialAssignment.m_propertyOverrides.empty();
materialAssignment.m_propertyOverrides[AZ::Name(propertyName)] = value;
ConvertAssetsForSerialization();
if (materialAssignment.RequiresLoading())
{
@ -586,6 +590,7 @@ namespace AZ
auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
const bool wasEmpty = materialAssignment.m_propertyOverrides.empty();
materialAssignment.m_propertyOverrides = propertyOverrides;
ConvertAssetsForSerialization();
if (materialAssignment.RequiresLoading())
{
@ -667,5 +672,33 @@ namespace AZ
TickBus::Handler::BusConnect();
}
}
void MaterialComponentController::ConvertAssetsForSerialization()
{
for (auto& materialAssignmentPair : m_configuration.m_materials)
{
MaterialAssignment& materialAssignment = materialAssignmentPair.second;
for (auto& propertyPair : materialAssignment.m_propertyOverrides)
{
auto& value = propertyPair.second;
if (value.is<AZ::Data::Asset<AZ::Data::AssetData>>())
{
value = AZStd::any_cast<AZ::Data::Asset<AZ::Data::AssetData>>(value).GetId();
}
else if (value.is<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>())
{
value = AZStd::any_cast<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(value).GetId();
}
else if (value.is<AZ::Data::Asset<AZ::RPI::ImageAsset>>())
{
value = AZStd::any_cast<AZ::Data::Asset<AZ::RPI::ImageAsset>>(value).GetId();
}
else if (value.is<AZ::Data::Instance<AZ::RPI::Image>>())
{
value = AZStd::any_cast<AZ::Data::Instance<AZ::RPI::Image>>(value)->GetAssetId();
}
}
}
}
} // namespace Render
} // namespace AZ

@ -99,6 +99,11 @@ namespace AZ
//! Queue material instance recreation notifiucations until tick
void QueueMaterialUpdateNotification();
//! Converts property overrides storing image asset references into asset IDs. This addresses a problem where image property
//! overrides are lost during prefab serialization and patching. This suboptimal function will be removed once the underlying
//! problem is resolved.
void ConvertAssetsForSerialization();
EntityId m_entityId;
MaterialComponentConfig m_configuration;
AZStd::unordered_set<MaterialAssignmentId> m_materialsWithDirtyProperties;

@ -11,6 +11,9 @@
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <Atom/RPI.Public/ViewportContextManager.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Base.h>
namespace AZ
{
@ -199,7 +202,14 @@ namespace AZ
}
const char* LutAttachment = "LutOutput";
const AZStd::vector<AZStd::string> LutGenerationPassHierarchy{ "LutGenerationPass" };
auto renderPipelineName = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get()
->GetDefaultViewportContext()
->GetCurrentPipeline()
->GetId();
const AZStd::vector<AZStd::string> LutGenerationPassHierarchy{
renderPipelineName.GetCStr(),
"LutGenerationPass"
};
char resolvedOutputFilePath[AZ_MAX_PATH_LEN] = { 0 };
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(m_currentTiffFilePath.c_str(), resolvedOutputFilePath, AZ_MAX_PATH_LEN);

@ -3,10 +3,10 @@
"AssetProcessor": {
"Settings": {
"Exclude AudioProject": {
"pattern": ".*\\\\/Sounds\\\\/.+_project.*"
"pattern": "(^|.+/)Sounds/.+_project.*"
},
"Exclude AudioTemp": {
"pattern": ".*\\\\/Sounds\\\\/.+\\\\.(txt|xml|dat)"
"pattern": "(^|.+/)Sounds/.+\\\\.(txt|xml|dat)"
},
"RC audio": {
"pattern": ".*\\\\.(wav|pcm)",

@ -10,6 +10,7 @@
"RC blastmaterial": {
"glob": "*.blastmaterial",
"params": "copy",
"critical": true,
"productAssetType": "{55F38C86-0767-4E7F-830A-A4BF624BE4DA}"
},
"RC blastconfiguration": {

@ -91,17 +91,19 @@ namespace GraphCanvas
}
void GeneralNodeTitleComponent::SetTitle(const AZStd::string& title)
void GeneralNodeTitleComponent::SetDetails(const AZStd::string& title, const AZStd::string& subtitle)
{
m_title.SetFallback(title);
m_title = title;
m_subTitle = subtitle;
if (m_generalNodeTitleWidget)
{
m_generalNodeTitleWidget->SetTitle(title);
m_generalNodeTitleWidget->SetDetails(title, subtitle);
}
}
void GeneralNodeTitleComponent::SetTranslationKeyedTitle(const TranslationKeyedString& title)
void GeneralNodeTitleComponent::SetTitle(const AZStd::string& title)
{
m_title = title;
@ -113,20 +115,10 @@ namespace GraphCanvas
AZStd::string GeneralNodeTitleComponent::GetTitle() const
{
return m_title.GetDisplayString();
return m_title;
}
void GeneralNodeTitleComponent::SetSubTitle(const AZStd::string& subtitle)
{
m_subTitle.SetFallback(subtitle);
if (m_generalNodeTitleWidget)
{
m_generalNodeTitleWidget->SetSubTitle(subtitle);
}
}
void GeneralNodeTitleComponent::SetTranslationKeyedSubTitle(const TranslationKeyedString& subtitle)
{
m_subTitle = subtitle;
@ -138,7 +130,7 @@ namespace GraphCanvas
AZStd::string GeneralNodeTitleComponent::GetSubTitle() const
{
return m_subTitle.GetDisplayString();
return m_subTitle;
}
QGraphicsWidget* GeneralNodeTitleComponent::GetGraphicsWidget()
@ -270,7 +262,23 @@ namespace GraphCanvas
SceneNotificationBus::Handler::BusDisconnect();
}
void GeneralNodeTitleGraphicsWidget::SetTitle(const TranslationKeyedString& title)
void GeneralNodeTitleGraphicsWidget::SetDetails(const AZStd::string& title, const AZStd::string& subtitle)
{
bool updateLayout = false;
if (m_titleWidget)
{
m_titleWidget->SetLabel(title);
updateLayout = true;
}
if (m_subTitleWidget)
{
m_subTitleWidget->SetLabel(subtitle);
updateLayout = true;
}
}
void GeneralNodeTitleGraphicsWidget::SetTitle(const AZStd::string& title)
{
if (m_titleWidget)
{
@ -279,7 +287,7 @@ namespace GraphCanvas
}
}
void GeneralNodeTitleGraphicsWidget::SetSubTitle(const TranslationKeyedString& subtitle)
void GeneralNodeTitleGraphicsWidget::SetSubTitle(const AZStd::string& subtitle)
{
if (m_subTitleWidget)
{

@ -19,7 +19,6 @@
#include <GraphCanvas/Components/StyleBus.h>
#include <GraphCanvas/Components/VisualBus.h>
#include <GraphCanvas/Types/EntitySaveData.h>
#include <GraphCanvas/Types/TranslationTypes.h>
#include <Widgets/GraphCanvasLabel.h>
namespace GraphCanvas
@ -68,12 +67,11 @@ namespace GraphCanvas
////
// NodeTitleRequestBus
void SetDetails(const AZStd::string& title, const AZStd::string& subtitle) override;
void SetTitle(const AZStd::string& title) override;
void SetTranslationKeyedTitle(const TranslationKeyedString& title) override;
AZStd::string GetTitle() const override;
void SetSubTitle(const AZStd::string& subtitle) override;
void SetTranslationKeyedSubTitle(const TranslationKeyedString& subtitle) override;
AZStd::string GetSubTitle() const override;
QGraphicsWidget* GetGraphicsWidget() override;
@ -96,8 +94,8 @@ namespace GraphCanvas
private:
GeneralNodeTitleComponent(const GeneralNodeTitleComponent&) = delete;
TranslationKeyedString m_title;
TranslationKeyedString m_subTitle;
AZStd::string m_title;
AZStd::string m_subTitle;
AZStd::string m_basePalette;
@ -123,9 +121,10 @@ namespace GraphCanvas
void Activate();
void Deactivate();
void SetTitle(const TranslationKeyedString& title);
void SetSubTitle(const TranslationKeyedString& subtitle);
void SetDetails(const AZStd::string& title, const AZStd::string& subtitle);
void SetTitle(const AZStd::string& title);
void SetSubTitle(const AZStd::string& subtitle);
void SetPaletteOverride(AZStd::string_view paletteOverride);
void SetPaletteOverride(const AZ::Uuid& uuid);

@ -1007,20 +1007,16 @@ namespace GraphCanvas
{
if (!configuration.m_name.empty())
{
cloneConfiguration->m_name.Clear();
cloneConfiguration->m_name.SetFallback(configuration.m_name);
cloneConfiguration->m_name = configuration.m_name;
}
else
{
AZStd::string nodeTitle;
NodeTitleRequestBus::EventResult(nodeTitle, configuration.m_targetEndpoint.GetNodeId(), &NodeTitleRequests::GetTitle);
AZStd::string displayName = AZStd::string::format("%s:%s", nodeTitle.c_str(), cloneConfiguration->m_name.GetDisplayString().c_str());
AZStd::string displayName = AZStd::string::format("%s:%s", nodeTitle.c_str(), cloneConfiguration->m_name.c_str());
// Gain some context. Lost the ability to refresh the strings.
// Should be fixable once we get an actual use case for this setup.
cloneConfiguration->m_name.Clear();
cloneConfiguration->m_name.SetFallback(displayName);
cloneConfiguration->m_name = displayName;
}
AZ::Entity* slotEntity = nullptr;

@ -315,12 +315,6 @@ namespace GraphCanvas
NodeNotificationBus::Event(GetEntityId(), &NodeNotifications::OnTooltipChanged, m_configuration.GetTooltip());
}
void NodeComponent::SetTranslationKeyedTooltip(const TranslationKeyedString& tooltip)
{
m_configuration.SetTooltip(tooltip.GetDisplayString());
NodeNotificationBus::Event(GetEntityId(), &NodeNotifications::OnTooltipChanged, m_configuration.GetTooltip());
}
void NodeComponent::AddSlot(const AZ::EntityId& slotId)
{
AZ_Assert(slotId.IsValid(), "Slot entity (ID: %s) is not valid!", slotId.ToString().data());

@ -106,7 +106,6 @@ namespace GraphCanvas
// NodeRequestBus
void SetTooltip(const AZStd::string& tooltip) override;
void SetTranslationKeyedTooltip(const TranslationKeyedString& tooltip) override;
const AZStd::string GetTooltip() const override { return m_configuration.GetTooltip(); }
void SetShowInOutliner(bool showInOutliner) override { m_configuration.SetShowInOutliner(showInOutliner); }

@ -669,6 +669,7 @@ namespace GraphCanvas
{
GeometryNotificationBus::Handler::BusDisconnect();
SceneNotificationBus::Handler::BusDisconnect();
AZ::SystemTickBus::Handler::BusDisconnect();
}
void GestureSceneHelper::TrackElement(const AZ::EntityId& elementId)

@ -337,12 +337,9 @@ namespace GraphCanvas
{
m_connectionType = slotRequests->GetConnectionType();
TranslationKeyedString slotName = slotRequests->GetTranslationKeyedName();
m_slotText->SetLabel(slotRequests->GetName());
m_slotText->SetLabel(slotName);
TranslationKeyedString toolTip = slotRequests->GetTranslationKeyedTooltip();
OnTooltipChanged(toolTip);
OnTooltipChanged(slotRequests->GetTooltip());
const SlotConfiguration& configuration = slotRequests->GetSlotConfiguration();
@ -393,12 +390,12 @@ namespace GraphCanvas
AZ::SystemTickBus::Handler::BusConnect();
}
void DataSlotLayout::OnNameChanged(const TranslationKeyedString& name)
void DataSlotLayout::OnNameChanged(const AZStd::string& name)
{
m_slotText->SetLabel(name);
}
void DataSlotLayout::OnTooltipChanged(const TranslationKeyedString& tooltip)
void DataSlotLayout::OnTooltipChanged(const AZStd::string& tooltip)
{
AZ::Uuid dataType;
DataSlotRequestBus::EventResult(dataType, m_owner.GetEntityId(), &DataSlotRequests::GetDataTypeId);
@ -406,7 +403,7 @@ namespace GraphCanvas
AZStd::string typeString;
GraphModelRequestBus::EventResult(typeString, GetSceneId(), &GraphModelRequests::GetDataTypeString, dataType);
AZStd::string displayText = tooltip.GetDisplayString();
AZStd::string displayText = tooltip;
if (!typeString.empty())
{
@ -486,7 +483,7 @@ namespace GraphCanvas
if (!iconPath.empty())
{
m_textDecoration = new GraphCanvasLabel();
m_textDecoration->SetLabel(iconPath, "", "");
m_textDecoration->SetLabel(iconPath);
m_textDecoration->setToolTip(toolTip.c_str());
ApplyTextStyle(m_textDecoration);

@ -120,8 +120,8 @@ namespace GraphCanvas
// SlotNotificationBus
void OnRegisteredToNode(const AZ::EntityId& nodeId) override;
void OnNameChanged(const TranslationKeyedString&) override;
void OnTooltipChanged(const TranslationKeyedString&) override;
void OnNameChanged(const AZStd::string&) override;
void OnTooltipChanged(const AZStd::string&) override;
////
// StyleNotificationBus

@ -58,13 +58,8 @@ namespace GraphCanvas
{
m_connectionType = slotRequests->GetConnectionType();
TranslationKeyedString slotName = slotRequests->GetTranslationKeyedName();
m_slotText->SetLabel(slotName);
TranslationKeyedString toolTip = slotRequests->GetTranslationKeyedTooltip();
OnTooltipChanged(toolTip);
m_slotText->SetLabel(slotRequests->GetName());
OnTooltipChanged(slotRequests->GetTooltip());
const SlotConfiguration& configuration = slotRequests->GetSlotConfiguration();
@ -88,17 +83,15 @@ namespace GraphCanvas
OnStyleChanged();
}
void ExecutionSlotLayout::OnNameChanged(const TranslationKeyedString& name)
void ExecutionSlotLayout::OnNameChanged(const AZStd::string& name)
{
m_slotText->SetLabel(name);
}
void ExecutionSlotLayout::OnTooltipChanged(const TranslationKeyedString& tooltip)
void ExecutionSlotLayout::OnTooltipChanged(const AZStd::string& tooltip)
{
AZStd::string displayText = tooltip.GetDisplayString();
m_slotConnectionPin->setToolTip(displayText.c_str());
m_slotText->setToolTip(displayText.c_str());
m_slotConnectionPin->setToolTip(tooltip.c_str());
m_slotText->setToolTip(tooltip.c_str());
}
void ExecutionSlotLayout::OnStyleChanged()
@ -132,7 +125,7 @@ namespace GraphCanvas
if (!textDecoration.empty())
{
m_textDecoration = new GraphCanvasLabel();
m_textDecoration->SetLabel(textDecoration, "", "");
m_textDecoration->SetLabel(textDecoration);
m_textDecoration->setToolTip(toolTip.c_str());
ApplyTextStyle(m_textDecoration);

@ -46,8 +46,8 @@ namespace GraphCanvas
// SlotNotificationBus
void OnRegisteredToNode(const AZ::EntityId& nodeId) override;
void OnNameChanged(const TranslationKeyedString& name) override;
void OnTooltipChanged(const TranslationKeyedString& tooltip) override;
void OnNameChanged(const AZStd::string& name) override;
void OnTooltipChanged(const AZStd::string& tooltip) override;
////
// StyleNotificationBus

@ -119,13 +119,13 @@ namespace GraphCanvas
{
SlotRequestBus::EventResult(m_connectionType, m_owner.GetEntityId(), &SlotRequests::GetConnectionType);
TranslationKeyedString slotName;
SlotRequestBus::EventResult(slotName, m_owner.GetEntityId(), &SlotRequests::GetTranslationKeyedName);
AZStd::string slotName;
SlotRequestBus::EventResult(slotName, m_owner.GetEntityId(), &SlotRequests::GetName);
m_slotText->SetLabel(slotName);
TranslationKeyedString toolTip;
SlotRequestBus::EventResult(toolTip, m_owner.GetEntityId(), &SlotRequests::GetTranslationKeyedTooltip);
AZStd::string toolTip;
SlotRequestBus::EventResult(toolTip, m_owner.GetEntityId(), &SlotRequests::GetTooltip);
OnTooltipChanged(toolTip);
@ -151,17 +151,15 @@ namespace GraphCanvas
OnStyleChanged();
}
void ExtenderSlotLayout::OnNameChanged(const TranslationKeyedString& name)
void ExtenderSlotLayout::OnNameChanged(const AZStd::string& name)
{
m_slotText->SetLabel(name);
}
void ExtenderSlotLayout::OnTooltipChanged(const TranslationKeyedString& tooltip)
void ExtenderSlotLayout::OnTooltipChanged(const AZStd::string& tooltip)
{
AZStd::string displayText = tooltip.GetDisplayString();
m_slotConnectionPin->setToolTip(displayText.c_str());
m_slotText->setToolTip(displayText.c_str());
m_slotConnectionPin->setToolTip(tooltip.c_str());
m_slotText->setToolTip(tooltip.c_str());
}
void ExtenderSlotLayout::OnStyleChanged()

@ -48,8 +48,8 @@ namespace GraphCanvas
// SlotNotificationBus
void OnRegisteredToNode(const AZ::EntityId& nodeId) override;
void OnNameChanged(const TranslationKeyedString& name) override;
void OnTooltipChanged(const TranslationKeyedString& tooltip) override;
void OnNameChanged(const AZStd::string& name) override;
void OnTooltipChanged(const AZStd::string& tooltip) override;
////
// StyleNotificationBus

@ -90,10 +90,10 @@ namespace GraphCanvas
TryAndSetupSlot();
}
void PropertySlotLayout::OnTooltipChanged(const TranslationKeyedString& tooltip)
void PropertySlotLayout::OnTooltipChanged(const AZStd::string& tooltip)
{
m_slotText->setToolTip(Tools::qStringFromUtf8(tooltip.GetDisplayString()));
m_nodePropertyDisplay->setToolTip(Tools::qStringFromUtf8(tooltip.GetDisplayString()));
m_slotText->setToolTip(Tools::qStringFromUtf8(tooltip));
m_nodePropertyDisplay->setToolTip(Tools::qStringFromUtf8(tooltip));
}
void PropertySlotLayout::OnStyleChanged()

@ -49,7 +49,7 @@ namespace GraphCanvas
// SlotNotificationBus
void OnRegisteredToNode(const AZ::EntityId& nodeId) override;
void OnTooltipChanged(const TranslationKeyedString& tooltip) override;
void OnTooltipChanged(const AZStd::string& tooltip) override;
////
// StyleNotificationBus

@ -91,14 +91,6 @@ namespace GraphCanvas
void SlotComponent::Activate()
{
SetTranslationKeyedName(m_slotConfiguration.m_name);
// Default tooltip.
if (m_slotConfiguration.m_tooltip.empty())
{
SetTranslationKeyedTooltip(m_slotConfiguration.m_name);
}
SlotRequestBus::Handler::BusConnect(GetEntityId());
SceneMemberRequestBus::Handler::BusConnect(GetEntityId());
}
@ -171,24 +163,6 @@ namespace GraphCanvas
}
void SlotComponent::SetName(const AZStd::string& name)
{
if (name == m_slotConfiguration.m_name.GetDisplayString())
{
return;
}
m_slotConfiguration.m_name.SetFallback(name);
// Default tooltip.
if (m_slotConfiguration.m_tooltip.empty())
{
m_slotConfiguration.m_tooltip = m_slotConfiguration.m_name;
}
SlotNotificationBus::Event(GetEntityId(), &SlotNotifications::OnNameChanged, m_slotConfiguration.m_name);
}
void SlotComponent::SetTranslationKeyedName(const TranslationKeyedString& name)
{
if (name == m_slotConfiguration.m_name)
{
@ -206,25 +180,22 @@ namespace GraphCanvas
SlotNotificationBus::Event(GetEntityId(), &SlotNotifications::OnNameChanged, m_slotConfiguration.m_name);
}
void SlotComponent::SetTooltip(const AZStd::string& tooltip)
void SlotComponent::SetDetails(const AZStd::string& name, const AZStd::string& tooltip)
{
if (tooltip == m_slotConfiguration.m_tooltip.GetDisplayString())
if (name != m_slotConfiguration.m_name)
{
return;
m_slotConfiguration.m_name = name;
SlotNotificationBus::Event(GetEntityId(), &SlotNotifications::OnNameChanged, m_slotConfiguration.m_name);
}
m_slotConfiguration.m_tooltip.SetFallback(tooltip);
// Default tooltip.
if (m_slotConfiguration.m_tooltip.empty())
if (tooltip != m_slotConfiguration.m_tooltip)
{
m_slotConfiguration.m_tooltip = m_slotConfiguration.m_name;
m_slotConfiguration.m_tooltip = tooltip;
SlotNotificationBus::Event(GetEntityId(), &SlotNotifications::OnTooltipChanged, m_slotConfiguration.m_tooltip);
}
SlotNotificationBus::Event(GetEntityId(), &SlotNotifications::OnTooltipChanged, m_slotConfiguration.m_tooltip);
}
void SlotComponent::SetTranslationKeyedTooltip(const TranslationKeyedString& tooltip)
void SlotComponent::SetTooltip(const AZStd::string& tooltip)
{
if (tooltip == m_slotConfiguration.m_tooltip)
{
@ -521,8 +492,8 @@ namespace GraphCanvas
{
slotConfiguration.m_connectionType = GetConnectionType();
slotConfiguration.m_name = GetTranslationKeyedName();
slotConfiguration.m_tooltip = GetTranslationKeyedTooltip();
slotConfiguration.m_name = m_slotConfiguration.m_name;
slotConfiguration.m_tooltip = m_slotConfiguration.m_tooltip;
slotConfiguration.m_slotGroup = GetSlotGroup();
}

@ -74,18 +74,14 @@ namespace GraphCanvas
Endpoint GetEndpoint() const override;
const AZStd::string GetName() const override { return m_slotConfiguration.m_name.GetDisplayString(); }
const AZStd::string GetName() const override { return m_slotConfiguration.m_name; }
void SetName(const AZStd::string& name) override;
TranslationKeyedString GetTranslationKeyedName() const override { return m_slotConfiguration.m_name; }
void SetTranslationKeyedName(const TranslationKeyedString&) override;
void SetDetails(const AZStd::string& name, const AZStd::string& tooltip) override;
const AZStd::string GetTooltip() const override { return m_slotConfiguration.m_tooltip.GetDisplayString(); }
const AZStd::string GetTooltip() const override { return m_slotConfiguration.m_tooltip; }
void SetTooltip(const AZStd::string& tooltip) override;
TranslationKeyedString GetTranslationKeyedTooltip() const override { return m_slotConfiguration.m_tooltip; }
void SetTranslationKeyedTooltip(const TranslationKeyedString&) override;
void DisplayProposedConnection(const AZ::EntityId& connectionId, const Endpoint& endpoint) override;
void RemoveProposedConnection(const AZ::EntityId& connectionId, const Endpoint& endpoint) override;

@ -58,7 +58,6 @@
#include <GraphCanvas/Types/ConstructPresets.h>
#include <GraphCanvas/Types/EntitySaveData.h>
#include <GraphCanvas/Types/TranslationTypes.h>
#include <GraphCanvas/Widgets/GraphCanvasEditor/GraphCanvasAssetEditorMainWindow.h>
#include <GraphCanvas/Widgets/GraphCanvasMimeEvent.h>
@ -140,7 +139,6 @@ namespace GraphCanvas
Styling::DefaultSelector::Reflect(serializeContext);
Styling::CompoundSelector::Reflect(serializeContext);
Styling::NestedSelector::Reflect(serializeContext);
TranslationKeyedString::Reflect(serializeContext);
Styling::Style::Reflect(serializeContext);
AssetEditorUserSettings::Reflect(serializeContext);
}
@ -218,6 +216,9 @@ namespace GraphCanvas
AzToolsFramework::ToolsAssetSystemBus::Broadcast(&AzToolsFramework::ToolsAssetSystemRequests::RegisterSourceAssetType, azrtti_typeid<TranslationAsset>(), TranslationAsset::GetFileFilter());
m_translationAssetWorker.Activate();
m_assetHandler = AZStd::make_unique<TranslationAssetHandler>();
m_assetHandler->Register();
}
}
@ -376,15 +377,27 @@ namespace GraphCanvas
// Find any TranslationAsset files that may have translation database key/values
AZ::Data::AssetCatalogRequests::AssetEnumerationCB collectAssetsCb = [this](const AZ::Data::AssetId assetId, const AZ::Data::AssetInfo& assetInfo)
{
const auto assetType = azrtti_typeid<TranslationAsset>();
if (assetInfo.m_assetType == assetType)
if (AZ::StringFunc::EndsWith(assetInfo.m_relativePath, ".names", false))
{
m_translationAssets.push_back(assetId);
}
};
m_translationAssets.clear();
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets, nullptr, collectAssetsCb, postEnumerateCb);
}
void GraphCanvasSystemComponent::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, assetId);
if (assetInfo.m_assetType == azrtti_typeid<TranslationAsset>())
{
GraphCanvas::TranslationRequestBus::Broadcast(&GraphCanvas::TranslationRequests::Restore);
}
}
void GraphCanvasSystemComponent::UnregisterAssetHandler()
{
if (m_assetHandler)
@ -405,7 +418,7 @@ namespace GraphCanvas
for (const AZ::Data::AssetId& assetId : m_translationAssets)
{
AZ::Data::AssetBus::MultiHandler::BusConnect(assetId);
AZ::Data::AssetManager::Instance().GetAsset<TranslationAsset>(assetId, AZ::Data::AssetLoadBehavior::Default);
AZ::Data::AssetManager::Instance().GetAsset<TranslationAsset>(assetId, AZ::Data::AssetLoadBehavior::Default);
}
}
}

@ -77,7 +77,10 @@ namespace GraphCanvas
AZ::EntityId CreateVirtualChild(const AZ::EntityId& real, const AZStd::string& virtualChildElement) const override;
////
// AzFramework::AssetCatalogEventBus::Handler
void OnCatalogLoaded(const char* /*catalogFile*/) override;
void OnCatalogAssetChanged(const AZ::Data::AssetId&) override;
////
AZStd::unique_ptr<TranslationAssetHandler> m_assetHandler;

@ -195,7 +195,7 @@ namespace GraphCanvas
}
else
{
AZ_Error("TranslationAsset", false, "Serialization of the TranslationFormat failed for: %s", asset.GetHint().c_str());
AZ_Warning("TranslationAsset", false, "Serialization of the TranslationFormat failed for: %s", asset.GetHint().c_str());
}
}
}

@ -59,14 +59,14 @@ namespace GraphCanvas
//!
//! Requirements:
//! - Must have a top level array called "entries"
//! - Must provide a "key" element for any entry added
//! - Must provide a "base" element for any entry added
//!
//! Example:
//!
//! {
//! "entries": [
//! {
//! "key": "Globals",
//! "base": "Globals",
//! "details": {
//! "name": "My Name",
//! "tooltip": "My Tooltip"
@ -90,21 +90,21 @@ namespace GraphCanvas
//! Globals.details.somearray.0.name
//! Globals.details.somearray.1.name
//!
//! There is one important aspect however, if an element in an array has a "key" value, the value of this key
//! There is one important aspect however, if an element in an array has a "base" value, the value of this key
//! will replace the index. This is useful when the index and/or ordering of an entry is not relevant or may
//! change.
//!
//! "somearray": [ {
//! "name": "First one"
//! "key": "a_key"
//! "base": "a_key"
//! }, {
//! "name": "Second one",
//! "key": "b_key"
//! "base": "b_key"
//! } ]
//!
//! Globals.details.somearray.0.key == "a_key"
//! Globals.details.somearray.0.base == "a_key"
//! Globals.details.somearray.0.name == "First one"
//! Globals.details.somearray.1.key == "b_key"
//! Globals.details.somearray.1.base == "b_key"
//! Globals.details.somearray.1.name == "Second one"
//!
class TranslationAssetHandler

@ -30,16 +30,7 @@ namespace GraphCanvas
void TranslationAssetWorker::Activate()
{
// Use AssetCatalog service to register ScriptCanvas asset type and extension
AZ::Data::AssetType assetType(azrtti_typeid<TranslationAsset>());
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddAssetType, assetType);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::EnableCatalogForAsset, assetType);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, TranslationAsset::GetFileFilter());
m_assetHandler = AZStd::make_unique<TranslationAssetHandler>();
if (!AZ::Data::AssetManager::Instance().GetHandler(assetType))
{
AZ::Data::AssetManager::Instance().RegisterHandler(m_assetHandler.get(), assetType);
}
AssetBuilderSDK::AssetBuilderCommandBus::Handler::BusConnect(GetUUID());
}

@ -10,8 +10,10 @@
#include "TranslationAsset.h"
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
namespace GraphCanvas
{
namespace Translation
@ -88,10 +90,16 @@ namespace GraphCanvas
static AZStd::string Sanitize(const AZStd::string& text)
{
AZStd::string result = text;
AZ::StringFunc::Replace(result, "*", "x");
AZ::StringFunc::Replace(result, "(", "_");
AZ::StringFunc::Replace(result, ")", "_");
AZ::StringFunc::Replace(result, "{", "_");
AZ::StringFunc::Replace(result, "}", "_");
AZ::StringFunc::Replace(result, ":", "_");
AZ::StringFunc::Replace(result, "<", "_");
AZ::StringFunc::Replace(result, ",", "_");
AZ::StringFunc::Replace(result, ">", " ");
AZ::StringFunc::Replace(result, "/", "");
AZ::StringFunc::Strip(result, " ");
AZ::StringFunc::Path::Normalize(result);
return result;
@ -117,32 +125,32 @@ namespace GraphCanvas
virtual bool HasKey(const AZStd::string& /*key*/) { return false; }
//! Returns the text value for a given key
virtual const char* Get(const AZStd::string& /*key*/) { return nullptr; }
virtual bool Get(const AZStd::string& /*key*/, AZStd::string& /*value*/) { return false; }
struct Details
{
AZStd::string Name;
AZStd::string Tooltip;
AZStd::string Category;
AZStd::string Subtitle;
AZStd::string m_name;
AZStd::string m_tooltip;
AZStd::string m_category;
AZStd::string m_subtitle;
bool Valid = false;
bool m_valid = false;
Details() = default;
Details(const Details& rhs)
{
Name = rhs.Name;
Tooltip = rhs.Tooltip;
Subtitle = rhs.Subtitle;
Category = rhs.Category;
Valid = rhs.Valid;
m_name = rhs.m_name;
m_tooltip = rhs.m_tooltip;
m_category = rhs.m_category;
m_subtitle = rhs.m_subtitle;
m_valid = rhs.m_valid;
}
Details(const char* name, const char* tooltip, const char* subtitle, const char* category)
: Name(name), Tooltip(tooltip), Subtitle(subtitle), Category(category)
: m_name(name), m_tooltip(tooltip), m_subtitle(subtitle), m_category(category)
{
Valid = !Name.empty();
m_valid = !m_name.empty();
}
};
@ -150,7 +158,7 @@ namespace GraphCanvas
virtual bool Add(const TranslationFormat& /*translationFormat*/) { return false; }
//! Get the details associated with a given key (assumes they are within a "details" object)
virtual Details GetDetails(const AZStd::string& /*key*/) { return Details(); }
virtual Details GetDetails(const AZStd::string& /*key*/, const Details& /*fallbackDetails*/) { return Details(); }
//! Generates the source JSON assets for all reflected elements
virtual void GenerateSourceAssets() {}

@ -104,35 +104,49 @@ namespace GraphCanvas
return m_database.find(key) != m_database.end();
}
GraphCanvas::TranslationRequests::Details TranslationDatabase::GetDetails(const AZStd::string& key)
GraphCanvas::TranslationRequests::Details TranslationDatabase::GetDetails(const AZStd::string& key, const Details& fallbackDetails)
{
const char* name = Get(key + ".name");
const char* tooltip = Get(key + ".tooltip");
const char* subtitle = Get(key + ".subtitle");
const char* category = Get(key + ".category");
Details details;
if (!Get(key + ".name", details.m_name))
{
details.m_name = fallbackDetails.m_name;
}
static bool s_traceMissingItems = true;
if (s_traceMissingItems)
if (!Get(key + ".tooltip", details.m_tooltip))
{
AZ_TracePrintf("GraphCanvas", AZStd::string::format("Value (name) not found for key: %s", key.c_str()).c_str());
AZ_TracePrintf("GraphCanvas", AZStd::string::format("Value (tooltip) not found for key: %s", key.c_str()).c_str());
AZ_TracePrintf("GraphCanvas", AZStd::string::format("Value (subtitle) not found for key: %s", key.c_str()).c_str());
AZ_TracePrintf("GraphCanvas", AZStd::string::format("Value (category) not found for key: %s", key.c_str()).c_str());
details.m_tooltip = fallbackDetails.m_tooltip;
}
return Details(name ? name : "", tooltip ? tooltip : "", subtitle ? subtitle : "", category ? category : "");
if (!Get(key + ".subtitle", details.m_subtitle))
{
details.m_subtitle = fallbackDetails.m_subtitle;
}
if (!Get(key + ".category", details.m_category))
{
details.m_category = fallbackDetails.m_category;
}
return details;
}
const char* TranslationDatabase::Get(const AZStd::string& key)
bool TranslationDatabase::Get(const AZStd::string& key, AZStd::string& value)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutex);
if (m_database.find(key) != m_database.end())
{
return m_database[key].c_str();
value = m_database[key];
return true;
}
static bool s_traceMissingItems = false;
if (s_traceMissingItems)
{
AZ_TracePrintf("GraphCanvas", AZStd::string::format("Value not found for key: %s", key.c_str()).c_str());
}
return "";
return false;
}
bool TranslationDatabase::Add(const TranslationFormat& format)

@ -43,9 +43,9 @@ namespace GraphCanvas
bool HasKey(const AZStd::string& key) override;
TranslationRequests::Details GetDetails(const AZStd::string& key) override;
TranslationRequests::Details GetDetails(const AZStd::string& key, const Details& value) override;
const char* Get(const AZStd::string& key) override;
bool Get(const AZStd::string& key, AZStd::string& value) override;
bool Add(const TranslationFormat& format) override;

@ -11,17 +11,6 @@
namespace GraphCanvas
{
namespace Schema
{
namespace Field
{
static constexpr char key[] = "key";
static constexpr char context[] = "context";
static constexpr char variant[] = "variant";
static constexpr char entries[] = "entries";
}
}
AZ_CLASS_ALLOCATOR_IMPL(TranslationFormatSerializer, AZ::SystemAllocator, 0);
void AddEntryToDatabase(const AZStd::string& baseKey, const AZStd::string& name, const rapidjson::Value& it, TranslationFormat* translationFormat)
@ -35,8 +24,10 @@ namespace GraphCanvas
}
else
{
AZStd::string existingValue = translationFormat->m_database[finalKey.c_str()];
// There is a name collision
AZStd::string error = AZStd::string::format("Unable to store key: %s with value: %s because that key already exists", finalKey.c_str(), it.GetString());
AZStd::string error = AZStd::string::format("Unable to store key: %s with value: %s because that key already exists with value: %s (proposed: %s)", finalKey.c_str(), it.GetString(), existingValue.c_str(), it.GetString());
AZ_Error("TranslationSerializer", false, error.c_str());
}
}
@ -74,8 +65,7 @@ namespace GraphCanvas
const rapidjson::Value& array = it;
for (rapidjson::SizeType i = 0; i < array.Size(); ++i)
{
// so, here, I need to go in and if there is a "key" member within the object, then I need to use that,
// if there isn't, I can use the %d
// if there is a "base" member within the object, then use it, otherwise use the index
if (array[i].IsObject())
{
if (array[i].HasMember(Schema::Field::key))
@ -156,7 +146,7 @@ namespace GraphCanvas
AZStd::string baseKey = contextStr;
if (keyStr.empty())
{
AZ_Error("TranslationDatabase", false, "Every entry in the Translation data must have a key: %s", baseKey.c_str());
AZ_Warning("TranslationDatabase", false, "Every entry in the Translation data must have a key: %s", baseKey.c_str());
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported, "Every entry in the Translation data must have a key");
}

@ -23,4 +23,15 @@ namespace GraphCanvas
AZ::JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue,
const void* defaultValue, const AZ::Uuid& valueTypeId, AZ::JsonSerializerContext& context) override;
};
namespace Schema
{
namespace Field
{
inline constexpr char key[] = "base";
inline constexpr char context[] = "context";
inline constexpr char variant[] = "variant";
inline constexpr char entries[] = "entries";
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save