forked from qt-creator/qt-creator
QmlDesigner: Implement exporting a local 3D component
Also some relevant plumbing. Fixes: QDS-12394 Change-Id: Ifead81f54cc4137f42a590ef05f1965791242bfc Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
@@ -134,4 +134,13 @@ StudioControls.Menu {
|
|||||||
|
|
||||||
onTriggered: MaterialBrowserBackend.rootView.addMaterialToContentLibrary()
|
onTriggered: MaterialBrowserBackend.rootView.addMaterialToContentLibrary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: implement
|
||||||
|
// StudioControls.MenuItem {
|
||||||
|
// text: qsTr("Export material")
|
||||||
|
// enabled: !materialBrowserModel.selectedMaterialIsComponent // TODO: support component materials
|
||||||
|
// visible: false
|
||||||
|
|
||||||
|
// onTriggered: MaterialBrowserBackend.rootView.exportMaterial()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@@ -492,7 +492,7 @@ add_qtc_plugin(QmlDesigner
|
|||||||
PLUGIN_MANUAL_DEPENDS LicenseChecker ${IDE_VERSION} optional
|
PLUGIN_MANUAL_DEPENDS LicenseChecker ${IDE_VERSION} optional
|
||||||
DEPENDS
|
DEPENDS
|
||||||
QmlJS LanguageUtils QmlEditorWidgets AdvancedDockingSystem
|
QmlJS LanguageUtils QmlEditorWidgets AdvancedDockingSystem
|
||||||
Qt::QuickWidgets Qt::CorePrivate Qt::Xml Qt::Svg QmlDesignerCore Sqlite
|
Qt::QuickWidgets Qt::CorePrivate Qt::Xml Qt::Svg QmlDesignerCore Sqlite Zip
|
||||||
PUBLIC_DEPENDS
|
PUBLIC_DEPENDS
|
||||||
QmlDesignerUtils QmlPuppetCommunication QmlDesignerBase
|
QmlDesignerUtils QmlPuppetCommunication QmlDesignerBase
|
||||||
DEFINES
|
DEFINES
|
||||||
|
@@ -308,8 +308,13 @@ QPair<QString, QString> ContentLibraryUserModel::getUniqueLib3DNames(const QStri
|
|||||||
QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QString &defaultName,
|
QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QString &defaultName,
|
||||||
const QJsonObject &bundleObj) const
|
const QJsonObject &bundleObj) const
|
||||||
{
|
{
|
||||||
QTC_ASSERT(!bundleObj.isEmpty(), return {});
|
QString uniqueQml = UniqueName::generateId(defaultName);
|
||||||
|
uniqueQml[0] = uniqueQml.at(0).toUpper();
|
||||||
|
uniqueQml.prepend("My");
|
||||||
|
|
||||||
|
QString uniqueIcon = defaultName;
|
||||||
|
|
||||||
|
if (!bundleObj.isEmpty()) {
|
||||||
const QJsonArray itemsArr = bundleObj.value("items").toArray();
|
const QJsonArray itemsArr = bundleObj.value("items").toArray();
|
||||||
|
|
||||||
QStringList itemQmls, itemIcons;
|
QStringList itemQmls, itemIcons;
|
||||||
@@ -319,17 +324,14 @@ QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QSt
|
|||||||
itemIcons.append(QFileInfo(obj.value("icon").toString()).baseName());
|
itemIcons.append(QFileInfo(obj.value("icon").toString()).baseName());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString baseQml = UniqueName::generateId(defaultName);
|
uniqueQml = UniqueName::generate(uniqueQml, [&] (const QString &name) {
|
||||||
baseQml[0] = baseQml.at(0).toUpper();
|
|
||||||
baseQml.prepend("My");
|
|
||||||
|
|
||||||
QString uniqueQml = UniqueName::generate(baseQml, [&] (const QString &name) {
|
|
||||||
return itemQmls.contains(name);
|
return itemQmls.contains(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
QString uniqueIcon = UniqueName::generate(defaultName, [&] (const QString &name) {
|
uniqueIcon = UniqueName::generate(uniqueIcon, [&] (const QString &name) {
|
||||||
return itemIcons.contains(name);
|
return itemIcons.contains(name);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {uniqueQml + ".qml", uniqueIcon + ".png"};
|
return {uniqueQml + ".qml", uniqueIcon + ".png"};
|
||||||
}
|
}
|
||||||
|
@@ -43,6 +43,8 @@ public:
|
|||||||
|
|
||||||
QPair<QString, QString> getUniqueLibMaterialNames(const QString &defaultName = "Material") const;
|
QPair<QString, QString> getUniqueLibMaterialNames(const QString &defaultName = "Material") const;
|
||||||
QPair<QString, QString> getUniqueLib3DNames(const QString &defaultName = "Item") const;
|
QPair<QString, QString> getUniqueLib3DNames(const QString &defaultName = "Item") const;
|
||||||
|
QPair<QString, QString> getUniqueLibItemNames(const QString &defaultName = "Item",
|
||||||
|
const QJsonObject &bundleObj = {}) const;
|
||||||
|
|
||||||
void setQuick3DImportVersion(int major, int minor);
|
void setQuick3DImportVersion(int major, int minor);
|
||||||
|
|
||||||
@@ -97,8 +99,6 @@ private:
|
|||||||
void loadTextureBundle();
|
void loadTextureBundle();
|
||||||
void removeMaterialFromContentLib(ContentLibraryMaterial *mat);
|
void removeMaterialFromContentLib(ContentLibraryMaterial *mat);
|
||||||
void remove3DFromContentLib(ContentLibraryItem *item);
|
void remove3DFromContentLib(ContentLibraryItem *item);
|
||||||
QPair<QString, QString> getUniqueLibItemNames(const QString &defaultName,
|
|
||||||
const QJsonObject &bundleObj) const;
|
|
||||||
|
|
||||||
ContentLibraryWidget *m_widget = nullptr;
|
ContentLibraryWidget *m_widget = nullptr;
|
||||||
QString m_searchText;
|
QString m_searchText;
|
||||||
|
@@ -27,6 +27,8 @@
|
|||||||
#include <utils3d.h>
|
#include <utils3d.h>
|
||||||
#include <variantproperty.h>
|
#include <variantproperty.h>
|
||||||
|
|
||||||
|
#include <solutions/zip/zipwriter.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
|
|
||||||
#ifndef QMLDESIGNER_TEST
|
#ifndef QMLDESIGNER_TEST
|
||||||
@@ -37,11 +39,14 @@
|
|||||||
#include <qtsupport/qtkitaspect.h>
|
#include <qtsupport/qtkitaspect.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QImage>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QTemporaryDir>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
@@ -373,6 +378,10 @@ void ContentLibraryView::customNotification(const AbstractView *view,
|
|||||||
addLib3DComponent(nodeList.first());
|
addLib3DComponent(nodeList.first());
|
||||||
else
|
else
|
||||||
addLib3DItem(nodeList.first());
|
addLib3DItem(nodeList.first());
|
||||||
|
} else if (identifier == "export_item_as_bundle") {
|
||||||
|
exportLib3DItem(nodeList.first());
|
||||||
|
} else if (identifier == "export_material_as_bundle") {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +519,7 @@ void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &ic
|
|||||||
auto [qml, icon] = m_widget->userModel()->getUniqueLibMaterialNames(node.id());
|
auto [qml, icon] = m_widget->userModel()->getUniqueLibMaterialNames(node.id());
|
||||||
|
|
||||||
QString iconPath = QLatin1String("icons/%1").arg(icon);
|
QString iconPath = QLatin1String("icons/%1").arg(icon);
|
||||||
QString fullIconPath = bundlePath.pathAppended(iconPath).toString();
|
QString fullIconPath = bundlePath.pathAppended(iconPath).toFSPathString();
|
||||||
|
|
||||||
// save icon
|
// save icon
|
||||||
bool iconSaved = iconPixmap.save(fullIconPath);
|
bool iconSaved = iconPixmap.save(fullIconPath);
|
||||||
@@ -518,66 +527,53 @@ void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &ic
|
|||||||
qWarning() << __FUNCTION__ << "icon save failed";
|
qWarning() << __FUNCTION__ << "icon save failed";
|
||||||
|
|
||||||
// generate and save material Qml file
|
// generate and save material Qml file
|
||||||
const QStringList depAssets = writeLibItemQml(node, qml);
|
auto [qmlString, depAssets] = modelNodeToQmlString(node);
|
||||||
|
const QStringList depAssetsList = depAssets.values();
|
||||||
|
|
||||||
|
auto result = bundlePath.pathAppended(qml).writeFileContents(qmlString.toUtf8());
|
||||||
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
|
|
||||||
// add the material to the bundle json
|
// add the material to the bundle json
|
||||||
QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef();
|
QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef();
|
||||||
QJsonArray itemsArr = jsonRef.value("items").toArray();
|
QJsonArray itemsArr = jsonRef.value("items").toArray();
|
||||||
QJsonObject itemObj;
|
itemsArr.append(QJsonObject {
|
||||||
itemObj.insert("name", name);
|
{"name", name},
|
||||||
itemObj.insert("qml", qml);
|
{"qml", qml},
|
||||||
itemObj.insert("icon", iconPath);
|
{"icon", iconPath},
|
||||||
QJsonArray filesArr;
|
{"files", QJsonArray::fromStringList(depAssetsList)}
|
||||||
for (const QString &assetPath : depAssets)
|
});
|
||||||
filesArr.append(assetPath);
|
|
||||||
itemObj.insert("files", filesArr);
|
|
||||||
|
|
||||||
itemsArr.append(itemObj);
|
|
||||||
jsonRef["items"] = itemsArr;
|
jsonRef["items"] = itemsArr;
|
||||||
|
|
||||||
auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
|
result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
|
||||||
.writeFileContents(QJsonDocument(jsonRef).toJson());
|
.writeFileContents(QJsonDocument(jsonRef).toJson());
|
||||||
if (!result)
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
|
|
||||||
// copy material assets to bundle folder
|
// copy material assets to bundle folder
|
||||||
for (const QString &assetPath : depAssets) {
|
for (const QString &assetPath : depAssetsList) {
|
||||||
Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
|
Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
|
||||||
Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath);
|
Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath);
|
||||||
assetPathTarget.parentDir().ensureWritableDir();
|
assetPathTarget.parentDir().ensureWritableDir();
|
||||||
|
|
||||||
auto result = assetPathSource.copyFile(assetPathTarget);
|
auto result = assetPathSource.copyFile(assetPathTarget);
|
||||||
if (!result)
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets);
|
m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssetsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList ContentLibraryView::writeLibItemQml(const ModelNode &node, const QString &qml)
|
QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const ModelNode &node, int depth)
|
||||||
{
|
{
|
||||||
QStringList depListIds;
|
static QStringList depListIds;
|
||||||
auto [qmlString, assets] = modelNodeToQmlString(node, depListIds);
|
|
||||||
|
|
||||||
qmlString.prepend("import QtQuick\nimport QtQuick3D\n\n");
|
|
||||||
|
|
||||||
QString itemType = QLatin1String(node.metaInfo().isQtQuick3DMaterial() ? "materials" : "3d");
|
|
||||||
auto qmlPath = Utils::FilePath::fromString(QLatin1String("%1/User/%2/%3")
|
|
||||||
.arg(Paths::bundlesPathSetting(), itemType, qml));
|
|
||||||
auto result = qmlPath.writeFileContents(qmlString.toUtf8());
|
|
||||||
if (!result)
|
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
|
|
||||||
return assets.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const ModelNode &node,
|
|
||||||
QStringList &depListIds,
|
|
||||||
int depth)
|
|
||||||
{
|
|
||||||
QString qml;
|
QString qml;
|
||||||
QSet<QString> assets;
|
QSet<QString> assets;
|
||||||
|
|
||||||
|
if (depth == 0) {
|
||||||
|
qml.append("import QtQuick\nimport QtQuick3D\n\n");
|
||||||
|
depListIds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
QString indent = QString(" ").repeated(depth * 4);
|
QString indent = QString(" ").repeated(depth * 4);
|
||||||
|
|
||||||
qml += indent + node.simplifiedTypeName() + " {\n";
|
qml += indent + node.simplifiedTypeName() + " {\n";
|
||||||
@@ -629,7 +625,7 @@ QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const Mod
|
|||||||
|
|
||||||
if (depNode && !depListIds.contains(depNode.id())) {
|
if (depNode && !depListIds.contains(depNode.id())) {
|
||||||
depListIds.append(depNode.id());
|
depListIds.append(depNode.id());
|
||||||
auto [depQml, depAssets] = modelNodeToQmlString(depNode, depListIds, depth + 1);
|
auto [depQml, depAssets] = modelNodeToQmlString(depNode, depth + 1);
|
||||||
qml += "\n" + depQml + "\n";
|
qml += "\n" + depQml + "\n";
|
||||||
assets.unite(depAssets);
|
assets.unite(depAssets);
|
||||||
}
|
}
|
||||||
@@ -662,7 +658,7 @@ void ContentLibraryView::addLibAssets(const QStringList &paths)
|
|||||||
|
|
||||||
// save icon
|
// save icon
|
||||||
QString iconSavePath = bundlePath.pathAppended("icons/" + assetFilePath.baseName() + ".png")
|
QString iconSavePath = bundlePath.pathAppended("icons/" + assetFilePath.baseName() + ".png")
|
||||||
.toString();
|
.toFSPathString();
|
||||||
QPixmap icon = asset.pixmap({120, 120});
|
QPixmap icon = asset.pixmap({120, 120});
|
||||||
bool iconSaved = icon.save(iconSavePath);
|
bool iconSaved = icon.save(iconSavePath);
|
||||||
if (!iconSaved)
|
if (!iconSaved)
|
||||||
@@ -670,10 +666,9 @@ void ContentLibraryView::addLibAssets(const QStringList &paths)
|
|||||||
|
|
||||||
// save asset
|
// save asset
|
||||||
auto result = assetFilePath.copyFile(bundlePath.pathAppended(asset.fileName()));
|
auto result = assetFilePath.copyFile(bundlePath.pathAppended(asset.fileName()));
|
||||||
if (!result)
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
|
|
||||||
pathsInBundle.append(bundlePath.pathAppended(asset.fileName()).toString());
|
pathsInBundle.append(bundlePath.pathAppended(asset.fileName()).toFSPathString());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_widget->userModel()->addTextures(pathsInBundle);
|
m_widget->userModel()->addTextures(pathsInBundle);
|
||||||
@@ -706,8 +701,15 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node)
|
|||||||
|
|
||||||
// generate and save icon
|
// generate and save icon
|
||||||
QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png");
|
QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png");
|
||||||
QString fullIconPath = bundlePath.pathAppended(iconPath).toString();
|
m_iconSavePath = bundlePath.pathAppended(iconPath);
|
||||||
genAndSaveIcon(compDir.pathAppended(compFileName).path(), fullIconPath);
|
m_iconSavePath.parentDir().ensureWritableDir();
|
||||||
|
getImageFromCache(compDir.pathAppended(compFileName).path(), [&](const QImage &image) {
|
||||||
|
bool iconSaved = image.save(m_iconSavePath.toFSPathString());
|
||||||
|
if (iconSaved)
|
||||||
|
m_widget->userModel()->refresh3DSection();
|
||||||
|
else
|
||||||
|
qWarning() << "ContentLibraryView::getImageFromCache(): icon save failed" << iconPath;
|
||||||
|
});
|
||||||
|
|
||||||
const Utils::FilePaths sourceFiles = compDir.dirEntries({{}, QDir::Files, QDirIterator::Subdirectories});
|
const Utils::FilePaths sourceFiles = compDir.dirEntries({{}, QDir::Files, QDirIterator::Subdirectories});
|
||||||
const QStringList ignoreList {"_importdata.json", "qmldir", compBaseName + ".hints"};
|
const QStringList ignoreList {"_importdata.json", "qmldir", compBaseName + ".hints"};
|
||||||
@@ -723,8 +725,7 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node)
|
|||||||
|
|
||||||
// copy item from project to user bundle
|
// copy item from project to user bundle
|
||||||
auto result = sourcePath.copyFile(targetPath);
|
auto result = sourcePath.copyFile(targetPath);
|
||||||
if (!result)
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
|
|
||||||
if (sourcePath.fileName() != compFileName) // skip component file (only collect dependencies)
|
if (sourcePath.fileName() != compFileName) // skip component file (only collect dependencies)
|
||||||
filesList.append(relativePath.path());
|
filesList.append(relativePath.path());
|
||||||
@@ -744,11 +745,9 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node)
|
|||||||
|
|
||||||
auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
|
auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
|
||||||
.writeFileContents(QJsonDocument(jsonRef).toJson());
|
.writeFileContents(QJsonDocument(jsonRef).toJson());
|
||||||
if (!result)
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
|
|
||||||
m_widget->userModel()->add3DItem(compBaseName, compFileName, QUrl::fromLocalFile(fullIconPath),
|
m_widget->userModel()->add3DItem(compBaseName, compFileName, m_iconSavePath.toUrl(), filesList);
|
||||||
filesList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentLibraryView::addLib3DItem(const ModelNode &node)
|
void ContentLibraryView::addLib3DItem(const ModelNode &node)
|
||||||
@@ -756,19 +755,31 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node)
|
|||||||
auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/");
|
auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/");
|
||||||
|
|
||||||
QString name = node.variantProperty("objectName").value().toString();
|
QString name = node.variantProperty("objectName").value().toString();
|
||||||
auto [qml, icon] = m_widget->userModel()->getUniqueLib3DNames(node.id());
|
|
||||||
QString iconPath = QLatin1String("icons/%1").arg(icon);
|
|
||||||
|
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
name = node.id();
|
name = node.id();
|
||||||
|
|
||||||
// generate and save item Qml file
|
auto [qml, icon] = m_widget->userModel()->getUniqueLibItemNames(node.id(),
|
||||||
const QStringList depAssets = writeLibItemQml(node, qml);
|
m_widget->userModel()->bundleJson3DObjectRef());
|
||||||
|
|
||||||
|
// generate and save Qml file
|
||||||
|
auto [qmlString, depAssets] = modelNodeToQmlString(node);
|
||||||
|
const QStringList depAssetsList = depAssets.values();
|
||||||
|
|
||||||
|
auto result = bundlePath.pathAppended(qml).writeFileContents(qmlString.toUtf8());
|
||||||
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
|
|
||||||
// generate and save icon
|
// generate and save icon
|
||||||
QString qmlPath = QLatin1String("%1/User/3d/%2").arg(Paths::bundlesPathSetting(), qml);
|
QString qmlPath = bundlePath.pathAppended(qml).toFSPathString();
|
||||||
QString fullIconPath = bundlePath.pathAppended(iconPath).toString();
|
QString iconPath = QLatin1String("icons/%1").arg(icon);
|
||||||
genAndSaveIcon(qmlPath, fullIconPath);
|
m_iconSavePath = bundlePath.pathAppended(iconPath);
|
||||||
|
m_iconSavePath.parentDir().ensureWritableDir();
|
||||||
|
getImageFromCache(qmlPath, [&](const QImage &image) {
|
||||||
|
bool iconSaved = image.save(m_iconSavePath.toFSPathString());
|
||||||
|
if (iconSaved)
|
||||||
|
m_widget->userModel()->refresh3DSection();
|
||||||
|
else
|
||||||
|
qWarning() << "ContentLibraryView::getImageFromCache(): icon save failed" << iconPath;
|
||||||
|
});
|
||||||
|
|
||||||
// add the item to the bundle json
|
// add the item to the bundle json
|
||||||
QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef();
|
QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef();
|
||||||
@@ -777,28 +788,100 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node)
|
|||||||
{"name", name},
|
{"name", name},
|
||||||
{"qml", qml},
|
{"qml", qml},
|
||||||
{"icon", iconPath},
|
{"icon", iconPath},
|
||||||
{"files", QJsonArray::fromStringList(depAssets)}
|
{"files", QJsonArray::fromStringList(depAssetsList)}
|
||||||
});
|
});
|
||||||
|
|
||||||
jsonRef["items"] = itemsArr;
|
jsonRef["items"] = itemsArr;
|
||||||
|
|
||||||
auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
|
result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
|
||||||
.writeFileContents(QJsonDocument(jsonRef).toJson());
|
.writeFileContents(QJsonDocument(jsonRef).toJson());
|
||||||
if (!result)
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
|
|
||||||
// copy item's assets to bundle folder
|
// copy item's assets to target folder
|
||||||
for (const QString &assetPath : depAssets) {
|
for (const QString &assetPath : depAssetsList) {
|
||||||
Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
|
Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
|
||||||
Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath);
|
Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath);
|
||||||
assetPathTarget.parentDir().ensureWritableDir();
|
assetPathTarget.parentDir().ensureWritableDir();
|
||||||
|
|
||||||
auto result = assetPathSource.copyFile(assetPathTarget);
|
auto result = assetPathSource.copyFile(assetPathTarget);
|
||||||
if (!result)
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
qWarning() << __FUNCTION__ << result.error();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_widget->userModel()->add3DItem(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets);
|
m_widget->userModel()->add3DItem(name, qml, m_iconSavePath.toUrl(), depAssetsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentLibraryView::exportLib3DItem(const ModelNode &node)
|
||||||
|
{
|
||||||
|
// prompt and get the exported bundle path
|
||||||
|
QString defaultExportFileName = QLatin1String("%1.%2").arg(node.id(), Constants::BUNDLE_SUFFIX);
|
||||||
|
Utils::FilePath projectFP = DocumentManager::currentProjectDirPath();
|
||||||
|
if (projectFP.isEmpty()) {
|
||||||
|
projectFP = QmlDesignerPlugin::instance()->documentManager()
|
||||||
|
.currentDesignDocument()->fileName().parentDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString exportPath = QFileDialog::getSaveFileName(m_widget, tr("Export Component"),
|
||||||
|
projectFP.pathAppended(defaultExportFileName).toFSPathString(),
|
||||||
|
tr("Qt Design Studio Bundle Files (*.%1)").arg(Constants::BUNDLE_SUFFIX));
|
||||||
|
if (exportPath.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// targetPath is a temp path for collectiong and zipping assets, actual export target is where
|
||||||
|
// the user chose to export (i.e. exportPath)
|
||||||
|
QTemporaryDir tempDir;
|
||||||
|
QTC_ASSERT(tempDir.isValid(), return);
|
||||||
|
auto targetPath = Utils::FilePath::fromString(tempDir.path());
|
||||||
|
|
||||||
|
m_zipWriter = std::make_unique<ZipWriter>(exportPath);
|
||||||
|
|
||||||
|
QString name = node.variantProperty("objectName").value().toString();
|
||||||
|
if (name.isEmpty())
|
||||||
|
name = node.id();
|
||||||
|
|
||||||
|
auto [qml, icon] = m_widget->userModel()->getUniqueLibItemNames(node.id());
|
||||||
|
|
||||||
|
// generate and save Qml file
|
||||||
|
auto [qmlString, depAssets] = modelNodeToQmlString(node);
|
||||||
|
const QStringList depAssetsList = depAssets.values();
|
||||||
|
|
||||||
|
auto qmlFilePath = targetPath.pathAppended(qml);
|
||||||
|
auto result = qmlFilePath.writeFileContents(qmlString.toUtf8());
|
||||||
|
QTC_ASSERT_EXPECTED(result, return);
|
||||||
|
m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8());
|
||||||
|
|
||||||
|
// generate and save icon
|
||||||
|
QString iconPath = QLatin1String("icons/%1").arg(icon);
|
||||||
|
m_iconSavePath = targetPath.pathAppended(iconPath);
|
||||||
|
getImageFromCache(qmlFilePath.toFSPathString(), [&](const QImage &image) {
|
||||||
|
QByteArray iconByteArray;
|
||||||
|
QBuffer buffer(&iconByteArray);
|
||||||
|
buffer.open(QIODevice::WriteOnly);
|
||||||
|
image.save(&buffer, "PNG");
|
||||||
|
|
||||||
|
m_zipWriter->addFile("icons/" + m_iconSavePath.fileName(), iconByteArray);
|
||||||
|
m_zipWriter->close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// add the item to the bundle json
|
||||||
|
QJsonObject jsonObj;
|
||||||
|
QJsonArray itemsArr;
|
||||||
|
itemsArr.append(QJsonObject {
|
||||||
|
{"name", name},
|
||||||
|
{"qml", qml},
|
||||||
|
{"icon", iconPath},
|
||||||
|
{"files", QJsonArray::fromStringList(depAssetsList)}
|
||||||
|
});
|
||||||
|
|
||||||
|
jsonObj["items"] = itemsArr;
|
||||||
|
|
||||||
|
Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME);
|
||||||
|
m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson());
|
||||||
|
|
||||||
|
// add item's dependency assets to the bundle zip
|
||||||
|
for (const QString &assetPath : depAssetsList) {
|
||||||
|
Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
|
||||||
|
m_zipWriter->addFile(assetPath, assetPathSource.fileContents().value_or(""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -806,26 +889,21 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node)
|
|||||||
* @param qmlPath path to the qml component file to be rendered
|
* @param qmlPath path to the qml component file to be rendered
|
||||||
* @param iconPath output save path of the generated icon
|
* @param iconPath output save path of the generated icon
|
||||||
*/
|
*/
|
||||||
void ContentLibraryView::genAndSaveIcon(const QString &qmlPath, const QString &iconPath)
|
void ContentLibraryView::getImageFromCache(const QString &qmlPath,
|
||||||
|
std::function<void(const QImage &image)> successCallback)
|
||||||
{
|
{
|
||||||
m_imageCache.requestSmallImage(
|
m_imageCache.requestSmallImage(
|
||||||
Utils::PathString{qmlPath},
|
Utils::PathString{qmlPath},
|
||||||
[&, qmlPath, iconPath](const QImage &image) {
|
successCallback,
|
||||||
bool iconSaved = image.save(iconPath);
|
|
||||||
if (iconSaved)
|
|
||||||
m_widget->userModel()->refresh3DSection();
|
|
||||||
else
|
|
||||||
qWarning() << "ContentLibraryView::genAndSaveIcon(): icon save failed";
|
|
||||||
},
|
|
||||||
[&](ImageCache::AbortReason abortReason) {
|
[&](ImageCache::AbortReason abortReason) {
|
||||||
if (abortReason == ImageCache::AbortReason::Abort) {
|
if (abortReason == ImageCache::AbortReason::Abort) {
|
||||||
qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation "
|
qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation "
|
||||||
"failed for path %1, reason: Abort").arg(qmlPath);
|
"failed for path %1, reason: Abort").arg(qmlPath);
|
||||||
} else if (abortReason == ImageCache::AbortReason::Failed) {
|
} else if (abortReason == ImageCache::AbortReason::Failed) {
|
||||||
qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation "
|
qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation "
|
||||||
"failed for path %1, reason: Failed").arg(qmlPath);
|
"failed for path %1, reason: Failed").arg(qmlPath);
|
||||||
} else if (abortReason == ImageCache::AbortReason::NoEntry) {
|
} else if (abortReason == ImageCache::AbortReason::NoEntry) {
|
||||||
qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation "
|
qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation "
|
||||||
"failed for path %1, reason: NoEntry").arg(qmlPath);
|
"failed for path %1, reason: NoEntry").arg(qmlPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -8,10 +8,14 @@
|
|||||||
#include <createtexture.h>
|
#include <createtexture.h>
|
||||||
#include <nodemetainfo.h>
|
#include <nodemetainfo.h>
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
|
||||||
|
QT_FORWARD_DECLARE_CLASS(QImage)
|
||||||
QT_FORWARD_DECLARE_CLASS(QPixmap)
|
QT_FORWARD_DECLARE_CLASS(QPixmap)
|
||||||
|
QT_FORWARD_DECLARE_CLASS(ZipWriter)
|
||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
|
|
||||||
@@ -59,10 +63,10 @@ private:
|
|||||||
void addLibAssets(const QStringList &paths);
|
void addLibAssets(const QStringList &paths);
|
||||||
void addLib3DComponent(const ModelNode &node);
|
void addLib3DComponent(const ModelNode &node);
|
||||||
void addLib3DItem(const ModelNode &node);
|
void addLib3DItem(const ModelNode &node);
|
||||||
void genAndSaveIcon(const QString &qmlPath, const QString &iconPath);
|
void exportLib3DItem(const ModelNode &node);
|
||||||
QStringList writeLibItemQml(const ModelNode &node, const QString &qml);
|
void getImageFromCache(const QString &qmlPath,
|
||||||
QPair<QString, QSet<QString>> modelNodeToQmlString(const ModelNode &node, QStringList &depListIds,
|
std::function<void(const QImage &image)> successCallback);
|
||||||
int depth = 0);
|
QPair<QString, QSet<QString>> modelNodeToQmlString(const ModelNode &node, int depth = 0);
|
||||||
|
|
||||||
#ifdef QDS_USE_PROJECTSTORAGE
|
#ifdef QDS_USE_PROJECTSTORAGE
|
||||||
void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const TypeName &typeName = {});
|
void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const TypeName &typeName = {});
|
||||||
@@ -89,6 +93,8 @@ private:
|
|||||||
bool m_hasQuick3DImport = false;
|
bool m_hasQuick3DImport = false;
|
||||||
qint32 m_sceneId = -1;
|
qint32 m_sceneId = -1;
|
||||||
CreateTexture m_createTexture;
|
CreateTexture m_createTexture;
|
||||||
|
Utils::FilePath m_iconSavePath;
|
||||||
|
std::unique_ptr<ZipWriter> m_zipWriter;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -367,7 +367,19 @@ void Edit3DWidget::createContextMenu()
|
|||||||
m_addToContentLibAction = m_contextMenu->addAction(
|
m_addToContentLibAction = m_contextMenu->addAction(
|
||||||
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
||||||
tr("Add to Content Library"), [&] {
|
tr("Add to Content Library"), [&] {
|
||||||
view()->emitCustomNotification("add_3d_to_content_lib", {m_contextMenuTarget});
|
view()->emitCustomNotification("add_3d_to_content_lib", {m_contextMenuTarget}); // To ContentLibrary
|
||||||
|
});
|
||||||
|
|
||||||
|
m_importBundleAction = m_contextMenu->addAction(
|
||||||
|
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
||||||
|
tr("Import components"), [&] {
|
||||||
|
// TODO: implement importing components
|
||||||
|
});
|
||||||
|
|
||||||
|
m_exportBundleAction = m_contextMenu->addAction(
|
||||||
|
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
||||||
|
tr("Export components"), [&] {
|
||||||
|
view()->emitCustomNotification("export_item_as_bundle", {m_contextMenuTarget}); // To ContentLibrary
|
||||||
});
|
});
|
||||||
|
|
||||||
m_contextMenu->addSeparator();
|
m_contextMenu->addSeparator();
|
||||||
@@ -650,6 +662,8 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
|
|||||||
m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible());
|
m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible());
|
||||||
m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled());
|
m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled());
|
||||||
m_addToContentLibAction->setEnabled(isNode && !isInBundle);
|
m_addToContentLibAction->setEnabled(isNode && !isInBundle);
|
||||||
|
m_importBundleAction->setEnabled(isNode);
|
||||||
|
m_exportBundleAction->setEnabled(isNode);
|
||||||
|
|
||||||
if (m_view) {
|
if (m_view) {
|
||||||
int idx = m_view->activeSplit();
|
int idx = m_view->activeSplit();
|
||||||
|
@@ -100,6 +100,8 @@ private:
|
|||||||
QPointer<QAction> m_selectParentAction;
|
QPointer<QAction> m_selectParentAction;
|
||||||
QPointer<QAction> m_toggleGroupAction;
|
QPointer<QAction> m_toggleGroupAction;
|
||||||
QPointer<QAction> m_wireFrameAction;
|
QPointer<QAction> m_wireFrameAction;
|
||||||
|
QPointer<QAction> m_importBundleAction;
|
||||||
|
QPointer<QAction> m_exportBundleAction;
|
||||||
QPointer<QAction> m_addToContentLibAction;
|
QPointer<QAction> m_addToContentLibAction;
|
||||||
QHash<int, QPointer<QAction>> m_matOverrideActions;
|
QHash<int, QPointer<QAction>> m_matOverrideActions;
|
||||||
QPointer<QMenu> m_createSubMenu;
|
QPointer<QMenu> m_createSubMenu;
|
||||||
|
@@ -368,7 +368,14 @@ void MaterialBrowserWidget::addMaterialToContentLibrary()
|
|||||||
{
|
{
|
||||||
ModelNode mat = m_materialBrowserModel->selectedMaterial();
|
ModelNode mat = m_materialBrowserModel->selectedMaterial();
|
||||||
m_materialBrowserView->emitCustomNotification("add_material_to_content_lib", {mat},
|
m_materialBrowserView->emitCustomNotification("add_material_to_content_lib", {mat},
|
||||||
{m_previewImageProvider->getPixmap(mat)});
|
{m_previewImageProvider->getPixmap(mat)}); // to ContentLibrary
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialBrowserWidget::exportMaterial()
|
||||||
|
{
|
||||||
|
ModelNode mat = m_materialBrowserModel->selectedMaterial();
|
||||||
|
m_materialBrowserView->emitCustomNotification("export_material_as_bundle", {mat},
|
||||||
|
{m_previewImageProvider->getPixmap(mat)}); // to ContentLibrary
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MaterialBrowserWidget::qmlSourcesPath()
|
QString MaterialBrowserWidget::qmlSourcesPath()
|
||||||
|
@@ -63,6 +63,7 @@ public:
|
|||||||
Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId);
|
Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId);
|
||||||
Q_INVOKABLE void focusMaterialSection(bool focusMatSec);
|
Q_INVOKABLE void focusMaterialSection(bool focusMatSec);
|
||||||
Q_INVOKABLE void addMaterialToContentLibrary();
|
Q_INVOKABLE void addMaterialToContentLibrary();
|
||||||
|
Q_INVOKABLE void exportMaterial();
|
||||||
|
|
||||||
StudioQuickWidget *quickWidget() const;
|
StudioQuickWidget *quickWidget() const;
|
||||||
|
|
||||||
|
@@ -79,6 +79,7 @@ inline constexpr char EDIT3D_CAMERA_SPEED_CONFIG[] = "QmlDesigner.Editor3D.Camer
|
|||||||
|
|
||||||
inline constexpr char QML_DESIGNER_SUBFOLDER[] = "/designer/";
|
inline constexpr char QML_DESIGNER_SUBFOLDER[] = "/designer/";
|
||||||
inline constexpr char BUNDLE_JSON_FILENAME[] = "bundle.json";
|
inline constexpr char BUNDLE_JSON_FILENAME[] = "bundle.json";
|
||||||
|
inline constexpr char BUNDLE_SUFFIX[] = "qdsbundle";
|
||||||
inline constexpr char COMPONENT_BUNDLES_TYPE[] = "Bundles";
|
inline constexpr char COMPONENT_BUNDLES_TYPE[] = "Bundles";
|
||||||
inline constexpr char COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE[] = "Materials";
|
inline constexpr char COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE[] = "Materials";
|
||||||
inline constexpr char COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE[] = "Effects";
|
inline constexpr char COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE[] = "Effects";
|
||||||
|
Reference in New Issue
Block a user