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 00000000000..62d5dc2d7da Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d.png differ diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d@2x.png new file mode 100644 index 00000000000..8c727ea70e3 Binary files /dev/null and b/src/plugins/qmldesigner/components/assetslibrary/images/asset_imported3d@2x.png differ diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index b4b136d3ec9..3ae46fdcc70 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1879,6 +1879,13 @@ Utils::FilePath getImagesDefaultDirectory() QmlDesignerPlugin::instance()->documentManager().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 00000000000..bbdafae8872 Binary files /dev/null and b/src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon.png differ 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 00000000000..7ab1e27ac3c Binary files /dev/null and b/src/plugins/qmldesigner/components/edit3d/images/item-3D_model-icon@2x.png differ 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);