Show 3D import preview

Show preview of 3D import in item library import dialog
when importing a single 3D asset.

Fixes: QDS-11111
Change-Id: I13135be1e931cbee034ca8a89654c0827b937bdf
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
This commit is contained in:
Miikka Heikkinen
2024-05-07 13:41:14 +03:00
parent 872c4bf092
commit 14e0196442
21 changed files with 832 additions and 72 deletions

View File

@@ -18,6 +18,7 @@ public:
ActiveSceneChanged,
ActiveSplitChanged,
RenderModelNodePreviewImage,
Import3DPreviewImage,
Import3DSupport,
NodeAtPos,
BakeLightsProgress,

View File

@@ -55,7 +55,8 @@ enum class View3DActionType {
EditCameraRotation,
EditCameraMove,
EditCameraStopAllMoves,
SetLastSceneEnvData
SetLastSceneEnvData,
Import3dUpdatePreviewImage
};
constexpr bool isNanotraceEnabled()

View File

@@ -741,6 +741,8 @@ extend_qtc_plugin(QmlDesigner
assetimportupdatetreeitemdelegate.cpp assetimportupdatetreeitemdelegate.h
assetimportupdatetreemodel.cpp assetimportupdatetreemodel.h
assetimportupdatetreeview.cpp assetimportupdatetreeview.h
import3dcanvas.cpp import3dcanvas.h
import3dconnectionmanager.cpp import3dconnectionmanager.h
itemlibrary.qrc
itemlibraryconstants.h
itemlibraryimageprovider.cpp itemlibraryimageprovider.h

View File

@@ -0,0 +1,56 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "import3dcanvas.h"
#include <QImage>
#include <QLinearGradient>
#include <QPainter>
namespace QmlDesigner {
static QImage createGradientImage(int width, int height) {
QImage image(width, height, QImage::Format_ARGB32_Premultiplied);
QLinearGradient gradient(0, 0, 0, height);
gradient.setColorAt(0, QColor(0x999999));
gradient.setColorAt(1, QColor(0x222222));
QPainter painter(&image);
painter.fillRect(0, 0, width, height, gradient);
return image;
}
Import3dCanvas::Import3dCanvas(QWidget *parent)
: QWidget(parent)
{
}
void Import3dCanvas::updateRenderImage(const QImage &img)
{
m_image = img;
update();
}
void Import3dCanvas::paintEvent([[maybe_unused]] QPaintEvent *e)
{
QWidget::paintEvent(e);
QPainter painter(this);
if (m_image.isNull()) {
QImage image = createGradientImage(width(), height());
painter.drawImage(rect(), image, QRect(0, 0, image.width(), image.height()));
} else {
painter.drawImage(rect(), m_image, QRect(0, 0, m_image.width(), m_image.height()));
}
}
void Import3dCanvas::resizeEvent(QResizeEvent *)
{
emit requestImageUpdate();
}
}

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QEvent>
#include <QImage>
#include <QPointer>
#include <QWidget>
namespace QmlDesigner {
class Import3dCanvas : public QWidget
{
Q_OBJECT
public:
Import3dCanvas(QWidget *parent);
void updateRenderImage(const QImage &img);
signals:
void requestImageUpdate();
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
QImage m_image;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,47 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "import3dconnectionmanager.h"
#include <imagecontainer.h>
#include <puppettocreatorcommand.h>
#include <QImage>
namespace QmlDesigner {
Import3dConnectionManager::Import3dConnectionManager()
{
connections().clear(); // Remove default interactive puppets
connections().emplace_back("Import 3D", "import3dmode");
}
void Import3dConnectionManager::setPreviewImageCallback(ImageCallback callback)
{
m_previewImageCallback = std::move(callback);
}
void Import3dConnectionManager::dispatchCommand(const QVariant &command,
ConnectionManagerInterface::Connection &connection)
{
static const int commandType = QMetaType::type("PuppetToCreatorCommand");
if (command.typeId() == commandType) {
auto cmd = command.value<PuppetToCreatorCommand>();
switch (cmd.type()) {
case PuppetToCreatorCommand::Import3DPreviewImage: {
ImageContainer container = qvariant_cast<ImageContainer>(cmd.data());
QImage image = container.image();
if (!image.isNull())
m_previewImageCallback(image);
break;
}
default:
break;
}
} else {
InteractiveConnectionManager::dispatchCommand(command, connection);
}
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "interactiveconnectionmanager.h"
QT_FORWARD_DECLARE_CLASS(QImage)
namespace QmlDesigner {
class Import3dConnectionManager : public InteractiveConnectionManager
{
public:
using ImageCallback = std::function<void(const QImage &)>;
Import3dConnectionManager();
void setPreviewImageCallback(ImageCallback callback);
protected:
void dispatchCommand(const QVariant &command, Connection &connection) override;
private:
ImageCallback m_previewImageCallback;
};
} // namespace QmlDesigner

View File

@@ -4,15 +4,19 @@
#include "itemlibraryassetimportdialog.h"
#include "ui_itemlibraryassetimportdialog.h"
#include "import3dcanvas.h"
#include "import3dconnectionmanager.h"
#include <model.h>
#include <model/modelutils.h>
#include <nodeinstanceview.h>
#include <nodemetainfo.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <rewriterview.h>
#include <variantproperty.h>
#include <theme.h>
#include <utils/filepath.h>
#include <utils/outputformatter.h>
#include <projectexplorer/project.h>
@@ -74,9 +78,10 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
const QStringList &importFiles, const QString &defaulTargetDirectory,
const QVariantMap &supportedExts, const QVariantMap &supportedOpts,
const QJsonObject &defaultOpts, const QSet<QString> &preselectedFilesForOverwrite,
QWidget *parent)
AbstractView *view, QWidget *parent)
: QDialog(parent)
, ui(new Ui::ItemLibraryAssetImportDialog)
, m_view(view)
, m_importer(this)
, m_preselectedFilesForOverwrite(preselectedFilesForOverwrite)
{
@@ -107,11 +112,9 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
if (m_quick3DFiles.size() != importFiles.size())
addWarning("Cannot import 3D and other assets simultaneously. Skipping non-3D assets.");
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Import"));
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked,
this, &ItemLibraryAssetImportDialog::onImport);
connect(ui->importButton, &QPushButton::clicked, this, &ItemLibraryAssetImportDialog::onImport);
ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
ui->importButton->setDefault(true);
ui->advancedSettingsButton->setStyleSheet(
"QPushButton#advancedSettingsButton {background-color: transparent}");
@@ -210,10 +213,14 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
ui->tabWidget->setCurrentIndex(0);
}
connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked,
connect(ui->closeButton, &QPushButton::clicked,
this, &ItemLibraryAssetImportDialog::onClose);
connect(ui->acceptButton, &QPushButton::clicked,
this, &ItemLibraryAssetImportDialog::onAccept);
connect(ui->tabWidget, &QTabWidget::currentChanged,
this, &ItemLibraryAssetImportDialog::updateUi);
connect(canvas(), &Import3dCanvas::requestImageUpdate,
this, &ItemLibraryAssetImportDialog::onRequestImageUpdate);
connect(&m_importer, &ItemLibraryAssetImporter::errorReported,
this, &ItemLibraryAssetImportDialog::addError);
@@ -227,6 +234,8 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
this, &ItemLibraryAssetImportDialog::onImportFinished);
connect(&m_importer, &ItemLibraryAssetImporter::progressChanged,
this, &ItemLibraryAssetImportDialog::setImportProgress);
connect(&m_importer, &ItemLibraryAssetImporter::importReadyForPreview,
this, &ItemLibraryAssetImportDialog::onImportReadyForPreview);
addInfo(tr("Select import options and press \"Import\" to import the following files:"));
for (const auto &file : std::as_const(m_quick3DFiles))
@@ -240,10 +249,12 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
ItemLibraryAssetImportDialog::~ItemLibraryAssetImportDialog()
{
cleanupPreviewPuppet();
delete ui;
}
void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode,
void ItemLibraryAssetImportDialog::updateImport(AbstractView *view,
const ModelNode &updateNode,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts)
{
@@ -332,7 +343,8 @@ void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode,
{sourceInfo.absoluteFilePath()},
node.model()->fileUrl().toLocalFile(),
supportedExts, supportedOpts, options,
preselectedFiles, Core::ICore::dialogParent());
preselectedFiles, view,
Core::ICore::dialogParent());
importDlg->show();
} else {
@@ -829,6 +841,140 @@ bool ItemLibraryAssetImportDialog::isHiddenOption(const QString &id)
return hiddenOptions.contains(id);
}
void ItemLibraryAssetImportDialog::startPreview()
{
cleanupPreviewPuppet();
// Preview is done via custom QML file added into the temporary folder of the preview
QString previewQml =
R"(
import QtQuick
import QtQuick3D
Rectangle {
id: root
width: %1
height: %2
property string sceneModelName: "%3"
property alias view3d: view3d
property string extents
gradient: Gradient {
GradientStop { position: 1.0; color: "#222222" }
GradientStop { position: 0.0; color: "#999999" }
}
View3D {
id: view3d
anchors.fill: parent
camera: viewCamera
environment: SceneEnvironment {
antialiasingMode: SceneEnvironment.MSAA
antialiasingQuality: SceneEnvironment.VeryHigh
}
PerspectiveCamera {
id: viewCamera
z: 600
y: 600
x: 600
eulerRotation.x: -45
eulerRotation.y: -45
clipFar: 100000
clipNear: 10
}
DirectionalLight {
rotation: viewCamera.rotation
}
}
Text {
anchors.bottom: parent.bottom
anchors.left: parent.left
color: "white"
text: root.extents
font.pixelSize: 14
}
}
)";
QSize size = canvas()->size();
previewQml = previewQml.arg(size.width()).arg(size.height()).arg(m_previewCompName);
m_previewFile.writeFileContents(previewQml.toUtf8());
if (!m_previewFile.exists()) {
addWarning("Failed to write preview file.");
return;
}
m_connectionManager = new Import3dConnectionManager;
m_rewriterView = new RewriterView{m_view->externalDependencies(), RewriterView::Amend};
m_nodeInstanceView = new NodeInstanceView{*m_connectionManager, m_view->externalDependencies()};
#ifdef QDS_USE_PROJECTSTORAGE
m_model = m_view->model()->createModel("Item");
#else
m_model = QmlDesigner::Model::create("QtQuick/Item", 2, 1);
m_model->setFileUrl(m_previewFile.toUrl());
#endif
auto textDocument = std::make_unique<QTextDocument>(previewQml);
auto modifier = std::make_unique<NotIndentingTextEditModifier>(textDocument.get(),
QTextCursor{textDocument.get()});
m_rewriterView->setTextModifier(modifier.get());
m_model->setRewriterView(m_rewriterView);
if (!m_rewriterView->errors().isEmpty()) {
addWarning("Preview scene creation failed.");
cleanupPreviewPuppet();
return;
}
m_nodeInstanceView->setTarget(m_view->nodeInstanceView()->target());
auto previewImageCallback = [this](const QImage &image) {
canvas()->updateRenderImage(image);
};
auto crashCallback = [&] {
addWarning("Preview process crashed.");
cleanupPreviewPuppet();
};
m_connectionManager->setPreviewImageCallback(std::move(previewImageCallback));
m_nodeInstanceView->setCrashCallback(std::move(crashCallback));
m_model->setNodeInstanceView(m_nodeInstanceView);
}
void ItemLibraryAssetImportDialog::cleanupPreviewPuppet()
{
if (m_model) {
m_model->setNodeInstanceView({});
m_model->setRewriterView({});
m_model.reset();
}
if (m_nodeInstanceView)
m_nodeInstanceView->setCrashCallback({});
if (m_connectionManager)
m_connectionManager->setPreviewImageCallback({});
delete m_rewriterView;
delete m_nodeInstanceView;
delete m_connectionManager;
}
Import3dCanvas *ItemLibraryAssetImportDialog::canvas()
{
return ui->import3dcanvas;
}
void ItemLibraryAssetImportDialog::resizeEvent(QResizeEvent *event)
{
m_dialogHeight = event->size().height();
@@ -837,8 +983,8 @@ void ItemLibraryAssetImportDialog::resizeEvent(QResizeEvent *event)
void ItemLibraryAssetImportDialog::setCloseButtonState(bool importing)
{
ui->buttonBox->button(QDialogButtonBox::Close)->setEnabled(true);
ui->buttonBox->button(QDialogButtonBox::Close)->setText(importing ? tr("Cancel") : tr("Close"));
ui->closeButton->setEnabled(true);
ui->closeButton->setText(importing ? tr("Cancel") : tr("Close"));
}
void ItemLibraryAssetImportDialog::addError(const QString &error, const QString &srcPath)
@@ -860,16 +1006,21 @@ void ItemLibraryAssetImportDialog::addInfo(const QString &info, const QString &s
void ItemLibraryAssetImportDialog::onImport()
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
ui->acceptButton->setEnabled(false);
ui->importButton->setEnabled(false);
setCloseButtonState(true);
ui->progressBar->setValue(0);
if (!m_quick3DFiles.isEmpty()) {
if (!m_previewCompName.isEmpty()) {
m_importer.reImportQuick3D(m_previewCompName, m_importOptions);
} else {
m_importer.importQuick3D(m_quick3DFiles, m_quick3DImportPath,
m_importOptions, m_extToImportOptionsMap,
m_preselectedFilesForOverwrite);
}
}
}
void ItemLibraryAssetImportDialog::setImportProgress(int value, const QString &text)
{
@@ -881,10 +1032,28 @@ void ItemLibraryAssetImportDialog::setImportProgress(int value, const QString &t
ui->progressBar->setValue(value);
}
void ItemLibraryAssetImportDialog::onImportReadyForPreview(const QString &path, const QString &compName)
{
m_previewFile = Utils::FilePath::fromString(path).pathAppended(m_importer.previewFileName());
m_previewCompName = compName;
QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::startPreview);
ui->acceptButton->setEnabled(true);
ui->importButton->setEnabled(true);
addInfo(tr("Generating import preview for %1.").arg(compName));
}
void ItemLibraryAssetImportDialog::onRequestImageUpdate()
{
if (m_nodeInstanceView)
m_nodeInstanceView->view3DAction(View3DActionType::Import3dUpdatePreviewImage, canvas()->size());
}
void ItemLibraryAssetImportDialog::onImportNearlyFinished()
{
// Canceling import is no longer doable
ui->buttonBox->button(QDialogButtonBox::Close)->setEnabled(false);
ui->closeButton->setEnabled(false);
}
void ItemLibraryAssetImportDialog::onImportFinished()
@@ -920,6 +1089,16 @@ void ItemLibraryAssetImportDialog::onClose()
}
}
void ItemLibraryAssetImportDialog::onAccept()
{
cleanupPreviewPuppet();
ui->importButton->setEnabled(false);
ui->acceptButton->setEnabled(false);
m_importer.finalizeQuick3DImport();
}
void ItemLibraryAssetImportDialog::toggleAdvanced()
{
m_advancedMode = !m_advancedMode;

View File

@@ -3,14 +3,19 @@
#pragma once
#include "itemlibraryassetimporter.h"
#include "modelnode.h"
#include <modelnode.h>
#include <utils/filepath.h>
#include <QDialog>
#include <QJsonObject>
#include <QPointer>
#include <QSet>
QT_BEGIN_NAMESPACE
class QGridLayout;
class QPushButton;
QT_END_NAMESPACE
namespace Utils {
@@ -19,6 +24,10 @@ class OutputFormatter;
namespace QmlDesigner {
class ItemLibraryAssetImporter;
class Import3dCanvas;
class Import3dConnectionManager;
class NodeInstanceView;
class RewriterView;
namespace Ui {
class ItemLibraryAssetImportDialog;
@@ -35,10 +44,12 @@ public:
const QVariantMap &supportedOpts,
const QJsonObject &defaultOpts,
const QSet<QString> &preselectedFilesForOverwrite,
AbstractView *view,
QWidget *parent = nullptr);
~ItemLibraryAssetImportDialog();
static void updateImport(const ModelNode &updateNode,
static void updateImport(AbstractView *view,
const ModelNode &updateNode,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts);
@@ -55,9 +66,12 @@ private:
void onImport();
void setImportProgress(int value, const QString &text);
void onImportReadyForPreview(const QString &path, const QString &compName);
void onRequestImageUpdate();
void onImportNearlyFinished();
void onImportFinished();
void onClose();
void onAccept();
void toggleAdvanced();
void createTab(const QString &tabLabel, int optionsIndex, const QJsonObject &groups);
@@ -69,8 +83,19 @@ private:
bool isSimpleOption(const QString &id);
bool isHiddenOption(const QString &id);
void startPreview();
void cleanupPreviewPuppet();
Import3dCanvas *canvas();
Ui::ItemLibraryAssetImportDialog *ui = nullptr;
Utils::OutputFormatter *m_outputFormatter = nullptr;
QPointer<Import3dConnectionManager> m_connectionManager;
QPointer<NodeInstanceView> m_nodeInstanceView;
QPointer<RewriterView> m_rewriterView;
QPointer<AbstractView> m_view;
ModelPointer m_model;
Utils::FilePath m_previewFile;
QString m_previewCompName;
struct OptionsData
{

View File

@@ -6,15 +6,15 @@
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<width>1100</width>
<height>350</height>
</rect>
</property>
<property name="windowTitle">
<string>Asset Import</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
@@ -24,6 +24,12 @@
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>550</width>
<height>0</height>
</size>
</property>
<property name="currentIndex">
<number>0</number>
</property>
@@ -98,6 +104,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
@@ -111,16 +123,74 @@
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importButton">
<property name="text">
<string>Import</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="acceptButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Accept</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Import3dCanvas" name="import3dcanvas" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>300</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Import3dCanvas</class>
<extends>QWidget</extends>
<header>import3dcanvas.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -21,6 +21,7 @@
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/filepath.h>
#include <utils/qtcassert.h>
#include <QApplication>
@@ -58,8 +59,6 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite)
{
if (m_isImporting)
cancelImport();
reset();
m_isImporting = true;
@@ -92,6 +91,53 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
}
}
void ItemLibraryAssetImporter::reImportQuick3D(const QString &assetName,
const QVector<QJsonObject> &options)
{
if (!assetName.isEmpty() && !m_parseData.contains(assetName)) {
addError(tr("Attempted to reimport non-existing asset: %1").arg(assetName));
return;
}
ParseData &pd = m_parseData[assetName];
// Change outDir just in case reimport generates different files
QDir oldDir = pd.outDir;
QString assetFolder = generateAssetFolderName(pd.assetName);
pd.outDir.cdUp();
pd.outDir.mkpath(assetFolder);
if (!pd.outDir.cd(assetFolder)) {
addError(tr("Could not access temporary asset directory: \"%1\".")
.arg(pd.outDir.filePath(assetFolder)));
return;
}
if (oldDir.absolutePath().contains(tempDirNameBase()))
oldDir.removeRecursively();
m_isImporting = false;
m_cancelled = false;
m_puppetProcess.reset();
m_requiredImports.clear();
m_currentImportId = 0;
m_puppetQueue.clear();
for (ParseData &pd : m_parseData)
pd.importId = -1;
pd.options = options[pd.optionsIndex];
pd.importId = 1;
m_importFiles.remove(assetName);
m_importIdToAssetNameMap.clear();
m_importIdToAssetNameMap[pd.importId] = assetName;
m_puppetQueue.append(pd.importId);
startNextImportProcess();
}
bool ItemLibraryAssetImporter::isImporting() const
{
return m_isImporting;
@@ -104,19 +150,19 @@ void ItemLibraryAssetImporter::cancelImport()
notifyFinished();
}
void ItemLibraryAssetImporter::addError(const QString &errMsg, const QString &srcPath) const
void ItemLibraryAssetImporter::addError(const QString &errMsg, const QString &srcPath)
{
qCDebug(importerLog) << "Error: "<< errMsg << srcPath;
emit errorReported(errMsg, srcPath);
}
void ItemLibraryAssetImporter::addWarning(const QString &warningMsg, const QString &srcPath) const
void ItemLibraryAssetImporter::addWarning(const QString &warningMsg, const QString &srcPath)
{
qCDebug(importerLog) << "Warning: " << warningMsg << srcPath;
emit warningReported(warningMsg, srcPath);
}
void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &srcPath) const
void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &srcPath)
{
qCDebug(importerLog) << "Info: " << infoMsg << srcPath;
emit infoReported(infoMsg, srcPath);
@@ -127,8 +173,8 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo
{
m_puppetProcess.reset();
if (m_parseData.contains(m_currentImportId)) {
const ParseData &pd = m_parseData[m_currentImportId];
if (m_importIdToAssetNameMap.contains(m_currentImportId)) {
const ParseData &pd = m_parseData[m_importIdToAssetNameMap[m_currentImportId]];
QString errStr;
if (exitStatus == QProcess::ExitStatus::CrashExit) {
errStr = tr("Import process crashed.");
@@ -151,11 +197,12 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo
addError(tr("Asset import process failed: \"%1\".")
.arg(pd.sourceInfo.absoluteFilePath()));
addError(errStr);
m_parseData.remove(m_currentImportId);
m_parseData.remove(m_importIdToAssetNameMap[m_currentImportId]);
m_importIdToAssetNameMap.remove(m_currentImportId);
}
}
int finishedCount = m_parseData.size() - m_puppetQueue.size();
int finishedCount = m_importIdToAssetNameMap.size() - m_puppetQueue.size();
if (!m_puppetQueue.isEmpty())
startNextImportProcess();
@@ -163,7 +210,7 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo
notifyProgress(100);
QTimer::singleShot(0, this, &ItemLibraryAssetImporter::postImport);
} else {
notifyProgress(int(100. * (double(finishedCount) / double(m_parseData.size()))));
notifyProgress(int(100. * (double(finishedCount) / double(m_importIdToAssetNameMap.size()))));
}
}
@@ -179,7 +226,7 @@ void ItemLibraryAssetImporter::reset()
m_cancelled = false;
delete m_tempDir;
m_tempDir = new QTemporaryDir;
m_tempDir = new QTemporaryDir(QDir::tempPath() + tempDirNameBase());
m_importFiles.clear();
m_overwrittenImports.clear();
m_puppetProcess.reset();
@@ -187,6 +234,7 @@ void ItemLibraryAssetImporter::reset()
m_requiredImports.clear();
m_currentImportId = 0;
m_puppetQueue.clear();
m_importIdToAssetNameMap.clear();
}
void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
@@ -208,9 +256,11 @@ void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
int index = extToImportOptionsMap.value(QFileInfo(file).suffix());
ParseData pd;
pd.options = options[index];
pd.optionsIndex = index;
if (preParseQuick3DAsset(file, pd, preselectedFilesForOverwrite)) {
pd.importId = ++m_importIdCounter;
m_parseData.insert(pd.importId, pd);
m_importIdToAssetNameMap[pd.importId] = pd.assetName;
m_parseData.insert(pd.assetName, pd);
}
notifyProgress(qRound(++count * quota), progressTitle);
}
@@ -239,7 +289,7 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
pd.targetDirPath = pd.targetDir.filePath(pd.assetName);
if (pd.outDir.exists(pd.assetName)) {
if (m_parseData.contains(pd.assetName)) {
addWarning(tr("Skipped import of duplicate asset: \"%1\".").arg(pd.assetName));
return false;
}
@@ -288,11 +338,12 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
}
}
pd.outDir.mkpath(pd.assetName);
QString assetFolder = generateAssetFolderName(pd.assetName);
pd.outDir.mkpath(assetFolder);
if (!pd.outDir.cd(pd.assetName)) {
if (!pd.outDir.cd(assetFolder)) {
addError(tr("Could not access temporary asset directory: \"%1\".")
.arg(pd.outDir.filePath(pd.assetName)));
.arg(pd.outDir.filePath(assetFolder)));
return false;
}
return true;
@@ -425,7 +476,7 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd)
// Copy the original asset into a subdirectory
assetFiles.insert(sourcePath, sourceSceneTargetFilePath(pd));
m_importFiles.insert(assetFiles);
m_importFiles.insert(pd.assetName, assetFiles);
}
void ItemLibraryAssetImporter::copyImportedFiles()
@@ -500,6 +551,12 @@ void ItemLibraryAssetImporter::keepUiAlive() const
QApplication::processEvents();
}
QString ItemLibraryAssetImporter::generateAssetFolderName(const QString &assetName) const
{
static int counter = 0;
return assetName + "_QDS_" + QString::number(counter++);
}
ItemLibraryAssetImporter::OverwriteResult ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName)
{
const QString title = tr("Overwrite Existing Asset?");
@@ -534,7 +591,8 @@ void ItemLibraryAssetImporter::startNextImportProcess()
if (model && view) {
bool done = false;
while (!m_puppetQueue.isEmpty() && !done) {
const ParseData pd = m_parseData.value(m_puppetQueue.takeLast());
const ParseData pd = m_parseData.value(
m_importIdToAssetNameMap.value(m_puppetQueue.takeLast()));
QStringList puppetArgs;
QJsonDocument optDoc(pd.options);
@@ -557,7 +615,8 @@ void ItemLibraryAssetImporter::startNextImportProcess()
} else {
addError(tr("Failed to start import 3D asset process."),
pd.sourceInfo.absoluteFilePath());
m_parseData.remove(pd.importId);
const QString assetName = m_importIdToAssetNameMap.take(pd.importId);
m_parseData.remove(assetName);
m_puppetProcess.reset();
}
}
@@ -573,8 +632,16 @@ void ItemLibraryAssetImporter::postImport()
postParseQuick3DAsset(pd);
}
if (!isCancelled())
if (!isCancelled()) {
// TODO: Currently we only support import preview for single imports
if (m_parseData.size() != 1) {
finalizeQuick3DImport();
} else {
const ParseData &pd = m_parseData[m_parseData.keys().first()];
const QString importedComponentName = pd.assetName;
emit importReadyForPreview(pd.outDir.absolutePath(), importedComponentName);
}
}
}
void ItemLibraryAssetImporter::finalizeQuick3DImport()

View File

@@ -2,8 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "import.h"
#include <qprocessuniqueptr.h>
#include <QSet>
@@ -35,20 +33,28 @@ public:
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite);
void reImportQuick3D(const QString &assetName, const QVector<QJsonObject> &options);
bool isImporting() const;
void cancelImport();
bool isCancelled() const;
void addError(const QString &errMsg, const QString &srcPath = {}) const;
void addWarning(const QString &warningMsg, const QString &srcPath = {}) const;
void addInfo(const QString &infoMsg, const QString &srcPath = {}) const;
void addError(const QString &errMsg, const QString &srcPath = {});
void addWarning(const QString &warningMsg, const QString &srcPath = {});
void addInfo(const QString &infoMsg, const QString &srcPath = {});
QString previewFileName() const { return "QDSImport3dPreviewScene.qml"; }
QString tempDirNameBase() const { return "/qds3dimport"; }
void finalizeQuick3DImport();
signals:
void errorReported(const QString &, const QString &) const;
void warningReported(const QString &, const QString &) const;
void infoReported(const QString &, const QString &) const;
void progressChanged(int value, const QString &text) const;
void importNearlyFinished() const;
void errorReported(const QString &, const QString &);
void warningReported(const QString &, const QString &);
void infoReported(const QString &, const QString &);
void progressChanged(int value, const QString &text);
void importReadyForPreview(const QString &path, const QString &compName);
void importNearlyFinished();
void importFinished();
private slots:
@@ -63,7 +69,8 @@ private:
QFileInfo sourceInfo;
QString assetName;
QString originalAssetName;
int importId;
int importId = -1;
int optionsIndex = -1;
};
void notifyFinished();
@@ -79,6 +86,7 @@ private:
void notifyProgress(int value, const QString &text);
void notifyProgress(int value);
void keepUiAlive() const;
QString generateAssetFolderName(const QString &assetName) const;
enum class OverwriteResult {
Skip,
@@ -89,10 +97,9 @@ private:
OverwriteResult confirmAssetOverwrite(const QString &assetName);
void startNextImportProcess();
void postImport();
void finalizeQuick3DImport();
QString sourceSceneTargetFilePath(const ParseData &pd);
QSet<QHash<QString, QString>> m_importFiles;
QHash<QString, QHash<QString, QString>> m_importFiles; // Key: asset name
QHash<QString, QStringList> m_overwrittenImports;
bool m_isImporting = false;
bool m_cancelled = false;
@@ -101,7 +108,8 @@ private:
QProcessUniquePointer m_puppetProcess;
int m_importIdCounter = 0;
int m_currentImportId = 0;
QHash<int, ParseData> m_parseData;
QHash<int, QString> m_importIdToAssetNameMap;
QHash<QString, ParseData> m_parseData; // Key: asset name
QString m_progressTitle;
QStringList m_requiredImports;
QList<int> m_puppetQueue;

View File

@@ -164,7 +164,7 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap)
auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir,
m_importableExtensions3DMap,
m_importOptions3DMap, {}, {},
Core::ICore::dialogParent());
this, Core::ICore::dialogParent());
int result = importDlg->exec();
return result == QDialog::Accepted ? AddFilesResult::succeeded() : AddFilesResult::cancelled();
@@ -198,7 +198,7 @@ void ItemLibraryView::customNotification(const AbstractView *view, const QString
const QList<ModelNode> &nodeList, const QList<QVariant> &data)
{
if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) {
ItemLibraryAssetImportDialog::updateImport(nodeList[0],
ItemLibraryAssetImportDialog::updateImport(this, nodeList[0],
m_importableExtensions3DMap,
m_importOptions3DMap);

View File

@@ -39,7 +39,8 @@ namespace Internal {
static QStringList puppetModes()
{
static QStringList puppetModeList{"", "all", "editormode", "rendermode", "previewmode", "bakelightsmode"};
static QStringList puppetModeList{"", "all", "editormode", "rendermode", "previewmode",
"bakelightsmode", "import3dmode"};
return puppetModeList;
}

View File

@@ -140,6 +140,7 @@ extend_qtc_executable(qml2puppet
qmltransitionnodeinstance.cpp qmltransitionnodeinstance.h
qt3dpresentationnodeinstance.cpp qt3dpresentationnodeinstance.h
qt5bakelightsnodeinstanceserver.cpp qt5bakelightsnodeinstanceserver.h
qt5import3dnodeinstanceserver.cpp qt5import3dnodeinstanceserver.h
qt5informationnodeinstanceserver.cpp qt5informationnodeinstanceserver.h
qt5nodeinstanceclientproxy.cpp qt5nodeinstanceclientproxy.h
qt5nodeinstanceserver.cpp qt5nodeinstanceserver.h

View File

@@ -462,7 +462,7 @@ QVector4D GeneralHelper::approachNode(
// a selection box for bound calculations to work. This is used to focus the view for
// various preview image generations, where doing things asynchronously is not good
// and recalculating bounds for every frame is not a problem.
void GeneralHelper::calculateNodeBoundsAndFocusCamera(
QVector3D GeneralHelper::calculateNodeBoundsAndFocusCamera(
QQuick3DCamera *camera, QQuick3DNode *node, QQuick3DViewport *viewPort,
float defaultLookAtDistance, bool closeUp)
{
@@ -505,8 +505,9 @@ void GeneralHelper::calculateNodeBoundsAndFocusCamera(
perspectiveCamera->setClipNear(minDist * 0.99);
perspectiveCamera->setClipFar(maxDist * 1.01);
}
}
return extents;
}
// Aligns any cameras found in nodes list to a camera.

View File

@@ -72,9 +72,11 @@ public:
bool closeUp = false);
Q_INVOKABLE QVector4D approachNode(QQuick3DCamera *camera, float defaultLookAtDistance,
QObject *node, QQuick3DViewport *viewPort);
Q_INVOKABLE void calculateNodeBoundsAndFocusCamera(QQuick3DCamera *camera, QQuick3DNode *node,
Q_INVOKABLE QVector3D calculateNodeBoundsAndFocusCamera(QQuick3DCamera *camera,
QQuick3DNode *node,
QQuick3DViewport *viewPort,
float defaultLookAtDistance, bool closeUp);
float defaultLookAtDistance,
bool closeUp);
Q_INVOKABLE void alignCameras(QQuick3DCamera *camera, const QVariant &nodes);
Q_INVOKABLE QVector4D alignView(QQuick3DCamera *camera, const QVariant &nodes,
const QVector3D &lookAtPoint, float defaultLookAtDistance);

View File

@@ -6,6 +6,7 @@
#include "qt5bakelightsnodeinstanceserver.h"
#include "qt5captureimagenodeinstanceserver.h"
#include "qt5capturepreviewnodeinstanceserver.h"
#include "qt5import3dnodeinstanceserver.h"
#include "qt5informationnodeinstanceserver.h"
#include "qt5rendernodeinstanceserver.h"
@@ -173,6 +174,8 @@ std::unique_ptr<NodeInstanceServer> createNodeInstanceServer(
return std::make_unique<Qt5PreviewNodeInstanceServer>(nodeInstanceClient);
else if (serverName == "bakelightsmode")
return std::make_unique<Qt5BakeLightsNodeInstanceServer>(nodeInstanceClient);
else if (serverName == "import3dmode")
return std::make_unique<Qt5Import3dNodeInstanceServer>(nodeInstanceClient);
return {};
}

View File

@@ -0,0 +1,186 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "qt5import3dnodeinstanceserver.h"
#include "createscenecommand.h"
#include "view3dactioncommand.h"
#include "imagecontainer.h"
#include "nodeinstanceclientinterface.h"
#include "puppettocreatorcommand.h"
#include <QFileInfo>
#include <QLocale>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QQmlProperty>
#include <private/qquick3dnode_p.h>
#include <private/qquick3dviewport_p.h>
#include <private/qquickdesignersupport_p.h>
namespace QmlDesigner {
Qt5Import3dNodeInstanceServer::Qt5Import3dNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) :
Qt5NodeInstanceServer(nodeInstanceClient)
{
setSlowRenderTimerInterval(100000000);
setRenderTimerInterval(20);
#ifdef QUICK3D_MODULE
m_generalHelper = new Internal::GeneralHelper();
QObject::connect(m_generalHelper, &Internal::GeneralHelper::requestRender, this, [this]() {
startRenderTimer();
});
#endif
}
Qt5Import3dNodeInstanceServer::~Qt5Import3dNodeInstanceServer()
{
cleanup();
}
void Qt5Import3dNodeInstanceServer::createScene(const CreateSceneCommand &command)
{
initializeView();
registerFonts(command.resourceUrl);
setTranslationLanguage(command.language);
setupScene(command);
startRenderTimer();
}
void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DActionCommand &command)
{
switch (command.type()) {
case View3DActionType::Import3dUpdatePreviewImage: {
QObject *obj = rootItem();
if (obj) {
QSize size = command.value().toSize();
QQmlProperty wProp(obj, "width", context());
QQmlProperty hProp(obj, "height", context());
wProp.write(size.width());
hProp.write(size.height());
resizeCanvasToRootItem();
startRenderTimer();
}
break;
}
default:
break;
}
}
void Qt5Import3dNodeInstanceServer::startRenderTimer()
{
if (timerId() != 0)
killTimer(timerId());
int timerId = startTimer(renderTimerInterval());
setTimerId(timerId);
}
void Qt5Import3dNodeInstanceServer::cleanup()
{
#ifdef QUICK3D_MODULE
delete m_previewNode;
delete m_generalHelper;
#endif
}
void Qt5Import3dNodeInstanceServer::finish()
{
cleanup();
}
void Qt5Import3dNodeInstanceServer::collectItemChangesAndSendChangeCommands()
{
static bool inFunction = false;
if (!rootNodeInstance().holdsGraphical())
return;
if (!inFunction) {
inFunction = true;
QQuickDesignerSupport::polishItems(quickWindow());
render();
inFunction = false;
}
}
void Qt5Import3dNodeInstanceServer::render()
{
#ifdef QUICK3D_MODULE
++m_renderCount;
if (m_renderCount == 1) {
QObject *obj = rootItem();
QQmlProperty viewProp(obj, "view3d", context());
QObject *viewObj = viewProp.read().value<QObject *>();
m_view3D = qobject_cast<QQuick3DViewport *>(viewObj);
if (m_view3D) {
QQmlProperty sceneModelNameProp(obj, "sceneModelName", context());
QString sceneModelName = sceneModelNameProp.read().toString();
QFileInfo fi(fileUrl().toLocalFile());
QString compPath = fi.absolutePath() + '/' + sceneModelName + ".qml";
QQmlComponent comp(engine(), compPath, QQmlComponent::PreferSynchronous);
m_previewNode = qobject_cast<QQuick3DNode *>(comp.create(context()));
if (m_previewNode) {
engine()->setObjectOwnership(m_previewNode, QJSEngine::CppOwnership);
m_previewNode->setParent(m_view3D);
m_view3D->setImportScene(m_previewNode);
}
}
}
// Render scene once to ensure geometries are intialized so bounds calculations work correctly
if (m_renderCount == 2 && m_view3D) {
QVector3D extents =
m_generalHelper->calculateNodeBoundsAndFocusCamera(m_view3D->camera(), m_previewNode,
m_view3D, 1040, false);
auto getExtentStr = [&extents](int idx) -> QString {
int prec = 0;
float val = extents[idx];
while (val < 100.f) {
++prec;
val *= 10.f;
}
// Strip unnecessary zeroes after decimal separator
if (prec > 0) {
QString checkStr = QString::number(extents[idx], 'f', prec);
while (prec > 0 && (checkStr.last(1) == "0" || checkStr.last(1) == ".")) {
--prec;
checkStr.chop(1);
}
}
QString retval = QLocale().toString(extents[idx], 'f', prec);
return retval;
};
QQmlProperty extentsProp(rootItem(), "extents", context());
extentsProp.write(tr("Dimensions: %1 x %2 x %3").arg(getExtentStr(0))
.arg(getExtentStr(1))
.arg(getExtentStr(2)));
}
rootNodeInstance().updateDirtyNodeRecursive();
QImage renderImage = grabWindow();
if (m_renderCount >= 2) {
ImageContainer imgContainer(0, renderImage, m_renderCount);
nodeInstanceClient()->handlePuppetToCreatorCommand(
{PuppetToCreatorCommand::Import3DPreviewImage,
QVariant::fromValue(imgContainer)});
slowDownRenderTimer(); // No more renders needed for now
}
#else
slowDownRenderTimer();
#endif
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "generalhelper.h"
#include "qt5nodeinstanceserver.h"
QT_BEGIN_NAMESPACE
class QQuick3DNode;
QT_END_NAMESPACE
namespace QmlDesigner {
class Qt5Import3dNodeInstanceServer : public Qt5NodeInstanceServer
{
Q_OBJECT
public:
explicit Qt5Import3dNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient);
public:
virtual ~Qt5Import3dNodeInstanceServer();
void createScene(const CreateSceneCommand &command) override;
void view3DAction(const View3DActionCommand &command) override;
void render();
protected:
void collectItemChangesAndSendChangeCommands() override;
void startRenderTimer() override;
private:
void finish();
void cleanup();
int m_renderCount = 0;
#ifdef QUICK3D_MODULE
QQuick3DViewport *m_view3D = nullptr;
Internal::GeneralHelper *m_generalHelper = nullptr;
QQuick3DNode *m_previewNode = nullptr;
#endif
};
} // namespace QmlDesigner

View File

@@ -10,6 +10,7 @@
#include "qt5captureimagenodeinstanceserver.h"
#include "qt5capturepreviewnodeinstanceserver.h"
#include "qt5informationnodeinstanceserver.h"
#include "qt5import3dnodeinstanceserver.h"
#include "qt5previewnodeinstanceserver.h"
#include "qt5rendernodeinstanceserver.h"
#include "qt5testnodeinstanceserver.h"
@@ -70,6 +71,9 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) :
} else if (QCoreApplication::arguments().at(2) == QLatin1String("bakelightsmode")) {
setNodeInstanceServer(std::make_unique<Qt5BakeLightsNodeInstanceServer>(this));
initializeSocket();
} else if (QCoreApplication::arguments().at(2) == QLatin1String("import3dmode")) {
setNodeInstanceServer(std::make_unique<Qt5Import3dNodeInstanceServer>(this));
initializeSocket();
}
}