diff options
author | Vikas Pachdha <vikas.pachdha@qt.io> | 2020-06-19 10:00:47 +0200 |
---|---|---|
committer | Vikas Pachdha <vikas.pachdha@qt.io> | 2020-06-26 10:26:43 +0000 |
commit | 9daf5c130da6edb0f03b9f7f61c34c368d4cca56 (patch) | |
tree | 7db58ba08141334805f3bb415f3ebc1e599fcb2a | |
parent | 000281fed770b5af96d093724fb1be1347bf5eed (diff) | |
download | qt-creator-9daf5c130da6edb0f03b9f7f61c34c368d4cca56.tar.gz |
AssetExport: Export assets from renderable nodes
Task-number: QDS-1555
Change-Id: I3d5b60ee8214aeee054587f45045beea020d1f13
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
9 files changed, 288 insertions, 5 deletions
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 94a4f449a7..12b58ac14f 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -52,6 +52,7 @@ add_qtc_plugin(assetexporterplugin assetexporterplugin/componentexporter.h assetexporterplugin/componentexporter.cpp assetexporterplugin/exportnotification.h assetexporterplugin/exportnotification.cpp assetexporterplugin/filepathmodel.h assetexporterplugin/filepathmodel.cpp + assetexporterplugin/parsers/assetnodeparser.h assetexporterplugin/parsers/assetnodeparser.cpp assetexporterplugin/parsers/modelitemnodeparser.h assetexporterplugin/parsers/modelitemnodeparser.cpp assetexporterplugin/parsers/modelnodeparser.h assetexporterplugin/parsers/modelnodeparser.cpp assetexporterplugin/parsers/textnodeparser.h assetexporterplugin/parsers/textnodeparser.cpp diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp index e85ac11cab..49d763f6b4 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp @@ -26,15 +26,39 @@ #include "componentexporter.h" #include "exportnotification.h" +#include "qmlitemnode.h" +#include "qmlobjectnode.h" #include "utils/qtcassert.h" +#include "utils/runextensions.h" +#include "variantproperty.h" +#include <QCryptographicHash> +#include <QDir> #include <QJsonArray> #include <QJsonDocument> #include <QLoggingCategory> +#include <QWaitCondition> -using namespace ProjectExplorer; +#include <random> +#include <queue> +using namespace ProjectExplorer; +using namespace std; namespace { +bool makeParentPath(const Utils::FilePath &path) +{ + QDir d; + return d.mkpath(path.toFileInfo().absolutePath()); +} + +QByteArray generateHash(const QString &token) { + static uint counter = 0; + std::mt19937 gen(std::random_device().operator()()); + std::uniform_int_distribution<> distribution(1, 99999); + QByteArray data = QString("%1%2%3").arg(token).arg(++counter).arg(distribution(gen)).toLatin1(); + return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex(); +} + Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg) Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg) Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg) @@ -42,6 +66,34 @@ Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", namespace QmlDesigner { +class AssetDumper +{ +public: + AssetDumper(); + ~AssetDumper(); + + void dumpAsset(const QPixmap &p, const Utils::FilePath &path); + + /* Keeps on dumping until all assets are dumped, then quits */ + void quitDumper(); + + /* Aborts dumping */ + void abortDumper(); + +private: + void addAsset(const QPixmap &p, const Utils::FilePath &path); + void doDumping(QFutureInterface<void> &fi); + void savePixmap(const QPixmap &p, Utils::FilePath &path) const; + + QFuture<void> m_dumpFuture; + QMutex m_queueMutex; + QWaitCondition m_queueCondition; + std::queue<std::pair<QPixmap, Utils::FilePath>> m_assets; + std::atomic<bool> m_quitDumper; +}; + + + AssetExporter::AssetExporter(AssetExporterView *view, ProjectExplorer::Project *project, QObject *parent) : QObject(parent), m_currentState(*this), @@ -71,11 +123,13 @@ void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::Fil m_exportPath = exportPath; m_currentState.change(ParsingState::Parsing); triggerLoadNextFile(); + m_assetDumper = make_unique<AssetDumper>(); } void AssetExporter::cancel() { // TODO Cancel export + m_assetDumper.reset(); } bool AssetExporter::isBusy() const @@ -85,6 +139,21 @@ bool AssetExporter::isBusy() const m_currentState == AssetExporter::ParsingState::WritingJson; } +Utils::FilePath AssetExporter::exportAsset(const QmlObjectNode &node) +{ + // TODO: Use this hash as UUID and add to the node. + QByteArray hash; + do { + hash = generateHash(node.id()); + } while (m_usedHashes.contains(hash)); + m_usedHashes.insert(hash); + + Utils::FilePath assetPath = m_exportPath.pathAppended(QString("assets/%1.png") + .arg(QString::fromLatin1(hash))); + m_assetDumper->dumpAsset(node.toQmlItemNode().instanceRenderPixmap(), assetPath); + return assetPath; +} + void AssetExporter::exportComponent(const ModelNode &rootNode) { qCDebug(loggerInfo) << "Exporting component" << rootNode.id(); @@ -149,6 +218,7 @@ void AssetExporter::writeMetadata() const Utils::FilePath metadataPath = m_exportPath.pathAppended(m_exportPath.fileName() + ".metadata"); ExportNotification::addInfo(tr("Writing metadata to file %1."). arg(metadataPath.toUserOutput())); + makeParentPath(metadataPath); m_currentState.change(ParsingState::WritingJson); QJsonObject jsonRoot; // TODO: Write plugin info to root jsonRoot.insert("artboards", m_components); @@ -165,6 +235,7 @@ void AssetExporter::writeMetadata() const } notifyProgress(1.0); ExportNotification::addInfo(tr("Export finished.")); + m_assetDumper->quitDumper(); m_currentState.change(ParsingState::ExportingDone); } @@ -189,4 +260,93 @@ QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s) return os; } +AssetDumper::AssetDumper(): + m_quitDumper(false) +{ + m_dumpFuture = Utils::runAsync(&AssetDumper::doDumping, this); +} + +AssetDumper::~AssetDumper() +{ + abortDumper(); +} + +void AssetDumper::dumpAsset(const QPixmap &p, const Utils::FilePath &path) +{ + addAsset(p, path); +} + +void AssetDumper::quitDumper() +{ + m_quitDumper = true; + m_queueCondition.wakeAll(); + if (!m_dumpFuture.isFinished()) + m_dumpFuture.waitForFinished(); +} + +void AssetDumper::abortDumper() +{ + if (!m_dumpFuture.isFinished()) { + m_dumpFuture.cancel(); + m_queueCondition.wakeAll(); + m_dumpFuture.waitForFinished(); + } +} + +void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path) +{ + QMutexLocker locker(&m_queueMutex); + qDebug() << "Save Asset:" << path; + m_assets.push({p, path}); +} + +void AssetDumper::doDumping(QFutureInterface<void> &fi) +{ + auto haveAsset = [this] (std::pair<QPixmap, Utils::FilePath> *asset) { + QMutexLocker locker(&m_queueMutex); + if (m_assets.empty()) + return false; + *asset = m_assets.front(); + m_assets.pop(); + return true; + }; + + forever { + std::pair<QPixmap, Utils::FilePath> asset; + if (haveAsset(&asset)) { + if (fi.isCanceled()) + break; + savePixmap(asset.first, asset.second); + } else { + if (m_quitDumper) + break; + QMutexLocker locker(&m_queueMutex); + m_queueCondition.wait(&m_queueMutex); + } + + if (fi.isCanceled()) + break; + } + fi.reportFinished(); +} + +void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const +{ + if (p.isNull()) { + qCDebug(loggerWarn) << "Dumping null pixmap" << path; + return; + } + + if (!makeParentPath(path)) { + ExportNotification::addError(AssetExporter::tr("Error creating asset directory. %1") + .arg(path.fileName())); + return; + } + + if (!p.save(path.toString())) { + ExportNotification::addError(AssetExporter::tr("Error saving asset. %1") + .arg(path.fileName())); + } +} + } diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h index 9c2212f5e2..a4d6df23c0 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h @@ -29,6 +29,7 @@ #include <QJsonArray> #include <QJsonObject> +#include <QSet> #include <memory> @@ -41,6 +42,7 @@ class Project; } namespace QmlDesigner { +class AssetDumper; class AssetExporter : public QObject { @@ -68,6 +70,8 @@ public: void cancel(); bool isBusy() const; + Utils::FilePath exportAsset(const QmlObjectNode& node); + signals: void stateChanged(ParsingState); void exportProgressChanged(double) const; @@ -82,6 +86,7 @@ private: void loadNextFile(); void onQmlFileLoaded(); + private: mutable class State { public: @@ -96,6 +101,8 @@ private: Utils::FilePaths m_exportFiles; Utils::FilePath m_exportPath; QJsonArray m_components; + QSet<QByteArray> m_usedHashes; + std::unique_ptr<AssetDumper> m_assetDumper; }; QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s); diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp index fcd492cb0e..e45e48ca0d 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp @@ -34,6 +34,7 @@ #include "parsers/modelitemnodeparser.h" #include "parsers/textnodeparser.h" +#include "parsers/assetnodeparser.h" #include "coreplugin/actionmanager/actionmanager.h" #include "coreplugin/actionmanager/actioncontainer.h" @@ -68,8 +69,9 @@ AssetExporterPlugin::AssetExporterPlugin() : viewManager.registerViewTakingOwnership(m_view); // Add parsers templates for factory instantiation. - ComponentExporter::addNodeParser<ItemNodeParser>(); - ComponentExporter::addNodeParser<TextNodeParser>(); + Component::addNodeParser<ItemNodeParser>(); + Component::addNodeParser<TextNodeParser>(); + Component::addNodeParser<AssetNodeParser>(); // Instantiate actions created by the plugin. addActions(); @@ -93,7 +95,8 @@ void AssetExporterPlugin::onExport() return; FilePathModel model(startupProject); - auto exportDir = startupProject->projectFilePath().parentDir(); + QString exportDirName = startupProject->displayName() + "_export"; + auto exportDir = startupProject->projectFilePath().parentDir().pathAppended(exportDirName); AssetExporter assetExporter(m_view, startupProject); AssetExportDialog assetExporterDialog(exportDir, assetExporter, model); assetExporterDialog.exec(); diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri index b0e4f6392a..713ab1184f 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri @@ -15,6 +15,7 @@ HEADERS += \ componentexporter.h \ exportnotification.h \ filepathmodel.h \ + parsers/assetnodeparser.h \ parsers/modelitemnodeparser.h \ parsers/modelnodeparser.h \ parsers/textnodeparser.h @@ -27,6 +28,7 @@ SOURCES += \ componentexporter.cpp \ exportnotification.cpp \ filepathmodel.cpp \ + parsers/assetnodeparser.cpp \ parsers/modelitemnodeparser.cpp \ parsers/modelnodeparser.cpp \ parsers/textnodeparser.cpp diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs index e8ce253736..e847525324 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs @@ -47,6 +47,8 @@ QtcProduct { "exportnotification.h", "filepathmodel.cpp", "filepathmodel.h", + "parsers/assetnodeparser.cpp", + "parsers/assetnodeparser.h", "parsers/modelitemnodeparser.cpp", "parsers/modelitemnodeparser.h", "parsers/modelnodeparser.cpp", diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h index 169d99ebfb..2e9ba7a56d 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h @@ -57,7 +57,7 @@ const char ImportsTag[] = "extraImports"; const char UuidTag[] = "uuid"; const char ClipTag[] = "clip"; const char AssetDataTag[] = "assetData"; -const char AssetPath[] = "assetPath"; +const char AssetPathTag[] = "assetPath"; const char AssetBoundsTag[] = "assetBounds"; const char OpacityTag[] = "opacity"; diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp new file mode 100644 index 0000000000..159eccec46 --- /dev/null +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "assetnodeparser.h" +#include "assetexportpluginconstants.h" +#include "assetexporter.h" + +#include "qmlitemnode.h" +#include "componentexporter.h" + +#include "utils/fileutils.h" + +#include <QPixmap> + +namespace QmlDesigner { +using namespace Constants; +AssetNodeParser::AssetNodeParser(const QByteArrayList &lineage, const ModelNode &node) : + ItemNodeParser(lineage, node) +{ + +} + +bool AssetNodeParser::isExportable() const +{ + auto hasType = [this](const QByteArray &type) { + return lineage().contains(type); + }; + return hasType("QtQuick.Image") || hasType("QtQuick.Rectangle"); +} + +QJsonObject AssetNodeParser::json(Component &component) const +{ + QJsonObject jsonObject = ItemNodeParser::json(component); + + QPixmap asset = objectNode().toQmlItemNode().instanceRenderPixmap(); + Utils::FilePath assetPath = component.exporter().exportAsset(objectNode()); + + QJsonObject assetData; + assetData.insert(AssetPathTag, assetPath.toString()); + jsonObject.insert(AssetDataTag, assetData); + return jsonObject; +} +} + diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.h b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.h new file mode 100644 index 0000000000..be764b17ec --- /dev/null +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "modelitemnodeparser.h" + +namespace QmlDesigner { +class Component; + +class AssetNodeParser : public ItemNodeParser +{ +public: + AssetNodeParser(const QByteArrayList &lineage, const ModelNode &node); + ~AssetNodeParser() override = default; + + bool isExportable() const override; + int priority() const override { return 200; } + QJsonObject json(Component &component) const override; +}; +} |