QmlDesigner: Block changing eulerRotation for 3D Node that has rotation

QtQuick3D doesn't support both rotation and eulerRotation set on same
node, so we delete existing quaternion rotation if user sets an euler
rotation to the node. If node has quaternion rotation animation,
we block any change to eulerRotation in the node.

3D editor also disables the rotation gizmo for blocked nodes.

Task-number: QDS-4335
Change-Id: I4ce7b022c732a9547751f101548ecea948279db8
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2021-05-12 14:15:47 +03:00
parent a3c51cdd4a
commit 125cdcc3a0
19 changed files with 360 additions and 52 deletions

View File

@@ -40,16 +40,28 @@ Node {
property real currentAngle property real currentAngle
property point currentMousePos property point currentMousePos
property alias freeDraggerArea: mouseAreaFree property alias freeDraggerArea: mouseAreaFree
property bool blocked: false
position: dragHelper.pivotScenePosition(targetNode) position: dragHelper.pivotScenePosition(targetNode)
onTargetNodeChanged: position = dragHelper.pivotScenePosition(targetNode) onTargetNodeChanged: {
position = dragHelper.pivotScenePosition(targetNode);
blocked = _generalHelper.isRotationBlocked(targetNode);
}
Connections { Connections {
target: rotateGizmo.targetNode target: rotateGizmo.targetNode
function onSceneTransformChanged() function onSceneTransformChanged()
{ {
rotateGizmo.position = rotateGizmo.dragHelper.pivotScenePosition(rotateGizmo.targetNode); rotateGizmo.position = dragHelper.pivotScenePosition(targetNode);
}
}
Connections {
target: _generalHelper
function onRotationBlocksChanged()
{
blocked = _generalHelper.isRotationBlocked(targetNode);
} }
} }
@@ -82,11 +94,12 @@ Node {
objectName: "Rotate Ring X" objectName: "Rotate Ring X"
eulerRotation: Qt.vector3d(0, 90, 0) eulerRotation: Qt.vector3d(0, 90, 0)
targetNode: rotateGizmo.targetNode targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1)
: Qt.rgba(1, 0, 0, 1) : highlightOnHover && (hovering || dragging)
? Qt.lighter(Qt.rgba(1, 0, 0, 1)) : Qt.rgba(1, 0, 0, 1)
priority: 40 priority: 40
view3D: rotateGizmo.view3D view3D: rotateGizmo.view3D
active: rotateGizmo.visible active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper dragHelper: rotateGizmo.dragHelper
onRotateCommit: rotateGizmo.rotateCommit() onRotateCommit: rotateGizmo.rotateCommit()
@@ -100,13 +113,14 @@ Node {
objectName: "Rotate Ring Y" objectName: "Rotate Ring Y"
eulerRotation: Qt.vector3d(90, 0, 0) eulerRotation: Qt.vector3d(90, 0, 0)
targetNode: rotateGizmo.targetNode targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1)
: Qt.rgba(0, 0.6, 0, 1) : highlightOnHover && (hovering || dragging)
? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) : Qt.rgba(0, 0.6, 0, 1)
// Just a smidge smaller than higher priority rings so that it doesn't obscure them // Just a smidge smaller than higher priority rings so that it doesn't obscure them
scale: Qt.vector3d(0.998, 0.998, 0.998) scale: Qt.vector3d(0.998, 0.998, 0.998)
priority: 30 priority: 30
view3D: rotateGizmo.view3D view3D: rotateGizmo.view3D
active: rotateGizmo.visible active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper dragHelper: rotateGizmo.dragHelper
onRotateCommit: rotateGizmo.rotateCommit() onRotateCommit: rotateGizmo.rotateCommit()
@@ -120,13 +134,14 @@ Node {
objectName: "Rotate Ring Z" objectName: "Rotate Ring Z"
eulerRotation: Qt.vector3d(0, 0, 0) eulerRotation: Qt.vector3d(0, 0, 0)
targetNode: rotateGizmo.targetNode targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1)
: Qt.rgba(0, 0, 1, 1) : highlightOnHover && (hovering || dragging)
? Qt.lighter(Qt.rgba(0, 0, 1, 1)) : Qt.rgba(0, 0, 1, 1)
// Just a smidge smaller than higher priority rings so that it doesn't obscure them // Just a smidge smaller than higher priority rings so that it doesn't obscure them
scale: Qt.vector3d(0.996, 0.996, 0.996) scale: Qt.vector3d(0.996, 0.996, 0.996)
priority: 20 priority: 20
view3D: rotateGizmo.view3D view3D: rotateGizmo.view3D
active: rotateGizmo.visible active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper dragHelper: rotateGizmo.dragHelper
onRotateCommit: rotateGizmo.rotateCommit() onRotateCommit: rotateGizmo.rotateCommit()
@@ -154,12 +169,14 @@ Node {
objectName: "cameraRing" objectName: "cameraRing"
rotation: rotateGizmo.view3D.camera.rotation rotation: rotateGizmo.view3D.camera.rotation
targetNode: rotateGizmo.targetNode targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1)) color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1)
: highlightOnHover && (hovering || dragging)
? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1))
: Qt.rgba(0.5, 0.5, 0.5, 1) : Qt.rgba(0.5, 0.5, 0.5, 1)
scale: Qt.vector3d(1.1, 1.1, 1.1) scale: Qt.vector3d(1.1, 1.1, 1.1)
priority: 10 priority: 10
view3D: rotateGizmo.view3D view3D: rotateGizmo.view3D
active: rotateGizmo.visible active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper dragHelper: rotateGizmo.dragHelper
visible: !rotRingX.dragging && !rotRingY.dragging && !rotRingZ.dragging && !freeRotator.dragging visible: !rotRingX.dragging && !rotRingY.dragging && !rotRingZ.dragging && !freeRotator.dragging
@@ -176,7 +193,7 @@ Node {
materials: DefaultMaterial { materials: DefaultMaterial {
id: material id: material
diffuseColor: "black" diffuseColor: "black"
opacity: mouseAreaFree.hovering ? 0.15 : 0 opacity: mouseAreaFree.hovering && !rotateGizmo.blocked ? 0.15 : 0
lighting: DefaultMaterial.NoLighting lighting: DefaultMaterial.NoLighting
} }
scale: Qt.vector3d(0.15, 0.15, 0.15) scale: Qt.vector3d(0.15, 0.15, 0.15)
@@ -200,6 +217,14 @@ Node {
_startRotation = Qt.vector3d(rotateGizmo.targetNode.eulerRotation.x, _startRotation = Qt.vector3d(rotateGizmo.targetNode.eulerRotation.x,
rotateGizmo.targetNode.eulerRotation.y, rotateGizmo.targetNode.eulerRotation.y,
rotateGizmo.targetNode.eulerRotation.z); rotateGizmo.targetNode.eulerRotation.z);
// Ensure we never set NaN values for rotation, even if target node originally has them
if (isNaN(_startRotation.x))
_startRotation.x = 0;
if (isNaN(_startRotation.y))
_startRotation.y = 0;
if (isNaN(_startRotation.z))
_startRotation.z = 0;
dragging = true; dragging = true;
} }
@@ -239,7 +264,7 @@ Node {
height: 100 height: 100
circlePickArea: Qt.point(25, 50) circlePickArea: Qt.point(25, 50)
grabsMouse: rotateGizmo.targetNode grabsMouse: rotateGizmo.targetNode
active: rotateGizmo.visible active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper dragHelper: rotateGizmo.dragHelper
onPressed: freeRotator.handlePressed(screenPos) onPressed: freeRotator.handlePressed(screenPos)

View File

@@ -90,6 +90,13 @@ Model {
_startRotation = Qt.vector3d(targetNode.eulerRotation.x, _startRotation = Qt.vector3d(targetNode.eulerRotation.x,
targetNode.eulerRotation.y, targetNode.eulerRotation.y,
targetNode.eulerRotation.z); targetNode.eulerRotation.z);
// Ensure we never set NaN values for rotation, even if target node originally has them
if (isNaN(_startRotation.x))
_startRotation.x = 0;
if (isNaN(_startRotation.y))
_startRotation.y = 0;
if (isNaN(_startRotation.z))
_startRotation.z = 0;
currentAngle = 0; currentAngle = 0;
currentMousePos = screenPos; currentMousePos = screenPos;
} }

View File

@@ -356,6 +356,24 @@ bool GeneralHelper::isMacOS() const
#endif #endif
} }
void GeneralHelper::addRotationBlocks(const QSet<QQuick3DNode *> &nodes)
{
m_rotationBlockedNodes.unite(nodes);
emit rotationBlocksChanged();
}
void GeneralHelper::removeRotationBlocks(const QSet<QQuick3DNode *> &nodes)
{
for (auto node : nodes)
m_rotationBlockedNodes.remove(node);
emit rotationBlocksChanged();
}
bool GeneralHelper::isRotationBlocked(QQuick3DNode *node) const
{
return m_rotationBlockedNodes.contains(node);
}
bool GeneralHelper::eventFilter(QObject *obj, QEvent *event) bool GeneralHelper::eventFilter(QObject *obj, QEvent *event)
{ {
if (event->type() == QEvent::DynamicPropertyChange) { if (event->type() == QEvent::DynamicPropertyChange) {

View File

@@ -93,11 +93,16 @@ public:
bool isMacOS() const; bool isMacOS() const;
void addRotationBlocks(const QSet<QQuick3DNode *> &nodes);
void removeRotationBlocks(const QSet<QQuick3DNode *> &nodes);
Q_INVOKABLE bool isRotationBlocked(QQuick3DNode *node) const;
signals: signals:
void overlayUpdateNeeded(); void overlayUpdateNeeded();
void toolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState); void toolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState);
void hiddenStateChanged(QQuick3DNode *node); void hiddenStateChanged(QQuick3DNode *node);
void lockedStateChanged(QQuick3DNode *node); void lockedStateChanged(QQuick3DNode *node);
void rotationBlocksChanged();
protected: protected:
bool eventFilter(QObject *obj, QEvent *event) final; bool eventFilter(QObject *obj, QEvent *event) final;
@@ -110,6 +115,7 @@ private:
QHash<QString, QVariantMap> m_toolStates; QHash<QString, QVariantMap> m_toolStates;
QHash<QString, QVariantMap> m_toolStatesPending; QHash<QString, QVariantMap> m_toolStatesPending;
QSet<QQuick3DNode *> m_gizmoTargets; QSet<QQuick3DNode *> m_gizmoTargets;
QSet<QQuick3DNode *> m_rotationBlockedNodes;
}; };
} }

View File

@@ -63,6 +63,7 @@
#include "inputeventcommand.h" #include "inputeventcommand.h"
#include "view3dactioncommand.h" #include "view3dactioncommand.h"
#include "requestmodelnodepreviewimagecommand.h" #include "requestmodelnodepreviewimagecommand.h"
#include "changeauxiliarycommand.h"
#include "dummycontextobject.h" #include "dummycontextobject.h"
#include "../editor3d/generalhelper.h" #include "../editor3d/generalhelper.h"
@@ -288,6 +289,57 @@ void Qt5InformationNodeInstanceServer::resolveImportSupport()
#endif #endif
} }
void Qt5InformationNodeInstanceServer::updateRotationBlocks(const QVector<PropertyValueContainer> &valueChanges)
{
#ifdef QUICK3D_MODULE
auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
if (helper) {
QSet<QQuick3DNode *> blockedNodes;
QSet<QQuick3DNode *> unblockedNodes;
const PropertyName propName = "rotBlocked@internal";
for (const auto &container : valueChanges) {
if (container.name() == propName) {
ServerNodeInstance instance = instanceForId(container.instanceId());
if (instance.isValid()) {
auto node = qobject_cast<QQuick3DNode *>(instance.internalObject());
if (node) {
if (container.value().toBool())
blockedNodes.insert(node);
else
unblockedNodes.insert(node);
}
}
}
}
helper->addRotationBlocks(blockedNodes);
helper->removeRotationBlocks(unblockedNodes);
}
#else
Q_UNUSED(valueChanges)
#endif
}
void Qt5InformationNodeInstanceServer::removeRotationBlocks(const QVector<qint32> &instanceIds)
{
#ifdef QUICK3D_MODULE
auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
if (helper) {
QSet<QQuick3DNode *> unblockedNodes;
for (const auto &id : instanceIds) {
ServerNodeInstance instance = instanceForId(id);
if (instance.isValid()) {
auto node = qobject_cast<QQuick3DNode *>(instance.internalObject());
if (node)
unblockedNodes.insert(node);
}
}
helper->removeRotationBlocks(unblockedNodes);
}
#else
Q_UNUSED(instanceIds)
#endif
}
void Qt5InformationNodeInstanceServer::createEditView3D() void Qt5InformationNodeInstanceServer::createEditView3D()
{ {
#ifdef QUICK3D_MODULE #ifdef QUICK3D_MODULE
@@ -1492,8 +1544,10 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com
sendChildrenChangedCommand(instanceList); sendChildrenChangedCommand(instanceList);
nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList)); nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList));
if (isQuick3DMode()) if (isQuick3DMode()) {
setup3DEditView(instanceList, command.edit3dToolStates); setup3DEditView(instanceList, command.edit3dToolStates);
updateRotationBlocks(command.auxiliaryChanges);
}
QObject::connect(&m_renderModelNodeImageViewTimer, &QTimer::timeout, QObject::connect(&m_renderModelNodeImageViewTimer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::doRenderModelNodeImageView); this, &Qt5InformationNodeInstanceServer::doRenderModelNodeImageView);
@@ -1656,6 +1710,8 @@ void Qt5InformationNodeInstanceServer::removeInstances(const RemoveInstancesComm
{ {
int nodeCount = m_3DSceneMap.size(); int nodeCount = m_3DSceneMap.size();
removeRotationBlocks(command.instanceIds());
Qt5NodeInstanceServer::removeInstances(command); Qt5NodeInstanceServer::removeInstances(command);
if (nodeCount != m_3DSceneMap.size()) { if (nodeCount != m_3DSceneMap.size()) {
@@ -1741,6 +1797,7 @@ void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const Reques
void Qt5InformationNodeInstanceServer::changeAuxiliaryValues(const ChangeAuxiliaryCommand &command) void Qt5InformationNodeInstanceServer::changeAuxiliaryValues(const ChangeAuxiliaryCommand &command)
{ {
updateRotationBlocks(command.auxiliaryChanges);
Qt5NodeInstanceServer::changeAuxiliaryValues(command); Qt5NodeInstanceServer::changeAuxiliaryValues(command);
render3DEditView(); render3DEditView();
} }

View File

@@ -30,6 +30,8 @@
#include "valueschangedcommand.h" #include "valueschangedcommand.h"
#include "changeselectioncommand.h" #include "changeselectioncommand.h"
#include "requestmodelnodepreviewimagecommand.h" #include "requestmodelnodepreviewimagecommand.h"
#include "propertybindingcontainer.h"
#include "propertyabstractcontainer.h"
#include <QTimer> #include <QTimer>
#include <QVariant> #include <QVariant>
@@ -132,6 +134,8 @@ private:
void updateLockedAndHiddenStates(const QSet<ServerNodeInstance> &instances); void updateLockedAndHiddenStates(const QSet<ServerNodeInstance> &instances);
void handleInputEvents(); void handleInputEvents();
void resolveImportSupport(); void resolveImportSupport();
void updateRotationBlocks(const QVector<PropertyValueContainer> &valueChanges);
void removeRotationBlocks(const QVector<qint32> &instanceIds);
void createAuxiliaryQuickView(const QUrl &url, RenderViewData &viewData); void createAuxiliaryQuickView(const QUrl &url, RenderViewData &viewData);

View File

@@ -105,6 +105,13 @@ Item {
extendedFunctionButton.menuVisible = false extendedFunctionButton.menuVisible = false
} }
Connections {
target: modelNodeBackend
onSelectionChanged: {
menu.close()
}
}
StudioControls.MenuItem { StudioControls.MenuItem {
text: qsTr("Reset") text: qsTr("Reset")
onTriggered: { onTriggered: {

View File

@@ -50,6 +50,17 @@ Item {
transaction.end(); transaction.end();
} }
Component.onCompleted: {
spinBox.enabled = !isBlocked(backendValue.name);
}
Connections {
target: modelNodeBackend
function onSelectionChanged() {
spinBox.enabled = !isBlocked(backendValue.name);
}
}
StudioControls.RealSpinBox { StudioControls.RealSpinBox {
id: spinBox id: spinBox

View File

@@ -303,6 +303,9 @@ void PropertyEditorContextObject::insertKeyframe(const QString &propertyName)
{ {
QTC_ASSERT(m_model && m_model->rewriterView(), return); QTC_ASSERT(m_model && m_model->rewriterView(), return);
if (isBlocked(propertyName))
return;
/* Ideally we should not missuse the rewriterView /* Ideally we should not missuse the rewriterView
* If we add more code here we have to forward the property editor view */ * If we add more code here we have to forward the property editor view */
RewriterView *rewriterView = m_model->rewriterView(); RewriterView *rewriterView = m_model->rewriterView();
@@ -557,6 +560,20 @@ QStringList PropertyEditorContextObject::allStatesForId(const QString &id)
return {}; return {};
} }
bool PropertyEditorContextObject::isBlocked(const QString &propName) const
{
if (m_model && m_model->rewriterView()) {
const QList<ModelNode> nodes = m_model->rewriterView()->selectedModelNodes();
QScopedPointer<QmlObjectNode> objNode;
for (const auto &node : nodes) {
objNode.reset(QmlObjectNode::getQmlObjectNodeOfCorrectType(node));
if (objNode->isBlocked(propName.toUtf8()))
return true;
}
}
return false;
}
void EasingCurveEditor::registerDeclarativeType() void EasingCurveEditor::registerDeclarativeType()
{ {
qmlRegisterType<EasingCurveEditor>("HelperWidgets", 2, 0, "EasingCurveEditor"); qmlRegisterType<EasingCurveEditor>("HelperWidgets", 2, 0, "EasingCurveEditor");

View File

@@ -99,6 +99,8 @@ public:
Q_INVOKABLE QStringList allStatesForId(const QString &id); Q_INVOKABLE QStringList allStatesForId(const QString &id);
Q_INVOKABLE bool isBlocked(const QString &propName) const;
int majorVersion() const; int majorVersion() const;
int majorQtQuickVersion() const; int majorQtQuickVersion() const;
int minorQtQuickVersion() const; int minorQtQuickVersion() const;

View File

@@ -39,6 +39,7 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QUrl> #include <QUrl>
#include <QScopedPointer>
//using namespace QmlDesigner; //using namespace QmlDesigner;
@@ -610,14 +611,15 @@ void PropertyEditorNodeWrapper::changeValue(const QString &propertyName)
if (name.isNull()) if (name.isNull())
return; return;
if (m_modelNode.isValid()) { if (m_modelNode.isValid()) {
QmlDesigner::QmlObjectNode qmlObjectNode(m_modelNode); QScopedPointer<QmlDesigner::QmlObjectNode> qmlObjectNode{
QmlDesigner::QmlObjectNode::getQmlObjectNodeOfCorrectType(m_modelNode)};
auto valueObject = qvariant_cast<PropertyEditorValue *>(m_valuesPropertyMap.value(QString::fromLatin1(name))); auto valueObject = qvariant_cast<PropertyEditorValue *>(m_valuesPropertyMap.value(QString::fromLatin1(name)));
if (valueObject->value().isValid()) if (valueObject->value().isValid())
qmlObjectNode.setVariantProperty(name, valueObject->value()); qmlObjectNode->setVariantProperty(name, valueObject->value());
else else
qmlObjectNode.removeProperty(name); qmlObjectNode->removeProperty(name);
} }
} }

View File

@@ -56,6 +56,7 @@
#include <QTimer> #include <QTimer>
#include <QShortcut> #include <QShortcut>
#include <QApplication> #include <QApplication>
#include <QScopedPointer>
enum { enum {
debug = false debug = false
@@ -251,7 +252,7 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
PropertyName underscoreName(name); PropertyName underscoreName(name);
underscoreName.replace('.', '_'); underscoreName.replace('.', '_');
QmlObjectNode qmlObjectNode(m_selectedNode); QScopedPointer<QmlObjectNode> qmlObjectNode {QmlObjectNode::getQmlObjectNodeOfCorrectType(m_selectedNode)};
PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(QString::fromLatin1(underscoreName)); PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(QString::fromLatin1(underscoreName));
if (!value) { if (!value) {
@@ -259,33 +260,33 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
return; return;
} }
if (qmlObjectNode.modelNode().metaInfo().isValid() && qmlObjectNode.modelNode().metaInfo().hasProperty(name)) { if (qmlObjectNode->modelNode().metaInfo().isValid() && qmlObjectNode->modelNode().metaInfo().hasProperty(name)) {
if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "QColor") { if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "QColor") {
if (QColor(value->expression().remove('"')).isValid()) { if (QColor(value->expression().remove('"')).isValid()) {
qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"'))); qmlObjectNode->setVariantProperty(name, QColor(value->expression().remove('"')));
return; return;
} }
} else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "bool") { } else if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "bool") {
if (value->expression().compare(QLatin1String("false"), Qt::CaseInsensitive) == 0 if (value->expression().compare(QLatin1String("false"), Qt::CaseInsensitive) == 0
|| value->expression().compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) { || value->expression().compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) {
if (value->expression().compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) if (value->expression().compare(QLatin1String("true"), Qt::CaseInsensitive) == 0)
qmlObjectNode.setVariantProperty(name, true); qmlObjectNode->setVariantProperty(name, true);
else else
qmlObjectNode.setVariantProperty(name, false); qmlObjectNode->setVariantProperty(name, false);
return; return;
} }
} else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "int") { } else if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "int") {
bool ok; bool ok;
int intValue = value->expression().toInt(&ok); int intValue = value->expression().toInt(&ok);
if (ok) { if (ok) {
qmlObjectNode.setVariantProperty(name, intValue); qmlObjectNode->setVariantProperty(name, intValue);
return; return;
} }
} else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "qreal") { } else if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "qreal") {
bool ok; bool ok;
qreal realValue = value->expression().toDouble(&ok); qreal realValue = value->expression().toDouble(&ok);
if (ok) { if (ok) {
qmlObjectNode.setVariantProperty(name, realValue); qmlObjectNode->setVariantProperty(name, realValue);
return; return;
} }
} }
@@ -296,8 +297,8 @@ void PropertyEditorView::changeExpression(const QString &propertyName)
return; return;
} }
if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name)) if (qmlObjectNode->expression(name) != value->expression() || !qmlObjectNode->propertyAffectedByCurrentState(name))
qmlObjectNode.setBindingProperty(name, value->expression()); qmlObjectNode->setBindingProperty(name, value->expression());
}); /* end of transaction */ }); /* end of transaction */
} }
@@ -465,11 +466,13 @@ void PropertyEditorView::setupQmlBackend()
m_stackedWidget->addWidget(currentQmlBackend->widget()); m_stackedWidget->addWidget(currentQmlBackend->widget());
m_qmlBackendHash.insert(qmlFile.toString(), currentQmlBackend); m_qmlBackendHash.insert(qmlFile.toString(), currentQmlBackend);
QmlObjectNode qmlObjectNode; QScopedPointer<QmlObjectNode> qmlObjectNode;
if (m_selectedNode.isValid()) { if (m_selectedNode.isValid()) {
qmlObjectNode = QmlObjectNode(m_selectedNode); qmlObjectNode.reset(QmlObjectNode::getQmlObjectNodeOfCorrectType(m_selectedNode));
Q_ASSERT(qmlObjectNode.isValid()); Q_ASSERT(qmlObjectNode->isValid());
currentQmlBackend->setup(qmlObjectNode, currentStateName, qmlSpecificsFile, this); currentQmlBackend->setup(*qmlObjectNode, currentStateName, qmlSpecificsFile, this);
} else {
qmlObjectNode.reset(new QmlObjectNode);
} }
currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(false)); currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(false));
if (specificQmlData.isEmpty()) if (specificQmlData.isEmpty())
@@ -480,14 +483,16 @@ void PropertyEditorView::setupQmlBackend()
currentQmlBackend->setSource(qmlFile); currentQmlBackend->setSource(qmlFile);
currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(true)); currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(true));
} else { } else {
QmlObjectNode qmlObjectNode; QScopedPointer<QmlObjectNode> qmlObjectNode;
if (m_selectedNode.isValid()) if (m_selectedNode.isValid())
qmlObjectNode = QmlObjectNode(m_selectedNode); qmlObjectNode.reset(QmlObjectNode::getQmlObjectNodeOfCorrectType(m_selectedNode));
else
qmlObjectNode.reset(new QmlObjectNode);
currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(false)); currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(false));
if (specificQmlData.isEmpty()) if (specificQmlData.isEmpty())
currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData);
currentQmlBackend->setup(qmlObjectNode, currentStateName, qmlSpecificsFile, this); currentQmlBackend->setup(*qmlObjectNode, currentStateName, qmlSpecificsFile, this);
currentQmlBackend->contextObject()->setGlobalBaseUrl(qmlFile); currentQmlBackend->contextObject()->setGlobalBaseUrl(qmlFile);
currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData);
} }
@@ -509,8 +514,10 @@ void PropertyEditorView::commitVariantValueToModel(const PropertyName &propertyN
RewriterTransaction transaction = beginRewriterTransaction("PropertyEditorView::commitVariantValueToMode"); RewriterTransaction transaction = beginRewriterTransaction("PropertyEditorView::commitVariantValueToMode");
for (const ModelNode &node : m_selectedNode.view()->selectedModelNodes()) { for (const ModelNode &node : m_selectedNode.view()->selectedModelNodes()) {
if (QmlObjectNode::isValidQmlObjectNode(node)) if (QmlObjectNode::isValidQmlObjectNode(node)) {
QmlObjectNode(node).setVariantProperty(propertyName, value); QScopedPointer<QmlObjectNode>{QmlObjectNode::getQmlObjectNodeOfCorrectType(node)}
->setVariantProperty(propertyName, value);
}
} }
transaction.commit(); transaction.commit();
} }

View File

@@ -56,6 +56,7 @@
#include <QLineEdit> #include <QLineEdit>
#include <QMenu> #include <QMenu>
#include <QPainter> #include <QPainter>
#include <QScopedPointer>
#include <algorithm> #include <algorithm>
@@ -354,8 +355,9 @@ void TimelinePropertyItem::changePropertyValue(const QVariant &value)
QTimer::singleShot(0, deferredFunc); QTimer::singleShot(0, deferredFunc);
} else { } else {
QmlObjectNode objectNode(m_frames.target()); QScopedPointer<QmlObjectNode> objectNode {
objectNode.setVariantProperty(m_frames.propertyName(), value); QmlObjectNode::getQmlObjectNodeOfCorrectType(m_frames.target())};
objectNode->setVariantProperty(m_frames.propertyName(), value);
} }
} }

View File

@@ -224,6 +224,8 @@ private: // functions
void updateWatcher(const QString &path); void updateWatcher(const QString &path);
void updateRotationBlocks();
private: private:
QHash<QString, ModelNodePreviewImageData> m_imageDataMap; QHash<QString, ModelNodePreviewImageData> m_imageDataMap;
@@ -251,6 +253,7 @@ private:
QTimer m_resetTimer; QTimer m_resetTimer;
QTimer m_updateWatcherTimer; QTimer m_updateWatcherTimer;
QSet<QString> m_pendingUpdateDirs; QSet<QString> m_pendingUpdateDirs;
QTimer m_rotBlockTimer;
}; };
} // namespace ProxyNodeInstanceView } // namespace ProxyNodeInstanceView

View File

@@ -49,6 +49,14 @@ public:
Qml3DNode(const ModelNode &modelNode) : QmlVisualNode(modelNode) {} Qml3DNode(const ModelNode &modelNode) : QmlVisualNode(modelNode) {}
bool isValid() const override; bool isValid() const override;
static bool isValidQml3DNode(const ModelNode &modelNode); static bool isValidQml3DNode(const ModelNode &modelNode);
// From QmlObjectNode
void setVariantProperty(const PropertyName &name, const QVariant &value) override;
void setBindingProperty(const PropertyName &name, const QString &expression) override;
bool isBlocked(const PropertyName &propName) const override;
private:
void handleEulerRotationSet();
}; };
QMLDESIGNERCORE_EXPORT uint qHash(const Qml3DNode &node); QMLDESIGNERCORE_EXPORT uint qHash(const Qml3DNode &node);

View File

@@ -69,8 +69,8 @@ public:
QmlModelState currentState() const; QmlModelState currentState() const;
QmlTimeline currentTimeline() const; QmlTimeline currentTimeline() const;
void setVariantProperty(const PropertyName &name, const QVariant &value); virtual void setVariantProperty(const PropertyName &name, const QVariant &value);
void setBindingProperty(const PropertyName &name, const QString &expression); virtual void setBindingProperty(const PropertyName &name, const QString &expression);
NodeAbstractProperty nodeAbstractProperty(const PropertyName &name) const; NodeAbstractProperty nodeAbstractProperty(const PropertyName &name) const;
NodeAbstractProperty defaultNodeAbstractProperty() const; NodeAbstractProperty defaultNodeAbstractProperty() const;
NodeProperty nodeProperty(const PropertyName &name) const; NodeProperty nodeProperty(const PropertyName &name) const;
@@ -123,6 +123,10 @@ public:
QStringList allStateNames() const; QStringList allStateNames() const;
static QmlObjectNode *getQmlObjectNodeOfCorrectType(const ModelNode &modelNode);
virtual bool isBlocked(const PropertyName &propName) const;
protected: protected:
NodeInstance nodeInstance() const; NodeInstance nodeInstance() const;
QmlObjectNode nodeForInstance(const NodeInstance &instance) const; QmlObjectNode nodeForInstance(const NodeInstance &instance) const;

View File

@@ -54,6 +54,7 @@
#include "nodeproperty.h" #include "nodeproperty.h"
#include "pixmapchangedcommand.h" #include "pixmapchangedcommand.h"
#include "puppettocreatorcommand.h" #include "puppettocreatorcommand.h"
#include "qml3dnode.h"
#include "qmlchangeset.h" #include "qmlchangeset.h"
#include "qmldesignerconstants.h" #include "qmldesignerconstants.h"
#include "qmlstate.h" #include "qmlstate.h"
@@ -103,6 +104,7 @@
#include <QPainter> #include <QPainter>
#include <QDirIterator> #include <QDirIterator>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QScopedPointer>
enum { enum {
debug = false debug = false
@@ -176,6 +178,10 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this] { connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this] {
m_resetTimer.start(); m_resetTimer.start();
}); });
m_rotBlockTimer.setSingleShot(true);
m_rotBlockTimer.setInterval(0);
QObject::connect(&m_rotBlockTimer, &QTimer::timeout, this, &NodeInstanceView::updateRotationBlocks);
} }
@@ -594,11 +600,12 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node,
const QVariant &value) const QVariant &value)
{ {
QTC_ASSERT(m_nodeInstanceServer, return); QTC_ASSERT(m_nodeInstanceServer, return);
if (((node.isRootNode() && (name == "width" || name == "height")) || name == "invisible" || name == "locked") const bool forceAuxChange = name == "invisible" || name == "locked" || name == "rotBlocked@internal";
if (((node.isRootNode() && (name == "width" || name == "height")) || forceAuxChange)
|| name.endsWith(PropertyName("@NodeInstance"))) { || name.endsWith(PropertyName("@NodeInstance"))) {
if (hasInstanceForModelNode(node)) { if (hasInstanceForModelNode(node)) {
NodeInstance instance = instanceForModelNode(node); NodeInstance instance = instanceForModelNode(node);
if (value.isValid() || name == "invisible" || name == "locked") { if (value.isValid() || forceAuxChange) {
PropertyValueContainer container{instance.instanceId(), name, value, TypeName()}; PropertyValueContainer container{instance.instanceId(), name, value, TypeName()};
m_nodeInstanceServer->changeAuxiliaryValues({{container}}); m_nodeInstanceServer->changeAuxiliaryValues({{container}});
} else { } else {
@@ -1341,10 +1348,10 @@ void NodeInstanceView::valuesModified(const ValuesModifiedCommand &command)
if (hasInstanceForId(container.instanceId())) { if (hasInstanceForId(container.instanceId())) {
NodeInstance instance = instanceForId(container.instanceId()); NodeInstance instance = instanceForId(container.instanceId());
if (instance.isValid()) { if (instance.isValid()) {
// QmlVisualNode is needed so timeline and state are updated QScopedPointer<QmlObjectNode> node {
QmlVisualNode node = instance.modelNode(); QmlObjectNode::getQmlObjectNodeOfCorrectType(instance.modelNode())};
if (node.modelValue(container.name()) != container.value()) if (node->modelValue(container.name()) != container.value())
node.setVariantProperty(container.name(), container.value()); node->setVariantProperty(container.name(), container.value());
} }
} }
} }
@@ -1593,6 +1600,7 @@ void NodeInstanceView::selectedNodesChanged(const QList<ModelNode> &selectedNode
const QList<ModelNode> & /*lastSelectedNodeList*/) const QList<ModelNode> & /*lastSelectedNodeList*/)
{ {
m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(selectedNodeList)); m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(selectedNodeList));
m_rotBlockTimer.start();
} }
void NodeInstanceView::sendInputEvent(QInputEvent *e) const void NodeInstanceView::sendInputEvent(QInputEvent *e) const
@@ -1850,4 +1858,46 @@ void NodeInstanceView::updateWatcher(const QString &path)
} }
} }
void NodeInstanceView::updateRotationBlocks()
{
QList<ModelNode> qml3DNodes;
QSet<ModelNode> rotationKeyframeTargets;
bool groupsResolved = false;
const PropertyName targetPropName {"target"};
const PropertyName propertyPropName {"property"};
const PropertyName rotationPropName {"rotation"};
const QList<ModelNode> selectedNodes = selectedModelNodes();
for (const auto &node : selectedNodes) {
if (Qml3DNode::isValidQml3DNode(node)) {
if (!groupsResolved) {
const QList<ModelNode> keyframeGroups = allModelNodesOfType("KeyframeGroup");
for (const auto &kfgNode : keyframeGroups) {
if (kfgNode.isValid()) {
VariantProperty varProp = kfgNode.variantProperty(propertyPropName);
if (varProp.isValid() && varProp.value().value<PropertyName>() == rotationPropName) {
BindingProperty bindProp = kfgNode.bindingProperty(targetPropName);
if (bindProp.isValid()) {
ModelNode targetNode = bindProp.resolveToModelNode();
if (Qml3DNode::isValidQml3DNode(targetNode))
rotationKeyframeTargets.insert(targetNode);
}
}
}
}
groupsResolved = true;
}
qml3DNodes.append(node);
}
}
if (!qml3DNodes.isEmpty()) {
const PropertyName auxDataProp {"rotBlocked@internal"};
for (const auto &node : qAsConst(qml3DNodes)) {
if (rotationKeyframeTargets.contains(node))
node.setAuxiliaryData(auxDataProp, true);
else
node.setAuxiliaryData(auxDataProp, false);
}
}
}
} }

View File

@@ -58,6 +58,68 @@ bool Qml3DNode::isValidQml3DNode(const ModelNode &modelNode)
&& (modelNode.metaInfo().isSubclassOf("QtQuick3D.Node")); && (modelNode.metaInfo().isSubclassOf("QtQuick3D.Node"));
} }
void Qml3DNode::setVariantProperty(const PropertyName &name, const QVariant &value)
{
if (isBlocked(name))
return;
if (name.startsWith("eulerRotation"))
handleEulerRotationSet();
QmlObjectNode::setVariantProperty(name, value);
}
void Qml3DNode::setBindingProperty(const PropertyName &name, const QString &expression)
{
if (isBlocked(name))
return;
if (name.startsWith("eulerRotation"))
handleEulerRotationSet();
QmlObjectNode::setBindingProperty(name, expression);
}
bool Qml3DNode::isBlocked(const PropertyName &propName) const
{
if (modelNode().isValid() && propName.startsWith("eulerRotation"))
return modelNode().auxiliaryData("rotBlocked@internal").toBool();
return false;
}
void Qml3DNode::handleEulerRotationSet()
{
ModelNode node = modelNode();
// The rotation property is quaternion, which is difficult to deal with for users, so QDS
// only supports eulerRotation. Since having both on the same object isn't supported,
// remove the rotation property if eulerRotation is set.
if (node.isValid() && node.isSubclassOf("QtQuick3D.Node")) {
if (!isInBaseState()) {
QmlPropertyChanges changeSet(currentState().propertyChanges(node));
Q_ASSERT(changeSet.isValid());
node = changeSet.modelNode();
}
if (node.hasProperty("rotation")) {
// We need to reset the eulerRotation values as removing rotation will zero them,
// which is not desirable if the change only targets one of the xyz subproperties.
// Get the eulerRotation value from instance, as they are not available in model.
QVector3D eulerVec = instanceValue("eulerRotation").value<QVector3D>();
node.removeProperty("rotation");
if (qIsNaN(eulerVec.x()))
eulerVec.setX(0.);
if (qIsNaN(eulerVec.y()))
eulerVec.setY(0.);
if (qIsNaN(eulerVec.z()))
eulerVec.setZ(0.);
node.variantProperty("eulerRotation.x").setValue(eulerVec.x());
node.variantProperty("eulerRotation.y").setValue(eulerVec.y());
node.variantProperty("eulerRotation.z").setValue(eulerVec.z());
}
}
}
QList<ModelNode> toModelNodeList(const QList<Qml3DNode> &qmlVisualNodeList) QList<ModelNode> toModelNodeList(const QList<Qml3DNode> &qmlVisualNodeList)
{ {
QList<ModelNode> modelNodeList; QList<ModelNode> modelNodeList;

View File

@@ -28,6 +28,7 @@
#include "qmlstate.h" #include "qmlstate.h"
#include "qmltimelinekeyframegroup.h" #include "qmltimelinekeyframegroup.h"
#include "qmlvisualnode.h" #include "qmlvisualnode.h"
#include "qml3dnode.h"
#include "variantproperty.h" #include "variantproperty.h"
#include "nodeproperty.h" #include "nodeproperty.h"
#include <invalidmodelnodeexception.h> #include <invalidmodelnodeexception.h>
@@ -738,4 +739,19 @@ QStringList QmlObjectNode::allStateNames() const
return nodeInstance().allStateNames(); return nodeInstance().allStateNames();
} }
QmlObjectNode *QmlObjectNode::getQmlObjectNodeOfCorrectType(const ModelNode &modelNode)
{
// Create QmlObjectNode of correct type for the modelNode
// Note: Currently we are only interested in differentiating 3D nodes, so no check for
// visual nodes is done for efficiency reasons
if (modelNode.isValid() && modelNode.isSubclassOf("QtQuick3D.Node"))
return new Qml3DNode(modelNode);
return new QmlObjectNode(modelNode);
}
bool QmlObjectNode::isBlocked(const PropertyName &propName) const
{
return false;
}
} //QmlDesigner } //QmlDesigner