diff --git a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h index c564bb2ec59..43bd32e1a20 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h @@ -40,6 +40,8 @@ public: ScaleTool, RotateTool, FitToView, + AlignCamerasToView, + AlignViewToCamera, SelectionModeToggle, CameraToggle, OrientationToggle, diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml index ca4a7c77c1c..ada7263df9d 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml @@ -110,6 +110,37 @@ Item { storeCameraState(0); } + function alignCameras(targetNodes) + { + if (!camera) + return; + + // targetNodes could be a list of nodes or a single node + var nodes = []; + if (targetNodes instanceof Node) + nodes.push(targetNodes); + else + nodes = targetNodes + + _generalHelper.alignCameras(camera, nodes); + } + + function alignView(targetNodes) + { + if (!camera) + return; + + // targetNodes could be a list of nodes or a single node + var nodes = []; + if (targetNodes instanceof Node) + nodes.push(targetNodes); + else + nodes = targetNodes + + _lookAtPoint = _generalHelper.alignView(camera, nodes, _lookAtPoint); + storeCameraState(0); + } + function zoomRelative(distance) { if (!camera) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml index 2aabb4d1bbf..592e4ab72eb 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml @@ -190,6 +190,22 @@ Item { } } + function alignCamerasToView() + { + if (editView) { + cameraControl.alignCameras(selectedNodes); + var propertyNames = ["position", "eulerRotation"]; + viewRoot.changeObjectProperty(selectedNodes, propertyNames); + viewRoot.commitObjectProperty(selectedNodes, propertyNames); + } + } + + function alignViewToCamera() + { + if (editView) + cameraControl.alignView(selectedNodes); + } + // If resetToDefault is true, tool states not specifically set to anything will be reset to // their default state. function updateToolStates(toolStates, resetToDefault) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml index f69c3e35dac..3a4d90ea8b6 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml @@ -110,6 +110,37 @@ Item { storeCameraState(0); } + function alignCameras(targetNodes) + { + if (!camera) + return; + + // targetNodes could be a list of nodes or a single node + var nodes = []; + if (targetNodes instanceof Node) + nodes.push(targetNodes); + else + nodes = targetNodes + + _generalHelper.alignCameras(camera, nodes); + } + + function alignView(targetNodes) + { + if (!camera) + return; + + // targetNodes could be a list of nodes or a single node + var nodes = []; + if (targetNodes instanceof Node) + nodes.push(targetNodes); + else + nodes = targetNodes + + _lookAtPoint = _generalHelper.alignView(camera, nodes, _lookAtPoint); + storeCameraState(0); + } + function zoomRelative(distance) { if (!camera) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml index c9abab7ab72..944c42b653b 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml @@ -181,6 +181,22 @@ Item { } } + function alignCamerasToView() + { + if (editView) { + cameraControl.alignCameras(selectedNodes); + var propertyNames = ["position", "eulerRotation"]; + viewRoot.changeObjectProperty(selectedNodes, propertyNames); + viewRoot.commitObjectProperty(selectedNodes, propertyNames); + } + } + + function alignViewToCamera() + { + if (editView) + cameraControl.alignView(selectedNodes); + } + // If resetToDefault is true, tool states not specifically set to anything will be reset to // their default state. function updateToolStates(toolStates, resetToDefault) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index 27302c374b7..f0131c80895 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -269,6 +269,53 @@ QVector4D GeneralHelper::focusNodesToCamera(QQuick3DCamera *camera, float defaul return QVector4D(lookAt, cameraZoomFactor); } +// Aligns any cameras found in nodes list to a camera. +// Only position and rotation are copied, rest of the camera properties stay the same. +void GeneralHelper::alignCameras(QQuick3DCamera *camera, const QVariant &nodes) +{ + QList nodeList; + const QVariantList varNodes = nodes.value(); + for (const auto &varNode : varNodes) { + auto cameraNode = varNode.value(); + if (cameraNode) + nodeList.append(cameraNode); + } + + for (QQuick3DCamera *node : qAsConst(nodeList)) { + node->setPosition(camera->position()); + node->setRotation(camera->rotation()); + } +} + +// Aligns the camera to the first camera in nodes list. +// Aligning means taking the position and XY rotation from the source camera. Rest of the properties +// remain the same, as this is used to align edit cameras, which have fixed Z-rot, fov, and clips. +// The new lookAt is set at same distance away as it was previously and scale isn't adjusted, so +// the zoom factor of the edit camera stays the same. +QVector3D GeneralHelper::alignView(QQuick3DCamera *camera, const QVariant &nodes, + const QVector3D &lookAtPoint) +{ + float lastDistance = (lookAtPoint - camera->position()).length(); + const QVariantList varNodes = nodes.value(); + QQuick3DCamera *cameraNode = nullptr; + for (const auto &varNode : varNodes) { + cameraNode = varNode.value(); + if (cameraNode) + break; + } + + if (cameraNode) { + camera->setPosition(cameraNode->position()); + QVector3D newRotation = cameraNode->eulerRotation(); + newRotation.setZ(0.f); + camera->setEulerRotation(newRotation); + } + + QVector3D lookAt = camera->position() + camera->forward() * lastDistance; + + return lookAt; +} + bool GeneralHelper::fuzzyCompare(double a, double b) { return qFuzzyCompare(a, b); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 4751bb3d316..06068601537 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -72,6 +72,9 @@ public: const QVariant &nodes, QQuick3DViewport *viewPort, float oldZoom, bool updateZoom = true, bool closeUp = false); + Q_INVOKABLE void alignCameras(QQuick3DCamera *camera, const QVariant &nodes); + Q_INVOKABLE QVector3D alignView(QQuick3DCamera *camera, const QVariant &nodes, + const QVector3D &lookAtPoint); Q_INVOKABLE bool fuzzyCompare(double a, double b); Q_INVOKABLE void delayedPropertySet(QObject *obj, int delay, const QString &property, const QVariant& value); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index a806db9bd36..d74bdacf5da 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -2090,6 +2090,12 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c case View3DActionCommand::FitToView: QMetaObject::invokeMethod(m_editView3DData.rootItem, "fitToView"); break; + case View3DActionCommand::AlignCamerasToView: + QMetaObject::invokeMethod(m_editView3DData.rootItem, "alignCamerasToView"); + break; + case View3DActionCommand::AlignViewToCamera: + QMetaObject::invokeMethod(m_editView3DData.rootItem, "alignViewToCamera"); + break; case View3DActionCommand::SelectionModeToggle: updatedState.insert("selectionMode", command.isEnabled() ? 1 : 0); break; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3d.qrc b/src/plugins/qmldesigner/components/edit3d/edit3d.qrc index 13ac3b0a499..bb1f6fd09c1 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3d.qrc +++ b/src/plugins/qmldesigner/components/edit3d/edit3d.qrc @@ -44,5 +44,9 @@ images/particles_pause@2x.png images/particles_restart.png images/particles_restart@2x.png - + images/align_camera_on.png + images/align_camera_on@2x.png + images/align_view_on.png + images/align_view_on@2x.png + diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp index ce1e40a9365..e7838e720e8 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp @@ -30,6 +30,9 @@ #include #include #include +#include + +#include #include @@ -94,5 +97,21 @@ bool Edit3DAction::isEnabled(const SelectionContext &selectionContext) const return isVisible(selectionContext); } +Edit3DCameraAction::Edit3DCameraAction(const QByteArray &menuId, View3DActionCommand::Type type, + const QString &description, const QKeySequence &key, + bool checkable, bool checked, const QIcon &iconOff, + const QIcon &iconOn, + SelectionContextOperation selectionAction) + : Edit3DAction(menuId, type, description, key, checkable, checked, iconOff, iconOn, selectionAction) +{ +} + +bool Edit3DCameraAction::isEnabled(const SelectionContext &selectionContext) const +{ + return Utils::anyOf(selectionContext.selectedModelNodes(), [](const ModelNode &node) { + return node.isValid() && node.metaInfo().isSubclassOf("QQuick3D.Camera"); + }); +} + } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h index 80a6ed8c814..27b8fb7342d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h @@ -81,4 +81,15 @@ private: QByteArray m_menuId; }; +class Edit3DCameraAction : public Edit3DAction +{ +public: + Edit3DCameraAction(const QByteArray &menuId, View3DActionCommand::Type type, + const QString &description, const QKeySequence &key, bool checkable, bool checked, + const QIcon &iconOff, const QIcon &iconOn, + SelectionContextOperation selectionAction = nullptr); +protected: + bool isEnabled(const SelectionContext &selectionContext) const override; +}; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index ba5ac595fbd..8070cacac02 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -90,6 +90,16 @@ Edit3DWidget *Edit3DView::edit3DWidget() const return m_edit3DWidget.data(); } +void Edit3DView::selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) +{ + SelectionContext selectionContext(this); + selectionContext.setUpdateMode(SelectionContext::UpdateMode::Fast); + if (m_alignCamerasAction) + m_alignCamerasAction->currentContextChanged(selectionContext); + if (m_alignViewAction) + m_alignViewAction->currentContextChanged(selectionContext); +} + void Edit3DView::renderImage3DChanged(const QImage &img) { edit3DWidget()->canvas()->updateRenderImage(img); @@ -256,6 +266,16 @@ void Edit3DView::createEdit3DActions() QCoreApplication::translate("FitToViewAction", "Fit Selected Object to View"), QKeySequence(Qt::Key_F), false, false, Icons::EDIT3D_FIT_SELECTED_OFF.icon(), {}); + m_alignCamerasAction = new Edit3DCameraAction( + QmlDesigner::Constants::EDIT3D_ALIGN_CAMERAS, View3DActionCommand::AlignCamerasToView, + QCoreApplication::translate("AlignCamerasToViewAction", "Align Selected Cameras to View"), + QKeySequence(), false, false, Icons::EDIT3D_ALIGN_CAMERA_ON.icon(), {}); + + m_alignViewAction = new Edit3DCameraAction( + QmlDesigner::Constants::EDIT3D_ALIGN_VIEW, View3DActionCommand::AlignViewToCamera, + QCoreApplication::translate("AlignCamerasToViewAction", "Align View to Selected Camera"), + QKeySequence(), false, false, Icons::EDIT3D_ALIGN_VIEW_ON.icon(), {}); + m_cameraModeAction = new Edit3DAction( QmlDesigner::Constants::EDIT3D_EDIT_CAMERA, View3DActionCommand::CameraToggle, @@ -351,6 +371,9 @@ void Edit3DView::createEdit3DActions() m_leftActions << m_orientationModeAction; m_leftActions << m_editLightAction; m_leftActions << m_showGridAction; + m_leftActions << nullptr; + m_leftActions << m_alignCamerasAction; + m_leftActions << m_alignViewAction; m_rightActions << m_particleViewModeAction; m_rightActions << m_particlesPlayAction; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 2001dbbcf4d..a61f78301c0 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -41,6 +41,7 @@ namespace QmlDesigner { class Edit3DWidget; class Edit3DAction; +class Edit3DCameraAction; class QMLDESIGNERCORE_EXPORT Edit3DView : public AbstractView { @@ -54,6 +55,7 @@ public: Edit3DWidget *edit3DWidget() const; + void selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) override; void renderImage3DChanged(const QImage &img) override; void updateActiveScene3D(const QVariantMap &sceneState) override; void modelAttached(Model *model) override; @@ -87,6 +89,8 @@ private: Edit3DAction *m_rotateToolAction = nullptr; Edit3DAction *m_scaleToolAction = nullptr; Edit3DAction *m_fitAction = nullptr; + Edit3DCameraAction *m_alignCamerasAction = nullptr; + Edit3DCameraAction *m_alignViewAction = nullptr; Edit3DAction *m_cameraModeAction = nullptr; Edit3DAction *m_orientationModeAction = nullptr; Edit3DAction *m_editLightAction = nullptr; diff --git a/src/plugins/qmldesigner/components/edit3d/images/align_camera_on.png b/src/plugins/qmldesigner/components/edit3d/images/align_camera_on.png new file mode 100644 index 00000000000..fef79f2b6a0 Binary files /dev/null and b/src/plugins/qmldesigner/components/edit3d/images/align_camera_on.png differ diff --git a/src/plugins/qmldesigner/components/edit3d/images/align_camera_on@2x.png b/src/plugins/qmldesigner/components/edit3d/images/align_camera_on@2x.png new file mode 100644 index 00000000000..6743b8dacba Binary files /dev/null and b/src/plugins/qmldesigner/components/edit3d/images/align_camera_on@2x.png differ diff --git a/src/plugins/qmldesigner/components/edit3d/images/align_view_on.png b/src/plugins/qmldesigner/components/edit3d/images/align_view_on.png new file mode 100644 index 00000000000..3617416f6f9 Binary files /dev/null and b/src/plugins/qmldesigner/components/edit3d/images/align_view_on.png differ diff --git a/src/plugins/qmldesigner/components/edit3d/images/align_view_on@2x.png b/src/plugins/qmldesigner/components/edit3d/images/align_view_on@2x.png new file mode 100644 index 00000000000..025b3524844 Binary files /dev/null and b/src/plugins/qmldesigner/components/edit3d/images/align_view_on@2x.png differ diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 2ed1daeb883..53edd711d5c 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -59,6 +59,8 @@ const char EDIT3D_MOVE_TOOL[] = "QmlDesigner.Editor3D.MoveTool"; const char EDIT3D_ROTATE_TOOL[] = "QmlDesigner.Editor3D.RotateTool"; const char EDIT3D_SCALE_TOOL[] = "QmlDesigner.Editor3D.ScaleTool"; const char EDIT3D_FIT_SELECTED[] = "QmlDesigner.Editor3D.FitSelected"; +const char EDIT3D_ALIGN_CAMERAS[] = "QmlDesigner.Editor3D.AlignCameras"; +const char EDIT3D_ALIGN_VIEW[] = "QmlDesigner.Editor3D.AlignView"; const char EDIT3D_EDIT_CAMERA[] = "QmlDesigner.Editor3D.EditCameraToggle"; const char EDIT3D_ORIENTATION[] = "QmlDesigner.Editor3D.OrientationToggle"; const char EDIT3D_EDIT_LIGHT[] = "QmlDesigner.Editor3D.EditLightToggle"; diff --git a/src/plugins/qmldesigner/qmldesignericons.h b/src/plugins/qmldesigner/qmldesignericons.h index 50e4ea1dd03..3323ceb49a9 100644 --- a/src/plugins/qmldesigner/qmldesignericons.h +++ b/src/plugins/qmldesigner/qmldesignericons.h @@ -91,6 +91,10 @@ const Utils::Icon EDIT3D_ORIENTATION_ON({ {":/edit3d/images/global.png", Utils::Theme::QmlDesigner_HighlightColor}}); const Utils::Icon EDIT3D_ORIENTATION_OFF({ {":/edit3d/images/local.png", Utils::Theme::IconsBaseColor}}); +const Utils::Icon EDIT3D_ALIGN_CAMERA_ON({ + {":/edit3d/images/align_camera_on.png", Utils::Theme::IconsBaseColor}}); +const Utils::Icon EDIT3D_ALIGN_VIEW_ON({ + {":/edit3d/images/align_view_on.png", Utils::Theme::IconsBaseColor}}); } // Icons } // QmlDesigner