From 01501e00dc84052d8bf5fb696c8beabe1cbe0cb4 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 31 May 2024 16:33:38 +0300 Subject: [PATCH] QmlDesigner: Show more information on import 3D dialog list Preview image and some additional info are now shown on 3D import dialog's list of imported objects. Individual items can be removed from the list by clicking "x" button on the list or pressing delete key. Fixes: QDS-12900 Change-Id: Iad366ea308203a25bc3379b47c1d9c4f75f13fa7 Reviewed-by: Ali Kianian Reviewed-by: Mahmoud Badri --- .../commands/puppettocreatorcommand.h | 1 + .../itemlibrary/import3dconnectionmanager.cpp | 14 + .../itemlibrary/import3dconnectionmanager.h | 3 + .../itemlibraryassetimportdialog.cpp | 275 ++++++++++++++---- .../itemlibraryassetimportdialog.h | 37 ++- .../itemlibraryassetimportdialog.ui | 2 +- .../itemlibrary/itemlibraryassetimporter.cpp | 49 +++- .../itemlibrary/itemlibraryassetimporter.h | 7 +- .../qt5import3dnodeinstanceserver.cpp | 226 ++++++++++---- .../instances/qt5import3dnodeinstanceserver.h | 21 +- 10 files changed, 484 insertions(+), 151 deletions(-) diff --git a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h index b0012f3eb24..a8f37a81e53 100644 --- a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h +++ b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h @@ -18,6 +18,7 @@ public: ActiveSceneChanged, ActiveSplitChanged, RenderModelNodePreviewImage, + Import3DPreviewIcon, Import3DPreviewImage, Import3DSupport, NodeAtPos, diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp index 4c455e3c6d2..efa5c27bca3 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp @@ -16,6 +16,11 @@ Import3dConnectionManager::Import3dConnectionManager() connections().emplace_back("Import 3D", "import3dmode"); } +void Import3dConnectionManager::setPreviewIconCallback(IconCallback callback) +{ + m_previewIconCallback = std::move(callback); +} + void Import3dConnectionManager::setPreviewImageCallback(ImageCallback callback) { m_previewImageCallback = std::move(callback); @@ -29,6 +34,15 @@ void Import3dConnectionManager::dispatchCommand(const QVariant &command, if (command.typeId() == commandType) { auto cmd = command.value(); switch (cmd.type()) { + case PuppetToCreatorCommand::Import3DPreviewIcon: { + const QVariantList data = cmd.data().toList(); + const QString assetName = data[0].toString(); + ImageContainer container = qvariant_cast(data[1]); + QImage image = container.image(); + if (!image.isNull()) + m_previewIconCallback(assetName, image); + break; + } case PuppetToCreatorCommand::Import3DPreviewImage: { ImageContainer container = qvariant_cast(cmd.data()); QImage image = container.image(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h index 458d612ad2d..cc6b90b3f2e 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h +++ b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h @@ -12,16 +12,19 @@ namespace QmlDesigner { class Import3dConnectionManager : public InteractiveConnectionManager { public: + using IconCallback = std::function; using ImageCallback = std::function; Import3dConnectionManager(); + void setPreviewIconCallback(IconCallback callback); void setPreviewImageCallback(ImageCallback callback); protected: void dispatchCommand(const QVariant &command, Connection &connection) override; private: + IconCallback m_previewIconCallback; ImageCallback m_previewImageCallback; }; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp index f6b4b388c16..6fca8a4b82c 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -7,6 +7,8 @@ #include "import3dcanvas.h" #include "import3dconnectionmanager.h" +#include +#include #include #include #include @@ -18,6 +20,7 @@ #include #include +#include #include #include @@ -25,7 +28,9 @@ #include #include +#include #include +#include #include #include #include @@ -63,6 +68,17 @@ void addFormattedMessage(Utils::OutputFormatter *formatter, formatter->plainTextEdit()->verticalScrollBar()->maximum()); } +QIcon iconFromIconFont(Theme::Icon iconType, const QColor &color) +{ + const QString unicode = Theme::getIconUnicode(iconType); + const QString fontName = "qtds_propertyIconFont.ttf"; + + const auto helper = Utils::StyleHelper::IconFontHelper( + unicode, color, QSize(28, 28), QIcon::Normal); + + return Utils::StyleHelper::getIconFromIconFont(fontName, {helper}); +} + const int rowHeight = 32; const int checkBoxColWidth = 18; const int labelMinWidth = 130; @@ -88,6 +104,11 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog( setModal(true); ui->setupUi(this); + m_selectedRemoveIcon = iconFromIconFont(Theme::Icon::delete_small, + Theme::getColor(Theme::IconsBaseColor)); + m_unselectedRemoveIcon = iconFromIconFont(Theme::Icon::delete_small, + Theme::getColor(Theme::IconsDisabledColor)); + m_outputFormatter = new Utils::OutputFormatter; m_outputFormatter->setPlainTextEdit(ui->plainTextEdit); @@ -239,8 +260,8 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog( connect(ui->advancedSettingsButton, &QPushButton::clicked, this, &ItemLibraryAssetImportDialog::toggleAdvanced); - connect(ui->importList, &QListWidget::currentRowChanged, - this, &ItemLibraryAssetImportDialog::onCurrentRowChanged); + connect(ui->importList, &QListWidget::currentItemChanged, + this, &ItemLibraryAssetImportDialog::onCurrentItemChanged); QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::updateUi); @@ -383,6 +404,15 @@ void ItemLibraryAssetImportDialog::updateImport(AbstractView *view, } } +void ItemLibraryAssetImportDialog::keyPressEvent(QKeyEvent *event) +{ + if ((event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) + && ui->importList->currentItem()) { + onRemoveAsset(assetNameForListItem(ui->importList->currentItem())); + } + return QDialog::keyPressEvent(event); +} + void ItemLibraryAssetImportDialog::createTab(const QString &tabLabel, int optionsIndex, const QJsonObject &groups) { @@ -828,6 +858,15 @@ void ItemLibraryAssetImportDialog::updateUi() } } +QString ItemLibraryAssetImportDialog::assetNameForListItem(QListWidgetItem *item) +{ + for (const ImportData &data : std::as_const(m_importData)) { + if (data.listItem == item) + return data.previewData.name; + } + return {}; +} + bool ItemLibraryAssetImportDialog::isSimpleGroup(const QString &id) { static QStringList simpleGroups { @@ -859,8 +898,8 @@ bool ItemLibraryAssetImportDialog::isHiddenOption(const QString &id) bool ItemLibraryAssetImportDialog::optionsChanged() { - for (const ItemLibraryAssetImporter::PreviewData &data : std::as_const(m_previewData)) { - if (data.renderedOptions != data.currentOptions) + for (const ImportData &data : std::as_const(m_importData)) { + if (data.previewData.renderedOptions != data.previewData.currentOptions) return true; } return false; @@ -883,6 +922,7 @@ Rectangle { property alias sceneNode: sceneNode property alias view3d: view3d + property alias iconView3d: iconView3d property string extents property string sceneModelName @@ -897,30 +937,40 @@ Rectangle { camera: viewCamera environment: SceneEnvironment { + id: sceneEnvironment antialiasingMode: SceneEnvironment.MSAA antialiasingQuality: SceneEnvironment.VeryHigh } - PerspectiveCamera { - id: viewCamera - x: 600 - y: 600 - z: 600 - eulerRotation.x: -45 - eulerRotation.y: -45 - clipFar: 100000 - clipNear: 10 - } - - DirectionalLight { - rotation: viewCamera.rotation - } - Node { id: sceneNode + PerspectiveCamera { + id: viewCamera + x: 600 + y: 600 + z: 600 + eulerRotation.x: -45 + eulerRotation.y: -45 + clipFar: 100000 + clipNear: 10 + } + + DirectionalLight { + rotation: viewCamera.rotation + } } } + View3D { + id: iconView3d + importScene: sceneNode + camera: viewCamera + environment: sceneEnvironment + visible: false + width: 48 + height: 48 + } + Text { anchors.bottom: parent.bottom anchors.left: parent.left @@ -966,6 +1016,15 @@ Rectangle { m_nodeInstanceView->setTarget(m_view->nodeInstanceView()->target()); + auto previewIconCallback = [this](const QString &assetName, const QImage &image) { + if (!m_importData.contains(assetName)) { + addWarning(tr("Preview icon generated for non-existent asset: %1").arg(assetName)); + return; + } + if (m_importData[assetName].iconLabel) + m_importData[assetName].iconLabel->setPixmap(QPixmap::fromImage(image)); + }; + auto previewImageCallback = [this](const QImage &image) { canvas()->updateRenderImage(image); }; @@ -975,6 +1034,7 @@ Rectangle { cleanupPreviewPuppet(); }; + m_connectionManager->setPreviewIconCallback(std::move(previewIconCallback)); m_connectionManager->setPreviewImageCallback(std::move(previewImageCallback)); m_nodeInstanceView->setCrashCallback(std::move(crashCallback)); @@ -992,8 +1052,10 @@ void ItemLibraryAssetImportDialog::cleanupPreviewPuppet() if (m_nodeInstanceView) m_nodeInstanceView->setCrashCallback({}); - if (m_connectionManager) + if (m_connectionManager) { + m_connectionManager->setPreviewIconCallback({}); m_connectionManager->setPreviewImageCallback({}); + } delete m_rewriterView; delete m_nodeInstanceView; @@ -1007,17 +1069,17 @@ Import3dCanvas *ItemLibraryAssetImportDialog::canvas() void ItemLibraryAssetImportDialog::resetOptionControls() { - const QString currentName = ui->importList->currentItem()->text(); - if (!m_previewData.contains(currentName)) + const QString currentName = assetNameForListItem(ui->importList->currentItem()); + if (!m_importData.contains(currentName)) return; m_updatingControlStates = true; - const ItemLibraryAssetImporter::PreviewData &data = m_previewData[currentName]; - const QJsonObject options = data.currentOptions; + const ImportData &data = m_importData[currentName]; + const QJsonObject options = data.previewData.currentOptions; const QStringList optKeys = options.keys(); for (const QString &optKey : optKeys) { - QWidget *w = m_labelToControlWidgetMaps[data.optionsIndex].value(optKey); + QWidget *w = m_labelToControlWidgetMaps[data.previewData.optionsIndex].value(optKey); const QJsonObject optObj = options.value(optKey).toObject(); const QJsonValue optValue = optObj.value("value"); if (auto *cb = qobject_cast(w)) @@ -1048,10 +1110,10 @@ void ItemLibraryAssetImportDialog::updatePreviewOptions() return; if (ui->importList->currentRow() >= 0) { - const QString assetName = ui->importList->currentItem()->text(); - if (m_previewData.contains(assetName)) { - ItemLibraryAssetImporter::PreviewData &data = m_previewData[assetName]; - data.currentOptions = m_importOptions[data.optionsIndex]; + const QString assetName = assetNameForListItem(ui->importList->currentItem()); + if (m_importData.contains(assetName)) { + ImportData &data = m_importData[assetName]; + data.previewData.currentOptions = m_importOptions[data.previewData.optionsIndex]; } } @@ -1079,25 +1141,26 @@ void ItemLibraryAssetImportDialog::onImport() { ui->importButton->setEnabled(false); ui->tabWidget->setEnabled(false); + ui->importList->setEnabled(false); - const ItemLibraryAssetImporter::PreviewData &data - = m_previewData.value(ui->importList->currentIndex().data().toString()); - - if (!data.name.isEmpty() && !optionsChanged()) { + if (!m_importData.isEmpty() && !optionsChanged()) { cleanupPreviewPuppet(); m_importer.finalizeQuick3DImport(); return; } + const QString assetName = assetNameForListItem(ui->importList->currentItem()); + const ImportData &data = m_importData.value(assetName); + setCloseButtonState(true); ui->progressBar->setValue(0); if (!m_quick3DFiles.isEmpty()) { - if (!m_previewData.isEmpty()) { + if (!m_importData.isEmpty()) { QHash importOptions; - for (const ItemLibraryAssetImporter::PreviewData &data : std::as_const(m_previewData)) { - if (data.renderedOptions != data.currentOptions) - importOptions.insert(data.name, data.currentOptions); + for (const ImportData &data : std::as_const(m_importData)) { + if (data.previewData.renderedOptions != data.previewData.currentOptions) + importOptions.insert(data.previewData.name, data.previewData.currentOptions); } m_importer.reImportQuick3D(importOptions); } else { @@ -1121,16 +1184,79 @@ void ItemLibraryAssetImportDialog::setImportProgress(int value, const QString &t void ItemLibraryAssetImportDialog::onImportReadyForPreview( const QString &path, const QList &previewData) { + if (previewData.isEmpty()) { + m_importer.cancelImport(); + return; + } + + QPixmap placeHolder = QPixmap(":/navigator/icon/tooltip_placeholder.png").scaled(48, 48); + + int maxNameLen = 150; + QStringList assetNames; for (const ItemLibraryAssetImporter::PreviewData &data : previewData) { - m_previewData[data.name] = data; - assetNames.append(data.name); + const QString assetName = data.name; + assetNames.append(assetName); + if (!m_importData.contains(assetName)) { + ImportData impData; + impData.previewData = data; + auto lwi = new QListWidgetItem(); + impData.listItem = lwi; + auto w = new QWidget(ui->importList); + w->setToolTip(assetName); + auto layout = new QHBoxLayout(w); + auto iconLabel = new QLabel(w); + iconLabel->setPixmap(placeHolder); + impData.iconLabel = iconLabel; + layout->addWidget(iconLabel); + auto infoLabel = new QLabel(w); + infoLabel->setFixedWidth(maxNameLen); + impData.infoLabel = infoLabel; + layout->addWidget(infoLabel); + layout->addStretch(1); + auto removeButton = new QPushButton(m_unselectedRemoveIcon, {}, w); + removeButton->setFlat(true); + impData.removeButton = removeButton; + layout->addWidget(removeButton, 0, Qt::AlignRight); + layout->setSizeConstraint(QLayout::SetNoConstraint); + w->setLayout(layout); + w->resize(w->height(), ui->importList->width()); + lwi->setSizeHint(w->sizeHint()); + ui->importList->addItem(lwi); + ui->importList->setItemWidget(lwi, w); + m_importData[assetName] = impData; + QObject::connect(removeButton, &QPushButton::clicked, this, [this, assetName]() { + onRemoveAsset(assetName); + }); + } else { + m_importData[assetName].previewData = data; + } - auto items = ui->importList->findItems(data.name, Qt::MatchExactly); - if (items.isEmpty()) - ui->importList->addItem(data.name); + if (!m_importData.contains(assetName)) + return; - addInfo(tr("Import ready for preview: %1").arg(data.name)); + const ImportData &impData = m_importData[assetName]; + + if (QLabel *l = impData.infoLabel) { + QFontMetrics fm = l->fontMetrics(); + QString truncNameBase = assetName; + QString truncName = assetName; + int truncNameLen = fm.boundingRect(truncName).width(); + while (!truncNameBase.isEmpty() && truncNameLen > maxNameLen) { + truncNameBase.chop(1); + truncName = truncNameBase + "..."; + truncNameLen = fm.boundingRect(truncName).width(); + } + + QString s; + s += truncName + '\n'; + s += tr("Object Type: %1\n").arg(data.type); + s += tr("Imported Size: %1").arg(QLocale::system().formattedDataSize( + data.size, 2, QLocale::DataSizeTraditionalFormat)); + l->setText(s); + } + + addInfo(tr("Import ready for preview: %1").arg(assetName)); } if (m_firstImport) { @@ -1147,11 +1273,11 @@ void ItemLibraryAssetImportDialog::onImportReadyForPreview( if (!m_nodeInstanceView) return; for (const QString &assetName : std::as_const(assetNames)) { - const ItemLibraryAssetImporter::PreviewData &data = m_previewData.value(assetName); - if (!data.name.isEmpty()) { + const ImportData &data = m_importData.value(assetName); + if (!data.previewData.name.isEmpty()) { QVariantHash msgData; - msgData.insert("name", data.name); - msgData.insert("folder", data.folderName); + msgData.insert("name", data.previewData.name); + msgData.insert("folder", data.previewData.folderName); m_nodeInstanceView->view3DAction(View3DActionType::Import3dAddPreviewModel, msgData); } } @@ -1159,6 +1285,7 @@ void ItemLibraryAssetImportDialog::onImportReadyForPreview( ui->importButton->setEnabled(true); ui->tabWidget->setEnabled(true); + ui->importList->setEnabled(true); updatePreviewOptions(); if (ui->importList->currentRow() < 0) ui->importList->setCurrentRow(0); @@ -1202,23 +1329,33 @@ void ItemLibraryAssetImportDialog::onImportFinished() } } -void ItemLibraryAssetImportDialog::onCurrentRowChanged(int) +void ItemLibraryAssetImportDialog::onCurrentItemChanged(QListWidgetItem *current, QListWidgetItem *) { - QTimer::singleShot(0, this, [this]() { + if (!current) + return; + + for (const ImportData &data : std::as_const(m_importData)) { + if (data.removeButton) { + if (current == data.listItem) + data.removeButton->setIcon(m_selectedRemoveIcon); + else + data.removeButton->setIcon(m_unselectedRemoveIcon); + } + } + const QString assetName = assetNameForListItem(ui->importList->currentItem()); + resetOptionControls(); + + const ImportData data = m_importData.value(assetName); + for (int i = 0; i < ui->tabWidget->count(); ++i) + ui->tabWidget->widget(i)->setVisible(i == data.previewData.optionsIndex); + ui->tabWidget->setCurrentIndex(data.previewData.optionsIndex); + + QTimer::singleShot(0, this, [this, assetName]() { if (!m_nodeInstanceView) return; - int row = ui->importList->currentRow(); - QString compName; - if (row >= 0) - compName = ui->importList->item(row)->text(); - m_nodeInstanceView->view3DAction(View3DActionType::Import3dSetCurrentPreviewModel, compName); - resetOptionControls(); - - if (m_previewData.contains(compName)) { - const ItemLibraryAssetImporter::PreviewData data = m_previewData[compName]; - for (int i = 0; i < ui->tabWidget->count(); ++i) - ui->tabWidget->widget(i)->setVisible(i == data.optionsIndex); - ui->tabWidget->setCurrentIndex(data.optionsIndex); + if (m_importData.contains(assetName)) { + m_nodeInstanceView->view3DAction(View3DActionType::Import3dSetCurrentPreviewModel, + assetName); } }); } @@ -1268,4 +1405,20 @@ void ItemLibraryAssetImportDialog::toggleAdvanced() updateUi(); } +void ItemLibraryAssetImportDialog::onRemoveAsset(const QString &assetName) +{ + m_importer.removeAssetFromImport(assetName); + if (m_importData.contains(assetName)) { + ImportData data = m_importData.take(assetName); + addInfo(tr("Removed %1 from the import.").arg(assetName)); + if (data.listItem) { + ui->importList->removeItemWidget(data.listItem); + delete data.listItem; + } + } + + if (m_importData.isEmpty()) + onClose(); +} + } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h index 20cfa7b1485..17df31502b3 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h @@ -9,12 +9,15 @@ #include #include +#include #include #include #include QT_BEGIN_NAMESPACE class QGridLayout; +class QLabel; +class QListWidgetItem; class QPushButton; QT_END_NAMESPACE @@ -23,7 +26,6 @@ class OutputFormatter; } namespace QmlDesigner { -class ItemLibraryAssetImporter; class Import3dCanvas; class Import3dConnectionManager; class NodeInstanceView; @@ -53,6 +55,8 @@ public: const QVariantMap &supportedExts, const QVariantMap &supportedOpts); + void keyPressEvent(QKeyEvent *event) override; + protected: void resizeEvent(QResizeEvent *event) override; @@ -62,6 +66,22 @@ private slots: void addInfo(const QString &info, const QString &srcPath = {}); private: + struct ImportData + { + QListWidgetItem *listItem = {}; + QLabel *iconLabel = {}; + QLabel *infoLabel = {}; + QPushButton *removeButton = {}; + ItemLibraryAssetImporter::PreviewData previewData; + }; + + struct OptionsData + { + int optionsRows = 0; + int optionsHeight = 0; + QList contentWidgets; + }; + void setCloseButtonState(bool importing); void updatePreviewOptions(); @@ -73,15 +93,17 @@ private: void onRequestRotation(const QPointF &delta); void onImportNearlyFinished(); void onImportFinished(); - void onCurrentRowChanged(int row); + void onCurrentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void onClose(); void doClose(); void toggleAdvanced(); + void onRemoveAsset(const QString &assetName); void createTab(const QString &tabLabel, int optionsIndex, const QJsonObject &groups); QGridLayout *createOptionsGrid(QWidget *contentWidget, bool advanced, int optionsIndex, const QJsonObject &groups); void updateUi(); + QString assetNameForListItem(QListWidgetItem *item); bool isSimpleGroup(const QString &id); bool isSimpleOption(const QString &id); @@ -101,16 +123,9 @@ private: QPointer m_view; ModelPointer m_model; - QMap m_previewData; + QMap m_importData; Utils::FilePath m_previewFile; - struct OptionsData - { - int optionsRows = 0; - int optionsHeight = 0; - QList contentWidgets; // Tab content widgets - }; - QStringList m_quick3DFiles; QString m_quick3DImportPath; ItemLibraryAssetImporter m_importer; @@ -126,5 +141,7 @@ private: bool m_explicitClose = false; bool m_updatingControlStates = true; bool m_firstImport = true; + QIcon m_selectedRemoveIcon; + QIcon m_unselectedRemoveIcon; }; } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui index 4a1a85c355d..26ea77f172b 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui @@ -33,7 +33,7 @@ - 150 + 250 0 diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index bdc703ca742..9e0d559d53f 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -238,7 +238,6 @@ void ItemLibraryAssetImporter::reset() delete m_tempDir; m_tempDir = new QTemporaryDir(QDir::tempPath() + tempDirNameBase()); m_importFiles.clear(); - m_overwrittenImports.clear(); m_puppetProcess.reset(); m_parseData.clear(); m_requiredImports.clear(); @@ -337,14 +336,14 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa overwriteFiles = dlg.selectedFiles(); if (!overwriteFiles.isEmpty()) { overwriteFiles.append(::Utils::toList(alwaysOverwrite)); - m_overwrittenImports.insert(pd.targetDirPath, overwriteFiles); + pd.overwrittenImports.insert(pd.targetDirPath, overwriteFiles); } else { addWarning(tr("No files selected for overwrite, skipping import: \"%1\".").arg(pd.assetName)); return false; } } else { - m_overwrittenImports.insert(pd.targetDirPath, {}); + pd.overwrittenImports.insert(pd.targetDirPath, {}); } } @@ -396,9 +395,7 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd) qmlInfo.append("."); qmlInfo.append(pd.assetName); qmlInfo.append('\n'); - const QString reqImp = QStringLiteral("%1.%2").arg( - QmlDesignerPlugin::instance()->documentManager() - .generatedComponentUtils().import3dTypePrefix(), pd.assetName); + const QString reqImp = generateRequiredImportForAsset(pd.assetName); if (!m_requiredImports.contains(reqImp)) m_requiredImports.append(reqImp); while (qmlIt.hasNext()) { @@ -480,9 +477,13 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd) // Gather all generated files QDirIterator dirIt(outDir.path(), QDir::Files, QDirIterator::Subdirectories); + pd.assetSize = 0; while (dirIt.hasNext()) { dirIt.next(); insertAsset(dirIt.filePath()); + QFileInfo fi = dirIt.fileInfo(); + if (fi.isFile()) + pd.assetSize += fi.size(); } // Copy the original asset into a subdirectory @@ -492,14 +493,17 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd) void ItemLibraryAssetImporter::copyImportedFiles() { - if (!m_overwrittenImports.isEmpty()) { + QHash allOverwrites; + for (const ParseData &pd: std::as_const(m_parseData)) + allOverwrites.insert(pd.overwrittenImports); + if (!allOverwrites.isEmpty()) { const QString progressTitle = tr("Removing old overwritten assets."); addInfo(progressTitle); notifyProgress(0, progressTitle); int counter = 0; - auto it = m_overwrittenImports.constBegin(); - while (it != m_overwrittenImports.constEnd()) { + auto it = allOverwrites.constBegin(); + while (it != allOverwrites.constEnd()) { Utils::FilePath dir = Utils::FilePath::fromUserInput(it.key()); if (dir.exists()) { const auto &overwrittenFiles = it.value(); @@ -512,7 +516,7 @@ void ItemLibraryAssetImporter::copyImportedFiles() QFile::remove(fileName); } } - notifyProgress((100 * ++counter) / m_overwrittenImports.size(), progressTitle); + notifyProgress((100 * ++counter) / allOverwrites.size(), progressTitle); ++it; } } @@ -568,6 +572,13 @@ QString ItemLibraryAssetImporter::generateAssetFolderName(const QString &assetNa return assetName + "_QDS_" + QString::number(counter++); } +QString ItemLibraryAssetImporter::generateRequiredImportForAsset(const QString &assetName) const +{ + return QStringLiteral("%1.%2").arg( + QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().import3dTypePrefix(), assetName); +} + ItemLibraryAssetImporter::OverwriteResult ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName) { const QString title = tr("Overwrite Existing Asset?"); @@ -653,8 +664,11 @@ void ItemLibraryAssetImporter::postImport() data.renderedOptions = pd.options; data.currentOptions = pd.options; data.optionsIndex = pd.optionsIndex; + data.type = pd.sourceInfo.suffix().toLower(); + data.size = pd.assetSize; dataList.append(data); } + emit importReadyForPreview(m_tempDir->path(), dataList); } } @@ -716,8 +730,12 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport() addError(tr("Failed to update imports: %1").arg(e.description())); } } else if (counter >= 50) { - if (!m_overwrittenImports.isEmpty()) - model->rewriterView()->emitCustomNotification("asset_import_update"); + for (const ParseData &pd : std::as_const(m_parseData)) { + if (!pd.overwrittenImports.isEmpty()) { + model->rewriterView()->emitCustomNotification("asset_import_update"); + break; + } + } timer->stop(); notifyFinished(); } @@ -732,6 +750,13 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport() } } +void ItemLibraryAssetImporter::removeAssetFromImport(const QString &assetName) +{ + m_parseData.remove(assetName); + m_importFiles.remove(assetName); + m_requiredImports.removeOne(generateRequiredImportForAsset(assetName)); +} + QString ItemLibraryAssetImporter::sourceSceneTargetFilePath(const ParseData &pd) { return pd.targetDirPath + QStringLiteral("/source scene/") + pd.sourceInfo.fileName(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h index eaf2fd50b1f..0739976b77d 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -47,6 +47,7 @@ public: QString tempDirNameBase() const { return "/qds3dimport"; } void finalizeQuick3DImport(); + void removeAssetFromImport(const QString &assetName); struct PreviewData { @@ -55,6 +56,8 @@ public: QJsonObject currentOptions; QString name; QString folderName; + QString type; + qint64 size; }; signals: @@ -78,8 +81,10 @@ private: QFileInfo sourceInfo; QString assetName; QString originalAssetName; + qint64 assetSize; int importId = -1; int optionsIndex = -1; + QHash overwrittenImports; }; void notifyFinished(); @@ -96,6 +101,7 @@ private: void notifyProgress(int value); void keepUiAlive() const; QString generateAssetFolderName(const QString &assetName) const; + QString generateRequiredImportForAsset(const QString &assetName) const; enum class OverwriteResult { Skip, @@ -109,7 +115,6 @@ private: QString sourceSceneTargetFilePath(const ParseData &pd); QHash> m_importFiles; // Key: asset name - QHash m_overwrittenImports; bool m_isImporting = false; bool m_cancelled = false; QString m_importPath; diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.cpp index 5ed372b7ad8..755bb91ef96 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.cpp @@ -35,7 +35,7 @@ Qt5Import3dNodeInstanceServer::Qt5Import3dNodeInstanceServer(NodeInstanceClientI #ifdef QUICK3D_MODULE m_generalHelper = new Internal::GeneralHelper(); QObject::connect(m_generalHelper, &Internal::GeneralHelper::requestRender, this, [this]() { - startRenderTimer(); + addCurrentNodeToRenderQueue(); }); #endif } @@ -54,16 +54,22 @@ void Qt5Import3dNodeInstanceServer::createScene(const CreateSceneCommand &comman #ifdef QUICK3D_MODULE QObject *obj = rootItem(); - QQmlProperty viewProp(obj, "view3d", context()); - QObject *viewObj = viewProp.read().value(); - m_view3D = qobject_cast(viewObj); + auto initView = [&obj, this](const QString &viewId, QQuick3DViewport *&view3D) { + QQmlProperty viewProp(obj, viewId, context()); + QObject *viewObj = viewProp.read().value(); + view3D = qobject_cast(viewObj); + }; + initView("view3d", m_view3D); + initView("iconView3d", m_iconView3D); + if (m_view3D) { QQmlProperty sceneNodeProp(obj, "sceneNode", context()); m_sceneNode = sceneNodeProp.read().value(); + m_defaultCameraRotation = m_view3D->camera()->rotation(); + m_defaultCameraPosition = m_view3D->camera()->position(); + addInitToRenderQueue(); } #endif - - startRenderTimer(); } void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DActionCommand &command) @@ -79,7 +85,7 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc wProp.write(size.width()); hProp.write(size.height()); resizeCanvasToRootItem(); - startRenderTimer(); + addCurrentNodeToRenderQueue(); } break; } @@ -92,8 +98,8 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc QPointF delta = command.value().toPointF(); m_generalHelper->orbitCamera(m_view3D->camera(), m_view3D->camera()->eulerRotation(), data.lookAt, {}, {float(delta.x()), float(delta.y()), 0.f}); - m_keepRendering = true; - startRenderTimer(); + // Add 2 renders to keep render timer alive for smooth rotation + addCurrentNodeToRenderQueue(2); } break; } @@ -123,28 +129,23 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc engine()->setObjectOwnership(data.node, QJSEngine::CppOwnership); data.node->setParentItem(m_sceneNode); data.node->setParent(m_sceneNode); + + addInitToRenderQueue(); + + if (m_currentNode == name) + addCurrentNodeToRenderQueue(); + + addIconToRenderQueue(name); } - if (m_currentNode == data.name) { - m_renderCount = 0; - startRenderTimer(); - } else if (data.node) { - data.node->setVisible(false); - } break; } case View3DActionType::Import3dSetCurrentPreviewModel: { QString newName = command.value().toString(); if (m_previewData.contains(newName) && m_currentNode != newName) { - const PreviewData &newData = m_previewData[newName]; - const PreviewData oldData = m_previewData.value(m_currentNode); - if (oldData.node) - oldData.node->setVisible(false); - if (newData.node) - newData.node->setVisible(true); - m_renderCount = 0; m_currentNode = newName; - startRenderTimer(); + addInitToRenderQueue(); + addCurrentNodeToRenderQueue(); } break; } @@ -157,8 +158,10 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc void Qt5Import3dNodeInstanceServer::startRenderTimer() { - if (m_keepRendering && timerMode() == TimerMode::NormalTimer) +#ifdef QUICK3D_MODULE + if (!m_renderQueue.isEmpty() && timerMode() == TimerMode::NormalTimer) return; +#endif NodeInstanceServer::startRenderTimer(); } @@ -173,6 +176,45 @@ void Qt5Import3dNodeInstanceServer::cleanup() #endif } +#ifdef QUICK3D_MODULE +void Qt5Import3dNodeInstanceServer::addInitToRenderQueue() +{ + startRenderTimer(); + + // "Init" render is simply a rendering of the entire scene without producing any images. + // This is done to make sure everything is initialized properly for subsequent renders. + + if (m_renderQueue.isEmpty() || m_renderQueue[0] != RenderType::Init) + m_renderQueue.prepend(RenderType::Init); +} + +void Qt5Import3dNodeInstanceServer::addCurrentNodeToRenderQueue(int count) +{ + startRenderTimer(); + + int remaining = count; + for (const RenderType &type : std::as_const(m_renderQueue)) { + if (type == RenderType::CurrentNode && --remaining <= 0) + return; + } + + int index = !m_renderQueue.isEmpty() && m_renderQueue[0] == RenderType::Init ? 1 : 0; + + while (remaining > 0) { + m_renderQueue.insert(index, RenderType::CurrentNode); + --remaining; + } +} + +void Qt5Import3dNodeInstanceServer::addIconToRenderQueue(const QString &assetName) +{ + startRenderTimer(); + + m_generateIconQueue.append(assetName); + m_renderQueue.append(RenderType::NextIcon); +} +#endif + void Qt5Import3dNodeInstanceServer::collectItemChangesAndSendChangeCommands() { static bool inFunction = false; @@ -194,54 +236,112 @@ void Qt5Import3dNodeInstanceServer::collectItemChangesAndSendChangeCommands() void Qt5Import3dNodeInstanceServer::render() { #ifdef QUICK3D_MODULE - ++m_renderCount; + if (m_renderQueue.isEmpty() || !m_view3D || !m_iconView3D) + return; - // Render scene at least once before calculating bounds to ensure geometries are intialized - if (m_renderCount == 2 && m_view3D && m_previewData.contains(m_currentNode)) { - PreviewData &data = m_previewData[m_currentNode]; - m_generalHelper->calculateBoundsAndFocusCamera(m_view3D->camera(), data.node, - m_view3D, 1050, false, data.lookAt, - data.extents); + RenderType currentType = m_renderQueue.takeFirst(); + PreviewData data; + QQuick3DViewport *currentView = m_view3D; + QVector3D cameraPosition = m_view3D->camera()->position(); + QQuaternion cameraRotation = m_view3D->camera()->rotation(); - auto getExtentStr = [&data](int idx) -> QString { - int prec = 0; - float val = data.extents[idx]; - while (val < 100.f) { - ++prec; - val *= 10.f; - } - // Strip unnecessary zeroes after decimal separator - if (prec > 0) { - QString checkStr = QString::number(data.extents[idx], 'f', prec); - while (prec > 0 && (checkStr.last(1) == "0" || checkStr.last(1) == ".")) { - --prec; - checkStr.chop(1); - } - } - QString retval = QLocale().toString(data.extents[idx], 'f', prec); - return retval; + if (currentType == RenderType::Init) { + m_view3D->setVisible(true); + m_iconView3D->setVisible(true); + } else { + auto showNode = [this](const QString &name) { + for (const PreviewData &data : std::as_const(m_previewData)) + data.node->setVisible(data.name == name); }; - QQmlProperty extentsProp(rootItem(), "extents", context()); - extentsProp.write(tr("Dimensions: %1 x %2 x %3").arg(getExtentStr(0)) - .arg(getExtentStr(1)) - .arg(getExtentStr(2))); + if (currentType == RenderType::CurrentNode) { + if (m_previewData.contains(m_currentNode)) { + showNode(m_currentNode); + data = m_previewData[m_currentNode]; + m_view3D->setVisible(true); + m_iconView3D->setVisible(false); + } + } else if (currentType == RenderType::NextIcon) { + if (!m_generateIconQueue.isEmpty()) { + const QString assetName = m_generateIconQueue.takeFirst(); + if (m_previewData.contains(assetName)) { + m_view3D->setVisible(false); + m_iconView3D->setVisible(true); + showNode(assetName); + data = m_previewData[assetName]; + m_refocus = true; + currentView = m_iconView3D; + currentView->camera()->setRotation(m_defaultCameraRotation); + currentView->camera()->setPosition(m_defaultCameraPosition); + } + } + } + + if (m_refocus && data.node) { + m_generalHelper->calculateBoundsAndFocusCamera(currentView->camera(), data.node, + currentView, 1050, false, data.lookAt, + data.extents); + if (currentType == RenderType::CurrentNode) { + auto getExtentStr = [&data](int idx) -> QString { + int prec = 0; + float val = data.extents[idx]; + + if (val == 0.f) { + prec = 1; + } else { + while (val < 100.f) { + ++prec; + val *= 10.f; + } + } + // Strip unnecessary zeroes after decimal separator + if (prec > 0) { + QString checkStr = QString::number(data.extents[idx], 'f', prec); + while (prec > 0 && (checkStr.last(1) == "0" || checkStr.last(1) == ".")) { + --prec; + checkStr.chop(1); + } + } + QString retval = QLocale().toString(data.extents[idx], 'f', prec); + return retval; + }; + + QQmlProperty extentsProp(rootItem(), "extents", context()); + extentsProp.write(tr("Dimensions: %1 x %2 x %3").arg(getExtentStr(0)) + .arg(getExtentStr(1)) + .arg(getExtentStr(2))); + } + } } - rootNodeInstance().updateDirtyNodeRecursive(); + currentView->update(); QImage renderImage = grabWindow(); - if (m_renderCount >= 2) { - ImageContainer imgContainer(0, renderImage, m_renderCount); - nodeInstanceClient()->handlePuppetToCreatorCommand( - {PuppetToCreatorCommand::Import3DPreviewImage, - QVariant::fromValue(imgContainer)}); - - if (!m_keepRendering) - slowDownRenderTimer(); - - m_keepRendering = false; + if (currentType == RenderType::Init) { + m_refocus = true; + } else if (currentType == RenderType::CurrentNode) { + if (m_previewData.contains(m_currentNode)) { + ImageContainer imgContainer(0, renderImage, 1000000); + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::Import3DPreviewImage, QVariant::fromValue(imgContainer)}); + } + } else if (currentType == RenderType::NextIcon) { + if (!data.name.isEmpty()) { + QSizeF iconSize = m_iconView3D->size(); + QImage iconImage = renderImage.copy(0, 0, iconSize.width(), iconSize.height()); + ImageContainer imgContainer(0, iconImage, 1000001); + QVariantList cmdData; + cmdData.append(data.name); + cmdData.append(QVariant::fromValue(imgContainer)); + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::Import3DPreviewIcon, cmdData}); + } + m_refocus = true; + currentView->camera()->setRotation(cameraRotation); + currentView->camera()->setPosition(cameraPosition); } + if (m_renderQueue.isEmpty()) + slowDownRenderTimer(); #else slowDownRenderTimer(); #endif diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.h index 3243d3a8ca3..1b790eb717d 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.h +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5import3dnodeinstanceserver.h @@ -35,11 +35,13 @@ protected: private: void cleanup(); - int m_renderCount = 0; - bool m_keepRendering = false; - #ifdef QUICK3D_MODULE + void addInitToRenderQueue(); + void addCurrentNodeToRenderQueue(int count = 1); + void addIconToRenderQueue(const QString &assetName); + QQuick3DViewport *m_view3D = nullptr; + QQuick3DViewport *m_iconView3D = nullptr; Internal::GeneralHelper *m_generalHelper = nullptr; struct PreviewData @@ -50,8 +52,21 @@ private: QQuick3DNode *node = {}; }; QHash m_previewData; + + enum class RenderType + { + Init, + CurrentNode, + NextIcon + }; + QList m_renderQueue; + + bool m_refocus = false; QString m_currentNode; QQuick3DNode *m_sceneNode = {}; + QStringList m_generateIconQueue; + QQuaternion m_defaultCameraRotation; + QVector3D m_defaultCameraPosition; #endif };