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 point currentMousePos
property alias freeDraggerArea: mouseAreaFree
property bool blocked: false
position: dragHelper.pivotScenePosition(targetNode)
onTargetNodeChanged: position = dragHelper.pivotScenePosition(targetNode)
onTargetNodeChanged: {
position = dragHelper.pivotScenePosition(targetNode);
blocked = _generalHelper.isRotationBlocked(targetNode);
}
Connections {
target: rotateGizmo.targetNode
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"
eulerRotation: Qt.vector3d(0, 90, 0)
targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1))
: Qt.rgba(1, 0, 0, 1)
color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1)
: highlightOnHover && (hovering || dragging)
? Qt.lighter(Qt.rgba(1, 0, 0, 1)) : Qt.rgba(1, 0, 0, 1)
priority: 40
view3D: rotateGizmo.view3D
active: rotateGizmo.visible
active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper
onRotateCommit: rotateGizmo.rotateCommit()
@@ -100,13 +113,14 @@ Node {
objectName: "Rotate Ring Y"
eulerRotation: Qt.vector3d(90, 0, 0)
targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1))
: Qt.rgba(0, 0.6, 0, 1)
color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 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
scale: Qt.vector3d(0.998, 0.998, 0.998)
priority: 30
view3D: rotateGizmo.view3D
active: rotateGizmo.visible
active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper
onRotateCommit: rotateGizmo.rotateCommit()
@@ -120,13 +134,14 @@ Node {
objectName: "Rotate Ring Z"
eulerRotation: Qt.vector3d(0, 0, 0)
targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1))
: Qt.rgba(0, 0, 1, 1)
color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 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
scale: Qt.vector3d(0.996, 0.996, 0.996)
priority: 20
view3D: rotateGizmo.view3D
active: rotateGizmo.visible
active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper
onRotateCommit: rotateGizmo.rotateCommit()
@@ -154,12 +169,14 @@ Node {
objectName: "cameraRing"
rotation: rotateGizmo.view3D.camera.rotation
targetNode: rotateGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1))
: 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)
scale: Qt.vector3d(1.1, 1.1, 1.1)
priority: 10
view3D: rotateGizmo.view3D
active: rotateGizmo.visible
active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper
visible: !rotRingX.dragging && !rotRingY.dragging && !rotRingZ.dragging && !freeRotator.dragging
@@ -176,7 +193,7 @@ Node {
materials: DefaultMaterial {
id: material
diffuseColor: "black"
opacity: mouseAreaFree.hovering ? 0.15 : 0
opacity: mouseAreaFree.hovering && !rotateGizmo.blocked ? 0.15 : 0
lighting: DefaultMaterial.NoLighting
}
scale: Qt.vector3d(0.15, 0.15, 0.15)
@@ -200,6 +217,14 @@ Node {
_startRotation = Qt.vector3d(rotateGizmo.targetNode.eulerRotation.x,
rotateGizmo.targetNode.eulerRotation.y,
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;
}
@@ -239,7 +264,7 @@ Node {
height: 100
circlePickArea: Qt.point(25, 50)
grabsMouse: rotateGizmo.targetNode
active: rotateGizmo.visible
active: rotateGizmo.visible && !rotateGizmo.blocked
dragHelper: rotateGizmo.dragHelper
onPressed: freeRotator.handlePressed(screenPos)

View File

@@ -90,6 +90,13 @@ Model {
_startRotation = Qt.vector3d(targetNode.eulerRotation.x,
targetNode.eulerRotation.y,
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;
currentMousePos = screenPos;
}

View File

@@ -356,6 +356,24 @@ bool GeneralHelper::isMacOS() const
#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)
{
if (event->type() == QEvent::DynamicPropertyChange) {

View File

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

View File

@@ -63,6 +63,7 @@
#include "inputeventcommand.h"
#include "view3dactioncommand.h"
#include "requestmodelnodepreviewimagecommand.h"
#include "changeauxiliarycommand.h"
#include "dummycontextobject.h"
#include "../editor3d/generalhelper.h"
@@ -288,6 +289,57 @@ void Qt5InformationNodeInstanceServer::resolveImportSupport()
#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()
{
#ifdef QUICK3D_MODULE
@@ -1492,8 +1544,10 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com
sendChildrenChangedCommand(instanceList);
nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList));
if (isQuick3DMode())
if (isQuick3DMode()) {
setup3DEditView(instanceList, command.edit3dToolStates);
updateRotationBlocks(command.auxiliaryChanges);
}
QObject::connect(&m_renderModelNodeImageViewTimer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::doRenderModelNodeImageView);
@@ -1656,6 +1710,8 @@ void Qt5InformationNodeInstanceServer::removeInstances(const RemoveInstancesComm
{
int nodeCount = m_3DSceneMap.size();
removeRotationBlocks(command.instanceIds());
Qt5NodeInstanceServer::removeInstances(command);
if (nodeCount != m_3DSceneMap.size()) {
@@ -1741,6 +1797,7 @@ void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const Reques
void Qt5InformationNodeInstanceServer::changeAuxiliaryValues(const ChangeAuxiliaryCommand &command)
{
updateRotationBlocks(command.auxiliaryChanges);
Qt5NodeInstanceServer::changeAuxiliaryValues(command);
render3DEditView();
}

View File

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

View File

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

View File

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

View File

@@ -303,6 +303,9 @@ void PropertyEditorContextObject::insertKeyframe(const QString &propertyName)
{
QTC_ASSERT(m_model && m_model->rewriterView(), return);
if (isBlocked(propertyName))
return;
/* Ideally we should not missuse the rewriterView
* If we add more code here we have to forward the property editor view */
RewriterView *rewriterView = m_model->rewriterView();
@@ -557,6 +560,20 @@ QStringList PropertyEditorContextObject::allStatesForId(const QString &id)
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()
{
qmlRegisterType<EasingCurveEditor>("HelperWidgets", 2, 0, "EasingCurveEditor");

View File

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

View File

@@ -39,6 +39,7 @@
#include <QRegularExpression>
#include <QUrl>
#include <QScopedPointer>
//using namespace QmlDesigner;
@@ -610,14 +611,15 @@ void PropertyEditorNodeWrapper::changeValue(const QString &propertyName)
if (name.isNull())
return;
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)));
if (valueObject->value().isValid())
qmlObjectNode.setVariantProperty(name, valueObject->value());
qmlObjectNode->setVariantProperty(name, valueObject->value());
else
qmlObjectNode.removeProperty(name);
qmlObjectNode->removeProperty(name);
}
}

View File

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

View File

@@ -56,6 +56,7 @@
#include <QLineEdit>
#include <QMenu>
#include <QPainter>
#include <QScopedPointer>
#include <algorithm>
@@ -354,8 +355,9 @@ void TimelinePropertyItem::changePropertyValue(const QVariant &value)
QTimer::singleShot(0, deferredFunc);
} else {
QmlObjectNode objectNode(m_frames.target());
objectNode.setVariantProperty(m_frames.propertyName(), value);
QScopedPointer<QmlObjectNode> objectNode {
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 updateRotationBlocks();
private:
QHash<QString, ModelNodePreviewImageData> m_imageDataMap;
@@ -251,6 +253,7 @@ private:
QTimer m_resetTimer;
QTimer m_updateWatcherTimer;
QSet<QString> m_pendingUpdateDirs;
QTimer m_rotBlockTimer;
};
} // namespace ProxyNodeInstanceView

View File

@@ -49,6 +49,14 @@ public:
Qml3DNode(const ModelNode &modelNode) : QmlVisualNode(modelNode) {}
bool isValid() const override;
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);

View File

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

View File

@@ -54,6 +54,7 @@
#include "nodeproperty.h"
#include "pixmapchangedcommand.h"
#include "puppettocreatorcommand.h"
#include "qml3dnode.h"
#include "qmlchangeset.h"
#include "qmldesignerconstants.h"
#include "qmlstate.h"
@@ -103,6 +104,7 @@
#include <QPainter>
#include <QDirIterator>
#include <QFileSystemWatcher>
#include <QScopedPointer>
enum {
debug = false
@@ -176,6 +178,10 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this] {
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)
{
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"))) {
if (hasInstanceForModelNode(node)) {
NodeInstance instance = instanceForModelNode(node);
if (value.isValid() || name == "invisible" || name == "locked") {
if (value.isValid() || forceAuxChange) {
PropertyValueContainer container{instance.instanceId(), name, value, TypeName()};
m_nodeInstanceServer->changeAuxiliaryValues({{container}});
} else {
@@ -1341,10 +1348,10 @@ void NodeInstanceView::valuesModified(const ValuesModifiedCommand &command)
if (hasInstanceForId(container.instanceId())) {
NodeInstance instance = instanceForId(container.instanceId());
if (instance.isValid()) {
// QmlVisualNode is needed so timeline and state are updated
QmlVisualNode node = instance.modelNode();
if (node.modelValue(container.name()) != container.value())
node.setVariantProperty(container.name(), container.value());
QScopedPointer<QmlObjectNode> node {
QmlObjectNode::getQmlObjectNodeOfCorrectType(instance.modelNode())};
if (node->modelValue(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*/)
{
m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(selectedNodeList));
m_rotBlockTimer.start();
}
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"));
}
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> modelNodeList;

View File

@@ -28,6 +28,7 @@
#include "qmlstate.h"
#include "qmltimelinekeyframegroup.h"
#include "qmlvisualnode.h"
#include "qml3dnode.h"
#include "variantproperty.h"
#include "nodeproperty.h"
#include <invalidmodelnodeexception.h>
@@ -738,4 +739,19 @@ QStringList QmlObjectNode::allStateNames() const
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