forked from qt-creator/qt-creator
QmlDesigner: Make imported 3D scenes available via assets
A placeholder .q3d file is created under content for imported 3D components found under Generated/QtQuick3D on asset view attach and every time new import is done. .q3d file contains a project root relative path to component's import folder. .q3d files get generated preview as icon in assets view. Imported 3D items are no longer shown in Components view. Removing .q3d file will remove the corresponding module as well as all model nodes created from that asset. Removing last model node of asset will remove the import statement on next document save. Fixes: QDS-12193 Fixes: QDS-14565 Change-Id: If01546ca4c78334bac73b055ed156276f6f8f2a4 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
@@ -239,6 +239,9 @@ T.TreeViewDelegate {
|
||||
+ size.width + " x " + size.height
|
||||
+ "\n" + fileSize
|
||||
+ " " + fileExt
|
||||
} else if (rootView.assetIsImported3d(model.filePath)) {
|
||||
return filePath + "\n"
|
||||
+ fileExt
|
||||
} else {
|
||||
return filePath + "\n"
|
||||
+ fileSize
|
||||
|
@@ -187,50 +187,6 @@ void EffectComposerView::selectedNodesChanged(const QList<QmlDesigner::ModelNode
|
||||
m_widget->effectComposerModel()->setHasValidTarget(hasValidTarget);
|
||||
}
|
||||
|
||||
void EffectComposerView::nodeAboutToBeRemoved(const QmlDesigner::ModelNode &removedNode)
|
||||
{
|
||||
QList<QmlDesigner::ModelNode> nodes = removedNode.allSubModelNodesAndThisNode();
|
||||
bool effectRemoved = false;
|
||||
for (const QmlDesigner::ModelNode &node : nodes) {
|
||||
QmlDesigner::QmlItemNode qmlNode(node);
|
||||
if (qmlNode.isEffectItem()) {
|
||||
effectRemoved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (effectRemoved)
|
||||
QTimer::singleShot(0, this, &EffectComposerView::removeUnusedEffectImports);
|
||||
}
|
||||
|
||||
void EffectComposerView::removeUnusedEffectImports()
|
||||
{
|
||||
QTC_ASSERT(model(), return);
|
||||
|
||||
const QString effectPrefix = m_componentUtils.composedEffectsTypePrefix();
|
||||
|
||||
const QmlDesigner::Imports &imports = model()->imports();
|
||||
QHash<QString, QmlDesigner::Import> effectImports;
|
||||
for (const QmlDesigner::Import &import : imports) {
|
||||
if (import.url().startsWith(effectPrefix)) {
|
||||
QString type = import.url().split('.').last();
|
||||
effectImports.insert(type, import);
|
||||
}
|
||||
}
|
||||
|
||||
const QList<QmlDesigner::ModelNode> allNodes = allModelNodes();
|
||||
for (const QmlDesigner::ModelNode &node : allNodes) {
|
||||
if (QmlDesigner::QmlItemNode(node).isEffectItem())
|
||||
effectImports.remove(node.simplifiedTypeName());
|
||||
}
|
||||
|
||||
if (!effectImports.isEmpty()) {
|
||||
QmlDesigner::Imports removeImports;
|
||||
for (const QmlDesigner::Import &import : effectImports)
|
||||
removeImports.append(import);
|
||||
model()->changeImports({}, removeImports);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectComposerView::highlightSupportedProperties(bool highlight, const QString &suffix)
|
||||
{
|
||||
QQmlContext *ctxObj = m_widget->quickWidget()->rootContext();
|
||||
|
@@ -29,7 +29,6 @@ public:
|
||||
void modelAboutToBeDetached(QmlDesigner::Model *model) override;
|
||||
void selectedNodesChanged(const QList<QmlDesigner::ModelNode> &selectedNodeList,
|
||||
const QList<QmlDesigner::ModelNode> &lastSelectedNodeList) override;
|
||||
void nodeAboutToBeRemoved(const QmlDesigner::ModelNode &removedNode) override;
|
||||
|
||||
void dragStarted(QMimeData *mimeData) override;
|
||||
void dragEnded() override;
|
||||
@@ -41,7 +40,6 @@ public:
|
||||
private:
|
||||
void customNotification(const AbstractView *view, const QString &identifier,
|
||||
const QList<QmlDesigner::ModelNode> &nodeList, const QList<QVariant> &data) override;
|
||||
void removeUnusedEffectImports();
|
||||
|
||||
QPointer<EffectComposerWidget> m_widget;
|
||||
QString m_currProjectPath;
|
||||
|
@@ -24,5 +24,7 @@
|
||||
<file>images/asset_ktx_128.png</file>
|
||||
<file>images/asset_folder.png</file>
|
||||
<file>images/asset_folder@2x.png</file>
|
||||
<file>images/asset_imported3d.png</file>
|
||||
<file>images/asset_imported3d@2x.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@@ -77,7 +77,7 @@ QPixmap AssetsLibraryIconProvider::generateFontIcons(const QString &filePath, co
|
||||
"Abc"}).pixmap(reqSize);
|
||||
}
|
||||
|
||||
QPair<QPixmap, qint64> AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) const
|
||||
QPair<QPixmap, qint64> AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize)
|
||||
{
|
||||
Asset asset(id);
|
||||
|
||||
@@ -100,6 +100,18 @@ QPair<QPixmap, qint64> AssetsLibraryIconProvider::fetchPixmap(const QString &id,
|
||||
qint64 size = QFileInfo(id).size();
|
||||
QString filePath = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/asset_ktx.png");
|
||||
return {QPixmap{filePath}, size};
|
||||
} else if (asset.isImported3D()) {
|
||||
static QPixmap defaultPreview = QPixmap::fromImage(QImage(":/AssetsLibrary/images/asset_imported3d.png"));
|
||||
QPixmap pixmap{requestedSize};
|
||||
QString assetId = id.mid(id.lastIndexOf('/') + 1);
|
||||
assetId.chop(asset.suffix().size() - 1); // Remove suffix
|
||||
if (m_pixmaps.contains(assetId)) {
|
||||
pixmap = m_pixmaps.value(assetId);
|
||||
} else {
|
||||
pixmap = defaultPreview;
|
||||
emit asyncAssetPreviewRequested(assetId, id);
|
||||
}
|
||||
return {pixmap, 0};
|
||||
} else {
|
||||
QString type;
|
||||
if (asset.isShader())
|
||||
@@ -145,5 +157,18 @@ qint64 AssetsLibraryIconProvider::fileSize(const QString &id)
|
||||
return m_thumbnails.contains(id) ? m_thumbnails[id].fileSize : 0;
|
||||
}
|
||||
|
||||
QString AssetsLibraryIconProvider::setPixmap(const QString &id, const QPixmap &pixmap,
|
||||
const QString &suffix)
|
||||
{
|
||||
m_pixmaps.insert(id, pixmap);
|
||||
const QStringList thumbs = m_thumbnails.keys();
|
||||
const QString checkName = id + "." + suffix;
|
||||
for (const auto &thumb : thumbs) {
|
||||
if (thumb.endsWith(checkName))
|
||||
return thumb;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
||||
|
@@ -20,6 +20,7 @@ struct Thumbnail
|
||||
|
||||
class AssetsLibraryIconProvider : public QQuickImageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetsLibraryIconProvider(SynchronousImageCache &fontImageCache);
|
||||
|
||||
@@ -28,10 +29,14 @@ public:
|
||||
void invalidateThumbnail(const QString &id);
|
||||
QSize imageSize(const QString &id);
|
||||
qint64 fileSize(const QString &id);
|
||||
QString setPixmap(const QString &id, const QPixmap &pixmap, const QString &suffix);
|
||||
|
||||
signals:
|
||||
void asyncAssetPreviewRequested(const QString &assetId, const QString &assetFile);
|
||||
|
||||
private:
|
||||
QPixmap generateFontIcons(const QString &filePath, const QSize &requestedSize) const;
|
||||
QPair<QPixmap, qint64> fetchPixmap(const QString &id, const QSize &requestedSize) const;
|
||||
QPair<QPixmap, qint64> fetchPixmap(const QString &id, const QSize &requestedSize);
|
||||
Thumbnail createThumbnail(const QString &id, const QSize &requestedSize);
|
||||
|
||||
SynchronousImageCache &m_fontImageCache;
|
||||
@@ -42,6 +47,7 @@ private:
|
||||
{96, 96}, // list @2x
|
||||
{48, 48}}; // list
|
||||
QHash<QString, Thumbnail> m_thumbnails;
|
||||
QHash<QString, QPixmap> m_pixmaps;
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -118,19 +118,33 @@ void AssetsLibraryModel::deleteFiles(const QStringList &filePaths, bool dontAskA
|
||||
if (dontAskAgain)
|
||||
QmlDesignerPlugin::settings().insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, false);
|
||||
|
||||
QStringList deletedEffects;
|
||||
QHash<QString, Utils::FilePath> deletedAssets;
|
||||
const GeneratedComponentUtils &compUtils = QmlDesignerPlugin::instance()->documentManager()
|
||||
.generatedComponentUtils();
|
||||
const QString effectTypePrefix = compUtils.composedEffectsTypePrefix();
|
||||
const Utils::FilePath effectBasePath = compUtils.composedEffectsBasePath();
|
||||
|
||||
for (const QString &filePath : filePaths) {
|
||||
QFileInfo fi(filePath);
|
||||
if (fi.exists()) {
|
||||
if (QFile::remove(filePath)) {
|
||||
if (Asset(filePath).isEffect()) {
|
||||
// If an effect composer effect was removed, also remove effect module from project
|
||||
QString effectName = fi.baseName();
|
||||
if (!effectName.isEmpty())
|
||||
deletedEffects.append(effectName);
|
||||
Utils::FilePath fp = Utils::FilePath::fromString(filePath);
|
||||
if (fp.exists()) {
|
||||
// If a generated asset was removed, also remove its module from project
|
||||
Asset asset(filePath);
|
||||
QString fullType;
|
||||
if (asset.isEffect()) {
|
||||
QString effectName = fp.baseName();
|
||||
fullType = QString("%1.%2.%2").arg(effectTypePrefix, effectName, effectName);
|
||||
deletedAssets.insert(fullType, effectBasePath.resolvePath(effectName));
|
||||
} else if (asset.isImported3D()) {
|
||||
Utils::FilePath qmlFile = compUtils.getImported3dQml(filePath);
|
||||
if (qmlFile.exists()) {
|
||||
QString importName = compUtils.getImported3dImportName(qmlFile);
|
||||
fullType = QString("%1.%2").arg(importName, qmlFile.baseName());
|
||||
deletedAssets.insert(fullType, qmlFile.absolutePath());
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!fp.removeFile()) {
|
||||
deletedAssets.remove(fullType);
|
||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||
Tr::tr("Failed to Delete File"),
|
||||
Tr::tr("Could not delete \"%1\".").arg(filePath));
|
||||
@@ -138,8 +152,8 @@ void AssetsLibraryModel::deleteFiles(const QStringList &filePaths, bool dontAskA
|
||||
}
|
||||
}
|
||||
|
||||
if (!deletedEffects.isEmpty())
|
||||
emit effectsDeleted(deletedEffects);
|
||||
if (!deletedAssets.isEmpty())
|
||||
emit generatedAssetsDeleted(deletedAssets);
|
||||
}
|
||||
|
||||
bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString &newName)
|
||||
|
@@ -72,7 +72,7 @@ signals:
|
||||
void rootPathChanged();
|
||||
void isEmptyChanged();
|
||||
void fileChanged(const QString &path);
|
||||
void effectsDeleted(const QStringList &effectNames);
|
||||
void generatedAssetsDeleted(const QHash<QString, Utils::FilePath> &assetData);
|
||||
|
||||
private:
|
||||
void setIsEmpty(bool value);
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include <imagecachecollectors/imagecachecollector.h>
|
||||
#include <imagecachecollectors/imagecacheconnectionmanager.h>
|
||||
#include <imagecachecollectors/imagecachefontcollector.h>
|
||||
#include <modelnodeoperations.h>
|
||||
#include <nodelistproperty.h>
|
||||
#include <projectexplorer/kit.h>
|
||||
#include <projectexplorer/project.h>
|
||||
@@ -41,11 +42,18 @@ public:
|
||||
TimeStampProvider timeStampProvider;
|
||||
AsynchronousImageCache asynchronousFontImageCache{storage, fontGenerator, timeStampProvider};
|
||||
SynchronousImageCache synchronousFontImageCache{storage, timeStampProvider, fontCollector};
|
||||
AsynchronousImageCache *mainImageCache = {};
|
||||
};
|
||||
|
||||
AssetsLibraryView::AssetsLibraryView(ExternalDependenciesInterface &externalDependencies)
|
||||
AssetsLibraryView::AssetsLibraryView(AsynchronousImageCache &imageCache,
|
||||
ExternalDependenciesInterface &externalDependencies)
|
||||
: AbstractView{externalDependencies}
|
||||
{}
|
||||
{
|
||||
imageCacheData()->mainImageCache = &imageCache;
|
||||
m_3dImportsSyncTimer.callOnTimeout(this, &AssetsLibraryView::sync3dImports);
|
||||
m_3dImportsSyncTimer.setInterval(1000);
|
||||
m_3dImportsSyncTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
AssetsLibraryView::~AssetsLibraryView()
|
||||
{}
|
||||
@@ -59,6 +67,7 @@ WidgetInfo AssetsLibraryView::widgetInfo()
|
||||
{
|
||||
if (!m_widget) {
|
||||
m_widget = Utils::makeUniqueObjectPtr<AssetsLibraryWidget>(
|
||||
*imageCacheData()->mainImageCache,
|
||||
imageCacheData()->asynchronousFontImageCache,
|
||||
imageCacheData()->synchronousFontImageCache,
|
||||
this);
|
||||
@@ -74,6 +83,8 @@ void AssetsLibraryView::customNotification(const AbstractView * /*view*/,
|
||||
{
|
||||
if (identifier == "delete_selected_assets")
|
||||
m_widget->deleteSelectedAssets();
|
||||
else if (identifier == "asset_import_finished")
|
||||
m_3dImportsSyncTimer.start();
|
||||
}
|
||||
|
||||
void AssetsLibraryView::modelAttached(Model *model)
|
||||
@@ -82,17 +93,20 @@ void AssetsLibraryView::modelAttached(Model *model)
|
||||
|
||||
m_widget->clearSearchFilter();
|
||||
|
||||
setResourcePath(DocumentManager::currentResourcePath().toFileInfo().absoluteFilePath());
|
||||
setResourcePath(DocumentManager::currentResourcePath().toFSPathString());
|
||||
|
||||
m_3dImportsSyncTimer.start();
|
||||
}
|
||||
|
||||
void AssetsLibraryView::modelAboutToBeDetached(Model *model)
|
||||
{
|
||||
AbstractView::modelAboutToBeDetached(model);
|
||||
|
||||
m_3dImportsSyncTimer.stop();
|
||||
}
|
||||
|
||||
void AssetsLibraryView::setResourcePath(const QString &resourcePath)
|
||||
{
|
||||
|
||||
if (resourcePath == m_lastResourcePath)
|
||||
return;
|
||||
|
||||
@@ -100,6 +114,7 @@ void AssetsLibraryView::setResourcePath(const QString &resourcePath)
|
||||
|
||||
if (!m_widget) {
|
||||
m_widget = Utils::makeUniqueObjectPtr<AssetsLibraryWidget>(
|
||||
*imageCacheData()->mainImageCache,
|
||||
imageCacheData()->asynchronousFontImageCache,
|
||||
imageCacheData()->synchronousFontImageCache,
|
||||
this);
|
||||
@@ -108,6 +123,68 @@ void AssetsLibraryView::setResourcePath(const QString &resourcePath)
|
||||
m_widget->setResourcePath(resourcePath);
|
||||
}
|
||||
|
||||
QHash<QString, Utils::FilePath> AssetsLibraryView::collectFiles(const Utils::FilePath &dirPath,
|
||||
const QString &suffix)
|
||||
{
|
||||
if (dirPath.isEmpty())
|
||||
return {};
|
||||
|
||||
QHash<QString, Utils::FilePath> files;
|
||||
|
||||
Utils::FilePaths entryList = dirPath.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for (const Utils::FilePath &entry : entryList) {
|
||||
if (entry.isDir()) {
|
||||
files.insert(collectFiles(entry.absoluteFilePath(), suffix));
|
||||
} else if (entry.suffix() == suffix) {
|
||||
QString baseName = entry.baseName();
|
||||
files.insert(baseName, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void AssetsLibraryView::sync3dImports()
|
||||
{
|
||||
if (!model())
|
||||
return;
|
||||
|
||||
// TODO: Once project storage supports notifications for new and removed types,
|
||||
// sync3dImports() should be called in that case as well.
|
||||
// Also, custom notification "asset_import_finished" should not be necessary in that case.
|
||||
|
||||
// Sync generated 3d imports to .q3d files in project content
|
||||
const GeneratedComponentUtils &compUtils
|
||||
= QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
|
||||
auto projPath = Utils::FilePath::fromString(externalDependencies().currentProjectDirPath());
|
||||
|
||||
const QList<Utils::FilePath> qmlFiles = compUtils.imported3dComponents();
|
||||
|
||||
Utils::FilePath resPath = DocumentManager::currentResourcePath();
|
||||
QHash<QString, Utils::FilePath> files = collectFiles(resPath, "q3d");
|
||||
|
||||
const QString pathTemplate("/%1.q3d");
|
||||
|
||||
for (const Utils::FilePath &qmlFile : qmlFiles) {
|
||||
const QString fileStr = qmlFile.baseName();
|
||||
if (files.contains(fileStr)) {
|
||||
files.remove(fileStr);
|
||||
} else {
|
||||
Utils::FilePath targetPath = ModelNodeOperations::getImported3dDefaultDirectory();
|
||||
if (!targetPath.isAbsolutePath() || !targetPath.exists())
|
||||
targetPath = resPath;
|
||||
Utils::FilePath newFile = targetPath.pathAppended(pathTemplate.arg(fileStr));
|
||||
QByteArray data;
|
||||
data.append(qmlFile.relativePathFrom(projPath).toFSPathString().toLatin1());
|
||||
newFile.writeFileContents(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove .q3d files that do not match any imported 3d
|
||||
for (const Utils::FilePath &f : std::as_const(files))
|
||||
f.removeFile();
|
||||
}
|
||||
|
||||
AssetsLibraryView::ImageCacheData *AssetsLibraryView::imageCacheData()
|
||||
{
|
||||
std::call_once(imageCacheFlag,
|
||||
|
@@ -5,9 +5,11 @@
|
||||
|
||||
#include "abstractview.h"
|
||||
|
||||
#include <utils/filepath.h>
|
||||
#include <utils/uniqueobjectptr.h>
|
||||
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
@@ -21,7 +23,8 @@ class AssetsLibraryView : public AbstractView
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AssetsLibraryView(ExternalDependenciesInterface &externalDependencies);
|
||||
AssetsLibraryView(AsynchronousImageCache &imageCache,
|
||||
ExternalDependenciesInterface &externalDependencies);
|
||||
~AssetsLibraryView() override;
|
||||
|
||||
bool hasWidget() const override;
|
||||
@@ -30,7 +33,6 @@ public:
|
||||
// AbstractView
|
||||
void modelAttached(Model *model) override;
|
||||
void modelAboutToBeDetached(Model *model) override;
|
||||
|
||||
void setResourcePath(const QString &resourcePath);
|
||||
|
||||
private:
|
||||
@@ -39,11 +41,15 @@ private:
|
||||
|
||||
void customNotification(const AbstractView *view, const QString &identifier,
|
||||
const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
|
||||
QHash<QString, Utils::FilePath> collectFiles(const Utils::FilePath &dirPath,
|
||||
const QString &suffix);
|
||||
void sync3dImports();
|
||||
|
||||
std::once_flag imageCacheFlag;
|
||||
std::unique_ptr<ImageCacheData> m_imageCacheData;
|
||||
Utils::UniqueObjectPtr<AssetsLibraryWidget> m_widget;
|
||||
QString m_lastResourcePath;
|
||||
QTimer m_3dImportsSyncTimer;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include "assetslibraryview.h"
|
||||
#include <qmldesignertr.h>
|
||||
|
||||
#include <asynchronousimagecache.h>
|
||||
#include <createtexture.h>
|
||||
#include <designeractionmanager.h>
|
||||
#include <designermcumanager.h>
|
||||
@@ -97,10 +98,12 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event)
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache,
|
||||
AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &mainImageCache,
|
||||
AsynchronousImageCache &asynchronousFontImageCache,
|
||||
SynchronousImageCache &synchronousFontImageCache,
|
||||
AssetsLibraryView *view)
|
||||
: m_itemIconSize{24, 24}
|
||||
, m_mainImageCache{mainImageCache}
|
||||
, m_fontImageCache{synchronousFontImageCache}
|
||||
, m_assetsIconProvider{new AssetsLibraryIconProvider(synchronousFontImageCache)}
|
||||
, m_assetsModel{new AssetsLibraryModel(this)}
|
||||
@@ -110,6 +113,44 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon
|
||||
setWindowTitle(Tr::tr("Assets Library", "Title of assets library widget"));
|
||||
setMinimumWidth(250);
|
||||
|
||||
connect(m_assetsIconProvider, &AssetsLibraryIconProvider::asyncAssetPreviewRequested,
|
||||
this, [this](const QString &assetId, const QString &assetFile) {
|
||||
Asset asset{assetFile};
|
||||
if (!asset.isImported3D())
|
||||
return;
|
||||
|
||||
Utils::FilePath fullPath = QmlDesignerPlugin::instance()->documentManager()
|
||||
.generatedComponentUtils().getImported3dQml(assetFile);
|
||||
|
||||
if (!fullPath.exists())
|
||||
return;
|
||||
|
||||
m_mainImageCache.requestImage(
|
||||
Utils::PathString{fullPath.toFSPathString()},
|
||||
[this, assetId](const QImage &image) {
|
||||
QMetaObject::invokeMethod(this, [this, assetId, image] {
|
||||
updateAssetPreview(assetId, QPixmap::fromImage(image), "q3d");
|
||||
}, Qt::QueuedConnection);
|
||||
},
|
||||
[assetFile](ImageCache::AbortReason abortReason) {
|
||||
if (abortReason == ImageCache::AbortReason::Abort) {
|
||||
qWarning() << QLatin1String(
|
||||
"AssetsLibraryIconProvider::asyncAssetPreviewRequested(): preview generation "
|
||||
"failed for path %1, reason: Abort").arg(assetFile);
|
||||
} else if (abortReason == ImageCache::AbortReason::Failed) {
|
||||
qWarning() << QLatin1String(
|
||||
"AssetsLibraryIconProvider::asyncAssetPreviewRequested(): preview generation "
|
||||
"failed for path %1, reason: Failed").arg(assetFile);
|
||||
} else if (abortReason == ImageCache::AbortReason::NoEntry) {
|
||||
qWarning() << QLatin1String(
|
||||
"AssetsLibraryIconProvider::asyncAssetPreviewRequested(): preview generation "
|
||||
"failed for path %1, reason: NoEntry").arg(assetFile);
|
||||
}
|
||||
},
|
||||
"libIcon",
|
||||
ImageCache::LibraryIconAuxiliaryData{true});
|
||||
});
|
||||
|
||||
m_assetsWidget->quickWidget()->installEventFilter(this);
|
||||
|
||||
m_fontPreviewTooltipBackend = std::make_unique<PreviewTooltipBackend>(asynchronousFontImageCache);
|
||||
@@ -135,8 +176,8 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon
|
||||
connect(m_assetsModel, &AssetsLibraryModel::fileChanged,
|
||||
QmlDesignerPlugin::instance(), &QmlDesignerPlugin::assetChanged);
|
||||
|
||||
connect(m_assetsModel, &AssetsLibraryModel::effectsDeleted,
|
||||
this, &AssetsLibraryWidget::handleDeleteEffects);
|
||||
connect(m_assetsModel, &AssetsLibraryModel::generatedAssetsDeleted,
|
||||
this, &AssetsLibraryWidget::handleDeletedGeneratedAssets);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins({});
|
||||
@@ -292,43 +333,63 @@ void AssetsLibraryWidget::setHasSceneEnv(bool b)
|
||||
emit hasSceneEnvChanged();
|
||||
}
|
||||
|
||||
void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList &effectNames)
|
||||
void AssetsLibraryWidget::handleDeletedGeneratedAssets(const QHash<QString, Utils::FilePath> &assetData)
|
||||
{
|
||||
#ifdef QDS_USE_PROJECTSTORAGE
|
||||
// That code has to rewritten with modules. Seem try to find all effects nodes.
|
||||
#else
|
||||
// assetData key: full type name including import, value: import dir
|
||||
|
||||
// This method removes all nodes of the deleted type (assetData.keys())
|
||||
// and removes the import statement for that type
|
||||
|
||||
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();
|
||||
if (!document)
|
||||
return;
|
||||
|
||||
bool clearStacks = false;
|
||||
|
||||
// Remove usages of deleted effects from the current document
|
||||
const Imports imports = m_assetsView->model()->imports();
|
||||
const GeneratedComponentUtils &compUtils = QmlDesignerPlugin::instance()->documentManager()
|
||||
.generatedComponentUtils();
|
||||
QString effectPrefix = compUtils.composedEffectsTypePrefix();
|
||||
QStringList effectNames;
|
||||
|
||||
// Remove usages of deleted assets from the current document
|
||||
m_assetsView->executeInTransaction(__FUNCTION__, [&]() {
|
||||
QList<ModelNode> allNodes = m_assetsView->allModelNodes();
|
||||
const QString typeTemplate = "%1.%2.%2";
|
||||
const QString importUrlTemplate = "%1.%2";
|
||||
const Imports imports = m_assetsView->model()->imports();
|
||||
Imports removedImports;
|
||||
const QString typePrefix = QmlDesignerPlugin::instance()->documentManager()
|
||||
.generatedComponentUtils().composedEffectsTypePrefix();
|
||||
for (const QString &effectName : effectNames) {
|
||||
if (effectName.isEmpty())
|
||||
continue;
|
||||
const TypeName type = typeTemplate.arg(typePrefix, effectName).toUtf8();
|
||||
for (ModelNode &node : allNodes) {
|
||||
if (node.metaInfo().typeName() == type) {
|
||||
clearStacks = true;
|
||||
node.destroy();
|
||||
|
||||
QList<Import> removedImports;
|
||||
|
||||
const QStringList assetTypes = assetData.keys();
|
||||
for (const QString &assetType : assetTypes) {
|
||||
QString removedImportUrl;
|
||||
int idx = assetType.lastIndexOf('.');
|
||||
if (idx >= 0) {
|
||||
if (assetType.startsWith(effectPrefix))
|
||||
effectNames.append(assetType.sliced(idx + 1));
|
||||
removedImportUrl = assetType.first(idx);
|
||||
#ifdef QDS_USE_PROJECTSTORAGE
|
||||
auto module = m_assetsView->model()->module(removedImportUrl.toUtf8(),
|
||||
Storage::ModuleKind::QmlLibrary);
|
||||
auto metaInfo = m_assetsView->model()->metaInfo(module, assetType.sliced(idx + 1).toUtf8());
|
||||
for (ModelNode &node : allNodes) {
|
||||
if (node.metaInfo() == metaInfo) {
|
||||
#else
|
||||
TypeName type = assetType.toUtf8();
|
||||
for (ModelNode &node : allNodes) {
|
||||
if (node.metaInfo().typeName() == type) {
|
||||
#endif
|
||||
clearStacks = true;
|
||||
node.destroy();
|
||||
}
|
||||
}
|
||||
if (!removedImportUrl.isEmpty()) {
|
||||
Import removedImport = Utils::findOrDefault(imports,
|
||||
[&removedImportUrl](const Import &import) {
|
||||
return import.url() == removedImportUrl;
|
||||
});
|
||||
if (!removedImport.isEmpty())
|
||||
removedImports.append(removedImport);
|
||||
}
|
||||
}
|
||||
|
||||
const QString importPath = importUrlTemplate.arg(typePrefix, effectName);
|
||||
Import removedImport = Utils::findOrDefault(imports, [&importPath](const Import &import) {
|
||||
return import.url() == importPath;
|
||||
});
|
||||
if (!removedImport.isEmpty())
|
||||
removedImports.append(removedImport);
|
||||
}
|
||||
|
||||
if (!removedImports.isEmpty()) {
|
||||
@@ -338,22 +399,20 @@ void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList
|
||||
});
|
||||
|
||||
// The size check here is to weed out cases where project path somehow resolves
|
||||
// to just slash. Shortest legal currentProjectDirPath() would be "/a/".
|
||||
if (m_assetsModel->currentProjectDirPath().size() < 3)
|
||||
// to just slash or drive + slash. (Shortest legal currentProjectDirPath() would be "/a/")
|
||||
if (m_assetsModel->currentProjectDirPath().size() < 4)
|
||||
return;
|
||||
|
||||
Utils::FilePath effectsDir = ModelNodeOperations::getEffectsImportDirectory();
|
||||
|
||||
// Delete the effect modules
|
||||
for (const QString &effectName : effectNames) {
|
||||
Utils::FilePath eDir = effectsDir.pathAppended(effectName);
|
||||
if (eDir.exists() && eDir.toString().startsWith(m_assetsModel->currentProjectDirPath())) {
|
||||
// Delete the asset modules
|
||||
for (const Utils::FilePath &dir : assetData) {
|
||||
if (dir.exists() && dir.toFSPathString().startsWith(m_assetsModel->currentProjectDirPath())) {
|
||||
QString error;
|
||||
eDir.removeRecursively(&error);
|
||||
dir.removeRecursively(&error);
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||
Tr::tr("Failed to Delete Effect Resources"),
|
||||
Tr::tr("Could not delete \"%1\".").arg(eDir.toString()));
|
||||
Tr::tr("Failed to Delete Asset Resources"),
|
||||
Tr::tr("Could not delete \"%1\".").arg(dir.toFSPathString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,7 +423,16 @@ void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList
|
||||
document->clearUndoRedoStacks();
|
||||
|
||||
m_assetsView->emitCustomNotification("effectcomposer_effects_deleted", {}, {effectNames});
|
||||
#endif
|
||||
m_assetsView->emitCustomNotification("assets_deleted");
|
||||
}
|
||||
|
||||
void AssetsLibraryWidget::updateAssetPreview(const QString &id, const QPixmap &pixmap,
|
||||
const QString &suffix)
|
||||
{
|
||||
const QString thumb = m_assetsIconProvider->setPixmap(id, pixmap, suffix);
|
||||
|
||||
if (!thumb.isEmpty())
|
||||
emit m_assetsModel->fileChanged(thumb);
|
||||
}
|
||||
|
||||
void AssetsLibraryWidget::invalidateThumbnail(const QString &id)
|
||||
@@ -388,6 +456,11 @@ bool AssetsLibraryWidget::assetIsImageOrTexture(const QString &id)
|
||||
return Asset(id).isValidTextureSource();
|
||||
}
|
||||
|
||||
bool AssetsLibraryWidget::assetIsImported3d(const QString &id)
|
||||
{
|
||||
return Asset(id).isImported3D();
|
||||
}
|
||||
|
||||
// needed to deal with "Object 0xXXXX destroyed while one of its QML signal handlers is in progress..." error which would lead to a crash
|
||||
void AssetsLibraryWidget::invokeAssetsDrop(const QList<QUrl> &urls, const QString &targetDir)
|
||||
{
|
||||
@@ -615,6 +688,9 @@ QPair<QString, QByteArray> AssetsLibraryWidget::getAssetTypeAndData(const QStrin
|
||||
} else if (asset.isEffect()) {
|
||||
// Data: Effect Composer format (suffix)
|
||||
return {Constants::MIME_TYPE_ASSET_EFFECT, asset.suffix().toUtf8()};
|
||||
} else if (asset.isImported3D()) {
|
||||
// Data: Imported 3D component (suffix)
|
||||
return {Constants::MIME_TYPE_ASSET_IMPORTED3D, asset.suffix().toUtf8()};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
@@ -51,7 +51,8 @@ class AssetsLibraryWidget : public QFrame
|
||||
Q_PROPERTY(bool isDragging MEMBER m_isDragging NOTIFY isDraggingChanged)
|
||||
|
||||
public:
|
||||
AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache,
|
||||
AssetsLibraryWidget(AsynchronousImageCache &mainImageCache,
|
||||
AsynchronousImageCache &asynchronousFontImageCache,
|
||||
SynchronousImageCache &synchronousFontImageCache, AssetsLibraryView *view);
|
||||
~AssetsLibraryWidget();
|
||||
|
||||
@@ -88,6 +89,7 @@ public:
|
||||
Q_INVOKABLE QSize imageSize(const QString &id);
|
||||
Q_INVOKABLE QString assetFileSize(const QString &id);
|
||||
Q_INVOKABLE bool assetIsImageOrTexture(const QString &id);
|
||||
Q_INVOKABLE bool assetIsImported3d(const QString &id);
|
||||
Q_INVOKABLE void addTextures(const QStringList &filePaths);
|
||||
Q_INVOKABLE void addLightProbe(const QString &filePaths);
|
||||
Q_INVOKABLE void updateContextMenuActionsEnableState();
|
||||
@@ -130,10 +132,12 @@ private:
|
||||
void setHasSceneEnv(bool b);
|
||||
void setCanCreateEffects(bool newVal);
|
||||
|
||||
void handleDeleteEffects(const QStringList &effectNames);
|
||||
void handleDeletedGeneratedAssets(const QHash<QString, Utils::FilePath> &assetData);
|
||||
void updateAssetPreview(const QString &id, const QPixmap &pixmap, const QString &suffix);
|
||||
|
||||
QSize m_itemIconSize;
|
||||
|
||||
AsynchronousImageCache &m_mainImageCache;
|
||||
SynchronousImageCache &m_fontImageCache;
|
||||
|
||||
AssetsLibraryIconProvider *m_assetsIconProvider = nullptr;
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 471 B |
Binary file not shown.
After Width: | Height: | Size: 838 B |
@@ -1879,6 +1879,13 @@ Utils::FilePath getImagesDefaultDirectory()
|
||||
QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString()));
|
||||
}
|
||||
|
||||
FilePath getImported3dDefaultDirectory()
|
||||
{
|
||||
return Utils::FilePath::fromString(getAssetDefaultDirectory(
|
||||
"3d",
|
||||
QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString()));
|
||||
}
|
||||
|
||||
void jumpToCode(const ModelNode &modelNode)
|
||||
{
|
||||
QmlDesignerPlugin::instance()->viewManager().jumpToCodeInTextEditor(modelNode);
|
||||
@@ -2009,6 +2016,36 @@ ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const ModelNode
|
||||
return newModelNode;
|
||||
}
|
||||
|
||||
ModelNode handleImported3dAssetDrop(const QString &assetPath, const ModelNode &targetNode,
|
||||
const QVector3D &position)
|
||||
{
|
||||
AbstractView *view = targetNode.view();
|
||||
QTC_ASSERT(view, return {});
|
||||
QTC_ASSERT(targetNode.isValid(), return {});
|
||||
|
||||
ModelNode newModelNode;
|
||||
|
||||
const GeneratedComponentUtils &compUtils = QmlDesignerPlugin::instance()->documentManager()
|
||||
.generatedComponentUtils();
|
||||
|
||||
Utils::FilePath qmlFile = compUtils.getImported3dQml(assetPath);
|
||||
if (qmlFile.exists()) {
|
||||
TypeName qmlType = qmlFile.baseName().toUtf8();
|
||||
QString importName = compUtils.getImported3dImportName(qmlFile);
|
||||
if (!importName.isEmpty() && !qmlType.isEmpty())
|
||||
newModelNode = QmlVisualNode::createQml3DNode(view, qmlType, targetNode, importName, position);
|
||||
} else {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText(Tr::tr("Asset %1 is not complete.").arg(qmlFile.baseName()));
|
||||
msgBox.setInformativeText(Tr::tr("Please reimport the asset."));
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Information);
|
||||
msgBox.exec();
|
||||
}
|
||||
|
||||
return newModelNode;
|
||||
}
|
||||
|
||||
void handleTextureDrop(const QMimeData *mimeData, const ModelNode &targetModelNode)
|
||||
{
|
||||
AbstractView *view = targetModelNode.view();
|
||||
|
@@ -8,6 +8,8 @@
|
||||
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#include <QVector3D>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
class AddFilesResult
|
||||
@@ -138,10 +140,14 @@ bool validateEffect(const QString &effectPath);
|
||||
bool isEffectComposerActivated();
|
||||
|
||||
QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getImagesDefaultDirectory();
|
||||
Utils::FilePath getImported3dDefaultDirectory();
|
||||
|
||||
//Item Library and Assets related drop operations
|
||||
QMLDESIGNERCOMPONENTS_EXPORT ModelNode handleItemLibraryEffectDrop(const QString &effectPath,
|
||||
const ModelNode &targetNode);
|
||||
ModelNode handleImported3dAssetDrop(const QString &assetPath,
|
||||
const ModelNode &targetNode,
|
||||
const QVector3D &position = {});
|
||||
void handleTextureDrop(const QMimeData *mimeData, const ModelNode &targetModelNode);
|
||||
void handleMaterialDrop(const QMimeData *mimeData, const ModelNode &targetNode);
|
||||
ModelNode handleItemLibraryImageDrop(const QString &imagePath,
|
||||
|
@@ -68,7 +68,7 @@ public:
|
||||
#endif
|
||||
, formEditorView{externalDependencies}
|
||||
, textEditorView{externalDependencies}
|
||||
, assetsLibraryView{externalDependencies}
|
||||
, assetsLibraryView{imageCache, externalDependencies}
|
||||
, itemLibraryView(imageCache, externalDependencies)
|
||||
, navigatorView{externalDependencies}
|
||||
, propertyEditorView(imageCache, externalDependencies)
|
||||
|
@@ -46,5 +46,7 @@
|
||||
<file>images/align_view_on@2x.png</file>
|
||||
<file>images/color_palette.png</file>
|
||||
<file>images/color_palette@2x.png</file>
|
||||
<file>images/item-3D_model-icon.png</file>
|
||||
<file>images/item-3D_model-icon@2x.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@@ -35,6 +35,8 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/messagebox.h>
|
||||
|
||||
#include <qmldesignerutils/asset.h>
|
||||
|
||||
#include <projectexplorer/target.h>
|
||||
#include <projectexplorer/kit.h>
|
||||
|
||||
@@ -305,7 +307,11 @@ void Edit3DView::modelAttached(Model *model)
|
||||
if (QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit()))
|
||||
m_isBakingLightsSupported = qtVer->qtVersion() >= QVersionNumber(6, 5, 0);
|
||||
}
|
||||
#ifndef QDS_USE_PROJECTSTORAGE
|
||||
#ifdef QDS_USE_PROJECTSTORAGE
|
||||
// TODO: Handle actual entries changed signal/notification once it is available.
|
||||
// Until then, we simply get what entries are available at model attach time.
|
||||
onEntriesChanged();
|
||||
#else
|
||||
connect(model->metaInfo().itemLibraryInfo(),
|
||||
&ItemLibraryInfo::entriesChanged,
|
||||
this,
|
||||
@@ -333,15 +339,14 @@ void Edit3DView::handleEntriesChanged()
|
||||
enum ItemLibraryEntryKeys : int { // used to maintain order
|
||||
EK_cameras,
|
||||
EK_lights,
|
||||
EK_primitives,
|
||||
EK_importedModels
|
||||
EK_primitives
|
||||
};
|
||||
|
||||
QMap<ItemLibraryEntryKeys, ItemLibraryDetails> entriesMap{
|
||||
{EK_cameras, {tr("Cameras"), contextIcon(DesignerIcons::CameraIcon)}},
|
||||
{EK_lights, {tr("Lights"), contextIcon(DesignerIcons::LightIcon)}},
|
||||
{EK_primitives, {tr("Primitives"), contextIcon(DesignerIcons::PrimitivesIcon)}},
|
||||
{EK_importedModels, {tr("Imported Models"), contextIcon(DesignerIcons::ImportedModelsIcon)}}};
|
||||
{EK_primitives, {tr("Primitives"), contextIcon(DesignerIcons::PrimitivesIcon)}}
|
||||
};
|
||||
|
||||
#ifdef QDS_USE_PROJECTSTORAGE
|
||||
auto append = [&](const NodeMetaInfo &metaInfo, ItemLibraryEntryKeys key) {
|
||||
@@ -356,16 +361,6 @@ void Edit3DView::handleEntriesChanged()
|
||||
append(model()->qtQuick3DPointLightMetaInfo(), EK_lights);
|
||||
append(model()->qtQuick3DOrthographicCameraMetaInfo(), EK_cameras);
|
||||
append(model()->qtQuick3DPerspectiveCameraMetaInfo(), EK_cameras);
|
||||
|
||||
Utils::PathString import3dTypePrefix = QmlDesignerPlugin::instance()
|
||||
->documentManager()
|
||||
.generatedComponentUtils()
|
||||
.import3dTypePrefix();
|
||||
|
||||
auto assetsModule = model()->module(import3dTypePrefix, Storage::ModuleKind::QmlLibrary);
|
||||
|
||||
for (const auto &metaInfo : model()->metaInfosForModule(assetsModule))
|
||||
append(metaInfo, EK_importedModels);
|
||||
#else
|
||||
const QList<ItemLibraryEntry> itemLibEntries = model()->metaInfo().itemLibraryInfo()->entries();
|
||||
for (const ItemLibraryEntry &entry : itemLibEntries) {
|
||||
@@ -379,13 +374,6 @@ void Edit3DView::handleEntriesChanged()
|
||||
} else if (entry.typeName() == "QtQuick3D.OrthographicCamera"
|
||||
|| entry.typeName() == "QtQuick3D.PerspectiveCamera") {
|
||||
entryKey = EK_cameras;
|
||||
} else if (entry.typeName().startsWith(QmlDesignerPlugin::instance()
|
||||
->documentManager()
|
||||
.generatedComponentUtils()
|
||||
.import3dTypePrefix()
|
||||
.toUtf8())
|
||||
&& NodeHints::fromItemLibraryEntry(entry, model()).canBeDroppedInView3D()) {
|
||||
entryKey = EK_importedModels;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@@ -464,6 +452,8 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view,
|
||||
self->m_nodeAtPosReqType = NodeAtPosReqType::MainScenePick;
|
||||
self->m_pickView3dNode = self->modelNodeForInternalId(qint32(data[1].toInt()));
|
||||
});
|
||||
} else if (identifier == "asset_import_finished" || identifier == "assets_deleted") {
|
||||
handleEntriesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,9 +501,22 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
|
||||
emitCustomNotification("apply_texture_to_model3D", {modelNode, m_droppedModelNode});
|
||||
} else if (m_nodeAtPosReqType == NodeAtPosReqType::AssetDrop) {
|
||||
bool isModel = modelNode.metaInfo().isQtQuick3DModel();
|
||||
if (!m_droppedFile.isEmpty() && isModel) {
|
||||
if (!m_droppedTexture.isEmpty() && isModel) {
|
||||
QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser");
|
||||
emitCustomNotification("apply_asset_to_model3D", {modelNode}, {m_droppedFile}); // To MaterialBrowserView
|
||||
emitCustomNotification("apply_asset_to_model3D", {modelNode}, {m_droppedTexture}); // To MaterialBrowserView
|
||||
} else if (!m_dropped3dImports.isEmpty()) {
|
||||
ModelNode sceneNode = Utils3D::active3DSceneNode(this);
|
||||
if (!sceneNode.isValid())
|
||||
sceneNode = rootModelNode();
|
||||
ModelNode createdNode;
|
||||
executeInTransaction(__FUNCTION__, [&] {
|
||||
for (const QString &asset : std::as_const(m_dropped3dImports)) {
|
||||
createdNode = ModelNodeOperations::handleImported3dAssetDrop(
|
||||
asset, sceneNode, pos3d);
|
||||
}
|
||||
});
|
||||
if (createdNode.isValid())
|
||||
setSelectedModelNode(createdNode);
|
||||
}
|
||||
} else if (m_nodeAtPosReqType == NodeAtPosReqType::MainScenePick) {
|
||||
if (modelNode.isValid())
|
||||
@@ -524,7 +527,8 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
|
||||
}
|
||||
|
||||
m_droppedModelNode = {};
|
||||
m_droppedFile.clear();
|
||||
m_dropped3dImports.clear();
|
||||
m_droppedTexture.clear();
|
||||
m_nodeAtPosReqType = NodeAtPosReqType::None;
|
||||
}
|
||||
|
||||
@@ -1462,10 +1466,22 @@ void Edit3DView::dropComponent(const ItemLibraryEntry &entry, const QPointF &pos
|
||||
nodeAtPosReady({}, {}); // No need to actually resolve position for non-node items
|
||||
}
|
||||
|
||||
void Edit3DView::dropAsset(const QString &file, const QPointF &pos)
|
||||
void QmlDesigner::Edit3DView::dropAssets(const QList<QUrl> &urls, const QPointF &pos)
|
||||
{
|
||||
m_nodeAtPosReqType = NodeAtPosReqType::AssetDrop;
|
||||
m_droppedFile = file;
|
||||
m_dropped3dImports.clear();
|
||||
|
||||
for (const QUrl &url : urls) {
|
||||
Asset asset(url.toLocalFile());
|
||||
// For textures we only support single drops
|
||||
if (m_dropped3dImports.isEmpty() && asset.isTexture3D()) {
|
||||
m_droppedTexture = asset.fileName();
|
||||
break;
|
||||
} else if (asset.isImported3D()) {
|
||||
m_dropped3dImports.append(asset.id());
|
||||
}
|
||||
}
|
||||
|
||||
emitView3DAction(View3DActionType::GetNodeAtPos, pos);
|
||||
}
|
||||
|
||||
|
@@ -101,7 +101,7 @@ public:
|
||||
void dropBundleItem(const QPointF &pos);
|
||||
void dropTexture(const ModelNode &textureNode, const QPointF &pos);
|
||||
void dropComponent(const ItemLibraryEntry &entry, const QPointF &pos);
|
||||
void dropAsset(const QString &file, const QPointF &pos);
|
||||
void dropAssets(const QList<QUrl> &urls, const QPointF &pos);
|
||||
|
||||
bool isBakingLightsSupported() const;
|
||||
|
||||
@@ -203,7 +203,8 @@ private:
|
||||
ModelCache<QImage> m_canvasCache;
|
||||
ModelNode m_droppedModelNode;
|
||||
ItemLibraryEntry m_droppedEntry;
|
||||
QString m_droppedFile;
|
||||
QStringList m_dropped3dImports;
|
||||
QString m_droppedTexture;
|
||||
NodeAtPosReqType m_nodeAtPosReqType;
|
||||
QPoint m_contextMenuPosMouse;
|
||||
QVector3D m_contextMenuPos3D;
|
||||
|
@@ -17,10 +17,8 @@
|
||||
#include <designmodewidget.h>
|
||||
#include <externaldependenciesinterface.h>
|
||||
#include <generatedcomponentutils.h>
|
||||
#include <import.h>
|
||||
#include <materialutils.h>
|
||||
#include <metainfo.h>
|
||||
#include <modelnodeoperations.h>
|
||||
#include <nodeabstractproperty.h>
|
||||
#include <nodehints.h>
|
||||
#include <nodeinstanceview.h>
|
||||
@@ -455,6 +453,7 @@ void Edit3DWidget::updateCreateSubMenu(const QList<ItemLibraryDetails> &entriesL
|
||||
m_createSubMenu->deleteLater();
|
||||
}
|
||||
|
||||
m_nameToImport.clear();
|
||||
m_nameToEntry.clear();
|
||||
|
||||
m_createSubMenu = new QmlEditorMenu(tr("Create"), m_contextMenu);
|
||||
@@ -493,9 +492,43 @@ void Edit3DWidget::updateCreateSubMenu(const QList<ItemLibraryDetails> &entriesL
|
||||
QAction *action = catMenu->addAction(getEntryIcon(entry), entry.name());
|
||||
connect(action, &QAction::triggered, this, [this, action] { onCreateAction(action); });
|
||||
action->setData(entry.name());
|
||||
Import import = Import::createLibraryImport(entry.requiredImport(),
|
||||
QString::number(entry.majorVersion())
|
||||
+ QLatin1Char('.')
|
||||
+ QString::number(entry.minorVersion()));
|
||||
m_nameToImport.insert(entry.name(), import);
|
||||
m_nameToEntry.insert(entry.name(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Create menu for imported 3d models, which don't have ItemLibraryEntries
|
||||
const GeneratedComponentUtils &compUtils
|
||||
= QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
|
||||
QList<Utils::FilePath> qmlFiles = compUtils.imported3dComponents();
|
||||
QMenu *catMenu = nullptr;
|
||||
|
||||
if (!qmlFiles.isEmpty()) {
|
||||
catMenu = new QmlEditorMenu(tr("Imported Models"), m_createSubMenu);
|
||||
catMenu->setIcon(contextIcon(DesignerIcons::ImportedModelsIcon));
|
||||
m_createSubMenu->addMenu(catMenu);
|
||||
|
||||
std::ranges::sort(qmlFiles, {}, &Utils::FilePath::baseName);
|
||||
|
||||
const QIcon icon = QIcon(":/edit3d/images/item-3D_model-icon.png");
|
||||
|
||||
for (const Utils::FilePath &qmlFile : std::as_const(qmlFiles)) {
|
||||
QString qmlName = qmlFile.baseName();
|
||||
QAction *action = catMenu->addAction(icon, qmlName);
|
||||
connect(action, &QAction::triggered, this, [this, action] { onCreateAction(action); });
|
||||
action->setData(qmlName);
|
||||
|
||||
QString importName = compUtils.getImported3dImportName(qmlFile);
|
||||
if (!importName.isEmpty()) {
|
||||
Import import = Import::createLibraryImport(importName);
|
||||
m_nameToImport.insert(qmlName, import);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action triggered from the "create" sub-menu
|
||||
@@ -505,17 +538,29 @@ void Edit3DWidget::onCreateAction(QAction *action)
|
||||
return;
|
||||
|
||||
m_view->executeInTransaction(__FUNCTION__, [&] {
|
||||
ItemLibraryEntry entry = m_nameToEntry.value(action->data().toString());
|
||||
Import import = Import::createLibraryImport(entry.requiredImport(),
|
||||
QString::number(entry.majorVersion())
|
||||
+ QLatin1Char('.')
|
||||
+ QString::number(entry.minorVersion()));
|
||||
const QString actionName = action->data().toString();
|
||||
Import import = m_nameToImport.value(actionName);
|
||||
|
||||
if (!m_view->model()->hasImport(import, true, true))
|
||||
m_view->model()->changeImports({import}, {});
|
||||
|
||||
int activeScene = Utils3D::active3DSceneId(m_view->model());
|
||||
auto modelNode = QmlVisualNode::createQml3DNode(m_view, entry,
|
||||
activeScene, m_contextMenuPos3d).modelNode();
|
||||
ModelNode modelNode;
|
||||
if (m_nameToEntry.contains(actionName)) {
|
||||
int activeScene = Utils3D::active3DSceneId(m_view->model());
|
||||
ItemLibraryEntry entry = m_nameToEntry.value(actionName);
|
||||
|
||||
modelNode = QmlVisualNode::createQml3DNode(m_view, entry, activeScene,
|
||||
m_contextMenuPos3d).modelNode();
|
||||
} else {
|
||||
ModelNode sceneNode = Utils3D::active3DSceneNode(m_view);
|
||||
if (!sceneNode.isValid())
|
||||
sceneNode = m_view->rootModelNode();
|
||||
|
||||
modelNode = QmlVisualNode::QmlVisualNode::createQml3DNode(
|
||||
m_view, actionName.toUtf8(), sceneNode, import.url(),
|
||||
m_contextMenuPos3d).modelNode();
|
||||
}
|
||||
|
||||
QTC_ASSERT(modelNode.isValid(), return);
|
||||
m_view->setSelectedModelNode(modelNode);
|
||||
|
||||
@@ -717,9 +762,12 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent)
|
||||
if (dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ASSETS)
|
||||
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_TEXTURE)) {
|
||||
const auto urls = dragEnterEvent->mimeData()->urls();
|
||||
if (!urls.isEmpty()) {
|
||||
if (Asset(urls.first().toLocalFile()).isValidTextureSource())
|
||||
for (const QUrl &url : urls) {
|
||||
Asset asset(url.toLocalFile());
|
||||
if (asset.isImported3D() || asset.isTexture3D()) {
|
||||
dragEnterEvent->acceptProposedAction();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData())
|
||||
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)
|
||||
@@ -787,7 +835,7 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent)
|
||||
// handle dropping image assets
|
||||
if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_ASSETS)
|
||||
|| dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_TEXTURE)) {
|
||||
m_view->dropAsset(dropEvent->mimeData()->urls().first().toLocalFile(), pos);
|
||||
m_view->dropAssets(dropEvent->mimeData()->urls(), pos);
|
||||
m_view->model()->endDrag();
|
||||
return;
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <bundlehelper.h>
|
||||
#include <import.h>
|
||||
#include <itemlibraryentry.h>
|
||||
#include <modelnode.h>
|
||||
|
||||
@@ -111,6 +112,7 @@ private:
|
||||
QPointer<QMenu> m_createSubMenu;
|
||||
ModelNode m_contextMenuTarget;
|
||||
QVector3D m_contextMenuPos3d;
|
||||
QHash<QString, Import> m_nameToImport;
|
||||
QHash<QString, ItemLibraryEntry> m_nameToEntry;
|
||||
ItemLibraryEntry m_draggedEntry;
|
||||
QHash<QAction *, Core::Command *> m_actionToCommandHash;
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 407 B |
Binary file not shown.
After Width: | Height: | Size: 733 B |
@@ -398,9 +398,6 @@ void Import3dImporter::postParseQuick3DAsset(ParseData &pd)
|
||||
qmlInfo.append(".");
|
||||
qmlInfo.append(pd.assetName);
|
||||
qmlInfo.append('\n');
|
||||
const QString reqImp = generateRequiredImportForAsset(pd.assetName);
|
||||
if (!m_requiredImports.contains(reqImp))
|
||||
m_requiredImports.append(reqImp);
|
||||
while (qmlIt.hasNext()) {
|
||||
qmlIt.next();
|
||||
QFileInfo fi = QFileInfo(qmlIt.filePath());
|
||||
@@ -454,8 +451,10 @@ void Import3dImporter::postParseQuick3DAsset(ParseData &pd)
|
||||
}
|
||||
|
||||
// Add quick3D import unless it is already added
|
||||
if (impVersionMajor > 0 && m_requiredImports.first() != "QtQuick3D")
|
||||
if (impVersionMajor > 0 && (m_requiredImports.isEmpty()
|
||||
|| m_requiredImports.first() != "QtQuick3D")) {
|
||||
m_requiredImports.prepend("QtQuick3D");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -577,13 +576,6 @@ QString Import3dImporter::generateAssetFolderName(const QString &assetName) cons
|
||||
return assetName + "_QDS_" + QString::number(counter++);
|
||||
}
|
||||
|
||||
QString Import3dImporter::generateRequiredImportForAsset(const QString &assetName) const
|
||||
{
|
||||
return QStringLiteral("%1.%2").arg(
|
||||
QmlDesignerPlugin::instance()->documentManager()
|
||||
.generatedComponentUtils().import3dTypePrefix(), assetName);
|
||||
}
|
||||
|
||||
Import3dImporter::OverwriteResult Import3dImporter::confirmAssetOverwrite(const QString &assetName)
|
||||
{
|
||||
const QString title = tr("Overwrite Existing Asset?");
|
||||
@@ -744,9 +736,6 @@ void Import3dImporter::finalizeQuick3DImport()
|
||||
addError(tr("Failed to insert import statement into qml document."));
|
||||
transaction.commit();
|
||||
#else
|
||||
// TODO: ModelUtils::addImportsWithCheck requires Model::possibleImports()
|
||||
// to return correct list instead of empty list, so until that is
|
||||
// fixed we need to just trust the missing modules are available
|
||||
const Imports &imports = model->imports();
|
||||
Imports importsToAdd;
|
||||
for (const QString &importName : std::as_const(m_requiredImports)) {
|
||||
@@ -775,6 +764,7 @@ void Import3dImporter::finalizeQuick3DImport()
|
||||
}
|
||||
timer->stop();
|
||||
notifyFinished();
|
||||
model->rewriterView()->emitCustomNotification("asset_import_finished");
|
||||
}
|
||||
} else {
|
||||
timer->stop();
|
||||
@@ -791,7 +781,6 @@ void Import3dImporter::removeAssetFromImport(const QString &assetName)
|
||||
{
|
||||
m_parseData.remove(assetName);
|
||||
m_importFiles.remove(assetName);
|
||||
m_requiredImports.removeOne(generateRequiredImportForAsset(assetName));
|
||||
}
|
||||
|
||||
QString Import3dImporter::sourceSceneTargetFilePath(const ParseData &pd)
|
||||
|
@@ -685,6 +685,55 @@ RewriterView *DesignDocument::rewriterView() const
|
||||
return m_rewriterView.get();
|
||||
}
|
||||
|
||||
#ifndef QDS_USE_PROJECTSTORAGE
|
||||
static void removeUnusedImports(RewriterView *rewriter)
|
||||
{
|
||||
// Remove any import statements for asset based nodes (composed effect or imported3d)
|
||||
// if there is no nodes using them in the scene.
|
||||
QTC_ASSERT(rewriter && rewriter->model(), return);
|
||||
|
||||
GeneratedComponentUtils compUtils{rewriter->externalDependencies()};
|
||||
|
||||
const QString effectPrefix = compUtils.composedEffectsTypePrefix();
|
||||
const QString imported3dPrefix = compUtils.import3dTypePrefix();
|
||||
const QList<Utils::FilePath> qmlFiles = compUtils.imported3dComponents();
|
||||
QHash<QString, QString> m_imported3dTypeMap;
|
||||
for (const Utils::FilePath &qmlFile : qmlFiles) {
|
||||
QString importName = compUtils.getImported3dImportName(qmlFile);
|
||||
QString type = qmlFile.baseName();
|
||||
m_imported3dTypeMap.insert(importName, type);
|
||||
}
|
||||
|
||||
const Imports &imports = rewriter->model()->imports();
|
||||
QHash<QString, Import> assetImports;
|
||||
for (const Import &import : imports) {
|
||||
if (import.url().startsWith(effectPrefix)) {
|
||||
QString type = import.url().split('.').last();
|
||||
assetImports.insert(type, import);
|
||||
} else if (import.url().startsWith(imported3dPrefix)) {
|
||||
assetImports.insert(m_imported3dTypeMap[import.url()], import);
|
||||
}
|
||||
}
|
||||
|
||||
const QList<ModelNode> allNodes = rewriter->allModelNodes();
|
||||
for (const ModelNode &node : allNodes) {
|
||||
if (QmlItemNode(node).isEffectItem()
|
||||
|| (node.isComponent() && node.metaInfo().isQtQuick3DNode())) {
|
||||
assetImports.remove(node.simplifiedTypeName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!assetImports.isEmpty()) {
|
||||
Imports removeImports;
|
||||
for (const Import &import : assetImports)
|
||||
removeImports.append(import);
|
||||
rewriter->model()->changeImports({}, removeImports);
|
||||
}
|
||||
|
||||
rewriter->forceAmend();
|
||||
}
|
||||
#endif
|
||||
|
||||
void DesignDocument::setEditor(Core::IEditor *editor)
|
||||
{
|
||||
m_textEditor = editor;
|
||||
@@ -694,6 +743,12 @@ void DesignDocument::setEditor(Core::IEditor *editor)
|
||||
this, [this](Core::IDocument *document) {
|
||||
if (m_textEditor && m_textEditor->document() == document) {
|
||||
if (m_documentModel && m_documentModel->rewriterView()) {
|
||||
|
||||
#ifdef QDS_USE_PROJECTSTORAGE
|
||||
// TODO: ProjectStorage should handle this via Model somehow (QDS-14519)
|
||||
#else
|
||||
removeUnusedImports(rewriterView());
|
||||
#endif
|
||||
m_documentModel->rewriterView()->writeAuxiliaryData();
|
||||
}
|
||||
}
|
||||
|
@@ -19,9 +19,6 @@ QString ItemLibraryImport::importName() const
|
||||
if (m_sectionType == SectionType::User)
|
||||
return userComponentsTitle();
|
||||
|
||||
if (m_sectionType == SectionType::Quick3DAssets)
|
||||
return quick3DAssetsTitle();
|
||||
|
||||
if (m_sectionType == SectionType::Unimported)
|
||||
return unimportedComponentsTitle();
|
||||
|
||||
@@ -39,9 +36,6 @@ QString ItemLibraryImport::importUrl() const
|
||||
if (m_sectionType == SectionType::User)
|
||||
return userComponentsTitle();
|
||||
|
||||
if (m_sectionType == SectionType::Quick3DAssets)
|
||||
return quick3DAssetsTitle();
|
||||
|
||||
if (m_sectionType == SectionType::Unimported)
|
||||
return unimportedComponentsTitle();
|
||||
|
||||
@@ -61,9 +55,6 @@ QString ItemLibraryImport::sortingName() const
|
||||
if (m_sectionType == SectionType::User)
|
||||
return "_"; // user components always come first
|
||||
|
||||
if (m_sectionType == SectionType::Quick3DAssets)
|
||||
return "__"; // Quick3DAssets come second
|
||||
|
||||
if (m_sectionType == SectionType::Unimported)
|
||||
return "zzzzzz"; // Unimported components come last
|
||||
|
||||
@@ -235,12 +226,6 @@ QString ItemLibraryImport::userComponentsTitle()
|
||||
return tr("My Components");
|
||||
}
|
||||
|
||||
// static
|
||||
QString ItemLibraryImport::quick3DAssetsTitle()
|
||||
{
|
||||
return tr("My 3D Components");
|
||||
}
|
||||
|
||||
// static
|
||||
QString ItemLibraryImport::unimportedComponentsTitle()
|
||||
{
|
||||
|
@@ -28,7 +28,6 @@ public:
|
||||
enum class SectionType {
|
||||
Default,
|
||||
User,
|
||||
Quick3DAssets,
|
||||
Unimported
|
||||
};
|
||||
|
||||
@@ -67,7 +66,6 @@ public:
|
||||
bool importUnimported() const { return m_sectionType == SectionType::Unimported; }
|
||||
|
||||
static QString userComponentsTitle();
|
||||
static QString quick3DAssetsTitle();
|
||||
static QString unimportedComponentsTitle();
|
||||
|
||||
SectionType sectionType() const;
|
||||
|
@@ -319,12 +319,7 @@ void ItemLibraryModel::update(Model *model)
|
||||
auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
|
||||
|
||||
QStringList excludedImports {
|
||||
projectName,
|
||||
compUtils.materialsBundleType(),
|
||||
compUtils.effectsBundleType(),
|
||||
compUtils.userMaterialsBundleType(),
|
||||
compUtils.user3DBundleType(),
|
||||
compUtils.userEffectsBundleType()
|
||||
projectName
|
||||
};
|
||||
|
||||
// create import sections
|
||||
@@ -332,23 +327,17 @@ void ItemLibraryModel::update(Model *model)
|
||||
QHash<QString, ItemLibraryImport *> importHash;
|
||||
for (const Import &import : model->imports()) {
|
||||
if (excludedImports.contains(import.url())
|
||||
|| import.url().startsWith(compUtils.composedEffectsTypePrefix())) {
|
||||
|| import.url().startsWith(compUtils.generatedComponentTypePrefix())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool addNew = true;
|
||||
bool isQuick3DAsset = import.url().startsWith(compUtils.import3dTypePrefix());
|
||||
QString importUrl = import.url();
|
||||
if (isQuick3DAsset)
|
||||
importUrl = ItemLibraryImport::quick3DAssetsTitle();
|
||||
else if (import.isFileImport())
|
||||
if (import.isFileImport())
|
||||
importUrl = import.toString(true, true).remove("\"");
|
||||
|
||||
ItemLibraryImport *oldImport = importHash.value(importUrl);
|
||||
if (oldImport && oldImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets
|
||||
&& isQuick3DAsset) {
|
||||
addNew = false; // add only 1 Quick3DAssets import section
|
||||
} else if (oldImport && oldImport->importEntry().url() == import.url()) {
|
||||
if (oldImport && oldImport->importEntry().url() == import.url()) {
|
||||
// Retain the higher version if multiples exist
|
||||
if (oldImport->importEntry().toVersion() >= import.toVersion() || import.hasVersion())
|
||||
addNew = false;
|
||||
@@ -357,8 +346,7 @@ void ItemLibraryModel::update(Model *model)
|
||||
}
|
||||
|
||||
if (addNew) {
|
||||
auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets
|
||||
: ItemLibraryImport::SectionType::Default;
|
||||
auto sectionType = ItemLibraryImport::SectionType::Default;
|
||||
ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType);
|
||||
itemLibImport->setImportUsed(usedImports.contains(import));
|
||||
importHash.insert(importUrl, itemLibImport);
|
||||
@@ -445,8 +433,6 @@ void ItemLibraryModel::update(Model *model)
|
||||
importSection = importHash[entry.requiredImport()];
|
||||
|
||||
}
|
||||
} else if (catName == ItemLibraryImport::quick3DAssetsTitle()) {
|
||||
importSection = importHash[ItemLibraryImport::quick3DAssetsTitle()];
|
||||
} else {
|
||||
if (catName.contains("Qt Quick - ")) {
|
||||
QString sortingName = catName;
|
||||
@@ -549,8 +535,6 @@ ItemLibraryImport *ItemLibraryModel::importByUrl(const QString &importUrl) const
|
||||
|| (importUrl.isEmpty() && itemLibraryImport->importUrl() == "QtQuick")
|
||||
|| (importUrl == ItemLibraryImport::userComponentsTitle()
|
||||
&& itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::User)
|
||||
|| (importUrl == ItemLibraryImport::quick3DAssetsTitle()
|
||||
&& itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets)
|
||||
|| (importUrl == ItemLibraryImport::unimportedComponentsTitle()
|
||||
&& itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::Unimported)) {
|
||||
return itemLibraryImport;
|
||||
|
@@ -1015,7 +1015,7 @@ void MaterialEditorView::modelNodePreviewPixmapChanged(const ModelNode &node,
|
||||
const QPixmap &pixmap,
|
||||
const QByteArray &requestId)
|
||||
{
|
||||
if (node != m_selectedMaterial || requestId != m_previewRequestId)
|
||||
if (!m_qmlBackEnd || node != m_selectedMaterial || requestId != m_previewRequestId)
|
||||
return;
|
||||
|
||||
m_qmlBackEnd->updateMaterialPreview(pixmap);
|
||||
|
@@ -239,6 +239,10 @@ void NameItemDelegate::paint(QPainter *painter,
|
||||
model->qtQuickBorderImageMetaInfo());
|
||||
} else if (dragType == Constants::MIME_TYPE_ASSET_EFFECT) {
|
||||
validDrop = metaInfo.isBasedOn(node.model()->qtQuickItemMetaInfo());
|
||||
} else if (dragType == Constants::MIME_TYPE_ASSET_IMPORTED3D) {
|
||||
Model *model = node.model();
|
||||
validDrop = metaInfo.isBasedOn(model->qtQuick3DNodeMetaInfo(),
|
||||
model->qtQuick3DView3DMetaInfo());
|
||||
} else {
|
||||
const NodeMetaInfo dragInfo = node.model()->metaInfo(dragType);
|
||||
ChooseFromPropertyListFilter *filter = new ChooseFromPropertyListFilter(dragInfo, metaInfo, true);
|
||||
|
@@ -669,6 +669,10 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
|
||||
currNode = ModelNodeOperations::handleItemLibraryEffectDrop(
|
||||
assetPath, modelNodeForIndex(rowModelIndex));
|
||||
moveNodesAfter = false;
|
||||
} else if (assetType == Constants::MIME_TYPE_ASSET_IMPORTED3D) {
|
||||
currNode = ModelNodeOperations::handleImported3dAssetDrop(
|
||||
assetPath, modelNodeForIndex(rowModelIndex));
|
||||
moveNodesAfter = false;
|
||||
}
|
||||
|
||||
if (currNode.isValid())
|
||||
|
@@ -306,6 +306,9 @@ void NavigatorView::dragStarted(QMimeData *mimeData)
|
||||
if (assetType == Constants::MIME_TYPE_ASSET_EFFECT) {
|
||||
m_widget->setDragType(Constants::MIME_TYPE_ASSET_EFFECT);
|
||||
m_widget->update();
|
||||
} else if (assetType == Constants::MIME_TYPE_ASSET_IMPORTED3D) {
|
||||
m_widget->setDragType(Constants::MIME_TYPE_ASSET_IMPORTED3D);
|
||||
m_widget->update();
|
||||
} else if (assetType == Constants::MIME_TYPE_ASSET_TEXTURE3D) {
|
||||
m_widget->setDragType(Constants::MIME_TYPE_ASSET_TEXTURE3D);
|
||||
m_widget->update();
|
||||
|
@@ -337,4 +337,61 @@ QString GeneratedComponentUtils::user3DBundleType() const
|
||||
return componentBundlesTypePrefix() + '.' + user3DBundleId();
|
||||
}
|
||||
|
||||
QList<Utils::FilePath> GeneratedComponentUtils::imported3dComponents() const
|
||||
{
|
||||
auto import3dPath = Utils::FilePath::fromString(import3dTypePath());
|
||||
auto projPath = Utils::FilePath::fromString(m_externalDependencies.currentProjectDirPath());
|
||||
auto fullPath = projPath.resolvePath(import3dPath);
|
||||
|
||||
if (fullPath.isEmpty())
|
||||
return {};
|
||||
|
||||
return collectFiles(fullPath, "qml");
|
||||
}
|
||||
|
||||
QString GeneratedComponentUtils::getImported3dImportName(const Utils::FilePath &qmlFile) const
|
||||
{
|
||||
const QStringList sl = qmlFile.toFSPathString().split('/');
|
||||
int i = sl.size() - 4;
|
||||
if (i >= 0)
|
||||
return QStringView(u"%1.%2.%3").arg(sl[i], sl[i + 1], sl[i + 2]);
|
||||
return {};
|
||||
}
|
||||
|
||||
Utils::FilePath GeneratedComponentUtils::getImported3dQml(const QString &assetPath) const
|
||||
{
|
||||
Utils::FilePath assetFilePath = Utils::FilePath::fromString(assetPath);
|
||||
const Utils::expected_str<QByteArray> data = assetFilePath.fileContents();
|
||||
|
||||
if (!data)
|
||||
return {};
|
||||
|
||||
Utils::FilePath assetQmlFilePath = Utils::FilePath::fromUtf8(data.value());
|
||||
Utils::FilePath projectPath = Utils::FilePath::fromString(m_externalDependencies.currentProjectDirPath());
|
||||
|
||||
assetQmlFilePath = projectPath.resolvePath(assetQmlFilePath);
|
||||
|
||||
return assetQmlFilePath;
|
||||
}
|
||||
|
||||
// Recursively find files of certain suffix in a dir
|
||||
QList<Utils::FilePath> GeneratedComponentUtils::collectFiles(const Utils::FilePath &dirPath,
|
||||
const QString &suffix) const
|
||||
{
|
||||
if (dirPath.isEmpty())
|
||||
return {};
|
||||
|
||||
QList<Utils::FilePath> files;
|
||||
|
||||
const Utils::FilePaths entryList = dirPath.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for (const Utils::FilePath &entry : entryList) {
|
||||
if (entry.isDir())
|
||||
files.append(collectFiles(entry.absoluteFilePath(), suffix));
|
||||
else if (entry.suffix() == suffix)
|
||||
files.append(entry);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -50,8 +50,14 @@ public:
|
||||
QString userEffectsBundleType() const;
|
||||
QString user3DBundleType() const;
|
||||
|
||||
QList<Utils::FilePath> imported3dComponents() const;
|
||||
QString getImported3dImportName(const Utils::FilePath &qmlFile) const;
|
||||
Utils::FilePath getImported3dQml(const QString &assetPath) const;
|
||||
|
||||
private:
|
||||
ExternalDependenciesInterface &m_externalDependencies;
|
||||
|
||||
QList<Utils::FilePath> collectFiles(const Utils::FilePath &dirPath, const QString &suffix) const;
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -182,6 +182,7 @@ public:
|
||||
NodeMetaInfo qtQuick3DSpotLightMetaInfo() const;
|
||||
NodeMetaInfo qtQuick3DTextureMetaInfo() const;
|
||||
NodeMetaInfo qtQuick3DTextureInputMetaInfo() const;
|
||||
NodeMetaInfo qtQuick3DView3DMetaInfo() const;
|
||||
NodeMetaInfo qtQuickBorderImageMetaInfo() const;
|
||||
NodeMetaInfo qtQuickControlsLabelMetaInfo() const;
|
||||
NodeMetaInfo qtQuickControlsTextAreaMetaInfo() const;
|
||||
|
@@ -2613,6 +2613,16 @@ NodeMetaInfo Model::qtQuick3DTextureInputMetaInfo() const
|
||||
}
|
||||
}
|
||||
|
||||
NodeMetaInfo Model::qtQuick3DView3DMetaInfo() const
|
||||
{
|
||||
if constexpr (useProjectStorage()) {
|
||||
using namespace Storage::Info;
|
||||
return createNodeMetaInfo<QtQuick3D, View3D>();
|
||||
} else {
|
||||
return metaInfo("QtQuick3D.View3D");
|
||||
}
|
||||
}
|
||||
|
||||
NodeMetaInfo Model::qtQuickBorderImageMetaInfo() const
|
||||
{
|
||||
if constexpr (useProjectStorage()) {
|
||||
|
@@ -85,6 +85,12 @@ const QStringList &Asset::supportedEffectComposerSuffixes()
|
||||
return retList;
|
||||
}
|
||||
|
||||
const QStringList &Asset::supportedImported3dSuffixes()
|
||||
{
|
||||
static QStringList retList {"*.q3d"};
|
||||
return retList;
|
||||
}
|
||||
|
||||
const QSet<QString> &Asset::supportedSuffixes()
|
||||
{
|
||||
static QSet<QString> allSuffixes;
|
||||
@@ -100,6 +106,7 @@ const QSet<QString> &Asset::supportedSuffixes()
|
||||
insertSuffixes(supportedVideoSuffixes());
|
||||
insertSuffixes(supportedTexture3DSuffixes());
|
||||
insertSuffixes(supportedEffectComposerSuffixes());
|
||||
insertSuffixes(supportedImported3dSuffixes());
|
||||
}
|
||||
return allSuffixes;
|
||||
}
|
||||
@@ -182,6 +189,11 @@ bool Asset::isEffect() const
|
||||
return m_type == Asset::Type::Effect;
|
||||
}
|
||||
|
||||
bool Asset::isImported3D() const
|
||||
{
|
||||
return m_type == Asset::Type::Imported3D;
|
||||
}
|
||||
|
||||
const QString Asset::suffix() const
|
||||
{
|
||||
return m_suffix;
|
||||
@@ -236,7 +248,8 @@ void Asset::resolveType()
|
||||
m_type = Asset::Type::Texture3D;
|
||||
else if (supportedEffectComposerSuffixes().contains(m_suffix))
|
||||
m_type = Asset::Type::Effect;
|
||||
}
|
||||
else if (supportedImported3dSuffixes().contains(m_suffix))
|
||||
m_type = Asset::Type::Imported3D;}
|
||||
|
||||
bool Asset::hasSuffix() const
|
||||
{
|
||||
|
@@ -15,17 +15,20 @@ namespace QmlDesigner {
|
||||
class QMLDESIGNERUTILS_EXPORT Asset
|
||||
{
|
||||
public:
|
||||
enum Type { Unknown,
|
||||
Image,
|
||||
MissingImage,
|
||||
FragmentShader,
|
||||
VertexShader,
|
||||
Font,
|
||||
Audio,
|
||||
Video,
|
||||
Texture3D,
|
||||
Effect,
|
||||
Folder };
|
||||
enum Type {
|
||||
Unknown,
|
||||
Image,
|
||||
MissingImage,
|
||||
FragmentShader,
|
||||
VertexShader,
|
||||
Font,
|
||||
Audio,
|
||||
Video,
|
||||
Texture3D,
|
||||
Effect,
|
||||
Folder,
|
||||
Imported3D
|
||||
};
|
||||
|
||||
Asset(const QString &filePath);
|
||||
|
||||
@@ -38,6 +41,7 @@ public:
|
||||
static const QStringList &supportedVideoSuffixes();
|
||||
static const QStringList &supportedTexture3DSuffixes();
|
||||
static const QStringList &supportedEffectComposerSuffixes();
|
||||
static const QStringList &supportedImported3dSuffixes();
|
||||
static const QSet<QString> &supportedSuffixes();
|
||||
static bool isSupported(const QString &path);
|
||||
|
||||
@@ -59,6 +63,7 @@ public:
|
||||
bool isHdrFile() const;
|
||||
bool isKtxFile() const;
|
||||
bool isEffect() const;
|
||||
bool isImported3D() const;
|
||||
bool isSupported() const;
|
||||
bool isValidTextureSource();
|
||||
bool isFolder() const;
|
||||
|
@@ -87,6 +87,7 @@ inline constexpr char MIME_TYPE_ASSET_TEXTURE3D[]
|
||||
= "application/vnd.qtdesignstudio.asset.texture3d";
|
||||
inline constexpr char MIME_TYPE_MODELNODE_LIST[] = "application/vnd.qtdesignstudio.modelnode.list";
|
||||
inline constexpr char MIME_TYPE_ASSET_EFFECT[] = "application/vnd.qtdesignstudio.asset.effect";
|
||||
inline constexpr char MIME_TYPE_ASSET_IMPORTED3D[] = "application/vnd.qtdesignstudio.asset.imported3d";
|
||||
|
||||
// Menus
|
||||
inline constexpr char M_VIEW_WORKSPACES[] = "QmlDesigner.Menu.View.Workspaces";
|
||||
|
@@ -482,6 +482,58 @@ QmlVisualNode QmlVisualNode::createQml3DNode(AbstractView *view,
|
||||
return createQmlObjectNode(view, itemLibraryEntry, position, sceneNodeProperty, createInTransaction).modelNode();
|
||||
}
|
||||
|
||||
QmlVisualNode QmlVisualNode::createQml3DNode(AbstractView *view,
|
||||
const TypeName &typeName,
|
||||
const ModelNode &parentNode,
|
||||
const QString &importName,
|
||||
const QVector3D &position,
|
||||
bool createInTransaction)
|
||||
{
|
||||
NodeAbstractProperty targetParentProperty = parentNode.defaultNodeListProperty();
|
||||
|
||||
QTC_ASSERT(targetParentProperty.isValid(), return {});
|
||||
|
||||
QTC_ASSERT(!typeName.isEmpty(), return {});
|
||||
|
||||
QmlVisualNode newQmlObjectNode;
|
||||
|
||||
auto createNodeFunc = [&]() {
|
||||
if (!importName.isEmpty()) {
|
||||
Import import = Import::createLibraryImport(importName);
|
||||
view->model()->changeImports({import}, {});
|
||||
}
|
||||
|
||||
QList<QPair<PropertyName, QVariant> > propertyPairList;
|
||||
propertyPairList.append(Position(position).propertyPairList());
|
||||
#ifdef QDS_USE_PROJECTSTORAGE
|
||||
newQmlObjectNode = QmlVisualNode(view->createModelNode(typeName,
|
||||
propertyPairList));
|
||||
#else
|
||||
NodeMetaInfo metaInfo = view->model()->metaInfo(typeName);
|
||||
newQmlObjectNode = QmlVisualNode(view->createModelNode(typeName,
|
||||
metaInfo.majorVersion(),
|
||||
metaInfo.minorVersion(),
|
||||
propertyPairList));
|
||||
#endif
|
||||
|
||||
if (newQmlObjectNode.id().isEmpty()) {
|
||||
newQmlObjectNode.modelNode().setIdWithoutRefactoring(
|
||||
view->model()->generateNewId(QString::fromUtf8(typeName)));
|
||||
}
|
||||
|
||||
if (targetParentProperty.isValid())
|
||||
targetParentProperty.reparentHere(newQmlObjectNode);
|
||||
};
|
||||
|
||||
if (createInTransaction)
|
||||
view->executeInTransaction(__FUNCTION__, createNodeFunc);
|
||||
else
|
||||
createNodeFunc();
|
||||
|
||||
return newQmlObjectNode;
|
||||
}
|
||||
|
||||
|
||||
NodeListProperty QmlVisualNode::findSceneNodeProperty(AbstractView *view, qint32 sceneRootId)
|
||||
{
|
||||
QTC_ASSERT(view, return {});
|
||||
|
@@ -88,6 +88,13 @@ public:
|
||||
qint32 sceneRootId = -1, const QVector3D &position = {},
|
||||
bool createInTransaction = true);
|
||||
|
||||
static QmlVisualNode createQml3DNode(AbstractView *view,
|
||||
const TypeName &typeName,
|
||||
const ModelNode &parentNode,
|
||||
const QString &importName = {},
|
||||
const QVector3D &position = {},
|
||||
bool createInTransaction = true);
|
||||
|
||||
static NodeListProperty findSceneNodeProperty(AbstractView *view, qint32 sceneRootId);
|
||||
|
||||
static bool isFlowTransition(const ModelNode &node);
|
||||
|
Reference in New Issue
Block a user