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 };