Merge remote-tracking branch 'upstream/stabilization/2110' into Atom/santorac/MaterialEditorHandlesMissingTextures

This commit is contained in:
santorac
2021-11-16 10:06:28 -08:00
143 changed files with 1014 additions and 1749 deletions
+1 -1
View File
@@ -125,7 +125,7 @@
</size>
</property>
<property name="text">
<string>General Availability</string>
<string>Stable 21.11</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
@@ -199,41 +199,6 @@ namespace AzAssetBrowserRequestHandlerPrivate
}
}
}
// Helper utility - determines if the thing being dragged is a FBX from the scene import pipeline
// This is important to differentiate.
// when someone drags a MTL file directly into the viewport, even from a FBX, we want to spawn it as a decal
// but when someone drags a FBX that contains MTL files, we want only to spawn the meshes.
// so we have to specifically differentiate here between the mimeData type that contains the source as the root
// (dragging the fbx file itself)
// and one which contains the actual product at its root.
bool IsDragOfFBX(const QMimeData* mimeData)
{
AZStd::vector<AssetBrowserEntry*> entries;
if (!AssetBrowserEntry::FromMimeData(mimeData, entries))
{
// if mimedata does not even contain entries, no point in proceeding.
return false;
}
for (auto entry : entries)
{
if (entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Source)
{
continue;
}
// this is a source file. Is it the filetype we're looking for?
if (SourceAssetBrowserEntry* source = azrtti_cast<SourceAssetBrowserEntry*>(entry))
{
if (AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false))
{
return true;
}
}
}
return false;
}
}
AzAssetBrowserRequestHandler::AzAssetBrowserRequestHandler()
+2
View File
@@ -4181,6 +4181,8 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[])
"\nThis could be because of incorrectly configured components, or missing required gems."
"\nSee other errors for more details.");
AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEvents::NotifyEditorInitialized);
if (didCryEditStart)
{
app->EnableOnIdle();
@@ -9,10 +9,6 @@
#define CRYINCLUDE_EDITOR_MATERIAL_IEDITORMATERIALMANAGER_H
#pragma once
#define MATERIAL_FILE_EXT ".mtl"
#define DCC_MATERIAL_FILE_EXT ".dccmtl"
#define MATERIALS_PATH "materials/"
#include <Include/IBaseLibraryManager.h>
#include <IMaterial.h>
@@ -18,7 +18,6 @@
#include <LmbrCentral/Rendering/LensFlareAsset.h>
#include <LmbrCentral/Rendering/MeshAsset.h>
#include <LmbrCentral/Rendering/MaterialAsset.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/TypeInfo.h>
@@ -136,14 +135,6 @@ AssetCatalogModel::AssetCatalogModel(QObject* parent)
}
}
// Special cases for SimpleAssets. If these get full-fledged AssetData types, these cases can be removed.
QString textureExtensions = LmbrCentral::TextureAsset::GetFileFilter();
m_extensionToAssetType.insert(AZStd::make_pair(textureExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::TextureAsset>::Uuid() }));
QString materialExtensions = LmbrCentral::MaterialAsset::GetFileFilter();
m_extensionToAssetType.insert(AZStd::make_pair(materialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::MaterialAsset>::Uuid() }));
QString dccMaterialExtensions = LmbrCentral::DccMaterialAsset::GetFileFilter();
m_extensionToAssetType.insert(AZStd::make_pair(dccMaterialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::DccMaterialAsset>::Uuid() }));
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
+1 -1
View File
@@ -103,7 +103,7 @@
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>General Availability</string>
<string>Stable 21.11</string>
</property>
</widget>
</item>
@@ -1677,9 +1677,13 @@ namespace AZ
// they will trigger a ReleaseAsset call sometime after the AssetManager has begun to shut down, which can lead to
// race conditions.
// Make sure the streamer request is removed first before the asset is released
// If the asset is released first it could lead to a race condition where another thread starts loading the asset
// again and attempts to add a new streamer request with the same ID before the old one has been removed, causing
// that load request to fail
RemoveActiveStreamerRequest(assetId);
weakAsset = {};
loadingAsset.Reset();
RemoveActiveStreamerRequest(assetId);
};
auto&& [deadline, priority] = GetEffectiveDeadlineAndPriority(*handler, asset.GetType(), loadParams);
@@ -56,11 +56,12 @@ namespace AZ
int numberOfWorkerThreads = m_numberOfWorkerThreads;
if (numberOfWorkerThreads <= 0) // spawn default number of threads
{
#if (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS;
#else
uint32_t scaledHardwareThreads = Threading::CalcNumWorkerThreads(cl_jobThreadsConcurrencyRatio, cl_jobThreadsMinNumber, cl_jobThreadsNumReserved);
numberOfWorkerThreads = AZ::GetMin(static_cast<unsigned int>(desc.m_workerThreads.capacity()), scaledHardwareThreads);
#if (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
numberOfWorkerThreads = AZ::GetMin(numberOfWorkerThreads, AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS);
#endif // (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
#endif // (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
}
threadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
@@ -104,6 +104,8 @@ namespace AZ
->HandlesType<AZStd::variant>();
jsonContext->Serializer<JsonOptionalSerializer>()
->HandlesType<AZStd::optional>();
jsonContext->Serializer<JsonBitsetSerializer>()
->HandlesType<AZStd::bitset>();
MathReflect(jsonContext);
}
@@ -15,6 +15,7 @@ namespace AZ
AZ_CLASS_ALLOCATOR_IMPL(JsonAnySerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonVariantSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonOptionalSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonBitsetSerializer, SystemAllocator, 0);
JsonSerializationResult::Result JsonUnsupportedTypesSerializer::Load(void*, const Uuid&, const rapidjson::Value&,
JsonDeserializerContext& context)
@@ -49,4 +50,10 @@ namespace AZ
return "The Json Serialization doesn't support AZStd::optional by design. No JSON format has yet been found that wasn't deemed too "
"complex or overly verbose.";
}
AZStd::string_view JsonBitsetSerializer::GetMessage() const
{
return "The Json Serialization doesn't support AZStd::bitset by design. No JSON format has yet been found that is content creator "
"friendly i.e., easy to comprehend the intent.";
}
} // namespace AZ
@@ -65,4 +65,14 @@ namespace AZ
protected:
AZStd::string_view GetMessage() const override;
};
class JsonBitsetSerializer : public JsonUnsupportedTypesSerializer
{
public:
AZ_RTTI(JsonBitsetSerializer, "{10CE969D-D69E-4B3F-8593-069736F8F705}", JsonUnsupportedTypesSerializer);
AZ_CLASS_ALLOCATOR_DECL;
protected:
AZStd::string_view GetMessage() const override;
};
} // namespace AZ
@@ -30,8 +30,13 @@ namespace AZ
if (Interface<TaskGraphActiveInterface>::Get() == nullptr)
{
#if (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
const uint32_t numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS;
#else
const uint32_t numberOfWorkerThreads = Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved);
#endif // (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
Interface<TaskGraphActiveInterface>::Register(this); // small window that another thread can try to use taskgraph between this line and the set instance.
m_taskExecutor = aznew TaskExecutor(Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved));
m_taskExecutor = aznew TaskExecutor(numberOfWorkerThreads);
TaskExecutor::SetInstance(m_taskExecutor);
}
}
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 0
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -75,7 +75,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 1
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 1
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -76,7 +76,6 @@
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
@@ -652,7 +652,7 @@ namespace UnitTest
threads.emplace_back([this, &threadCount, &cv, assetUuid]() {
bool checkLoaded = true;
for (int i = 0; i < 5000; i++)
for (int i = 0; i < 1000; i++)
{
Asset<AssetWithAssetReference> asset1 =
m_testAssetManager->GetAsset(assetUuid, azrtti_typeid<AssetWithAssetReference>(), AZ::Data::AssetLoadBehavior::PreLoad);
@@ -678,7 +678,7 @@ namespace UnitTest
while (threadCount > 0 && !timedOut)
{
AZStd::unique_lock<AZStd::mutex> lock(mutex);
timedOut = (AZStd::cv_status::timeout == cv.wait_until(lock, AZStd::chrono::system_clock::now() + DefaultTimeoutSeconds * 20000));
timedOut = (AZStd::cv_status::timeout == cv.wait_until(lock, AZStd::chrono::system_clock::now() + DefaultTimeoutSeconds));
}
ASSERT_EQ(threadCount, 0) << "Thread count is non-zero, a thread has likely deadlocked. Test will not shut down cleanly.";
@@ -1190,7 +1190,7 @@ namespace UnitTest
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
#else
TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
TEST_F(AssetJobsFloodTest, ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
@@ -1241,6 +1241,10 @@ namespace AZ::IO
m_arrZips.insert(revItZip.base(), desc);
// This lock is for m_arrZips.
// Unlock it now because the modification is complete, and events responding to this signal
// will attempt to lock the same mutex, causing the application to lock up.
lock.unlock();
m_levelOpenEvent.Signal(levelDirs);
}
@@ -927,6 +927,9 @@ namespace AzToolsFramework
/// Notify that the MainWindow has been fully initialized
virtual void NotifyMainWindowInitialized(QMainWindow* /*mainWindow*/) {}
/// Notify that the Editor has been fully initialized
virtual void NotifyEditorInitialized() {}
/// Signal that an asset should be highlighted / selected
virtual void SelectAsset(const QString& /* assetPath */) {}
};
@@ -214,12 +214,17 @@ namespace AzToolsFramework
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator,
NotifyRegisterViews);
NotifyRegisterViews, NotifyEditorInitialized);
void NotifyRegisterViews() override
{
Call(FN_NotifyRegisterViews);
}
void NotifyEditorInitialized() override
{
Call(FN_NotifyEditorInitialized);
}
};
} // Internal
@@ -443,6 +448,7 @@ namespace AzToolsFramework
->Attribute(AZ::Script::Attributes::Module, "editor")
->Handler<Internal::EditorEventsBusHandler>()
->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews)
->Event("NotifyEditorInitialized", &EditorEvents::NotifyEditorInitialized)
;
behaviorContext->EBus<ViewPaneCallbackBus>("ViewPaneCallbackBus")
@@ -234,11 +234,6 @@ namespace AzToolsFramework
return SourceFileDetails("Icons/AssetBrowser/Lua_16.svg");
}
if (AzFramework::StringFunc::Equal(extension.c_str(), ".mtl"))
{
return SourceFileDetails("Icons/AssetBrowser/Material_16.svg");
}
if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str()))
{
return SourceFileDetails("Icons/AssetBrowser/Slice_16.svg");
@@ -31,7 +31,10 @@ AZ_POP_DISABLE_WARNING
AZ_CVAR(
bool, ed_hideAssetPickerPathColumn, true, nullptr, AZ::ConsoleFunctorFlags::Null,
"Hide AssetPicker path column for a clearer view.");
AZ_CVAR_EXTERNED(bool, ed_useNewAssetBrowserTableView);
AZ_CVAR(
bool, ed_useNewAssetPickerView, false, nullptr, AZ::ConsoleFunctorFlags::Null,
"Uses the new Asset Picker View.");
namespace AzToolsFramework
{
@@ -106,7 +109,7 @@ namespace AzToolsFramework
m_persistentState = AZ::UserSettings::CreateFind<AzToolsFramework::QWidgetSavedState>(AZ::Crc32(("AssetBrowserTreeView_Dialog_" + name).toUtf8().data()), AZ::UserSettings::CT_GLOBAL);
m_ui->m_assetBrowserTableViewWidget->setVisible(false);
if (ed_useNewAssetBrowserTableView)
if (ed_useNewAssetPickerView)
{
m_ui->m_assetBrowserTreeViewWidget->setVisible(false);
m_ui->m_assetBrowserTableViewWidget->setVisible(true);
@@ -597,11 +597,13 @@ namespace AzToolsFramework
pte.SetVisibleEnforcement(true);
}
ScopedUndoBatch undo("Modify Entity Property");
PropertyOutcome result = pte.SetProperty(propertyPath, value);
if (result.IsSuccess())
{
PropertyEditorEntityChangeNotificationBus::Event(componentInstance.GetEntityId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged, componentInstance.GetComponentId());
}
undo.MarkEntityDirty(componentInstance.GetEntityId());
return result;
}
@@ -64,6 +64,9 @@ namespace AzToolsFramework::Prefab
);
m_backButton->setToolTip("Up one level (-)");
// Currently hide this button until we can correctly disable/enable it based on context.
m_backButton->hide();
}
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
@@ -4705,13 +4705,6 @@ namespace AzToolsFramework
{
if (mimeData->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType()))
{
// extra special case: MTLs from FBX drags are ignored. are we dragging a FBX file?
bool isDraggingFBXFile = false;
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::SourceAssetBrowserEntry>(mimeData, [&](const AssetBrowser::SourceAssetBrowserEntry* source)
{
isDraggingFBXFile = isDraggingFBXFile || AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false);
});
// the usual case - we only allow asset browser drops of assets that have actually been associated with a kind of component.
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::ProductAssetBrowserEntry>(mimeData, [&](const AssetBrowser::ProductAssetBrowserEntry* product)
{
@@ -4723,17 +4716,7 @@ namespace AzToolsFramework
if (canCreateComponent && !componentTypeId.IsNull())
{
// we have a component type that handles this asset.
// but we disallow it if its a MTL file from a FBX and the FBX itself is being dragged. Its still allowed
// to drag the actual MTL.
EBusFindAssetTypeByName materialAssetTypeResult("Material");
AZ::AssetTypeInfoBus::BroadcastResult(materialAssetTypeResult, &AZ::AssetTypeInfo::GetAssetType);
AZ::Data::AssetType materialAssetType = materialAssetTypeResult.GetAssetType();
if ((!isDraggingFBXFile) || (product->GetAssetType() != materialAssetType))
{
callbackFunction(product);
}
callbackFunction(product);
}
});
}
@@ -690,7 +690,6 @@ namespace AssetBuilderSDK
static const char* textureExtensions = ".dds";
static const char* staticMeshExtensions = ".cgf";
static const char* skinnedMeshExtensions = ".skin";
static const char* materialExtensions = ".mtl";
// MIPS
static const int c_MaxMipsCount = 11; // 11 is for 8k textures non-compressed. When not compressed it is using one file per mip.
@@ -805,11 +804,6 @@ namespace AssetBuilderSDK
return textureAssetType;
}
if (AzFramework::StringFunc::Find(materialExtensions, extension.c_str()) != AZStd::string::npos)
{
return materialAssetType;
}
if (AzFramework::StringFunc::Find(staticMeshExtensions, extension.c_str()) != AZStd::string::npos)
{
return meshAssetType;
@@ -9,3 +9,4 @@
#pragma once
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
@@ -96,5 +96,10 @@ namespace O3DE::ProjectManager
{
return AZ::Utils::GetExecutableDirectory();
}
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
{
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -9,3 +9,4 @@
#pragma once
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
@@ -137,5 +137,10 @@ namespace O3DE::ProjectManager
return editorPath;
}
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
{
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -9,3 +9,4 @@
#pragma once
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR true
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT true
@@ -13,6 +13,7 @@
#include <QFileInfo>
#include <QProcess>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <AzCore/Utils/Utils.h>
@@ -146,5 +147,26 @@ namespace O3DE::ProjectManager
{
return AZ::Utils::GetExecutableDirectory();
}
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments)
{
const QString cmd{"powershell.exe"};
const QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
const QString shortcutPath = QString("%1/%2.lnk").arg(desktopPath).arg(filename);
const QString arg = QString("$s=(New-Object -COM WScript.Shell).CreateShortcut('%1');$s.TargetPath='%2';$s.Arguments='%3';$s.Save();")
.arg(shortcutPath)
.arg(targetPath)
.arg(arguments.join(' '));
auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg}, QProcessEnvironment::systemEnvironment());
if (!createShortcutResult.IsSuccess())
{
return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>"
"Please verify you have permission to create files at the specified location.<br><br> %2")
.arg(shortcutPath)
.arg(createShortcutResult.GetError()));
}
return AZ::Success(QObject::tr("Desktop shortcut created at<br><a href=\"%1\">%2</a>").arg(desktopPath).arg(shortcutPath));
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -126,7 +126,8 @@ namespace O3DE::ProjectManager
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemModel->index(0, 0);
m_gemModel->GetSelectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
QModelIndex proxyIndex = m_proxyModel->mapFromSource(firstModelIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
});
}
@@ -209,7 +210,7 @@ namespace O3DE::ProjectManager
const bool gemFound = gemInfoHash.contains(gemName);
if (!gemFound && !m_gemModel->IsAdded(index) && !m_gemModel->IsAddedDependency(index))
{
m_gemModel->removeRow(i);
m_gemModel->RemoveGem(index);
}
else
{
@@ -239,7 +240,7 @@ namespace O3DE::ProjectManager
m_filterWidget->ResetAllFilters();
// Reselect the same selection to proc UI updates
m_proxyModel->GetSelectionModel()->select(m_proxyModel->GetSelectionModel()->selection(), QItemSelectionModel::Select);
m_proxyModel->GetSelectionModel()->setCurrentIndex(m_proxyModel->GetSelectionModel()->currentIndex(), QItemSelectionModel::Select);
}
void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies)
@@ -268,7 +269,7 @@ namespace O3DE::ProjectManager
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
{
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
GemModel::SetDownloadStatus(*m_proxyModel, m_proxyModel->mapFromSource(modelIndex), GemInfo::DownloadStatus::Downloading);
GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading);
}
}
@@ -300,7 +301,7 @@ namespace O3DE::ProjectManager
}
QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex);
m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
m_gemListView->scrollTo(proxyIndex);
}
@@ -362,6 +363,9 @@ namespace O3DE::ProjectManager
{
const QString selectedGemPath = m_gemModel->GetPath(modelIndex);
// Remove gem from gems to be added
GemModel::SetIsAdded(*m_gemModel, modelIndex, false);
// Unregister the gem
auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath);
if (!unregisterResult)
@@ -370,8 +374,10 @@ namespace O3DE::ProjectManager
}
else
{
const QString selectedGemName = m_gemModel->GetName(modelIndex);
// Remove gem from model
m_gemModel->removeRow(modelIndex.row());
m_gemModel->RemoveGem(modelIndex);
// Delete uninstalled gem directory
if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true))
@@ -382,6 +388,11 @@ namespace O3DE::ProjectManager
// Show undownloaded remote gem again
Refresh();
// Select remote gem
QModelIndex remoteGemIndex = m_gemModel->FindIndexByNameString(selectedGemName);
QModelIndex proxyIndex = m_proxyModel->mapFromSource(remoteGemIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
}
}
}
@@ -564,7 +575,8 @@ namespace O3DE::ProjectManager
if (succeeded)
{
// refresh the information for downloaded gems
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult =
PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
if (allGemInfosResult.IsSuccess())
{
// we should find the gem name now in all gem infos
@@ -572,15 +584,33 @@ namespace O3DE::ProjectManager
{
if (gemInfo.m_name == gemName)
{
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
if (index.isValid())
QModelIndex oldIndex = m_gemModel->FindIndexByNameString(gemName);
if (oldIndex.isValid())
{
m_proxyModel->setData(m_proxyModel->mapFromSource(index), GemInfo::DownloadSuccessful, GemModel::RoleDownloadStatus);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RolePath);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RoleDirectoryLink);
// Check if old gem is selected
bool oldGemSelected = false;
if (m_gemModel->GetSelectionModel()->currentIndex() == oldIndex)
{
oldGemSelected = true;
}
// Remove old remote gem
m_gemModel->RemoveGem(oldIndex);
// Add new downloaded version of gem
QModelIndex newIndex = m_gemModel->AddGem(gemInfo);
GemModel::SetDownloadStatus(*m_gemModel, newIndex, GemInfo::DownloadSuccessful);
GemModel::SetIsAdded(*m_gemModel, newIndex, true);
// Select new version of gem if it was previously selected
if (oldGemSelected)
{
QModelIndex proxyIndex = m_proxyModel->mapFromSource(newIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
}
}
return;
break;
}
}
}
@@ -590,7 +620,7 @@ namespace O3DE::ProjectManager
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
if (index.isValid())
{
m_proxyModel->setData(m_proxyModel->mapFromSource(index), GemInfo::DownloadFailed, GemModel::RoleDownloadStatus);
GemModel::SetDownloadStatus(*m_gemModel, index, GemInfo::DownloadFailed);
}
}
}
@@ -26,14 +26,14 @@ namespace O3DE::ProjectManager
return m_selectionModel;
}
void GemModel::AddGem(const GemInfo& gemInfo)
QModelIndex GemModel::AddGem(const GemInfo& gemInfo)
{
if (FindIndexByNameString(gemInfo.m_name).isValid())
{
// do not add gems with duplicate names
// this can happen by mistake or when a gem repo has a gem with the same name as a local gem
AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData());
return;
return QModelIndex();
}
QStandardItem* item = new QStandardItem();
@@ -67,6 +67,22 @@ namespace O3DE::ProjectManager
const QModelIndex modelIndex = index(rowCount()-1, 0);
m_nameToIndexMap[gemInfo.m_name] = modelIndex;
return modelIndex;
}
void GemModel::RemoveGem(const QModelIndex& modelIndex)
{
removeRow(modelIndex.row());
}
void GemModel::RemoveGem(const QString& gemName)
{
auto nameFind = m_nameToIndexMap.find(gemName);
if (nameFind != m_nameToIndexMap.end())
{
removeRow(nameFind->row());
}
}
void GemModel::Clear()
@@ -391,11 +407,11 @@ namespace O3DE::ProjectManager
// Select a valid row if currently selected row was removed
if (selectedRowRemoved)
{
for (const QModelIndex& index : m_nameToIndexMap)
for (const QModelIndex& index : m_nameToIndexMap)
{
if (index.isValid())
{
GetSelectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
GetSelectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
break;
}
}
@@ -55,7 +55,9 @@ namespace O3DE::ProjectManager
RoleRepoUri
};
void AddGem(const GemInfo& gemInfo);
QModelIndex AddGem(const GemInfo& gemInfo);
void RemoveGem(const QModelIndex& modelIndex);
void RemoveGem(const QString& gemName);
void Clear();
void UpdateGemDependencies();
@@ -75,7 +75,7 @@ namespace O3DE::ProjectManager
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
m_gemRepoListView->selectionModel()->setCurrentIndex(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
@@ -8,7 +8,11 @@
#include <ProjectButtonWidget.h>
#include <ProjectManagerDefs.h>
#include <ProjectUtils.h>
#include <ProjectManager_Traits_Platform.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
@@ -23,6 +27,7 @@
#include <QDir>
#include <QFileInfo>
#include <QDesktopServices>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
@@ -205,6 +210,29 @@ namespace O3DE::ProjectManager
{
AzQtComponents::ShowFileOnDesktop(m_projectInfo.m_path);
});
#if AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
menu->addAction(tr("Create Editor desktop shortcut..."), this, [this]()
{
AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory();
AZStd::string executableFilename = "Editor";
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
const QString shortcutName = QString("%1 Editor").arg(m_projectInfo.m_displayName);
const QString arg = QString("--regset=\"/Amazon/AzCore/Bootstrap/project_path=%1\"").arg(m_projectInfo.m_path);
auto result = ProjectUtils::CreateDesktopShortcut(shortcutName, editorExecutablePath.c_str(), { arg });
if(result.IsSuccess())
{
QMessageBox::information(this, tr("Desktop Shortcut Created"), result.GetValue());
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), result.GetError());
}
});
#endif // AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
menu->addSeparator();
menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo); });
menu->addSeparator();
@@ -628,11 +628,11 @@ namespace O3DE::ProjectManager
return AZ::Failure(QObject::tr("Process for command '%1' timed out at %2 seconds").arg(cmd).arg(commandTimeoutSeconds));
}
int resultCode = execProcess.exitCode();
QString resultOutput = execProcess.readAllStandardOutput();
if (resultCode != 0)
{
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2").arg(cmd).arg(resultCode));
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2) %3").arg(cmd).arg(resultCode).arg(resultOutput));
}
QString resultOutput = execProcess.readAllStandardOutput();
return AZ::Success(resultOutput);
}
@@ -68,6 +68,15 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
/**
* Create a desktop shortcut.
* @param filename the name of the desktop shorcut file
* @param target the path to the target to run
* @param arguments the argument list to provide to the target
* @return AZ::Outcome with the command result on success
*/
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments);
AZ::IO::FixedMaxPath GetEditorDirectory();