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,
EditCameraStopAllMoves,
SetLastSceneEnvData,
Import3dUpdatePreviewImage
Import3dUpdatePreviewImage,
Import3dRotatePreviewModel
};
constexpr bool isNanotraceEnabled()

View File

@@ -5,6 +5,7 @@
#include <QImage>
#include <QLinearGradient>
#include <QMouseEvent>
#include <QPainter>
namespace QmlDesigner {
@@ -52,5 +53,30 @@ void Import3dCanvas::resizeEvent(QResizeEvent *)
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 <QImage>
#include <QPointer>
#include <QPointF>
#include <QWidget>
namespace QmlDesigner {
@@ -20,13 +20,18 @@ public:
signals:
void requestImageUpdate();
void requestRotation(const QPointF &delta);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
private:
QImage m_image;
QPointF m_dragPos;
};
} // namespace QmlDesigner

View File

@@ -221,6 +221,8 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
this, &ItemLibraryAssetImportDialog::updateUi);
connect(canvas(), &Import3dCanvas::requestImageUpdate,
this, &ItemLibraryAssetImportDialog::onRequestImageUpdate);
connect(canvas(), &Import3dCanvas::requestRotation,
this, &ItemLibraryAssetImportDialog::onRequestRotation);
connect(&m_importer, &ItemLibraryAssetImporter::errorReported,
this, &ItemLibraryAssetImportDialog::addError);
@@ -856,9 +858,10 @@ Rectangle {
width: %1
height: %2
property string sceneModelName: "%3"
property alias sceneNode: sceneNode
property alias view3d: view3d
property string extents
property string sceneModelName: "%3"
gradient: Gradient {
GradientStop { position: 1.0; color: "#222222" }
@@ -877,9 +880,9 @@ Rectangle {
PerspectiveCamera {
id: viewCamera
z: 600
y: 600
x: 600
y: 600
z: 600
eulerRotation.x: -45
eulerRotation.y: -45
clipFar: 100000
@@ -889,6 +892,10 @@ Rectangle {
DirectionalLight {
rotation: viewCamera.rotation
}
Node {
id: sceneNode
}
}
Text {
@@ -1041,7 +1048,8 @@ void ItemLibraryAssetImportDialog::onImportReadyForPreview(const QString &path,
ui->acceptButton->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()
@@ -1050,6 +1058,12 @@ void ItemLibraryAssetImportDialog::onRequestImageUpdate()
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()
{
// Canceling import is no longer doable
@@ -1063,18 +1077,28 @@ void ItemLibraryAssetImportDialog::onImportFinished()
QString interruptStr = tr("Import interrupted.");
addError(interruptStr);
setImportProgress(0, interruptStr);
if (m_explicitClose)
QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::doClose);
} else {
QString doneStr = tr("Import done.");
addInfo(doneStr);
setImportProgress(100, doneStr);
if (m_closeOnFinish) {
// 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()
{
ui->importButton->setEnabled(false);
ui->acceptButton->setEnabled(false);
m_explicitClose = true;
doClose();
}
void ItemLibraryAssetImportDialog::doClose()
{
if (m_importer.isImporting()) {
addInfo(tr("Canceling import."));

View File

@@ -68,9 +68,11 @@ private:
void setImportProgress(int value, const QString &text);
void onImportReadyForPreview(const QString &path, const QString &compName);
void onRequestImageUpdate();
void onRequestRotation(const QPointF &delta);
void onImportNearlyFinished();
void onImportFinished();
void onClose();
void doClose();
void onAccept();
void toggleAdvanced();
@@ -116,5 +118,6 @@ private:
OptionsData m_advancedData;
bool m_advancedMode = false;
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
// various preview image generations, where doing things asynchronously is not good
// and recalculating bounds for every frame is not a problem.
QVector3D GeneralHelper::calculateNodeBoundsAndFocusCamera(
QQuick3DCamera *camera, QQuick3DNode *node, QQuick3DViewport *viewPort,
float defaultLookAtDistance, bool closeUp)
void GeneralHelper::calculateBoundsAndFocusCamera(QQuick3DCamera *camera, QQuick3DNode *node,
QQuick3DViewport *viewPort,
float defaultLookAtDistance,
bool closeUp, QVector3D &lookAt,
QVector3D &extents)
{
QVector3D minBounds;
QVector3D maxBounds;
getBounds(viewPort, node, minBounds, maxBounds);
QVector3D extents = maxBounds - minBounds;
QVector3D lookAt = minBounds + (extents / 2.f);
extents = maxBounds - minBounds;
lookAt = minBounds + (extents / 2.f);
float maxExtent = qSqrt(qreal(extents.x()) * qreal(extents.x())
+ qreal(extents.y()) * qreal(extents.y())
+ qreal(extents.z()) * qreal(extents.z()));
@@ -506,8 +508,17 @@ QVector3D GeneralHelper::calculateNodeBoundsAndFocusCamera(
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.

View File

@@ -72,11 +72,12 @@ public:
bool closeUp = false);
Q_INVOKABLE QVector4D approachNode(QQuick3DCamera *camera, float defaultLookAtDistance,
QObject *node, QQuick3DViewport *viewPort);
Q_INVOKABLE QVector3D calculateNodeBoundsAndFocusCamera(QQuick3DCamera *camera,
QQuick3DNode *node,
void calculateBoundsAndFocusCamera(QQuick3DCamera *camera, QQuick3DNode *node,
QQuick3DViewport *viewPort, float defaultLookAtDistance,
bool closeUp, QVector3D &lookAt, QVector3D &extents);
Q_INVOKABLE void 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

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

View File

@@ -68,6 +68,19 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc
}
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:
break;
}
@@ -75,12 +88,10 @@ void Qt5Import3dNodeInstanceServer::view3DAction([[maybe_unused]] const View3DAc
void Qt5Import3dNodeInstanceServer::startRenderTimer()
{
if (timerId() != 0)
killTimer(timerId());
if (m_keepRendering && timerMode() == TimerMode::NormalTimer)
return;
int timerId = startTimer(renderTimerInterval());
setTimerId(timerId);
NodeInstanceServer::startRenderTimer();
}
void Qt5Import3dNodeInstanceServer::cleanup()
@@ -126,6 +137,9 @@ void Qt5Import3dNodeInstanceServer::render()
m_view3D = qobject_cast<QQuick3DViewport *>(viewObj);
if (m_view3D) {
QQmlProperty sceneModelNameProp(obj, "sceneModelName", context());
QQmlProperty sceneNodeProp(obj, "sceneNode", context());
auto sceneNode = sceneNodeProp.read().value<QQuick3DNode *>();
if (sceneNode) {
QString sceneModelName = sceneModelNameProp.read().toString();
QFileInfo fi(fileUrl().toLocalFile());
QString compPath = fi.absolutePath() + '/' + sceneModelName + ".qml";
@@ -133,17 +147,18 @@ void Qt5Import3dNodeInstanceServer::render()
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);
m_previewNode->setParentItem(sceneNode);
m_previewNode->setParent(sceneNode);
}
}
}
}
// 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);
QVector3D extents;
m_generalHelper->calculateBoundsAndFocusCamera(m_view3D->camera(), m_previewNode,
m_view3D, 1050, false, m_lookAt, extents);
auto getExtentStr = [&extents](int idx) -> QString {
int prec = 0;
float val = extents[idx];
@@ -176,7 +191,11 @@ void Qt5Import3dNodeInstanceServer::render()
nodeInstanceClient()->handlePuppetToCreatorCommand(
{PuppetToCreatorCommand::Import3DPreviewImage,
QVariant::fromValue(imgContainer)});
slowDownRenderTimer(); // No more renders needed for now
if (!m_keepRendering)
slowDownRenderTimer();
m_keepRendering = false;
}
#else
slowDownRenderTimer();

View File

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