QmlDesigner: Add camera alignment buttons

Add a button to 3D edit view that aligns the selected cameras to the
view camera. Add another button that aligns the view camera to a
selected camera.

Task-number: QDS-4482
Change-Id: Ibe6ceaf498db10f45c8c351e3a108419d8d7a59b
Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Jere Tuliniemi
2021-08-12 05:31:36 +03:00
committed by Miikka Heikkinen
parent f6a26fb13e
commit a03a50a262
19 changed files with 220 additions and 1 deletions

View File

@@ -40,6 +40,8 @@ public:
ScaleTool,
RotateTool,
FitToView,
AlignCamerasToView,
AlignViewToCamera,
SelectionModeToggle,
CameraToggle,
OrientationToggle,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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<QQuick3DCamera *> nodeList;
const QVariantList varNodes = nodes.value<QVariantList>();
for (const auto &varNode : varNodes) {
auto cameraNode = varNode.value<QQuick3DCamera *>();
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<QVariantList>();
QQuick3DCamera *cameraNode = nullptr;
for (const auto &varNode : varNodes) {
cameraNode = varNode.value<QQuick3DCamera *>();
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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -44,5 +44,9 @@
<file>images/particles_pause@2x.png</file>
<file>images/particles_restart.png</file>
<file>images/particles_restart@2x.png</file>
</qresource>
<file>images/align_camera_on.png</file>
<file>images/align_camera_on@2x.png</file>
<file>images/align_view_on.png</file>
<file>images/align_view_on@2x.png</file>
</qresource>
</RCC>

View File

@@ -30,6 +30,9 @@
#include <viewmanager.h>
#include <nodeinstanceview.h>
#include <qmldesignerplugin.h>
#include <nodemetainfo.h>
#include <utils/algorithm.h>
#include <QDebug>
@@ -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");
});
}
}

View File

@@ -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

View File

@@ -90,6 +90,16 @@ Edit3DWidget *Edit3DView::edit3DWidget() const
return m_edit3DWidget.data();
}
void Edit3DView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &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;

View File

@@ -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<ModelNode> &selectedNodeList, const QList<ModelNode> &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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

View File

@@ -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";

View File

@@ -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