QmlDesigner: Allow rotation of 3D import preview

3D import preview can now be rotated using left mouse button and
dragging the preview image. This causes camera to orbit around the
previewed model, similar to rotation to 3D edit view orbit camera.

Close/Cancel button logic was also improved.

Fixes: QDS-12795
Change-Id: I0c7d1ad28f8fe779b9bedc4bf76be704078d91a6
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2024-05-23 12:34:57 +03:00
parent d345254579
commit 8bfe805645
10 changed files with 129 additions and 36 deletions

View File

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

View File

@@ -5,6 +5,7 @@
#include <QImage> #include <QImage>
#include <QLinearGradient> #include <QLinearGradient>
#include <QMouseEvent>
#include <QPainter> #include <QPainter>
namespace QmlDesigner { namespace QmlDesigner {
@@ -52,5 +53,30 @@ void Import3dCanvas::resizeEvent(QResizeEvent *)
emit requestImageUpdate(); emit requestImageUpdate();
} }
void Import3dCanvas::mousePressEvent(QMouseEvent *e)
{
if (e->buttons() == Qt::LeftButton)
m_dragPos = e->position();
else
m_dragPos = {};
}
void Import3dCanvas::mouseReleaseEvent(QMouseEvent *)
{
m_dragPos = {};
}
void Import3dCanvas::mouseMoveEvent(QMouseEvent *e)
{
if (m_dragPos.isNull())
return;
const QPointF curPos = e->position();
const QPointF delta = curPos - m_dragPos;
m_dragPos = curPos;
emit requestRotation(delta);
}
} }

View File

@@ -4,7 +4,7 @@
#include <QEvent> #include <QEvent>
#include <QImage> #include <QImage>
#include <QPointer> #include <QPointF>
#include <QWidget> #include <QWidget>
namespace QmlDesigner { namespace QmlDesigner {
@@ -20,13 +20,18 @@ public:
signals: signals:
void requestImageUpdate(); void requestImageUpdate();
void requestRotation(const QPointF &delta);
protected: protected:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
private: private:
QImage m_image; QImage m_image;
QPointF m_dragPos;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -221,6 +221,8 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
this, &ItemLibraryAssetImportDialog::updateUi); this, &ItemLibraryAssetImportDialog::updateUi);
connect(canvas(), &Import3dCanvas::requestImageUpdate, connect(canvas(), &Import3dCanvas::requestImageUpdate,
this, &ItemLibraryAssetImportDialog::onRequestImageUpdate); this, &ItemLibraryAssetImportDialog::onRequestImageUpdate);
connect(canvas(), &Import3dCanvas::requestRotation,
this, &ItemLibraryAssetImportDialog::onRequestRotation);
connect(&m_importer, &ItemLibraryAssetImporter::errorReported, connect(&m_importer, &ItemLibraryAssetImporter::errorReported,
this, &ItemLibraryAssetImportDialog::addError); this, &ItemLibraryAssetImportDialog::addError);
@@ -856,9 +858,10 @@ Rectangle {
width: %1 width: %1
height: %2 height: %2
property string sceneModelName: "%3" property alias sceneNode: sceneNode
property alias view3d: view3d property alias view3d: view3d
property string extents property string extents
property string sceneModelName: "%3"
gradient: Gradient { gradient: Gradient {
GradientStop { position: 1.0; color: "#222222" } GradientStop { position: 1.0; color: "#222222" }
@@ -877,9 +880,9 @@ Rectangle {
PerspectiveCamera { PerspectiveCamera {
id: viewCamera id: viewCamera
z: 600
y: 600
x: 600 x: 600
y: 600
z: 600
eulerRotation.x: -45 eulerRotation.x: -45
eulerRotation.y: -45 eulerRotation.y: -45
clipFar: 100000 clipFar: 100000
@@ -889,6 +892,10 @@ Rectangle {
DirectionalLight { DirectionalLight {
rotation: viewCamera.rotation rotation: viewCamera.rotation
} }
Node {
id: sceneNode
}
} }
Text { Text {
@@ -1041,7 +1048,8 @@ void ItemLibraryAssetImportDialog::onImportReadyForPreview(const QString &path,
ui->acceptButton->setEnabled(true); ui->acceptButton->setEnabled(true);
ui->importButton->setEnabled(true); ui->importButton->setEnabled(true);
addInfo(tr("Generating import preview for %1.").arg(compName)); addInfo(tr("Import is ready for preview."));
addInfo(tr("Click \"Accept\" to finish the import or adjust options an click \"Import\" to import again."));
} }
void ItemLibraryAssetImportDialog::onRequestImageUpdate() void ItemLibraryAssetImportDialog::onRequestImageUpdate()
@@ -1050,6 +1058,12 @@ void ItemLibraryAssetImportDialog::onRequestImageUpdate()
m_nodeInstanceView->view3DAction(View3DActionType::Import3dUpdatePreviewImage, canvas()->size()); m_nodeInstanceView->view3DAction(View3DActionType::Import3dUpdatePreviewImage, canvas()->size());
} }
void ItemLibraryAssetImportDialog::onRequestRotation(const QPointF &delta)
{
if (m_nodeInstanceView)
m_nodeInstanceView->view3DAction(View3DActionType::Import3dRotatePreviewModel, delta);
}
void ItemLibraryAssetImportDialog::onImportNearlyFinished() void ItemLibraryAssetImportDialog::onImportNearlyFinished()
{ {
// Canceling import is no longer doable // Canceling import is no longer doable
@@ -1063,18 +1077,28 @@ void ItemLibraryAssetImportDialog::onImportFinished()
QString interruptStr = tr("Import interrupted."); QString interruptStr = tr("Import interrupted.");
addError(interruptStr); addError(interruptStr);
setImportProgress(0, interruptStr); setImportProgress(0, interruptStr);
if (m_explicitClose)
QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::doClose);
} else { } else {
QString doneStr = tr("Import done."); QString doneStr = tr("Import done.");
addInfo(doneStr); addInfo(doneStr);
setImportProgress(100, doneStr); setImportProgress(100, doneStr);
if (m_closeOnFinish) { if (m_closeOnFinish) {
// Add small delay to allow user to visually confirm import finishing // Add small delay to allow user to visually confirm import finishing
QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::onClose); QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::doClose);
} }
} }
} }
void ItemLibraryAssetImportDialog::onClose() void ItemLibraryAssetImportDialog::onClose()
{
ui->importButton->setEnabled(false);
ui->acceptButton->setEnabled(false);
m_explicitClose = true;
doClose();
}
void ItemLibraryAssetImportDialog::doClose()
{ {
if (m_importer.isImporting()) { if (m_importer.isImporting()) {
addInfo(tr("Canceling import.")); addInfo(tr("Canceling import."));

View File

@@ -68,9 +68,11 @@ private:
void setImportProgress(int value, const QString &text); void setImportProgress(int value, const QString &text);
void onImportReadyForPreview(const QString &path, const QString &compName); void onImportReadyForPreview(const QString &path, const QString &compName);
void onRequestImageUpdate(); void onRequestImageUpdate();
void onRequestRotation(const QPointF &delta);
void onImportNearlyFinished(); void onImportNearlyFinished();
void onImportFinished(); void onImportFinished();
void onClose(); void onClose();
void doClose();
void onAccept(); void onAccept();
void toggleAdvanced(); void toggleAdvanced();
@@ -116,5 +118,6 @@ private:
OptionsData m_advancedData; OptionsData m_advancedData;
bool m_advancedMode = false; bool m_advancedMode = false;
int m_dialogHeight = 350; int m_dialogHeight = 350;
bool m_explicitClose = false;
}; };
} }

View File

@@ -462,17 +462,19 @@ QVector4D GeneralHelper::approachNode(
// a selection box for bound calculations to work. This is used to focus the view for // 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 // various preview image generations, where doing things asynchronously is not good
// and recalculating bounds for every frame is not a problem. // and recalculating bounds for every frame is not a problem.
QVector3D GeneralHelper::calculateNodeBoundsAndFocusCamera( void GeneralHelper::calculateBoundsAndFocusCamera(QQuick3DCamera *camera, QQuick3DNode *node,
QQuick3DCamera *camera, QQuick3DNode *node, QQuick3DViewport *viewPort, QQuick3DViewport *viewPort,
float defaultLookAtDistance, bool closeUp) float defaultLookAtDistance,
bool closeUp, QVector3D &lookAt,
QVector3D &extents)
{ {
QVector3D minBounds; QVector3D minBounds;
QVector3D maxBounds; QVector3D maxBounds;
getBounds(viewPort, node, minBounds, maxBounds); getBounds(viewPort, node, minBounds, maxBounds);
QVector3D extents = maxBounds - minBounds; extents = maxBounds - minBounds;
QVector3D lookAt = minBounds + (extents / 2.f); lookAt = minBounds + (extents / 2.f);
float maxExtent = qSqrt(qreal(extents.x()) * qreal(extents.x()) float maxExtent = qSqrt(qreal(extents.x()) * qreal(extents.x())
+ qreal(extents.y()) * qreal(extents.y()) + qreal(extents.y()) * qreal(extents.y())
+ qreal(extents.z()) * qreal(extents.z())); + qreal(extents.z()) * qreal(extents.z()));
@@ -506,8 +508,17 @@ QVector3D GeneralHelper::calculateNodeBoundsAndFocusCamera(
perspectiveCamera->setClipFar(maxDist * 1.01); perspectiveCamera->setClipFar(maxDist * 1.01);
} }
} }
}
return extents; void GeneralHelper::calculateNodeBoundsAndFocusCamera(QQuick3DCamera *camera, QQuick3DNode *node,
QQuick3DViewport *viewPort,
float defaultLookAtDistance,
bool closeUp)
{
QVector3D extents;
QVector3D lookAt;
calculateBoundsAndFocusCamera(camera, node, viewPort, defaultLookAtDistance, closeUp,
lookAt, extents);
} }
// Aligns any cameras found in nodes list to a camera. // Aligns any cameras found in nodes list to a camera.

View File

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

View File

@@ -258,6 +258,7 @@ protected:
void setRenderTimerInterval(int timerInterval); void setRenderTimerInterval(int timerInterval);
int renderTimerInterval() const; int renderTimerInterval() const;
void setSlowRenderTimerInterval(int timerInterval); void setSlowRenderTimerInterval(int timerInterval);
TimerMode timerMode() const { return m_timerMode; }
virtual void initializeView() = 0; virtual void initializeView() = 0;
virtual void initializeAuxiliaryViews(); virtual void initializeAuxiliaryViews();

View File

@@ -68,6 +68,19 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc
} }
break; break;
} }
case View3DActionType::Import3dRotatePreviewModel: {
QObject *obj = rootItem();
QQmlProperty sceneNodeProp(obj, "sceneNode", context());
auto sceneNode = sceneNodeProp.read().value<QQuick3DNode *>();
if (sceneNode) {
QPointF delta = command.value().toPointF();
m_generalHelper->orbitCamera(m_view3D->camera(), m_view3D->camera()->eulerRotation(),
m_lookAt, {}, {float(delta.x()), float(delta.y()), 0.f});
m_keepRendering = true;
startRenderTimer();
}
break;
}
default: default:
break; break;
} }
@@ -75,12 +88,10 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc
void Qt5Import3dNodeInstanceServer::startRenderTimer() void Qt5Import3dNodeInstanceServer::startRenderTimer()
{ {
if (timerId() != 0) if (m_keepRendering && timerMode() == TimerMode::NormalTimer)
killTimer(timerId()); return;
int timerId = startTimer(renderTimerInterval()); NodeInstanceServer::startRenderTimer();
setTimerId(timerId);
} }
void Qt5Import3dNodeInstanceServer::cleanup() void Qt5Import3dNodeInstanceServer::cleanup()
@@ -126,6 +137,9 @@ void Qt5Import3dNodeInstanceServer::render()
m_view3D = qobject_cast<QQuick3DViewport *>(viewObj); m_view3D = qobject_cast<QQuick3DViewport *>(viewObj);
if (m_view3D) { if (m_view3D) {
QQmlProperty sceneModelNameProp(obj, "sceneModelName", context()); QQmlProperty sceneModelNameProp(obj, "sceneModelName", context());
QQmlProperty sceneNodeProp(obj, "sceneNode", context());
auto sceneNode = sceneNodeProp.read().value<QQuick3DNode *>();
if (sceneNode) {
QString sceneModelName = sceneModelNameProp.read().toString(); QString sceneModelName = sceneModelNameProp.read().toString();
QFileInfo fi(fileUrl().toLocalFile()); QFileInfo fi(fileUrl().toLocalFile());
QString compPath = fi.absolutePath() + '/' + sceneModelName + ".qml"; QString compPath = fi.absolutePath() + '/' + sceneModelName + ".qml";
@@ -133,17 +147,18 @@ void Qt5Import3dNodeInstanceServer::render()
m_previewNode = qobject_cast<QQuick3DNode *>(comp.create(context())); m_previewNode = qobject_cast<QQuick3DNode *>(comp.create(context()));
if (m_previewNode) { if (m_previewNode) {
engine()->setObjectOwnership(m_previewNode, QJSEngine::CppOwnership); engine()->setObjectOwnership(m_previewNode, QJSEngine::CppOwnership);
m_previewNode->setParent(m_view3D); m_previewNode->setParentItem(sceneNode);
m_view3D->setImportScene(m_previewNode); m_previewNode->setParent(sceneNode);
}
} }
} }
} }
// Render scene once to ensure geometries are intialized so bounds calculations work correctly // Render scene once to ensure geometries are intialized so bounds calculations work correctly
if (m_renderCount == 2 && m_view3D) { if (m_renderCount == 2 && m_view3D) {
QVector3D extents = QVector3D extents;
m_generalHelper->calculateNodeBoundsAndFocusCamera(m_view3D->camera(), m_previewNode, m_generalHelper->calculateBoundsAndFocusCamera(m_view3D->camera(), m_previewNode,
m_view3D, 1040, false); m_view3D, 1050, false, m_lookAt, extents);
auto getExtentStr = [&extents](int idx) -> QString { auto getExtentStr = [&extents](int idx) -> QString {
int prec = 0; int prec = 0;
float val = extents[idx]; float val = extents[idx];
@@ -176,7 +191,11 @@ void Qt5Import3dNodeInstanceServer::render()
nodeInstanceClient()->handlePuppetToCreatorCommand( nodeInstanceClient()->handlePuppetToCreatorCommand(
{PuppetToCreatorCommand::Import3DPreviewImage, {PuppetToCreatorCommand::Import3DPreviewImage,
QVariant::fromValue(imgContainer)}); QVariant::fromValue(imgContainer)});
slowDownRenderTimer(); // No more renders needed for now
if (!m_keepRendering)
slowDownRenderTimer();
m_keepRendering = false;
} }
#else #else
slowDownRenderTimer(); slowDownRenderTimer();

View File

@@ -35,11 +35,13 @@ private:
void cleanup(); void cleanup();
int m_renderCount = 0; int m_renderCount = 0;
bool m_keepRendering = false;
#ifdef QUICK3D_MODULE #ifdef QUICK3D_MODULE
QQuick3DViewport *m_view3D = nullptr; QQuick3DViewport *m_view3D = nullptr;
Internal::GeneralHelper *m_generalHelper = nullptr; Internal::GeneralHelper *m_generalHelper = nullptr;
QQuick3DNode *m_previewNode = nullptr; QQuick3DNode *m_previewNode = nullptr;
QVector3D m_lookAt;
#endif #endif
}; };