From ede7969ea34488f1500750c77c950634b3d96704 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 13 Dec 2024 10:56:56 +0200 Subject: [PATCH] QmlDesigner: Make imported 3D scenes available via assets A placeholder .q3d file is created under content for imported 3D components found under Generated/QtQuick3D on asset view attach and every time new import is done. .q3d file contains a project root relative path to component's import folder. .q3d files get generated preview as icon in assets view. Imported 3D items are no longer shown in Components view. Removing .q3d file will remove the corresponding module as well as all model nodes created from that asset. Removing last model node of asset will remove the import statement on next document save. Fixes: QDS-12193 Fixes: QDS-14565 Change-Id: If01546ca4c78334bac73b055ed156276f6f8f2a4 Reviewed-by: Mahmoud Badri Reviewed-by: Marco Bubke --- .../assetsLibraryQmlSources/AssetDelegate.qml | 3 + .../effectcomposer/effectcomposerview.cpp | 44 ----- .../effectcomposer/effectcomposerview.h | 2 - .../assetslibrary/assetslibrary.qrc | 2 + .../assetslibraryiconprovider.cpp | 27 ++- .../assetslibrary/assetslibraryiconprovider.h | 8 +- .../assetslibrary/assetslibrarymodel.cpp | 38 +++-- .../assetslibrary/assetslibrarymodel.h | 2 +- .../assetslibrary/assetslibraryview.cpp | 85 +++++++++- .../assetslibrary/assetslibraryview.h | 10 +- .../assetslibrary/assetslibrarywidget.cpp | 158 +++++++++++++----- .../assetslibrary/assetslibrarywidget.h | 8 +- .../assetslibrary/images/asset_imported3d.png | Bin 0 -> 471 bytes .../images/asset_imported3d@2x.png | Bin 0 -> 838 bytes .../componentcore/modelnodeoperations.cpp | 37 ++++ .../componentcore/modelnodeoperations.h | 6 + .../components/componentcore/viewmanager.cpp | 2 +- .../qmldesigner/components/edit3d/edit3d.qrc | 2 + .../components/edit3d/edit3dview.cpp | 70 +++++--- .../components/edit3d/edit3dview.h | 5 +- .../components/edit3d/edit3dwidget.cpp | 74 ++++++-- .../components/edit3d/edit3dwidget.h | 2 + .../edit3d/images/item-3D_model-icon.png | Bin 0 -> 407 bytes .../edit3d/images/item-3D_model-icon@2x.png | Bin 0 -> 733 bytes .../components/import3d/import3dimporter.cpp | 19 +-- .../components/integration/designdocument.cpp | 55 ++++++ .../itemlibrary/itemlibraryimport.cpp | 15 -- .../itemlibrary/itemlibraryimport.h | 2 - .../itemlibrary/itemlibrarymodel.cpp | 26 +-- .../materialeditor/materialeditorview.cpp | 2 +- .../components/navigator/nameitemdelegate.cpp | 4 + .../navigator/navigatortreemodel.cpp | 4 + .../components/navigator/navigatorview.cpp | 3 + .../generatedcomponentutils.cpp | 57 +++++++ .../generatedcomponentutils.h | 6 + .../libs/designercore/include/model.h | 1 + .../libs/designercore/model/model.cpp | 10 ++ .../libs/qmldesignerutils/asset.cpp | 15 +- .../qmldesigner/libs/qmldesignerutils/asset.h | 27 +-- .../qmldesigner/qmldesignerconstants.h | 1 + .../qmldesigner/qmltools/qmlvisualnode.cpp | 52 ++++++ .../qmldesigner/qmltools/qmlvisualnode.h | 7 + 42 files changed, 672 insertions(+), 219 deletions(-) create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d@2x.png create mode 100644 src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon.png create mode 100644 src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon@2x.png diff --git a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml index 59d01e0f18f..621841e864a 100644 --- a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml +++ b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml @@ -239,6 +239,9 @@ T.TreeViewDelegate { + size.width + " x " + size.height + "\n" + fileSize + " " + fileExt + } else if (rootView.assetIsImported3d(model.filePath)) { + return filePath + "\n" + + fileExt } else { return filePath + "\n" + fileSize diff --git a/src/plugins/effectcomposer/effectcomposerview.cpp b/src/plugins/effectcomposer/effectcomposerview.cpp index 7a228c4e934..d1a46094719 100644 --- a/src/plugins/effectcomposer/effectcomposerview.cpp +++ b/src/plugins/effectcomposer/effectcomposerview.cpp @@ -187,50 +187,6 @@ void EffectComposerView::selectedNodesChanged(const QListeffectComposerModel()->setHasValidTarget(hasValidTarget); } -void EffectComposerView::nodeAboutToBeRemoved(const QmlDesigner::ModelNode &removedNode) -{ - QList nodes = removedNode.allSubModelNodesAndThisNode(); - bool effectRemoved = false; - for (const QmlDesigner::ModelNode &node : nodes) { - QmlDesigner::QmlItemNode qmlNode(node); - if (qmlNode.isEffectItem()) { - effectRemoved = true; - break; - } - } - if (effectRemoved) - QTimer::singleShot(0, this, &EffectComposerView::removeUnusedEffectImports); -} - -void EffectComposerView::removeUnusedEffectImports() -{ - QTC_ASSERT(model(), return); - - const QString effectPrefix = m_componentUtils.composedEffectsTypePrefix(); - - const QmlDesigner::Imports &imports = model()->imports(); - QHash effectImports; - for (const QmlDesigner::Import &import : imports) { - if (import.url().startsWith(effectPrefix)) { - QString type = import.url().split('.').last(); - effectImports.insert(type, import); - } - } - - const QList allNodes = allModelNodes(); - for (const QmlDesigner::ModelNode &node : allNodes) { - if (QmlDesigner::QmlItemNode(node).isEffectItem()) - effectImports.remove(node.simplifiedTypeName()); - } - - if (!effectImports.isEmpty()) { - QmlDesigner::Imports removeImports; - for (const QmlDesigner::Import &import : effectImports) - removeImports.append(import); - model()->changeImports({}, removeImports); - } -} - void EffectComposerView::highlightSupportedProperties(bool highlight, const QString &suffix) { QQmlContext *ctxObj = m_widget->quickWidget()->rootContext(); diff --git a/src/plugins/effectcomposer/effectcomposerview.h b/src/plugins/effectcomposer/effectcomposerview.h index fc6e65fc9a4..074e768068d 100644 --- a/src/plugins/effectcomposer/effectcomposerview.h +++ b/src/plugins/effectcomposer/effectcomposerview.h @@ -29,7 +29,6 @@ public: void modelAboutToBeDetached(QmlDesigner::Model *model) override; void selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) override; - void nodeAboutToBeRemoved(const QmlDesigner::ModelNode &removedNode) override; void dragStarted(QMimeData *mimeData) override; void dragEnded() override; @@ -41,7 +40,6 @@ public: private: void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; - void removeUnusedEffectImports(); QPointer m_widget; QString m_currProjectPath; diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc index 6f92dd861cf..eebf88e71b4 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc @@ -24,5 +24,7 @@ images/asset_ktx_128.png images/asset_folder.png images/asset_folder@2x.png + images/asset_imported3d.png + images/asset_imported3d@2x.png diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index 15e67b74aa1..73e0264b378 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -77,7 +77,7 @@ QPixmap AssetsLibraryIconProvider::generateFontIcons(const QString &filePath, co "Abc"}).pixmap(reqSize); } -QPair AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) const +QPair AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) { Asset asset(id); @@ -100,6 +100,18 @@ QPair AssetsLibraryIconProvider::fetchPixmap(const QString &id, qint64 size = QFileInfo(id).size(); QString filePath = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/asset_ktx.png"); return {QPixmap{filePath}, size}; + } else if (asset.isImported3D()) { + static QPixmap defaultPreview = QPixmap::fromImage(QImage(":/AssetsLibrary/images/asset_imported3d.png")); + QPixmap pixmap{requestedSize}; + QString assetId = id.mid(id.lastIndexOf('/') + 1); + assetId.chop(asset.suffix().size() - 1); // Remove suffix + if (m_pixmaps.contains(assetId)) { + pixmap = m_pixmaps.value(assetId); + } else { + pixmap = defaultPreview; + emit asyncAssetPreviewRequested(assetId, id); + } + return {pixmap, 0}; } else { QString type; if (asset.isShader()) @@ -145,5 +157,18 @@ qint64 AssetsLibraryIconProvider::fileSize(const QString &id) return m_thumbnails.contains(id) ? m_thumbnails[id].fileSize : 0; } +QString AssetsLibraryIconProvider::setPixmap(const QString &id, const QPixmap &pixmap, + const QString &suffix) +{ + m_pixmaps.insert(id, pixmap); + const QStringList thumbs = m_thumbnails.keys(); + const QString checkName = id + "." + suffix; + for (const auto &thumb : thumbs) { + if (thumb.endsWith(checkName)) + return thumb; + } + return {}; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h index d52779232f0..757f947da6b 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h @@ -20,6 +20,7 @@ struct Thumbnail class AssetsLibraryIconProvider : public QQuickImageProvider { + Q_OBJECT public: AssetsLibraryIconProvider(SynchronousImageCache &fontImageCache); @@ -28,10 +29,14 @@ public: void invalidateThumbnail(const QString &id); QSize imageSize(const QString &id); qint64 fileSize(const QString &id); + QString setPixmap(const QString &id, const QPixmap &pixmap, const QString &suffix); + +signals: + void asyncAssetPreviewRequested(const QString &assetId, const QString &assetFile); private: QPixmap generateFontIcons(const QString &filePath, const QSize &requestedSize) const; - QPair fetchPixmap(const QString &id, const QSize &requestedSize) const; + QPair fetchPixmap(const QString &id, const QSize &requestedSize); Thumbnail createThumbnail(const QString &id, const QSize &requestedSize); SynchronousImageCache &m_fontImageCache; @@ -42,6 +47,7 @@ private: {96, 96}, // list @2x {48, 48}}; // list QHash m_thumbnails; + QHash m_pixmaps; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 96c757a176a..06bc3f54efd 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -118,19 +118,33 @@ void AssetsLibraryModel::deleteFiles(const QStringList &filePaths, bool dontAskA if (dontAskAgain) QmlDesignerPlugin::settings().insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, false); - QStringList deletedEffects; + QHash deletedAssets; + const GeneratedComponentUtils &compUtils = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils(); + const QString effectTypePrefix = compUtils.composedEffectsTypePrefix(); + const Utils::FilePath effectBasePath = compUtils.composedEffectsBasePath(); for (const QString &filePath : filePaths) { - QFileInfo fi(filePath); - if (fi.exists()) { - if (QFile::remove(filePath)) { - if (Asset(filePath).isEffect()) { - // If an effect composer effect was removed, also remove effect module from project - QString effectName = fi.baseName(); - if (!effectName.isEmpty()) - deletedEffects.append(effectName); + Utils::FilePath fp = Utils::FilePath::fromString(filePath); + if (fp.exists()) { + // If a generated asset was removed, also remove its module from project + Asset asset(filePath); + QString fullType; + if (asset.isEffect()) { + QString effectName = fp.baseName(); + fullType = QString("%1.%2.%2").arg(effectTypePrefix, effectName, effectName); + deletedAssets.insert(fullType, effectBasePath.resolvePath(effectName)); + } else if (asset.isImported3D()) { + Utils::FilePath qmlFile = compUtils.getImported3dQml(filePath); + if (qmlFile.exists()) { + QString importName = compUtils.getImported3dImportName(qmlFile); + fullType = QString("%1.%2").arg(importName, qmlFile.baseName()); + deletedAssets.insert(fullType, qmlFile.absolutePath()); } - } else { + } + + if (!fp.removeFile()) { + deletedAssets.remove(fullType); QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Failed to Delete File"), Tr::tr("Could not delete \"%1\".").arg(filePath)); @@ -138,8 +152,8 @@ void AssetsLibraryModel::deleteFiles(const QStringList &filePaths, bool dontAskA } } - if (!deletedEffects.isEmpty()) - emit effectsDeleted(deletedEffects); + if (!deletedAssets.isEmpty()) + emit generatedAssetsDeleted(deletedAssets); } bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString &newName) diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index 52b5932a27a..464299a3dc1 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -72,7 +72,7 @@ signals: void rootPathChanged(); void isEmptyChanged(); void fileChanged(const QString &path); - void effectsDeleted(const QStringList &effectNames); + void generatedAssetsDeleted(const QHash &assetData); private: void setIsEmpty(bool value); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp index 28bbd0515ac..c3c8ed5e33a 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -41,11 +42,18 @@ public: TimeStampProvider timeStampProvider; AsynchronousImageCache asynchronousFontImageCache{storage, fontGenerator, timeStampProvider}; SynchronousImageCache synchronousFontImageCache{storage, timeStampProvider, fontCollector}; + AsynchronousImageCache *mainImageCache = {}; }; -AssetsLibraryView::AssetsLibraryView(ExternalDependenciesInterface &externalDependencies) +AssetsLibraryView::AssetsLibraryView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} -{} +{ + imageCacheData()->mainImageCache = &imageCache; + m_3dImportsSyncTimer.callOnTimeout(this, &AssetsLibraryView::sync3dImports); + m_3dImportsSyncTimer.setInterval(1000); + m_3dImportsSyncTimer.setSingleShot(true); +} AssetsLibraryView::~AssetsLibraryView() {} @@ -59,6 +67,7 @@ WidgetInfo AssetsLibraryView::widgetInfo() { if (!m_widget) { m_widget = Utils::makeUniqueObjectPtr( + *imageCacheData()->mainImageCache, imageCacheData()->asynchronousFontImageCache, imageCacheData()->synchronousFontImageCache, this); @@ -74,6 +83,8 @@ void AssetsLibraryView::customNotification(const AbstractView * /*view*/, { if (identifier == "delete_selected_assets") m_widget->deleteSelectedAssets(); + else if (identifier == "asset_import_finished") + m_3dImportsSyncTimer.start(); } void AssetsLibraryView::modelAttached(Model *model) @@ -82,17 +93,20 @@ void AssetsLibraryView::modelAttached(Model *model) m_widget->clearSearchFilter(); - setResourcePath(DocumentManager::currentResourcePath().toFileInfo().absoluteFilePath()); + setResourcePath(DocumentManager::currentResourcePath().toFSPathString()); + + m_3dImportsSyncTimer.start(); } void AssetsLibraryView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); + + m_3dImportsSyncTimer.stop(); } void AssetsLibraryView::setResourcePath(const QString &resourcePath) { - if (resourcePath == m_lastResourcePath) return; @@ -100,6 +114,7 @@ void AssetsLibraryView::setResourcePath(const QString &resourcePath) if (!m_widget) { m_widget = Utils::makeUniqueObjectPtr( + *imageCacheData()->mainImageCache, imageCacheData()->asynchronousFontImageCache, imageCacheData()->synchronousFontImageCache, this); @@ -108,6 +123,68 @@ void AssetsLibraryView::setResourcePath(const QString &resourcePath) m_widget->setResourcePath(resourcePath); } +QHash AssetsLibraryView::collectFiles(const Utils::FilePath &dirPath, + const QString &suffix) +{ + if (dirPath.isEmpty()) + return {}; + + QHash files; + + Utils::FilePaths entryList = dirPath.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + for (const Utils::FilePath &entry : entryList) { + if (entry.isDir()) { + files.insert(collectFiles(entry.absoluteFilePath(), suffix)); + } else if (entry.suffix() == suffix) { + QString baseName = entry.baseName(); + files.insert(baseName, entry); + } + } + + return files; +} + +void AssetsLibraryView::sync3dImports() +{ + if (!model()) + return; + + // TODO: Once project storage supports notifications for new and removed types, + // sync3dImports() should be called in that case as well. + // Also, custom notification "asset_import_finished" should not be necessary in that case. + + // Sync generated 3d imports to .q3d files in project content + const GeneratedComponentUtils &compUtils + = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + auto projPath = Utils::FilePath::fromString(externalDependencies().currentProjectDirPath()); + + const QList qmlFiles = compUtils.imported3dComponents(); + + Utils::FilePath resPath = DocumentManager::currentResourcePath(); + QHash files = collectFiles(resPath, "q3d"); + + const QString pathTemplate("/%1.q3d"); + + for (const Utils::FilePath &qmlFile : qmlFiles) { + const QString fileStr = qmlFile.baseName(); + if (files.contains(fileStr)) { + files.remove(fileStr); + } else { + Utils::FilePath targetPath = ModelNodeOperations::getImported3dDefaultDirectory(); + if (!targetPath.isAbsolutePath() || !targetPath.exists()) + targetPath = resPath; + Utils::FilePath newFile = targetPath.pathAppended(pathTemplate.arg(fileStr)); + QByteArray data; + data.append(qmlFile.relativePathFrom(projPath).toFSPathString().toLatin1()); + newFile.writeFileContents(data); + } + } + + // Remove .q3d files that do not match any imported 3d + for (const Utils::FilePath &f : std::as_const(files)) + f.removeFile(); +} + AssetsLibraryView::ImageCacheData *AssetsLibraryView::imageCacheData() { std::call_once(imageCacheFlag, diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h index ea8ac648d81..2dc8f47cdf4 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h @@ -5,9 +5,11 @@ #include "abstractview.h" +#include #include #include +#include #include @@ -21,7 +23,8 @@ class AssetsLibraryView : public AbstractView Q_OBJECT public: - AssetsLibraryView(ExternalDependenciesInterface &externalDependencies); + AssetsLibraryView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies); ~AssetsLibraryView() override; bool hasWidget() const override; @@ -30,7 +33,6 @@ public: // AbstractView void modelAttached(Model *model) override; void modelAboutToBeDetached(Model *model) override; - void setResourcePath(const QString &resourcePath); private: @@ -39,11 +41,15 @@ private: void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; + QHash collectFiles(const Utils::FilePath &dirPath, + const QString &suffix); + void sync3dImports(); std::once_flag imageCacheFlag; std::unique_ptr m_imageCacheData; Utils::UniqueObjectPtr m_widget; QString m_lastResourcePath; + QTimer m_3dImportsSyncTimer; }; } diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 5015e634782..625e0172189 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -8,6 +8,7 @@ #include "assetslibraryview.h" #include +#include #include #include #include @@ -97,10 +98,12 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event) return QObject::eventFilter(obj, event); } -AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache, +AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &mainImageCache, + AsynchronousImageCache &asynchronousFontImageCache, SynchronousImageCache &synchronousFontImageCache, AssetsLibraryView *view) : m_itemIconSize{24, 24} + , m_mainImageCache{mainImageCache} , m_fontImageCache{synchronousFontImageCache} , m_assetsIconProvider{new AssetsLibraryIconProvider(synchronousFontImageCache)} , m_assetsModel{new AssetsLibraryModel(this)} @@ -110,6 +113,44 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon setWindowTitle(Tr::tr("Assets Library", "Title of assets library widget")); setMinimumWidth(250); + connect(m_assetsIconProvider, &AssetsLibraryIconProvider::asyncAssetPreviewRequested, + this, [this](const QString &assetId, const QString &assetFile) { + Asset asset{assetFile}; + if (!asset.isImported3D()) + return; + + Utils::FilePath fullPath = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().getImported3dQml(assetFile); + + if (!fullPath.exists()) + return; + + m_mainImageCache.requestImage( + Utils::PathString{fullPath.toFSPathString()}, + [this, assetId](const QImage &image) { + QMetaObject::invokeMethod(this, [this, assetId, image] { + updateAssetPreview(assetId, QPixmap::fromImage(image), "q3d"); + }, Qt::QueuedConnection); + }, + [assetFile](ImageCache::AbortReason abortReason) { + if (abortReason == ImageCache::AbortReason::Abort) { + qWarning() << QLatin1String( + "AssetsLibraryIconProvider::asyncAssetPreviewRequested(): preview generation " + "failed for path %1, reason: Abort").arg(assetFile); + } else if (abortReason == ImageCache::AbortReason::Failed) { + qWarning() << QLatin1String( + "AssetsLibraryIconProvider::asyncAssetPreviewRequested(): preview generation " + "failed for path %1, reason: Failed").arg(assetFile); + } else if (abortReason == ImageCache::AbortReason::NoEntry) { + qWarning() << QLatin1String( + "AssetsLibraryIconProvider::asyncAssetPreviewRequested(): preview generation " + "failed for path %1, reason: NoEntry").arg(assetFile); + } + }, + "libIcon", + ImageCache::LibraryIconAuxiliaryData{true}); + }); + m_assetsWidget->quickWidget()->installEventFilter(this); m_fontPreviewTooltipBackend = std::make_unique(asynchronousFontImageCache); @@ -135,8 +176,8 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon connect(m_assetsModel, &AssetsLibraryModel::fileChanged, QmlDesignerPlugin::instance(), &QmlDesignerPlugin::assetChanged); - connect(m_assetsModel, &AssetsLibraryModel::effectsDeleted, - this, &AssetsLibraryWidget::handleDeleteEffects); + connect(m_assetsModel, &AssetsLibraryModel::generatedAssetsDeleted, + this, &AssetsLibraryWidget::handleDeletedGeneratedAssets); auto layout = new QVBoxLayout(this); layout->setContentsMargins({}); @@ -292,43 +333,63 @@ void AssetsLibraryWidget::setHasSceneEnv(bool b) emit hasSceneEnvChanged(); } -void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList &effectNames) +void AssetsLibraryWidget::handleDeletedGeneratedAssets(const QHash &assetData) { -#ifdef QDS_USE_PROJECTSTORAGE -// That code has to rewritten with modules. Seem try to find all effects nodes. -#else + // assetData key: full type name including import, value: import dir + + // This method removes all nodes of the deleted type (assetData.keys()) + // and removes the import statement for that type + DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); if (!document) return; bool clearStacks = false; - // Remove usages of deleted effects from the current document + const Imports imports = m_assetsView->model()->imports(); + const GeneratedComponentUtils &compUtils = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils(); + QString effectPrefix = compUtils.composedEffectsTypePrefix(); + QStringList effectNames; + + // Remove usages of deleted assets from the current document m_assetsView->executeInTransaction(__FUNCTION__, [&]() { QList allNodes = m_assetsView->allModelNodes(); - const QString typeTemplate = "%1.%2.%2"; - const QString importUrlTemplate = "%1.%2"; - const Imports imports = m_assetsView->model()->imports(); - Imports removedImports; - const QString typePrefix = QmlDesignerPlugin::instance()->documentManager() - .generatedComponentUtils().composedEffectsTypePrefix(); - for (const QString &effectName : effectNames) { - if (effectName.isEmpty()) - continue; - const TypeName type = typeTemplate.arg(typePrefix, effectName).toUtf8(); - for (ModelNode &node : allNodes) { - if (node.metaInfo().typeName() == type) { - clearStacks = true; - node.destroy(); + + QList removedImports; + + const QStringList assetTypes = assetData.keys(); + for (const QString &assetType : assetTypes) { + QString removedImportUrl; + int idx = assetType.lastIndexOf('.'); + if (idx >= 0) { + if (assetType.startsWith(effectPrefix)) + effectNames.append(assetType.sliced(idx + 1)); + removedImportUrl = assetType.first(idx); +#ifdef QDS_USE_PROJECTSTORAGE + auto module = m_assetsView->model()->module(removedImportUrl.toUtf8(), + Storage::ModuleKind::QmlLibrary); + auto metaInfo = m_assetsView->model()->metaInfo(module, assetType.sliced(idx + 1).toUtf8()); + for (ModelNode &node : allNodes) { + if (node.metaInfo() == metaInfo) { +#else + TypeName type = assetType.toUtf8(); + for (ModelNode &node : allNodes) { + if (node.metaInfo().typeName() == type) { +#endif + clearStacks = true; + node.destroy(); + } + } + if (!removedImportUrl.isEmpty()) { + Import removedImport = Utils::findOrDefault(imports, + [&removedImportUrl](const Import &import) { + return import.url() == removedImportUrl; + }); + if (!removedImport.isEmpty()) + removedImports.append(removedImport); } } - - const QString importPath = importUrlTemplate.arg(typePrefix, effectName); - Import removedImport = Utils::findOrDefault(imports, [&importPath](const Import &import) { - return import.url() == importPath; - }); - if (!removedImport.isEmpty()) - removedImports.append(removedImport); } if (!removedImports.isEmpty()) { @@ -338,22 +399,20 @@ void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList }); // The size check here is to weed out cases where project path somehow resolves - // to just slash. Shortest legal currentProjectDirPath() would be "/a/". - if (m_assetsModel->currentProjectDirPath().size() < 3) + // to just slash or drive + slash. (Shortest legal currentProjectDirPath() would be "/a/") + if (m_assetsModel->currentProjectDirPath().size() < 4) return; - Utils::FilePath effectsDir = ModelNodeOperations::getEffectsImportDirectory(); - - // Delete the effect modules - for (const QString &effectName : effectNames) { - Utils::FilePath eDir = effectsDir.pathAppended(effectName); - if (eDir.exists() && eDir.toString().startsWith(m_assetsModel->currentProjectDirPath())) { + // Delete the asset modules + for (const Utils::FilePath &dir : assetData) { + if (dir.exists() && dir.toFSPathString().startsWith(m_assetsModel->currentProjectDirPath())) { QString error; - eDir.removeRecursively(&error); + dir.removeRecursively(&error); + if (!error.isEmpty()) { QMessageBox::warning(Core::ICore::dialogParent(), - Tr::tr("Failed to Delete Effect Resources"), - Tr::tr("Could not delete \"%1\".").arg(eDir.toString())); + Tr::tr("Failed to Delete Asset Resources"), + Tr::tr("Could not delete \"%1\".").arg(dir.toFSPathString())); } } } @@ -364,7 +423,16 @@ void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList document->clearUndoRedoStacks(); m_assetsView->emitCustomNotification("effectcomposer_effects_deleted", {}, {effectNames}); -#endif + m_assetsView->emitCustomNotification("assets_deleted"); +} + +void AssetsLibraryWidget::updateAssetPreview(const QString &id, const QPixmap &pixmap, + const QString &suffix) +{ + const QString thumb = m_assetsIconProvider->setPixmap(id, pixmap, suffix); + + if (!thumb.isEmpty()) + emit m_assetsModel->fileChanged(thumb); } void AssetsLibraryWidget::invalidateThumbnail(const QString &id) @@ -388,6 +456,11 @@ bool AssetsLibraryWidget::assetIsImageOrTexture(const QString &id) return Asset(id).isValidTextureSource(); } +bool AssetsLibraryWidget::assetIsImported3d(const QString &id) +{ + return Asset(id).isImported3D(); +} + // needed to deal with "Object 0xXXXX destroyed while one of its QML signal handlers is in progress..." error which would lead to a crash void AssetsLibraryWidget::invokeAssetsDrop(const QList &urls, const QString &targetDir) { @@ -615,6 +688,9 @@ QPair AssetsLibraryWidget::getAssetTypeAndData(const QStrin } else if (asset.isEffect()) { // Data: Effect Composer format (suffix) return {Constants::MIME_TYPE_ASSET_EFFECT, asset.suffix().toUtf8()}; + } else if (asset.isImported3D()) { + // Data: Imported 3D component (suffix) + return {Constants::MIME_TYPE_ASSET_IMPORTED3D, asset.suffix().toUtf8()}; } } return {}; diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 6031b27ca5c..c3a2e188f22 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -51,7 +51,8 @@ class AssetsLibraryWidget : public QFrame Q_PROPERTY(bool isDragging MEMBER m_isDragging NOTIFY isDraggingChanged) public: - AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache, + AssetsLibraryWidget(AsynchronousImageCache &mainImageCache, + AsynchronousImageCache &asynchronousFontImageCache, SynchronousImageCache &synchronousFontImageCache, AssetsLibraryView *view); ~AssetsLibraryWidget(); @@ -88,6 +89,7 @@ public: Q_INVOKABLE QSize imageSize(const QString &id); Q_INVOKABLE QString assetFileSize(const QString &id); Q_INVOKABLE bool assetIsImageOrTexture(const QString &id); + Q_INVOKABLE bool assetIsImported3d(const QString &id); Q_INVOKABLE void addTextures(const QStringList &filePaths); Q_INVOKABLE void addLightProbe(const QString &filePaths); Q_INVOKABLE void updateContextMenuActionsEnableState(); @@ -130,10 +132,12 @@ private: void setHasSceneEnv(bool b); void setCanCreateEffects(bool newVal); - void handleDeleteEffects(const QStringList &effectNames); + void handleDeletedGeneratedAssets(const QHash &assetData); + void updateAssetPreview(const QString &id, const QPixmap &pixmap, const QString &suffix); QSize m_itemIconSize; + AsynchronousImageCache &m_mainImageCache; SynchronousImageCache &m_fontImageCache; AssetsLibraryIconProvider *m_assetsIconProvider = nullptr; diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d.png new file mode 100644 index 0000000000000000000000000000000000000000..62d5dc2d7daf7a6a5ecf4c232e86a30d3194a669 GIT binary patch literal 471 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4rT@hhJ-tuTNxM_j01c^T>t<7zk)=tV~WgI z1_lO!k|4ie1_1>Fhk%5Fh6xKc9Juh{L&$>_Cm0wQojqL~Lp+X8z2-fsMM1#zVta~a z6i500)o&ZWI(i$HjYiSi5%yGB$eJP0D51n^y0?$@5l2d&*>~0{6H| zZnIAs%Y(O`m)f47{Fq-e`7_^JM$=a1n@{Z@w99-F-k|Jjr!)1ZOpV9ncF6|q4@&n1 zwN01GSqLQaO8sKESMDF^cJEeo0_!KXMeJujF!p~>wn|X)Z*_GGi;&Dem^`WR@joMyaKmO5c^OhTuB*PG3PAZsY|6{SWR`8knb#^;TWZ_E^Ob>t zL7*haFPK3OGEu##tFP%7Mc79j+V;7t;MG>XKcfcu_ds;mfi80x};Zo3!m5 z3y!&Sy!+8Gf%y^R0T220hHElCO)PnG@)q$r-*#g-vtn`q14o0vYmP&DN$hW&{^mQ##$m2pBl&VLP04sMI=LI zk!uzcL(ig=b2hv${If~i3sSw?hk*^S`m9sgrQ+t zXKBd6S1UOfwsZ)r{NXCSIgF_w+e771zhM1Z7N&9k}xa#`4 zbM{56SU+jG1%vmZ5(|8Lm8$38}|6@Lff&@cp zNzP{$5eE0a2dd7l^G{%0u$wV)U7j?9JVUX0!uPthjDPGHN~K?0F|cPix;&G(!Ix09 zUHAnzA^n72Q1GWA|*6h{~cBsl6*lKU^mZ4yt z0CU0WzN@S(!fyNzem7P#L~Wa@%XwgJ)3f9Uj2|YhEe#a;aQS&n$?6|w;rt5ctv^=J z7L#Z!PAE2twCwV8Si>-3T|g^az}~sS*9xs9_ggVUe{H)`_hZTY(2PYrCBa{(&9bkv z&tJXiqxdocumentManager().currentProjectDirPath().toString())); } +FilePath getImported3dDefaultDirectory() +{ + return Utils::FilePath::fromString(getAssetDefaultDirectory( + "3d", + QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString())); +} + void jumpToCode(const ModelNode &modelNode) { QmlDesignerPlugin::instance()->viewManager().jumpToCodeInTextEditor(modelNode); @@ -2009,6 +2016,36 @@ ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const ModelNode return newModelNode; } +ModelNode handleImported3dAssetDrop(const QString &assetPath, const ModelNode &targetNode, + const QVector3D &position) +{ + AbstractView *view = targetNode.view(); + QTC_ASSERT(view, return {}); + QTC_ASSERT(targetNode.isValid(), return {}); + + ModelNode newModelNode; + + const GeneratedComponentUtils &compUtils = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils(); + + Utils::FilePath qmlFile = compUtils.getImported3dQml(assetPath); + if (qmlFile.exists()) { + TypeName qmlType = qmlFile.baseName().toUtf8(); + QString importName = compUtils.getImported3dImportName(qmlFile); + if (!importName.isEmpty() && !qmlType.isEmpty()) + newModelNode = QmlVisualNode::createQml3DNode(view, qmlType, targetNode, importName, position); + } else { + QMessageBox msgBox; + msgBox.setText(Tr::tr("Asset %1 is not complete.").arg(qmlFile.baseName())); + msgBox.setInformativeText(Tr::tr("Please reimport the asset.")); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Information); + msgBox.exec(); + } + + return newModelNode; +} + void handleTextureDrop(const QMimeData *mimeData, const ModelNode &targetModelNode) { AbstractView *view = targetModelNode.view(); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 116ea90d1c7..54fea337e36 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -8,6 +8,8 @@ #include +#include + namespace QmlDesigner { class AddFilesResult @@ -138,10 +140,14 @@ bool validateEffect(const QString &effectPath); bool isEffectComposerActivated(); QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getImagesDefaultDirectory(); +Utils::FilePath getImported3dDefaultDirectory(); //Item Library and Assets related drop operations QMLDESIGNERCOMPONENTS_EXPORT ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const ModelNode &targetNode); +ModelNode handleImported3dAssetDrop(const QString &assetPath, + const ModelNode &targetNode, + const QVector3D &position = {}); void handleTextureDrop(const QMimeData *mimeData, const ModelNode &targetModelNode); void handleMaterialDrop(const QMimeData *mimeData, const ModelNode &targetNode); ModelNode handleItemLibraryImageDrop(const QString &imagePath, diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index a07c58c8565..1924f771b26 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -68,7 +68,7 @@ public: #endif , formEditorView{externalDependencies} , textEditorView{externalDependencies} - , assetsLibraryView{externalDependencies} + , assetsLibraryView{imageCache, externalDependencies} , itemLibraryView(imageCache, externalDependencies) , navigatorView{externalDependencies} , propertyEditorView(imageCache, externalDependencies) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3d.qrc b/src/plugins/qmldesigner/components/edit3d/edit3d.qrc index 5784ac0e446..0d8083f2656 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3d.qrc +++ b/src/plugins/qmldesigner/components/edit3d/edit3d.qrc @@ -46,5 +46,7 @@ images/align_view_on@2x.png images/color_palette.png images/color_palette@2x.png + images/item-3D_model-icon.png + images/item-3D_model-icon@2x.png diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 7d4f2344a68..cd4f7540069 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -35,6 +35,8 @@ #include #include +#include + #include #include @@ -305,7 +307,11 @@ void Edit3DView::modelAttached(Model *model) if (QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit())) m_isBakingLightsSupported = qtVer->qtVersion() >= QVersionNumber(6, 5, 0); } -#ifndef QDS_USE_PROJECTSTORAGE +#ifdef QDS_USE_PROJECTSTORAGE + // TODO: Handle actual entries changed signal/notification once it is available. + // Until then, we simply get what entries are available at model attach time. + onEntriesChanged(); +#else connect(model->metaInfo().itemLibraryInfo(), &ItemLibraryInfo::entriesChanged, this, @@ -333,15 +339,14 @@ void Edit3DView::handleEntriesChanged() enum ItemLibraryEntryKeys : int { // used to maintain order EK_cameras, EK_lights, - EK_primitives, - EK_importedModels + EK_primitives }; QMap entriesMap{ {EK_cameras, {tr("Cameras"), contextIcon(DesignerIcons::CameraIcon)}}, {EK_lights, {tr("Lights"), contextIcon(DesignerIcons::LightIcon)}}, - {EK_primitives, {tr("Primitives"), contextIcon(DesignerIcons::PrimitivesIcon)}}, - {EK_importedModels, {tr("Imported Models"), contextIcon(DesignerIcons::ImportedModelsIcon)}}}; + {EK_primitives, {tr("Primitives"), contextIcon(DesignerIcons::PrimitivesIcon)}} + }; #ifdef QDS_USE_PROJECTSTORAGE auto append = [&](const NodeMetaInfo &metaInfo, ItemLibraryEntryKeys key) { @@ -356,16 +361,6 @@ void Edit3DView::handleEntriesChanged() append(model()->qtQuick3DPointLightMetaInfo(), EK_lights); append(model()->qtQuick3DOrthographicCameraMetaInfo(), EK_cameras); append(model()->qtQuick3DPerspectiveCameraMetaInfo(), EK_cameras); - - Utils::PathString import3dTypePrefix = QmlDesignerPlugin::instance() - ->documentManager() - .generatedComponentUtils() - .import3dTypePrefix(); - - auto assetsModule = model()->module(import3dTypePrefix, Storage::ModuleKind::QmlLibrary); - - for (const auto &metaInfo : model()->metaInfosForModule(assetsModule)) - append(metaInfo, EK_importedModels); #else const QList itemLibEntries = model()->metaInfo().itemLibraryInfo()->entries(); for (const ItemLibraryEntry &entry : itemLibEntries) { @@ -379,13 +374,6 @@ void Edit3DView::handleEntriesChanged() } else if (entry.typeName() == "QtQuick3D.OrthographicCamera" || entry.typeName() == "QtQuick3D.PerspectiveCamera") { entryKey = EK_cameras; - } else if (entry.typeName().startsWith(QmlDesignerPlugin::instance() - ->documentManager() - .generatedComponentUtils() - .import3dTypePrefix() - .toUtf8()) - && NodeHints::fromItemLibraryEntry(entry, model()).canBeDroppedInView3D()) { - entryKey = EK_importedModels; } else { continue; } @@ -464,6 +452,8 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view, self->m_nodeAtPosReqType = NodeAtPosReqType::MainScenePick; self->m_pickView3dNode = self->modelNodeForInternalId(qint32(data[1].toInt())); }); + } else if (identifier == "asset_import_finished" || identifier == "assets_deleted") { + handleEntriesChanged(); } } @@ -511,9 +501,22 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos emitCustomNotification("apply_texture_to_model3D", {modelNode, m_droppedModelNode}); } else if (m_nodeAtPosReqType == NodeAtPosReqType::AssetDrop) { bool isModel = modelNode.metaInfo().isQtQuick3DModel(); - if (!m_droppedFile.isEmpty() && isModel) { + if (!m_droppedTexture.isEmpty() && isModel) { QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser"); - emitCustomNotification("apply_asset_to_model3D", {modelNode}, {m_droppedFile}); // To MaterialBrowserView + emitCustomNotification("apply_asset_to_model3D", {modelNode}, {m_droppedTexture}); // To MaterialBrowserView + } else if (!m_dropped3dImports.isEmpty()) { + ModelNode sceneNode = Utils3D::active3DSceneNode(this); + if (!sceneNode.isValid()) + sceneNode = rootModelNode(); + ModelNode createdNode; + executeInTransaction(__FUNCTION__, [&] { + for (const QString &asset : std::as_const(m_dropped3dImports)) { + createdNode = ModelNodeOperations::handleImported3dAssetDrop( + asset, sceneNode, pos3d); + } + }); + if (createdNode.isValid()) + setSelectedModelNode(createdNode); } } else if (m_nodeAtPosReqType == NodeAtPosReqType::MainScenePick) { if (modelNode.isValid()) @@ -524,7 +527,8 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos } m_droppedModelNode = {}; - m_droppedFile.clear(); + m_dropped3dImports.clear(); + m_droppedTexture.clear(); m_nodeAtPosReqType = NodeAtPosReqType::None; } @@ -1462,10 +1466,22 @@ void Edit3DView::dropComponent(const ItemLibraryEntry &entry, const QPointF &pos nodeAtPosReady({}, {}); // No need to actually resolve position for non-node items } -void Edit3DView::dropAsset(const QString &file, const QPointF &pos) +void QmlDesigner::Edit3DView::dropAssets(const QList &urls, const QPointF &pos) { m_nodeAtPosReqType = NodeAtPosReqType::AssetDrop; - m_droppedFile = file; + m_dropped3dImports.clear(); + + for (const QUrl &url : urls) { + Asset asset(url.toLocalFile()); + // For textures we only support single drops + if (m_dropped3dImports.isEmpty() && asset.isTexture3D()) { + m_droppedTexture = asset.fileName(); + break; + } else if (asset.isImported3D()) { + m_dropped3dImports.append(asset.id()); + } + } + emitView3DAction(View3DActionType::GetNodeAtPos, pos); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 108a689a06e..5e45ee80889 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -101,7 +101,7 @@ public: void dropBundleItem(const QPointF &pos); void dropTexture(const ModelNode &textureNode, const QPointF &pos); void dropComponent(const ItemLibraryEntry &entry, const QPointF &pos); - void dropAsset(const QString &file, const QPointF &pos); + void dropAssets(const QList &urls, const QPointF &pos); bool isBakingLightsSupported() const; @@ -203,7 +203,8 @@ private: ModelCache m_canvasCache; ModelNode m_droppedModelNode; ItemLibraryEntry m_droppedEntry; - QString m_droppedFile; + QStringList m_dropped3dImports; + QString m_droppedTexture; NodeAtPosReqType m_nodeAtPosReqType; QPoint m_contextMenuPosMouse; QVector3D m_contextMenuPos3D; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 9808da6e696..ade6bda89c7 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -17,10 +17,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -455,6 +453,7 @@ void Edit3DWidget::updateCreateSubMenu(const QList &entriesL m_createSubMenu->deleteLater(); } + m_nameToImport.clear(); m_nameToEntry.clear(); m_createSubMenu = new QmlEditorMenu(tr("Create"), m_contextMenu); @@ -493,9 +492,43 @@ void Edit3DWidget::updateCreateSubMenu(const QList &entriesL QAction *action = catMenu->addAction(getEntryIcon(entry), entry.name()); connect(action, &QAction::triggered, this, [this, action] { onCreateAction(action); }); action->setData(entry.name()); + Import import = Import::createLibraryImport(entry.requiredImport(), + QString::number(entry.majorVersion()) + + QLatin1Char('.') + + QString::number(entry.minorVersion())); + m_nameToImport.insert(entry.name(), import); m_nameToEntry.insert(entry.name(), entry); } } + + // Create menu for imported 3d models, which don't have ItemLibraryEntries + const GeneratedComponentUtils &compUtils + = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + QList qmlFiles = compUtils.imported3dComponents(); + QMenu *catMenu = nullptr; + + if (!qmlFiles.isEmpty()) { + catMenu = new QmlEditorMenu(tr("Imported Models"), m_createSubMenu); + catMenu->setIcon(contextIcon(DesignerIcons::ImportedModelsIcon)); + m_createSubMenu->addMenu(catMenu); + + std::ranges::sort(qmlFiles, {}, &Utils::FilePath::baseName); + + const QIcon icon = QIcon(":/edit3d/images/item-3D_model-icon.png"); + + for (const Utils::FilePath &qmlFile : std::as_const(qmlFiles)) { + QString qmlName = qmlFile.baseName(); + QAction *action = catMenu->addAction(icon, qmlName); + connect(action, &QAction::triggered, this, [this, action] { onCreateAction(action); }); + action->setData(qmlName); + + QString importName = compUtils.getImported3dImportName(qmlFile); + if (!importName.isEmpty()) { + Import import = Import::createLibraryImport(importName); + m_nameToImport.insert(qmlName, import); + } + } + } } // Action triggered from the "create" sub-menu @@ -505,17 +538,29 @@ void Edit3DWidget::onCreateAction(QAction *action) return; m_view->executeInTransaction(__FUNCTION__, [&] { - ItemLibraryEntry entry = m_nameToEntry.value(action->data().toString()); - Import import = Import::createLibraryImport(entry.requiredImport(), - QString::number(entry.majorVersion()) - + QLatin1Char('.') - + QString::number(entry.minorVersion())); + const QString actionName = action->data().toString(); + Import import = m_nameToImport.value(actionName); + if (!m_view->model()->hasImport(import, true, true)) m_view->model()->changeImports({import}, {}); - int activeScene = Utils3D::active3DSceneId(m_view->model()); - auto modelNode = QmlVisualNode::createQml3DNode(m_view, entry, - activeScene, m_contextMenuPos3d).modelNode(); + ModelNode modelNode; + if (m_nameToEntry.contains(actionName)) { + int activeScene = Utils3D::active3DSceneId(m_view->model()); + ItemLibraryEntry entry = m_nameToEntry.value(actionName); + + modelNode = QmlVisualNode::createQml3DNode(m_view, entry, activeScene, + m_contextMenuPos3d).modelNode(); + } else { + ModelNode sceneNode = Utils3D::active3DSceneNode(m_view); + if (!sceneNode.isValid()) + sceneNode = m_view->rootModelNode(); + + modelNode = QmlVisualNode::QmlVisualNode::createQml3DNode( + m_view, actionName.toUtf8(), sceneNode, import.url(), + m_contextMenuPos3d).modelNode(); + } + QTC_ASSERT(modelNode.isValid(), return); m_view->setSelectedModelNode(modelNode); @@ -717,9 +762,12 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) if (dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ASSETS) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_TEXTURE)) { const auto urls = dragEnterEvent->mimeData()->urls(); - if (!urls.isEmpty()) { - if (Asset(urls.first().toLocalFile()).isValidTextureSource()) + for (const QUrl &url : urls) { + Asset asset(url.toLocalFile()); + if (asset.isImported3D() || asset.isTexture3D()) { dragEnterEvent->acceptProposedAction(); + break; + } } } else if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) @@ -787,7 +835,7 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) // handle dropping image assets if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ASSETS) || dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_TEXTURE)) { - m_view->dropAsset(dropEvent->mimeData()->urls().first().toLocalFile(), pos); + m_view->dropAssets(dropEvent->mimeData()->urls(), pos); m_view->model()->endDrag(); return; } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index d5fe2f1d207..9b286699977 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include @@ -111,6 +112,7 @@ private: QPointer m_createSubMenu; ModelNode m_contextMenuTarget; QVector3D m_contextMenuPos3d; + QHash m_nameToImport; QHash m_nameToEntry; ItemLibraryEntry m_draggedEntry; QHash m_actionToCommandHash; diff --git a/src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon.png b/src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bbdafae8872f0bd7fa378d972146d7722602a339 GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4rT@hhU+WOo?>8Ns0i>0ab;j&STPXb!okpK z3=9nFB|(0{42(?7Y&^UoGO}_Sre-d#0U;@=nK>nuEv-|QEZws8$oWh6UVQleN0Q@0 zBLf4&a!(h>5RKrq6K;z&DG0c_U%Fxv{ApU+y}AGYA8t6p@OJHImg@DU>}(0%H?}Fh zd3^mu&~LV~Q2_0 literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon@2x.png b/src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7ab1e27ac3c5e4aca75c175b33e582427aec2d1f GIT binary patch literal 733 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4rT@hhJ-tuTNxM_o&@-WxH2#>tQhWK-vr6m z3=9n3B|(0{42(?7ENmQ{Ts-_jqLR`watbP{8oCBX<~B~w9=;(_@d;@;g+&#$&0YNy zrcRqNXTiedYd7!Qf8^BZ^OtYleem?f>v!)ze*f|7&)EC4*b+z+KRYeWb%}eIW{R>#=`j;pAW%*Ruo;Oq#uVPe`DmaI)>H)R-BPkR2V)n@6`x{0C;m+#y> z@7wGBhCyOkhlZ(%{s$(RTm5gDWms(fHSZTG32}^l8{^g!u&7LE*2pNmLtmjB*6J(F}NW-FO1fBU5?c+LN0+w%Vmyf5Z& VJyhMSz`(%3;OXk;vd$@?2>^UZdBy+$ literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/import3d/import3dimporter.cpp b/src/plugins/qmldesigner/components/import3d/import3dimporter.cpp index e9e04a9a7f3..a249250122a 100644 --- a/src/plugins/qmldesigner/components/import3d/import3dimporter.cpp +++ b/src/plugins/qmldesigner/components/import3d/import3dimporter.cpp @@ -398,9 +398,6 @@ void Import3dImporter::postParseQuick3DAsset(ParseData &pd) qmlInfo.append("."); qmlInfo.append(pd.assetName); qmlInfo.append('\n'); - const QString reqImp = generateRequiredImportForAsset(pd.assetName); - if (!m_requiredImports.contains(reqImp)) - m_requiredImports.append(reqImp); while (qmlIt.hasNext()) { qmlIt.next(); QFileInfo fi = QFileInfo(qmlIt.filePath()); @@ -454,8 +451,10 @@ void Import3dImporter::postParseQuick3DAsset(ParseData &pd) } // Add quick3D import unless it is already added - if (impVersionMajor > 0 && m_requiredImports.first() != "QtQuick3D") + if (impVersionMajor > 0 && (m_requiredImports.isEmpty() + || m_requiredImports.first() != "QtQuick3D")) { m_requiredImports.prepend("QtQuick3D"); + } } } } @@ -577,13 +576,6 @@ QString Import3dImporter::generateAssetFolderName(const QString &assetName) cons return assetName + "_QDS_" + QString::number(counter++); } -QString Import3dImporter::generateRequiredImportForAsset(const QString &assetName) const -{ - return QStringLiteral("%1.%2").arg( - QmlDesignerPlugin::instance()->documentManager() - .generatedComponentUtils().import3dTypePrefix(), assetName); -} - Import3dImporter::OverwriteResult Import3dImporter::confirmAssetOverwrite(const QString &assetName) { const QString title = tr("Overwrite Existing Asset?"); @@ -744,9 +736,6 @@ void Import3dImporter::finalizeQuick3DImport() addError(tr("Failed to insert import statement into qml document.")); transaction.commit(); #else - // TODO: ModelUtils::addImportsWithCheck requires Model::possibleImports() - // to return correct list instead of empty list, so until that is - // fixed we need to just trust the missing modules are available const Imports &imports = model->imports(); Imports importsToAdd; for (const QString &importName : std::as_const(m_requiredImports)) { @@ -775,6 +764,7 @@ void Import3dImporter::finalizeQuick3DImport() } timer->stop(); notifyFinished(); + model->rewriterView()->emitCustomNotification("asset_import_finished"); } } else { timer->stop(); @@ -791,7 +781,6 @@ void Import3dImporter::removeAssetFromImport(const QString &assetName) { m_parseData.remove(assetName); m_importFiles.remove(assetName); - m_requiredImports.removeOne(generateRequiredImportForAsset(assetName)); } QString Import3dImporter::sourceSceneTargetFilePath(const ParseData &pd) diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index f726be89083..314d24ff176 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -685,6 +685,55 @@ RewriterView *DesignDocument::rewriterView() const return m_rewriterView.get(); } +#ifndef QDS_USE_PROJECTSTORAGE +static void removeUnusedImports(RewriterView *rewriter) +{ + // Remove any import statements for asset based nodes (composed effect or imported3d) + // if there is no nodes using them in the scene. + QTC_ASSERT(rewriter && rewriter->model(), return); + + GeneratedComponentUtils compUtils{rewriter->externalDependencies()}; + + const QString effectPrefix = compUtils.composedEffectsTypePrefix(); + const QString imported3dPrefix = compUtils.import3dTypePrefix(); + const QList qmlFiles = compUtils.imported3dComponents(); + QHash m_imported3dTypeMap; + for (const Utils::FilePath &qmlFile : qmlFiles) { + QString importName = compUtils.getImported3dImportName(qmlFile); + QString type = qmlFile.baseName(); + m_imported3dTypeMap.insert(importName, type); + } + + const Imports &imports = rewriter->model()->imports(); + QHash assetImports; + for (const Import &import : imports) { + if (import.url().startsWith(effectPrefix)) { + QString type = import.url().split('.').last(); + assetImports.insert(type, import); + } else if (import.url().startsWith(imported3dPrefix)) { + assetImports.insert(m_imported3dTypeMap[import.url()], import); + } + } + + const QList allNodes = rewriter->allModelNodes(); + for (const ModelNode &node : allNodes) { + if (QmlItemNode(node).isEffectItem() + || (node.isComponent() && node.metaInfo().isQtQuick3DNode())) { + assetImports.remove(node.simplifiedTypeName()); + } + } + + if (!assetImports.isEmpty()) { + Imports removeImports; + for (const Import &import : assetImports) + removeImports.append(import); + rewriter->model()->changeImports({}, removeImports); + } + + rewriter->forceAmend(); +} +#endif + void DesignDocument::setEditor(Core::IEditor *editor) { m_textEditor = editor; @@ -694,6 +743,12 @@ void DesignDocument::setEditor(Core::IEditor *editor) this, [this](Core::IDocument *document) { if (m_textEditor && m_textEditor->document() == document) { if (m_documentModel && m_documentModel->rewriterView()) { + +#ifdef QDS_USE_PROJECTSTORAGE + // TODO: ProjectStorage should handle this via Model somehow (QDS-14519) +#else + removeUnusedImports(rewriterView()); +#endif m_documentModel->rewriterView()->writeAuxiliaryData(); } } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.cpp index 2bed3eeefb3..bdac4a78cb5 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.cpp @@ -19,9 +19,6 @@ QString ItemLibraryImport::importName() const if (m_sectionType == SectionType::User) return userComponentsTitle(); - if (m_sectionType == SectionType::Quick3DAssets) - return quick3DAssetsTitle(); - if (m_sectionType == SectionType::Unimported) return unimportedComponentsTitle(); @@ -39,9 +36,6 @@ QString ItemLibraryImport::importUrl() const if (m_sectionType == SectionType::User) return userComponentsTitle(); - if (m_sectionType == SectionType::Quick3DAssets) - return quick3DAssetsTitle(); - if (m_sectionType == SectionType::Unimported) return unimportedComponentsTitle(); @@ -61,9 +55,6 @@ QString ItemLibraryImport::sortingName() const if (m_sectionType == SectionType::User) return "_"; // user components always come first - if (m_sectionType == SectionType::Quick3DAssets) - return "__"; // Quick3DAssets come second - if (m_sectionType == SectionType::Unimported) return "zzzzzz"; // Unimported components come last @@ -235,12 +226,6 @@ QString ItemLibraryImport::userComponentsTitle() return tr("My Components"); } -// static -QString ItemLibraryImport::quick3DAssetsTitle() -{ - return tr("My 3D Components"); -} - // static QString ItemLibraryImport::unimportedComponentsTitle() { diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.h index f15c4803af2..62f9a431c53 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimport.h @@ -28,7 +28,6 @@ public: enum class SectionType { Default, User, - Quick3DAssets, Unimported }; @@ -67,7 +66,6 @@ public: bool importUnimported() const { return m_sectionType == SectionType::Unimported; } static QString userComponentsTitle(); - static QString quick3DAssetsTitle(); static QString unimportedComponentsTitle(); SectionType sectionType() const; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index ad21b78b124..64f2c384caa 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -319,12 +319,7 @@ void ItemLibraryModel::update(Model *model) auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); QStringList excludedImports { - projectName, - compUtils.materialsBundleType(), - compUtils.effectsBundleType(), - compUtils.userMaterialsBundleType(), - compUtils.user3DBundleType(), - compUtils.userEffectsBundleType() + projectName }; // create import sections @@ -332,23 +327,17 @@ void ItemLibraryModel::update(Model *model) QHash importHash; for (const Import &import : model->imports()) { if (excludedImports.contains(import.url()) - || import.url().startsWith(compUtils.composedEffectsTypePrefix())) { + || import.url().startsWith(compUtils.generatedComponentTypePrefix())) { continue; } bool addNew = true; - bool isQuick3DAsset = import.url().startsWith(compUtils.import3dTypePrefix()); QString importUrl = import.url(); - if (isQuick3DAsset) - importUrl = ItemLibraryImport::quick3DAssetsTitle(); - else if (import.isFileImport()) + if (import.isFileImport()) importUrl = import.toString(true, true).remove("\""); ItemLibraryImport *oldImport = importHash.value(importUrl); - if (oldImport && oldImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets - && isQuick3DAsset) { - addNew = false; // add only 1 Quick3DAssets import section - } else if (oldImport && oldImport->importEntry().url() == import.url()) { + if (oldImport && oldImport->importEntry().url() == import.url()) { // Retain the higher version if multiples exist if (oldImport->importEntry().toVersion() >= import.toVersion() || import.hasVersion()) addNew = false; @@ -357,8 +346,7 @@ void ItemLibraryModel::update(Model *model) } if (addNew) { - auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets - : ItemLibraryImport::SectionType::Default; + auto sectionType = ItemLibraryImport::SectionType::Default; ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType); itemLibImport->setImportUsed(usedImports.contains(import)); importHash.insert(importUrl, itemLibImport); @@ -445,8 +433,6 @@ void ItemLibraryModel::update(Model *model) importSection = importHash[entry.requiredImport()]; } - } else if (catName == ItemLibraryImport::quick3DAssetsTitle()) { - importSection = importHash[ItemLibraryImport::quick3DAssetsTitle()]; } else { if (catName.contains("Qt Quick - ")) { QString sortingName = catName; @@ -549,8 +535,6 @@ ItemLibraryImport *ItemLibraryModel::importByUrl(const QString &importUrl) const || (importUrl.isEmpty() && itemLibraryImport->importUrl() == "QtQuick") || (importUrl == ItemLibraryImport::userComponentsTitle() && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::User) - || (importUrl == ItemLibraryImport::quick3DAssetsTitle() - && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets) || (importUrl == ItemLibraryImport::unimportedComponentsTitle() && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::Unimported)) { return itemLibraryImport; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index ef0f0d58c9e..47798ff4347 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -1015,7 +1015,7 @@ void MaterialEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap, const QByteArray &requestId) { - if (node != m_selectedMaterial || requestId != m_previewRequestId) + if (!m_qmlBackEnd || node != m_selectedMaterial || requestId != m_previewRequestId) return; m_qmlBackEnd->updateMaterialPreview(pixmap); diff --git a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp index a9ce65ca9ca..5da04da2ae3 100644 --- a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp @@ -239,6 +239,10 @@ void NameItemDelegate::paint(QPainter *painter, model->qtQuickBorderImageMetaInfo()); } else if (dragType == Constants::MIME_TYPE_ASSET_EFFECT) { validDrop = metaInfo.isBasedOn(node.model()->qtQuickItemMetaInfo()); + } else if (dragType == Constants::MIME_TYPE_ASSET_IMPORTED3D) { + Model *model = node.model(); + validDrop = metaInfo.isBasedOn(model->qtQuick3DNodeMetaInfo(), + model->qtQuick3DView3DMetaInfo()); } else { const NodeMetaInfo dragInfo = node.model()->metaInfo(dragType); ChooseFromPropertyListFilter *filter = new ChooseFromPropertyListFilter(dragInfo, metaInfo, true); diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index d93d39e1a10..7787e0ef829 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -669,6 +669,10 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, currNode = ModelNodeOperations::handleItemLibraryEffectDrop( assetPath, modelNodeForIndex(rowModelIndex)); moveNodesAfter = false; + } else if (assetType == Constants::MIME_TYPE_ASSET_IMPORTED3D) { + currNode = ModelNodeOperations::handleImported3dAssetDrop( + assetPath, modelNodeForIndex(rowModelIndex)); + moveNodesAfter = false; } if (currNode.isValid()) diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 5fa18097202..fa5efd65346 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -306,6 +306,9 @@ void NavigatorView::dragStarted(QMimeData *mimeData) if (assetType == Constants::MIME_TYPE_ASSET_EFFECT) { m_widget->setDragType(Constants::MIME_TYPE_ASSET_EFFECT); m_widget->update(); + } else if (assetType == Constants::MIME_TYPE_ASSET_IMPORTED3D) { + m_widget->setDragType(Constants::MIME_TYPE_ASSET_IMPORTED3D); + m_widget->update(); } else if (assetType == Constants::MIME_TYPE_ASSET_TEXTURE3D) { m_widget->setDragType(Constants::MIME_TYPE_ASSET_TEXTURE3D); m_widget->update(); diff --git a/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.cpp b/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.cpp index b1b7161c31c..00723e65047 100644 --- a/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.cpp +++ b/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.cpp @@ -337,4 +337,61 @@ QString GeneratedComponentUtils::user3DBundleType() const return componentBundlesTypePrefix() + '.' + user3DBundleId(); } +QList GeneratedComponentUtils::imported3dComponents() const +{ + auto import3dPath = Utils::FilePath::fromString(import3dTypePath()); + auto projPath = Utils::FilePath::fromString(m_externalDependencies.currentProjectDirPath()); + auto fullPath = projPath.resolvePath(import3dPath); + + if (fullPath.isEmpty()) + return {}; + + return collectFiles(fullPath, "qml"); +} + +QString GeneratedComponentUtils::getImported3dImportName(const Utils::FilePath &qmlFile) const +{ + const QStringList sl = qmlFile.toFSPathString().split('/'); + int i = sl.size() - 4; + if (i >= 0) + return QStringView(u"%1.%2.%3").arg(sl[i], sl[i + 1], sl[i + 2]); + return {}; +} + +Utils::FilePath GeneratedComponentUtils::getImported3dQml(const QString &assetPath) const +{ + Utils::FilePath assetFilePath = Utils::FilePath::fromString(assetPath); + const Utils::expected_str data = assetFilePath.fileContents(); + + if (!data) + return {}; + + Utils::FilePath assetQmlFilePath = Utils::FilePath::fromUtf8(data.value()); + Utils::FilePath projectPath = Utils::FilePath::fromString(m_externalDependencies.currentProjectDirPath()); + + assetQmlFilePath = projectPath.resolvePath(assetQmlFilePath); + + return assetQmlFilePath; +} + +// Recursively find files of certain suffix in a dir +QList GeneratedComponentUtils::collectFiles(const Utils::FilePath &dirPath, + const QString &suffix) const +{ + if (dirPath.isEmpty()) + return {}; + + QList files; + + const Utils::FilePaths entryList = dirPath.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + for (const Utils::FilePath &entry : entryList) { + if (entry.isDir()) + files.append(collectFiles(entry.absoluteFilePath(), suffix)); + else if (entry.suffix() == suffix) + files.append(entry); + } + + return files; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.h b/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.h index f35dee554a7..eafad8189c5 100644 --- a/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.h +++ b/src/plugins/qmldesigner/libs/designercore/designercoreutils/generatedcomponentutils.h @@ -50,8 +50,14 @@ public: QString userEffectsBundleType() const; QString user3DBundleType() const; + QList imported3dComponents() const; + QString getImported3dImportName(const Utils::FilePath &qmlFile) const; + Utils::FilePath getImported3dQml(const QString &assetPath) const; + private: ExternalDependenciesInterface &m_externalDependencies; + + QList collectFiles(const Utils::FilePath &dirPath, const QString &suffix) const; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designercore/include/model.h b/src/plugins/qmldesigner/libs/designercore/include/model.h index 9c8bcd10827..9ee93be5b09 100644 --- a/src/plugins/qmldesigner/libs/designercore/include/model.h +++ b/src/plugins/qmldesigner/libs/designercore/include/model.h @@ -182,6 +182,7 @@ public: NodeMetaInfo qtQuick3DSpotLightMetaInfo() const; NodeMetaInfo qtQuick3DTextureMetaInfo() const; NodeMetaInfo qtQuick3DTextureInputMetaInfo() const; + NodeMetaInfo qtQuick3DView3DMetaInfo() const; NodeMetaInfo qtQuickBorderImageMetaInfo() const; NodeMetaInfo qtQuickControlsLabelMetaInfo() const; NodeMetaInfo qtQuickControlsTextAreaMetaInfo() const; diff --git a/src/plugins/qmldesigner/libs/designercore/model/model.cpp b/src/plugins/qmldesigner/libs/designercore/model/model.cpp index eac261eca8c..c8956b45682 100644 --- a/src/plugins/qmldesigner/libs/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/libs/designercore/model/model.cpp @@ -2613,6 +2613,16 @@ NodeMetaInfo Model::qtQuick3DTextureInputMetaInfo() const } } +NodeMetaInfo Model::qtQuick3DView3DMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQuick3D.View3D"); + } +} + NodeMetaInfo Model::qtQuickBorderImageMetaInfo() const { if constexpr (useProjectStorage()) { diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/asset.cpp b/src/plugins/qmldesigner/libs/qmldesignerutils/asset.cpp index 445a0e4fb8b..54e814fb4e9 100644 --- a/src/plugins/qmldesigner/libs/qmldesignerutils/asset.cpp +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/asset.cpp @@ -85,6 +85,12 @@ const QStringList &Asset::supportedEffectComposerSuffixes() return retList; } +const QStringList &Asset::supportedImported3dSuffixes() +{ + static QStringList retList {"*.q3d"}; + return retList; +} + const QSet &Asset::supportedSuffixes() { static QSet allSuffixes; @@ -100,6 +106,7 @@ const QSet &Asset::supportedSuffixes() insertSuffixes(supportedVideoSuffixes()); insertSuffixes(supportedTexture3DSuffixes()); insertSuffixes(supportedEffectComposerSuffixes()); + insertSuffixes(supportedImported3dSuffixes()); } return allSuffixes; } @@ -182,6 +189,11 @@ bool Asset::isEffect() const return m_type == Asset::Type::Effect; } +bool Asset::isImported3D() const +{ + return m_type == Asset::Type::Imported3D; +} + const QString Asset::suffix() const { return m_suffix; @@ -236,7 +248,8 @@ void Asset::resolveType() m_type = Asset::Type::Texture3D; else if (supportedEffectComposerSuffixes().contains(m_suffix)) m_type = Asset::Type::Effect; -} + else if (supportedImported3dSuffixes().contains(m_suffix)) + m_type = Asset::Type::Imported3D;} bool Asset::hasSuffix() const { diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/asset.h b/src/plugins/qmldesigner/libs/qmldesignerutils/asset.h index e85c1462a40..0f6f2198d09 100644 --- a/src/plugins/qmldesigner/libs/qmldesignerutils/asset.h +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/asset.h @@ -15,17 +15,20 @@ namespace QmlDesigner { class QMLDESIGNERUTILS_EXPORT Asset { public: - enum Type { Unknown, - Image, - MissingImage, - FragmentShader, - VertexShader, - Font, - Audio, - Video, - Texture3D, - Effect, - Folder }; + enum Type { + Unknown, + Image, + MissingImage, + FragmentShader, + VertexShader, + Font, + Audio, + Video, + Texture3D, + Effect, + Folder, + Imported3D + }; Asset(const QString &filePath); @@ -38,6 +41,7 @@ public: static const QStringList &supportedVideoSuffixes(); static const QStringList &supportedTexture3DSuffixes(); static const QStringList &supportedEffectComposerSuffixes(); + static const QStringList &supportedImported3dSuffixes(); static const QSet &supportedSuffixes(); static bool isSupported(const QString &path); @@ -59,6 +63,7 @@ public: bool isHdrFile() const; bool isKtxFile() const; bool isEffect() const; + bool isImported3D() const; bool isSupported() const; bool isValidTextureSource(); bool isFolder() const; diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 919cba6b9b8..ef3f85d5b72 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -87,6 +87,7 @@ inline constexpr char MIME_TYPE_ASSET_TEXTURE3D[] = "application/vnd.qtdesignstudio.asset.texture3d"; inline constexpr char MIME_TYPE_MODELNODE_LIST[] = "application/vnd.qtdesignstudio.modelnode.list"; inline constexpr char MIME_TYPE_ASSET_EFFECT[] = "application/vnd.qtdesignstudio.asset.effect"; +inline constexpr char MIME_TYPE_ASSET_IMPORTED3D[] = "application/vnd.qtdesignstudio.asset.imported3d"; // Menus inline constexpr char M_VIEW_WORKSPACES[] = "QmlDesigner.Menu.View.Workspaces"; diff --git a/src/plugins/qmldesigner/qmltools/qmlvisualnode.cpp b/src/plugins/qmldesigner/qmltools/qmlvisualnode.cpp index d130215fbb1..bfea0e4d624 100644 --- a/src/plugins/qmldesigner/qmltools/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/qmltools/qmlvisualnode.cpp @@ -482,6 +482,58 @@ QmlVisualNode QmlVisualNode::createQml3DNode(AbstractView *view, return createQmlObjectNode(view, itemLibraryEntry, position, sceneNodeProperty, createInTransaction).modelNode(); } +QmlVisualNode QmlVisualNode::createQml3DNode(AbstractView *view, + const TypeName &typeName, + const ModelNode &parentNode, + const QString &importName, + const QVector3D &position, + bool createInTransaction) +{ + NodeAbstractProperty targetParentProperty = parentNode.defaultNodeListProperty(); + + QTC_ASSERT(targetParentProperty.isValid(), return {}); + + QTC_ASSERT(!typeName.isEmpty(), return {}); + + QmlVisualNode newQmlObjectNode; + + auto createNodeFunc = [&]() { + if (!importName.isEmpty()) { + Import import = Import::createLibraryImport(importName); + view->model()->changeImports({import}, {}); + } + + QList > propertyPairList; + propertyPairList.append(Position(position).propertyPairList()); +#ifdef QDS_USE_PROJECTSTORAGE + newQmlObjectNode = QmlVisualNode(view->createModelNode(typeName, + propertyPairList)); +#else + NodeMetaInfo metaInfo = view->model()->metaInfo(typeName); + newQmlObjectNode = QmlVisualNode(view->createModelNode(typeName, + metaInfo.majorVersion(), + metaInfo.minorVersion(), + propertyPairList)); +#endif + + if (newQmlObjectNode.id().isEmpty()) { + newQmlObjectNode.modelNode().setIdWithoutRefactoring( + view->model()->generateNewId(QString::fromUtf8(typeName))); + } + + if (targetParentProperty.isValid()) + targetParentProperty.reparentHere(newQmlObjectNode); + }; + + if (createInTransaction) + view->executeInTransaction(__FUNCTION__, createNodeFunc); + else + createNodeFunc(); + + return newQmlObjectNode; +} + + NodeListProperty QmlVisualNode::findSceneNodeProperty(AbstractView *view, qint32 sceneRootId) { QTC_ASSERT(view, return {}); diff --git a/src/plugins/qmldesigner/qmltools/qmlvisualnode.h b/src/plugins/qmldesigner/qmltools/qmlvisualnode.h index 4aea4872495..9671bcf3952 100644 --- a/src/plugins/qmldesigner/qmltools/qmlvisualnode.h +++ b/src/plugins/qmldesigner/qmltools/qmlvisualnode.h @@ -88,6 +88,13 @@ public: qint32 sceneRootId = -1, const QVector3D &position = {}, bool createInTransaction = true); + static QmlVisualNode createQml3DNode(AbstractView *view, + const TypeName &typeName, + const ModelNode &parentNode, + const QString &importName = {}, + const QVector3D &position = {}, + bool createInTransaction = true); + static NodeListProperty findSceneNodeProperty(AbstractView *view, qint32 sceneRootId); static bool isFlowTransition(const ModelNode &node);