forked from qt-creator/qt-creator
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>
This commit is contained in:
@@ -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
|
||||
|
@@ -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>
|
||||
|
||||
#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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
@@ -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",
|
||||
|
@@ -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";
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user