QmlDesigner: Move 3D asset import to puppet

Moved 3D asset import to puppet to ensure import always uses the
correct version of the QtQuick3D.

Fixes: QDS-3154
Change-Id: I630a833e7231383b87bf8b7214d3545d12de15ab
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2021-01-22 17:51:46 +02:00
parent f15fef7ee1
commit 99a3ddbd23
27 changed files with 507 additions and 251 deletions

View File

@@ -34,7 +34,13 @@ namespace QmlDesigner {
class PuppetToCreatorCommand
{
public:
enum Type { Edit3DToolState, Render3DView, ActiveSceneChanged, RenderModelNodePreviewImage, None };
enum Type {
Edit3DToolState,
Render3DView,
ActiveSceneChanged,
RenderModelNodePreviewImage,
Import3DSupport,
None };
PuppetToCreatorCommand(Type type, const QVariant &data);
PuppetToCreatorCommand() = default;

View File

@@ -0,0 +1,79 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "import3d.h"
#ifdef IMPORT_QUICK3D_ASSETS
#include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
#endif
#include <QJsonDocument>
#include <QJsonObject>
#include <QCoreApplication>
#include <QTimer>
#include <QScopedPointer>
namespace Import3D
{
void import3D(const QString &sourceAsset, const QString &outDir, int exitId, const QString &options)
{
#ifdef IMPORT_QUICK3D_ASSETS
QScopedPointer importer {new QSSGAssetImportManager};
QJsonParseError error;
QJsonDocument optDoc = QJsonDocument::fromJson(options.toUtf8(), &error);
if (!optDoc.isNull() && optDoc.isObject()) {
QString errorStr;
QJsonObject optObj = optDoc.object();
if (importer->importFile(sourceAsset, outDir, optObj.toVariantMap(), &errorStr)
!= QSSGAssetImportManager::ImportState::Success) {
qWarning() << __FUNCTION__ << "Failed to import 3D asset"
<< sourceAsset << "with error:" << errorStr;
} else {
// Allow little time for file operations to finish
QTimer::singleShot(2000, nullptr, [exitId]() {
qApp->exit(exitId);
});
return;
}
} else {
qWarning() << __FUNCTION__ << "Failed to parse import options:" << error.errorString();
}
#else
Q_UNUSED(sourceAsset)
Q_UNUSED(outDir)
Q_UNUSED(exitId)
Q_UNUSED(options)
qWarning() << __FUNCTION__ << "Failed to parse import options, Quick3DAssetImport not available";
#endif
QTimer::singleShot(0, nullptr, [exitId]() {
// Negative exitId means import failure
qApp->exit(-exitId);
});
}
}

View File

@@ -0,0 +1,33 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QString>
namespace Import3D
{
void import3D(const QString &sourceAsset, const QString &outDir, int id, const QString &options);
};

View File

@@ -0,0 +1,3 @@
HEADERS += $$PWD/import3d.h
SOURCES += $$PWD/import3d.cpp

View File

@@ -1,9 +1,15 @@
INCLUDEPATH += $$PWD/
versionAtLeast(QT_VERSION, 5.15.0):qtHaveModule(quick3d) {
versionAtLeast(QT_VERSION, 5.15.0) {
qtHaveModule(quick3d) {
QT *= quick3d-private
DEFINES *= QUICK3D_MODULE
}
qtHaveModule(quick3dassetimport) {
QT *= quick3dassetimport-private
DEFINES *= IMPORT_QUICK3D_ASSETS
}
}
HEADERS += $$PWD/qt5nodeinstanceserver.h \
$$PWD/capturenodeinstanceserverdispatcher.h \

View File

@@ -102,6 +102,10 @@
#endif
#endif
#ifdef IMPORT_QUICK3D_ASSETS
#include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
#endif
// Uncomment to display FPS counter on the lower left corner of edit 3D view
//#define FPS_COUNTER
#ifdef FPS_COUNTER
@@ -253,6 +257,37 @@ void Qt5InformationNodeInstanceServer::handleInputEvents()
}
}
void Qt5InformationNodeInstanceServer::resolveImportSupport()
{
#ifdef IMPORT_QUICK3D_ASSETS
QSSGAssetImportManager importManager;
const QHash<QString, QStringList> supportedExtensions = importManager.getSupportedExtensions();
const QHash<QString, QVariantMap> supportedOptions = importManager.getAllOptions();
QVariantMap supportMap;
QVariantMap extMap;
auto itExt = supportedExtensions.constBegin();
while (itExt != supportedExtensions.constEnd()) {
extMap.insert(itExt.key(), itExt.value());
++itExt;
}
QVariantMap optMap;
auto itOpt = supportedOptions.constBegin();
while (itOpt != supportedOptions.constEnd()) {
optMap.insert(itOpt.key(), itOpt.value());
++itOpt;
}
supportMap.insert("options", optMap);
supportMap.insert("extensions", extMap);
nodeInstanceClient()->handlePuppetToCreatorCommand(
{PuppetToCreatorCommand::Import3DSupport, QVariant(supportMap)});
#endif
}
void Qt5InformationNodeInstanceServer::createEditView3D()
{
#ifdef QUICK3D_MODULE
@@ -1462,6 +1497,9 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com
QObject::connect(&m_renderModelNodeImageViewTimer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::doRenderModelNodeImageView);
#ifdef IMPORT_QUICK3D_ASSETS
QTimer::singleShot(0, this, &Qt5InformationNodeInstanceServer::resolveImportSupport);
#endif
}
void Qt5InformationNodeInstanceServer::sendChildrenChangedCommand(const QList<ServerNodeInstance> &childList)

View File

@@ -131,6 +131,7 @@ private:
void doRenderModelNode2DImageView();
void updateLockedAndHiddenStates(const QSet<ServerNodeInstance> &instances);
void handleInputEvents();
void resolveImportSupport();
void createAuxiliaryQuickView(const QUrl &url, RenderViewData &viewData);

View File

@@ -24,6 +24,7 @@ include (../interfaces/interfaces.pri)
include (../types/types.pri)
include (../qmlprivategate/qmlprivategate.pri)
include (iconrenderer/iconrenderer.pri)
include (import3d/import3d.pri)
SOURCES += $$PWD/qml2puppetmain.cpp
RESOURCES += $$PWD/../qmlpuppet.qrc

View File

@@ -35,6 +35,7 @@
#include <stdlib.h>
#include "iconrenderer/iconrenderer.h"
#include "import3d/import3d.h"
#include <qt5nodeinstanceclientproxy.h>
#include <QQmlComponent>
@@ -152,12 +153,14 @@ int internalMain(QGuiApplication *application)
if (application->arguments().count() < 2
|| (application->arguments().at(1) == "--readcapturedstream" && application->arguments().count() < 3)
|| (application->arguments().at(1) == "--rendericon" && application->arguments().count() < 5)) {
|| (application->arguments().at(1) == "--rendericon" && application->arguments().count() < 5)
|| (application->arguments().at(1) == "--import3dAsset" && application->arguments().count() < 6)) {
qDebug() << "Usage:\n";
qDebug() << "--test";
qDebug() << "--version";
qDebug() << "--readcapturedstream <stream file> [control stream file]";
qDebug() << "--rendericon <icon size> <icon file name> <icon source qml>";
qDebug() << "--import3dAsset <source asset file name> <output dir> <id number> <import options JSON>";
return -1;
}
@@ -220,6 +223,17 @@ int internalMain(QGuiApplication *application)
return application->exec();
}
if (application->arguments().at(1) == "--import3dAsset") {
QString sourceAsset = application->arguments().at(2);
QString outDir = application->arguments().at(3);
int exitId = application->arguments().at(4).toInt();
QString options = application->arguments().at(5);
Import3D::import3D(sourceAsset, outDir, exitId, options);
return application->exec();
}
#ifdef ENABLE_QT_BREAKPAD
const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + RELATIVE_LIBEXEC_PATH;
QtSystemExceptionHandler systemExceptionHandler(libexecPath);

View File

@@ -326,14 +326,6 @@ extend_qtc_plugin(QmlDesigner
itemlibraryiconimageprovider.cpp itemlibraryiconimageprovider.h
)
find_package(Qt5 COMPONENTS Quick3DAssetImport QUIET)
extend_qtc_plugin(QmlDesigner
CONDITION TARGET Qt5::Quick3DAssetImport
FEATURE_INFO "Qt Quick 3D asset import"
DEPENDS Qt5::Quick3DAssetImportPrivate
DEFINES IMPORT_QUICK3D_ASSETS
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/navigator
SOURCES

View File

@@ -199,6 +199,15 @@ void DesignerActionManager::registerAddResourceHandler(const AddResourceHandler
m_addResourceHandler.append(handler);
}
void DesignerActionManager::unregisterAddResourceHandlers(const QString &category)
{
for (int i = m_addResourceHandler.size() - 1; i >= 0 ; --i) {
const AddResourceHandler &handler = m_addResourceHandler[i];
if (handler.category == category)
m_addResourceHandler.removeAt(i);
}
}
void DesignerActionManager::registerModelNodePreviewHandler(const ModelNodePreviewImageHandler &handler)
{
m_modelNodePreviewImageHandlers.append(handler);

View File

@@ -129,6 +129,7 @@ public:
QList<AddResourceHandler> addResourceHandler() const;
void registerAddResourceHandler(const AddResourceHandler &handler);
void unregisterAddResourceHandlers(const QString &category);
void registerModelNodePreviewHandler(const ModelNodePreviewImageHandler &handler);
bool hasModelNodePreviewHandler(const ModelNode &node) const;

View File

@@ -1,10 +1,5 @@
VPATH += $$PWD
qtHaveModule(quick3dassetimport) {
QT *= quick3dassetimport-private
DEFINES *= IMPORT_QUICK3D_ASSETS
}
# Input
HEADERS += itemlibraryview.h \
$$PWD/itemlibraryiconimageprovider.h \

View File

@@ -71,10 +71,13 @@ static const int rowHeight = 26;
}
ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &importFiles,
const QString &defaulTargetDirectory, QWidget *parent) :
QDialog(parent),
ui(new Ui::ItemLibraryAssetImportDialog),
m_importer(this)
const QString &defaulTargetDirectory,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts,
QWidget *parent) :
QDialog(parent)
, ui(new Ui::ItemLibraryAssetImportDialog)
, m_importer(this)
{
setModal(true);
ui->setupUi(this);
@@ -83,15 +86,24 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &im
m_outputFormatter->setPlainTextEdit(ui->plainTextEdit);
// Skip unsupported assets
bool skipSome = false;
QHash<QString, bool> supportMap;
for (const auto &file : importFiles) {
if (m_importer.isQuick3DAsset(file))
QString suffix = QFileInfo(file).suffix();
if (!supportMap.contains(suffix)) {
bool supported = false;
for (const auto &exts : supportedExts) {
if (exts.toStringList().contains(suffix)) {
supported = true;
break;
}
}
supportMap.insert(suffix, supported);
}
if (supportMap[suffix])
m_quick3DFiles << file;
else
skipSome = true;
}
if (skipSome)
if (m_quick3DFiles.size() != importFiles.size())
addWarning("Cannot import 3D and other assets simultaneously. Skipping non-3D assets.");
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Import"));
@@ -153,14 +165,12 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &im
m_quick3DImportPath = candidatePath;
if (!m_quick3DFiles.isEmpty()) {
const QHash<QString, QVariantMap> allOptions = m_importer.allOptions();
const QHash<QString, QStringList> supportedExtensions = m_importer.supportedExtensions();
QVector<QJsonObject> groups;
auto optIt = allOptions.constBegin();
auto optIt = supportedOpts.constBegin();
int optIndex = 0;
while (optIt != allOptions.constEnd()) {
QJsonObject options = QJsonObject::fromVariantMap(optIt.value());
while (optIt != supportedOpts.constEnd()) {
QJsonObject options = QJsonObject::fromVariantMap(qvariant_cast<QVariantMap>(optIt.value()));
m_importOptions << options.value("options").toObject();
groups << options.value("groups").toObject();
const auto &exts = optIt.key().split(':');
@@ -173,10 +183,10 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &im
// Create tab for each supported extension group that also has files included in the import
QMap<QString, int> tabMap; // QMap used for alphabetical order
for (const auto &file : qAsConst(m_quick3DFiles)) {
auto extIt = supportedExtensions.constBegin();
auto extIt = supportedExts.constBegin();
QString ext = QFileInfo(file).suffix().toLower();
while (extIt != supportedExtensions.constEnd()) {
if (!tabMap.contains(extIt.key()) && extIt.value().contains(ext)) {
while (extIt != supportedExts.constEnd()) {
if (!tabMap.contains(extIt.key()) && extIt.value().toStringList().contains(ext)) {
tabMap.insert(extIt.key(), m_extToImportOptionsMap.value(ext));
break;
}

View File

@@ -46,7 +46,10 @@ class ItemLibraryAssetImportDialog : public QDialog
public:
explicit ItemLibraryAssetImportDialog(const QStringList &importFiles,
const QString &defaulTargetDirectory, QWidget *parent = nullptr);
const QString &defaulTargetDirectory,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts,
QWidget *parent = nullptr);
~ItemLibraryAssetImportDialog();
protected:

View File

@@ -30,18 +30,15 @@
#include "model.h"
#include "puppetcreator.h"
#include <QtCore/qdir.h>
#include <QtCore/qdiriterator.h>
#include <QtCore/qsavefile.h>
#include <QtCore/qfile.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qtemporarydir.h>
#include <QtWidgets/qapplication.h>
#include <QtWidgets/qmessagebox.h>
#ifdef IMPORT_QUICK3D_ASSETS
#include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
#endif
#include <QDir>
#include <QDirIterator>
#include <QSaveFile>
#include <QFile>
#include <QLoggingCategory>
#include <QTemporaryDir>
#include <QApplication>
#include <QMessageBox>
#include <QJsonDocument>
namespace
{
@@ -53,9 +50,6 @@ namespace QmlDesigner {
ItemLibraryAssetImporter::ItemLibraryAssetImporter(QObject *parent) :
QObject (parent)
{
#ifdef IMPORT_QUICK3D_ASSETS
m_quick3DAssetImporter.reset(new QSSGAssetImportManager);
#endif
}
ItemLibraryAssetImporter::~ItemLibraryAssetImporter() {
@@ -73,7 +67,6 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
reset();
m_isImporting = true;
#ifdef IMPORT_QUICK3D_ASSETS
if (!m_tempDir->isValid()) {
addError(tr("Could not create a temporary directory for import."));
notifyFinished();
@@ -85,24 +78,27 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
parseFiles(inputFiles, options, extToImportOptionsMap);
if (!isCancelled()) {
// Wait for icon generation processes to finish
const auto parseData = m_parseData;
for (const auto &pd : parseData) {
if (!startImportProcess(pd)) {
addError(tr("Failed to start import 3D asset process"),
pd.sourceInfo.absoluteFilePath());
m_parseData.remove(pd.importId);
}
}
}
if (!isCancelled()) {
// Wait for puppet processes to finish
if (m_qmlPuppetProcesses.empty()) {
finalizeQuick3DImport();
postImport();
} else {
m_qmlPuppetCount = static_cast<int>(m_qmlPuppetProcesses.size());
const QString progressTitle = tr("Generating icons.");
const QString progressTitle = tr("Importing 3D assets.");
addInfo(progressTitle);
notifyProgress(0, progressTitle);
}
}
#else
Q_UNUSED(inputFiles)
Q_UNUSED(importPath)
Q_UNUSED(options)
Q_UNUSED(extToImportOptionsMap)
addError(tr("Importing 3D assets requires building against Qt Quick 3D module."));
notifyFinished();
#endif
}
bool ItemLibraryAssetImporter::isImporting() const
@@ -135,67 +131,47 @@ void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &sr
emit infoReported(infoMsg, srcPath);
}
bool ItemLibraryAssetImporter::isQuick3DAsset(const QString &fileName) const
void ItemLibraryAssetImporter::importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
#ifdef IMPORT_QUICK3D_ASSETS
static QStringList quick3DExt;
if (quick3DExt.isEmpty()) {
const auto exts = m_quick3DAssetImporter->getSupportedExtensions();
for (const auto &ext : exts)
quick3DExt << ext;
}
return quick3DExt.contains(QFileInfo(fileName).suffix().toLower());
#else
Q_UNUSED(fileName)
return false;
#endif
Q_UNUSED(exitStatus)
++m_qmlImportFinishedCount;
m_qmlPuppetProcesses.erase(
std::remove_if(m_qmlPuppetProcesses.begin(), m_qmlPuppetProcesses.end(),
[&](const auto &entry) {
return !entry || entry->state() == QProcess::NotRunning;
}));
if (m_parseData.contains(-exitCode)) {
const ParseData pd = m_parseData.take(-exitCode);
addError(tr("Asset import process failed for: \"%1\"").arg(pd.sourceInfo.absoluteFilePath()));
}
QVariantMap ItemLibraryAssetImporter::supportedOptions(const QString &modelFile) const
{
#ifdef IMPORT_QUICK3D_ASSETS
return m_quick3DAssetImporter->getOptionsForFile(modelFile);
#else
Q_UNUSED(modelFile)
return {};
#endif
if (m_qmlImportFinishedCount == m_qmlPuppetCount) {
notifyProgress(100);
QTimer::singleShot(0, this, &ItemLibraryAssetImporter::postImport);
} else {
notifyProgress(int(100. * (double(m_qmlImportFinishedCount) / double(m_qmlPuppetCount))));
}
}
QHash<QString, QVariantMap> ItemLibraryAssetImporter::allOptions() const
{
#ifdef IMPORT_QUICK3D_ASSETS
return m_quick3DAssetImporter->getAllOptions();
#else
return {};
#endif
}
QHash<QString, QStringList> ItemLibraryAssetImporter::supportedExtensions() const
{
#ifdef IMPORT_QUICK3D_ASSETS
return m_quick3DAssetImporter->getSupportedExtensions();
#else
return {};
#endif
}
void ItemLibraryAssetImporter::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
void ItemLibraryAssetImporter::iconProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
Q_UNUSED(exitCode)
Q_UNUSED(exitStatus)
m_qmlPuppetProcesses.erase(
std::remove_if(m_qmlPuppetProcesses.begin(), m_qmlPuppetProcesses.end(), [&](const auto &entry) {
std::remove_if(m_qmlPuppetProcesses.begin(), m_qmlPuppetProcesses.end(),
[&](const auto &entry) {
return !entry || entry->state() == QProcess::NotRunning;
}));
const QString progressTitle = tr("Generating icons.");
if (m_qmlPuppetProcesses.empty()) {
notifyProgress(100, progressTitle);
finalizeQuick3DImport();
notifyProgress(100);
QTimer::singleShot(0, this, &ItemLibraryAssetImporter::finalizeQuick3DImport);
} else {
notifyProgress(int(100. * (1. - double(m_qmlPuppetCount) / double(m_qmlPuppetProcesses.size()))),
progressTitle);
notifyProgress(int(100. * (1. - (double(m_qmlPuppetProcesses.size()) / double(m_qmlPuppetCount)))));
}
}
@@ -210,14 +186,14 @@ void ItemLibraryAssetImporter::reset()
m_isImporting = false;
m_cancelled = false;
#ifdef IMPORT_QUICK3D_ASSETS
delete m_tempDir;
m_tempDir = new QTemporaryDir;
m_importFiles.clear();
m_overwrittenImports.clear();
m_qmlPuppetProcesses.clear();
m_qmlPuppetCount = 0;
#endif
m_qmlImportFinishedCount = 0;
m_parseData.clear();
}
void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
@@ -235,99 +211,86 @@ void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
notifyProgress(qRound(quota * (count + value)), progressTitle);
};
for (const QString &file : filePaths) {
if (isCancelled())
return;
if (isQuick3DAsset(file)) {
int index = extToImportOptionsMap.value(QFileInfo(file).suffix());
parseQuick3DAsset(file, options[index].toVariantMap());
ParseData pd;
pd.options = options[index];
if (preParseQuick3DAsset(file, pd)) {
pd.importId = ++m_importIdCounter;
m_parseData.insert(pd.importId, pd);
}
notifyProgress(qRound(++count * quota), progressTitle);
}
notifyProgress(100, progressTitle);
}
void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVariantMap &options)
bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseData &pd)
{
#ifdef IMPORT_QUICK3D_ASSETS
addInfo(tr("Parsing 3D Model"), file);
pd.targetDir = QDir(m_importPath);
pd.outDir = QDir(m_tempDir->path());
pd.sourceInfo = QFileInfo(file);
pd.assetName = pd.sourceInfo.completeBaseName();
QString errorString;
QDir targetDir(m_importPath);
QDir outDir(m_tempDir->path());
QFileInfo sourceInfo(file);
QString assetName = sourceInfo.completeBaseName();
if (!assetName.isEmpty()) {
if (!pd.assetName.isEmpty()) {
// Fix name so it plays nice with imports
for (QChar &currentChar : assetName) {
for (QChar &currentChar : pd.assetName) {
if (!currentChar.isLetter() && !currentChar.isDigit())
currentChar = QLatin1Char('_');
}
const QChar firstChar = assetName[0];
const QChar firstChar = pd.assetName[0];
if (firstChar.isDigit())
assetName[0] = QLatin1Char('_');
pd.assetName[0] = QLatin1Char('_');
if (firstChar.isLower())
assetName[0] = firstChar.toUpper();
pd.assetName[0] = firstChar.toUpper();
}
QString targetDirPath = targetDir.filePath(assetName);
pd.targetDirPath = pd.targetDir.filePath(pd.assetName);
if (outDir.exists(assetName)) {
addWarning(tr("Skipped import of duplicate asset: \"%1\"").arg(assetName));
return;
if (pd.outDir.exists(pd.assetName)) {
addWarning(tr("Skipped import of duplicate asset: \"%1\"").arg(pd.assetName));
return false;
}
QString originalAssetName = assetName;
if (targetDir.exists(assetName)) {
pd.originalAssetName = pd.assetName;
if (pd.targetDir.exists(pd.assetName)) {
// If we have a file system with case insensitive filenames, assetName may be
// different from the existing name. Modify assetName to ensure exact match to
// the overwritten old asset capitalization
const QStringList assetDirs = targetDir.entryList({assetName}, QDir::Dirs);
const QStringList assetDirs = pd.targetDir.entryList({pd.assetName}, QDir::Dirs);
if (assetDirs.size() == 1) {
assetName = assetDirs[0];
targetDirPath = targetDir.filePath(assetName);
pd.assetName = assetDirs[0];
pd.targetDirPath = pd.targetDir.filePath(pd.assetName);
}
if (!confirmAssetOverwrite(assetName)) {
addWarning(tr("Skipped import of existing asset: \"%1\"").arg(assetName));
return;
if (!confirmAssetOverwrite(pd.assetName)) {
addWarning(tr("Skipped import of existing asset: \"%1\"").arg(pd.assetName));
return false;
}
m_overwrittenImports << targetDirPath;
m_overwrittenImports << pd.targetDirPath;
}
outDir.mkpath(assetName);
pd.outDir.mkpath(pd.assetName);
if (!outDir.cd(assetName)) {
if (!pd.outDir.cd(pd.assetName)) {
addError(tr("Could not access temporary asset directory: \"%1\"")
.arg(outDir.filePath(assetName)));
return;
.arg(pd.outDir.filePath(pd.assetName)));
return false;
}
return true;
}
addInfo(tr("Generating 3D assets for: \"%1\"").arg(targetDir.absoluteFilePath(assetName)));
if (m_quick3DAssetImporter->importFile(
sourceInfo.absoluteFilePath(), outDir, options, &errorString)
!= QSSGAssetImportManager::ImportState::Success) {
addError(tr("Failed to import 3D asset with error: %1").arg(errorString),
sourceInfo.absoluteFilePath());
return;
}
// The importer is reset after every import to avoid issues with it caching various things
m_quick3DAssetImporter.reset(new QSSGAssetImportManager);
if (originalAssetName != assetName) {
void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
{
QDir outDir = pd.outDir;
if (pd.originalAssetName != pd.assetName) {
// Fix the generated qml file name
const QString assetQml = originalAssetName + ".qml";
const QString assetQml = pd.originalAssetName + ".qml";
if (outDir.exists(assetQml))
outDir.rename(assetQml, assetName + ".qml");
outDir.rename(assetQml, pd.assetName + ".qml");
}
QHash<QString, QString> assetFiles;
const int outDirPathSize = outDir.path().size();
auto insertAsset = [&](const QString &filePath) {
QString targetPath = filePath.mid(outDirPathSize);
targetPath.prepend(targetDirPath);
targetPath.prepend(pd.targetDirPath);
assetFiles.insert(filePath, targetPath);
};
@@ -348,7 +311,7 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar
qmlInfo.append("module ");
qmlInfo.append(m_importPath.split('/').last());
qmlInfo.append(".");
qmlInfo.append(assetName);
qmlInfo.append(pd.assetName);
qmlInfo.append('\n');
while (qmlIt.hasNext()) {
qmlIt.next();
@@ -382,7 +345,7 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar
out << "canBeDroppedInView3D: true" << Qt::endl;
file.close();
}
if (generateComponentIcon(24, iconFileName, qmlIt.filePath())) {
if (startIconProcess(24, iconFileName, qmlIt.filePath())) {
// Since icon is generated by external process, the file won't be
// ready for asset gathering below, so assume its generation succeeds
// and add it now.
@@ -395,7 +358,7 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar
qmldirFile.write(qmlInfo.toUtf8());
qmldirFile.commit();
} else {
addError(tr("Failed to create qmldir file for asset: \"%1\"").arg(assetName));
addError(tr("Failed to create qmldir file for asset: \"%1\"").arg(pd.assetName));
}
}
}
@@ -408,19 +371,13 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar
}
// Copy the original asset into a subdirectory
assetFiles.insert(sourceInfo.absoluteFilePath(),
targetDirPath + QStringLiteral("/source scene/") + sourceInfo.fileName());
assetFiles.insert(pd.sourceInfo.absoluteFilePath(),
pd.targetDirPath + QStringLiteral("/source scene/") + pd.sourceInfo.fileName());
m_importFiles.insert(assetFiles);
#else
Q_UNUSED(file)
Q_UNUSED(options)
#endif
}
void ItemLibraryAssetImporter::copyImportedFiles()
{
#ifdef IMPORT_QUICK3D_ASSETS
if (!m_overwrittenImports.isEmpty()) {
const QString progressTitle = tr("Removing old overwritten assets.");
addInfo(progressTitle);
@@ -461,15 +418,20 @@ void ItemLibraryAssetImporter::copyImportedFiles()
}
notifyProgress(100, progressTitle);
}
#endif
}
void ItemLibraryAssetImporter::notifyProgress(int value, const QString &text) const
void ItemLibraryAssetImporter::notifyProgress(int value, const QString &text)
{
emit progressChanged(value, text);
m_progressTitle = text;
emit progressChanged(value, m_progressTitle);
keepUiAlive();
}
void ItemLibraryAssetImporter::notifyProgress(int value)
{
notifyProgress(value, m_progressTitle);
}
void ItemLibraryAssetImporter::keepUiAlive() const
{
QApplication::processEvents();
@@ -484,7 +446,41 @@ bool ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName)
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes;
}
bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &iconFile,
bool ItemLibraryAssetImporter::startImportProcess(const ParseData &pd)
{
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
Model *model = doc ? doc->currentModel() : nullptr;
if (model) {
PuppetCreator puppetCreator(doc->currentTarget(), model);
puppetCreator.createQml2PuppetExecutableIfMissing();
QStringList puppetArgs;
QJsonDocument optDoc(pd.options);
puppetArgs << "--import3dAsset" << pd.sourceInfo.absoluteFilePath()
<< pd.outDir.absolutePath() << QString::number(pd.importId)
<< QString::fromUtf8(optDoc.toJson());
QProcessUniquePointer process = puppetCreator.createPuppetProcess(
"custom",
{},
std::function<void()>(),
[&](int exitCode, QProcess::ExitStatus exitStatus) {
importProcessFinished(exitCode, exitStatus);
},
puppetArgs);
if (process->waitForStarted(5000)) {
m_qmlPuppetProcesses.push_back(std::move(process));
return true;
} else {
process.reset();
}
}
return false;
}
bool ItemLibraryAssetImporter::startIconProcess(int size, const QString &iconFile,
const QString &iconSource)
{
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
@@ -500,7 +496,7 @@ bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &ic
{},
std::function<void()>(),
[&](int exitCode, QProcess::ExitStatus exitStatus) {
processFinished(exitCode, exitStatus);
iconProcessFinished(exitCode, exitStatus);
},
puppetArgs);
@@ -514,9 +510,30 @@ bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &ic
return false;
}
void ItemLibraryAssetImporter::postImport()
{
Q_ASSERT(m_qmlPuppetProcesses.empty());
if (!isCancelled()) {
for (const auto &pd : qAsConst(m_parseData))
postParseQuick3DAsset(pd);
}
if (!isCancelled()) {
// Wait for icon generation processes to finish
if (m_qmlPuppetProcesses.empty()) {
finalizeQuick3DImport();
} else {
m_qmlPuppetCount = static_cast<int>(m_qmlPuppetProcesses.size());
const QString progressTitle = tr("Generating icons.");
addInfo(progressTitle);
notifyProgress(0, progressTitle);
}
}
}
void ItemLibraryAssetImporter::finalizeQuick3DImport()
{
#ifdef IMPORT_QUICK3D_ASSETS
if (!isCancelled()) {
// Don't allow cancel anymore as existing asset overwrites are not trivially recoverable.
// Also, on Windows at least you can't delete a subdirectory of a watched directory,
@@ -562,7 +579,6 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport()
notifyFinished();
}
}
#endif
}
bool ItemLibraryAssetImporter::isCancelled() const

View File

@@ -29,11 +29,13 @@
#include <qprocessuniqueptr.h>
#include <QSet>
#include <QtCore/qhash.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qobject.h>
#include <QtCore/qprocess.h>
#include <QtCore/qstringlist.h>
#include <QHash>
#include <QJsonObject>
#include <QObject>
#include <QProcess>
#include <QStringList>
#include <QDir>
#include <QFileInfo>
QT_BEGIN_NAMESPACE
class QSSGAssetImportManager;
@@ -62,11 +64,6 @@ public:
void addWarning(const QString &warningMsg, const QString &srcPath = {}) const;
void addInfo(const QString &infoMsg, const QString &srcPath = {}) const;
bool isQuick3DAsset(const QString &fileName) const;
QVariantMap supportedOptions(const QString &modelFile) const;
QHash<QString, QVariantMap> allOptions() const;
QHash<QString, QStringList> supportedExtensions() const;
signals:
void errorReported(const QString &, const QString &) const;
void warningReported(const QString &, const QString &) const;
@@ -76,32 +73,49 @@ signals:
void importFinished();
private slots:
void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
void importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
void iconProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
private:
struct ParseData {
QJsonObject options;
QDir targetDir;
QDir outDir;
QString targetDirPath;
QFileInfo sourceInfo;
QString assetName;
QString originalAssetName;
int importId;
};
void notifyFinished();
void reset();
void parseFiles(const QStringList &filePaths, const QVector<QJsonObject> &options,
const QHash<QString, int> &extToImportOptionsMap);
void parseQuick3DAsset(const QString &file, const QVariantMap &options);
bool preParseQuick3DAsset(const QString &file, ParseData &pd);
void postParseQuick3DAsset(const ParseData &pd);
void copyImportedFiles();
void notifyProgress(int value, const QString &text) const;
void notifyProgress(int value, const QString &text);
void notifyProgress(int value);
void keepUiAlive() const;
bool confirmAssetOverwrite(const QString &assetName);
bool generateComponentIcon(int size, const QString &iconFile, const QString &iconSource);
bool startImportProcess(const ParseData &pd);
bool startIconProcess(int size, const QString &iconFile, const QString &iconSource);
void postImport();
void finalizeQuick3DImport();
#ifdef IMPORT_QUICK3D_ASSETS
QScopedPointer<QSSGAssetImportManager> m_quick3DAssetImporter;
QSet<QHash<QString, QString>> m_importFiles;
QSet<QString> m_overwrittenImports;
#endif
bool m_isImporting = false;
bool m_cancelled = false;
QString m_importPath;
QTemporaryDir *m_tempDir = nullptr;
std::vector<QProcessUniquePointer> m_qmlPuppetProcesses;
int m_qmlPuppetCount = 0;
int m_qmlImportFinishedCount = 0;
int m_importIdCounter = 1000000; // Use ids in range unlikely to clash with any normal process exit codes
QHash<int, ParseData> m_parseData;
QString m_progressTitle;
};
} // QmlDesigner

View File

@@ -25,6 +25,7 @@
#include "itemlibraryview.h"
#include "itemlibrarywidget.h"
#include "itemlibraryassetimportdialog.h"
#include "metainfo.h"
#include <asynchronousimagecache.h>
#include <bindingproperty.h>
@@ -216,4 +217,46 @@ void ItemLibraryView::updateImports()
m_widget->delayedUpdateModel();
}
void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap)
{
QVariantMap extMap = qvariant_cast<QVariantMap>(supportMap.value("extensions"));
if (m_importableExtensions3DMap != extMap) {
DesignerActionManager *actionManager =
&QmlDesignerPlugin::instance()->viewManager().designerActionManager();
// All things importable by QSSGAssetImportManager are considered to be in the same category
// so we don't get multiple separate import dialogs when different file types are imported.
const QString category = tr("3D Assets");
if (!m_importableExtensions3DMap.isEmpty())
actionManager->unregisterAddResourceHandlers(category);
m_importableExtensions3DMap = extMap;
auto handle3DModel = [this](const QStringList &fileNames, const QString &defaultDir) -> bool {
auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir,
m_importableExtensions3DMap,
m_importOptions3DMap,
Core::ICore::mainWindow());
importDlg->show();
return true;
};
auto add3DHandler = [&](const QString &category, const QString &ext) {
const QString filter = QStringLiteral("*.%1").arg(ext);
actionManager->registerAddResourceHandler(
AddResourceHandler(category, filter, handle3DModel, 10));
};
const auto groups = extMap.keys();
for (const auto &group : groups) {
const QStringList exts = extMap[group].toStringList();
for (const auto &ext : exts)
add3DHandler(category, ext);
}
}
m_importOptions3DMap = qvariant_cast<QVariantMap>(supportMap.value("options"));
}
} // namespace QmlDesigner

View File

@@ -53,6 +53,7 @@ public:
void modelAboutToBeDetached(Model *model) override;
void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override;
void documentMessagesChanged(const QList<DocumentMessage> &errors, const QList<DocumentMessage> &warnings) override;
void updateImport3DSupport(const QVariantMap &supportMap) override;
void setResourcePath(const QString &resourcePath);
@@ -66,6 +67,8 @@ private:
QPointer<ItemLibraryWidget> m_widget;
ImportManagerView *m_importManagerView;
bool m_hasErrors = false;
QVariantMap m_importableExtensions3DMap;
QVariantMap m_importOptions3DMap;
};
}

View File

@@ -26,7 +26,6 @@
#include "itemlibrarywidget.h"
#include "customfilesystemmodel.h"
#include "itemlibraryassetimportdialog.h"
#include "itemlibraryiconimageprovider.h"
#include <theme.h>
@@ -53,10 +52,6 @@
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#ifdef IMPORT_QUICK3D_ASSETS
#include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
#endif
#include <QApplication>
#include <QDrag>
#include <QFileDialog>
@@ -205,49 +200,6 @@ ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache,
addResources({});
});
#ifdef IMPORT_QUICK3D_ASSETS
DesignerActionManager *actionManager =
&QmlDesignerPlugin::instance()->viewManager().designerActionManager();
auto handle3DModel = [](const QStringList &fileNames, const QString &defaultDir) -> bool {
auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir, Core::ICore::mainWindow());
importDlg->show();
return true;
};
auto add3DHandler = [&](const QString &category, const QString &ext) {
const QString filter = QStringLiteral("*.%1").arg(ext);
actionManager->registerAddResourceHandler(
AddResourceHandler(category, filter, handle3DModel, 10));
};
QSSGAssetImportManager importManager;
QHash<QString, QStringList> supportedExtensions = importManager.getSupportedExtensions();
// All things importable by QSSGAssetImportManager are considered to be in the same category
// so we don't get multiple separate import dialogs when different file types are imported.
const QString category = tr("3D Assets");
// Skip if 3D asset handlers have already been added
const QList<AddResourceHandler> handlers = actionManager->addResourceHandler();
bool categoryAlreadyAdded = false;
for (const auto &handler : handlers) {
if (handler.category == category) {
categoryAlreadyAdded = true;
break;
}
}
if (!categoryAlreadyAdded) {
const auto groups = supportedExtensions.keys();
for (const auto &group : groups) {
const auto extensions = supportedExtensions[group];
for (const auto &ext : extensions)
add3DHandler(category, ext);
}
}
#endif
const auto dropSupport = new Utils::DropSupport(
m_resourcesView.data(), [this](QDropEvent *event, Utils::DropSupport *) {
// Accept supported file types

View File

@@ -189,6 +189,7 @@ public:
void emitRenderImage3DChanged(const QImage &image);
void emitUpdateActiveScene3D(const QVariantMap &sceneState);
void emitModelNodelPreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap);
void emitImport3DSupportChanged(const QVariantMap &supportMap);
void sendTokenToInstances(const QString &token, int number, const QVector<ModelNode> &nodeVector);
@@ -248,6 +249,7 @@ public:
virtual void renderImage3DChanged(const QImage &image);
virtual void updateActiveScene3D(const QVariantMap &sceneState);
virtual void updateImport3DSupport(const QVariantMap &supportMap);
virtual void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap);
void changeRootNodeType(const TypeName &type, int majorVersion, int minorVersion);

View File

@@ -1578,6 +1578,9 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand
updatePreviewImageForNode(node, image);
}
}
} else if (command.type() == PuppetToCreatorCommand::Import3DSupport) {
const QVariantMap supportMap = qvariant_cast<QVariantMap>(command.data());
emitImport3DSupportChanged(supportMap);
}
}

View File

@@ -386,6 +386,10 @@ void AbstractView::updateActiveScene3D(const QVariantMap & /*sceneState*/)
{
}
void AbstractView::updateImport3DSupport(const QVariantMap & /*supportMap*/)
{
}
void AbstractView::modelNodePreviewPixmapChanged(const ModelNode & /*node*/, const QPixmap & /*pixmap*/)
{
}
@@ -804,6 +808,12 @@ void AbstractView::emitModelNodelPreviewPixmapChanged(const ModelNode &node, con
model()->d->notifyModelNodePreviewPixmapChanged(node, pixmap);
}
void AbstractView::emitImport3DSupportChanged(const QVariantMap &supportMap)
{
if (model())
model()->d->notifyImport3DSupportChanged(supportMap);
}
void AbstractView::emitRewriterEndTransaction()
{
if (model())

View File

@@ -575,6 +575,11 @@ void ModelPrivate::notifyModelNodePreviewPixmapChanged(const ModelNode &node, co
[&](AbstractView *view) { view->modelNodePreviewPixmapChanged(node, pixmap); });
}
void ModelPrivate::notifyImport3DSupportChanged(const QVariantMap &supportMap)
{
notifyInstanceChanges([&](AbstractView *view) { view->updateImport3DSupport(supportMap); });
}
void ModelPrivate::notifyRewriterBeginTransaction()
{
notifyNodeInstanceViewLast([&](AbstractView *view) { view->rewriterBeginTransaction(); });

View File

@@ -176,6 +176,7 @@ public:
void notifyRenderImage3DChanged(const QImage &image);
void notifyUpdateActiveScene3D(const QVariantMap &sceneState);
void notifyModelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap);
void notifyImport3DSupportChanged(const QVariantMap &supportMap);
void setDocumentMessages(const QList<DocumentMessage> &errors, const QList<DocumentMessage> &warnings);

View File

@@ -137,6 +137,14 @@ extend_qtc_executable(qml2puppet
icongizmoimageprovider.cpp icongizmoimageprovider.h
)
find_package(Qt5 5.15.0 COMPONENTS Quick3DAssetImport QUIET)
extend_qtc_executable(qml2puppet
CONDITION TARGET Qt5::Quick3DAssetImport
FEATURE_INFO "Qt Quick 3D asset import"
DEPENDS Qt5::Quick3DAssetImportPrivate
DEFINES IMPORT_QUICK3D_ASSETS
)
extend_qtc_executable(qml2puppet
CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0
@@ -151,6 +159,12 @@ extend_qtc_executable(qml2puppet
iconrenderer.cpp iconrenderer.h
)
extend_qtc_executable(qml2puppet
SOURCES_PREFIX "${SRCDIR}/qml2puppet/import3d"
SOURCES
import3d.cpp import3d.h
)
extend_qtc_executable(qml2puppet
SOURCES_PREFIX "${SRCDIR}/qml2puppet/instances"
SOURCES

View File

@@ -243,6 +243,8 @@ QtcTool {
"editor3d/icongizmoimageprovider.h",
"iconrenderer/iconrenderer.cpp",
"iconrenderer/iconrenderer.h",
"import3d/import3d.cpp",
"import3d/import3d.h",
"qml2puppetmain.cpp",
]