diff --git a/src/Gui/MetaTypes.h b/src/Gui/MetaTypes.h index 75047bca9c..99b69ed431 100644 --- a/src/Gui/MetaTypes.h +++ b/src/Gui/MetaTypes.h @@ -34,6 +34,7 @@ Q_DECLARE_METATYPE(Base::Vector3d) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(Base::Matrix4D) Q_DECLARE_METATYPE(Base::Placement) +Q_DECLARE_METATYPE(Base::Rotation) Q_DECLARE_METATYPE(Base::Quantity) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(App::SubObjectT) diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 066cb3e6d8..0dc42462fe 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -161,6 +161,7 @@ void Gui::SoFCDB::init() PropertyDirectionItem ::init(); PropertyMatrixItem ::init(); PropertyPlacementItem ::init(); + PropertyRotationItem ::init(); PropertyEnumItem ::init(); PropertyStringListItem ::init(); PropertyFloatListItem ::init(); diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 8211cca9ad..519816fcab 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -2092,6 +2092,232 @@ void PropertyMatrixItem::setA44(double A44) setData(QVariant::fromValue(Base::Matrix4D(getA11(),getA12(),getA13(),getA14(),getA21(),getA22(),getA23(),getA24(),getA31(),getA32(),getA33(),getA34(),getA41(),getA42(),getA43(),A44 ))); } +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyRotationItem) + +PropertyRotationItem::PropertyRotationItem() + : init_axis(false) + , changed_value(false) + , rot_angle(0) + , rot_axis(0,0,1) +{ + m_a = static_cast(PropertyUnitItem::create()); + m_a->setParent(this); + m_a->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Angle"))); + this->appendChild(m_a); + m_d = static_cast(PropertyVectorItem::create()); + m_d->setParent(this); + m_d->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Axis"))); + m_d->setReadOnly(true); + this->appendChild(m_d); +} + +PropertyRotationItem::~PropertyRotationItem() +{ +} + +Base::Quantity PropertyRotationItem::getAngle() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) + return Base::Quantity(0.0); + const Base::Rotation& val = value.value(); + double angle; + Base::Vector3d dir; + val.getRawValue(dir, angle); + if (dir * this->rot_axis < 0.0) + angle = -angle; + return Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle); +} + +void PropertyRotationItem::setAngle(Base::Quantity angle) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) + return; + + Base::Rotation rot; + rot.setValue(this->rot_axis, Base::toRadians(angle.getValue())); + changed_value = true; + rot_angle = angle.getValue(); + setValue(QVariant::fromValue(rot)); +} + +Base::Vector3d PropertyRotationItem::getAxis() const +{ + // We must store the rotation axis in a member because + // if we read the value from the property we would always + // get a normalized vector which makes it quite unhandy + // to work with + return this->rot_axis; +} + +void PropertyRotationItem::setAxis(const Base::Vector3d& axis) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) + return; + this->rot_axis = axis; + Base::Rotation rot = value.value(); + Base::Vector3d dummy; double angle; + rot.getValue(dummy, angle); + if (dummy * axis < 0.0) + angle = -angle; + rot.setValue(axis, angle); + changed_value = true; + setValue(QVariant::fromValue(rot)); +} + +void PropertyRotationItem::assignProperty(const App::Property* prop) +{ + // Choose an adaptive epsilon to avoid changing the axis when they are considered to + // be equal. See https://forum.freecadweb.org/viewtopic.php?f=10&t=24662&start=10 + double eps = std::pow(10.0, -2*(decimals()+1)); + if (prop->getTypeId().isDerivedFrom(App::PropertyRotation::getClassTypeId())) { + const Base::Rotation& value = static_cast(prop)->getValue(); + double angle; + Base::Vector3d dir; + value.getRawValue(dir, angle); + Base::Vector3d cross = this->rot_axis.Cross(dir); + double len2 = cross.Sqr(); + if (angle != 0) { + // vectors are not parallel + if (len2 > eps) + this->rot_axis = dir; + // vectors point into opposite directions + else if (this->rot_axis.Dot(dir) < 0) + this->rot_axis = -this->rot_axis; + } + this->rot_angle = Base::toDegrees(angle); + } +} + +QVariant PropertyRotationItem::value(const App::Property* prop) const +{ + assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyRotation::getClassTypeId())); + + const Base::Rotation& value = static_cast(prop)->getValue(); + double angle; + Base::Vector3d dir; + value.getRawValue(dir, angle); + if (!init_axis) { + if (m_a->hasExpression()) { + QString str = m_a->expressionAsString(); + const_cast(this)->rot_angle = str.toDouble(); + } + else { + const_cast(this)->rot_angle = Base::toDegrees(angle); + } + + PropertyItem* x = m_d->child(0); + PropertyItem* y = m_d->child(1); + PropertyItem* z = m_d->child(2); + if (x->hasExpression()) { + QString str = x->expressionAsString(); + dir.x = str.toDouble(); + } + if (y->hasExpression()) { + QString str = y->expressionAsString(); + dir.y = str.toDouble(); + } + if (z->hasExpression()) { + QString str = z->expressionAsString(); + dir.z = str.toDouble(); + } + const_cast(this)->rot_axis = dir; + const_cast(this)->init_axis = true; + } + return QVariant::fromValue(value); +} + +QVariant PropertyRotationItem::toolTip(const App::Property* prop) const +{ + assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyRotation::getClassTypeId())); + + const Base::Rotation& p = static_cast(prop)->getValue(); + double angle; + Base::Vector3d dir; + p.getRawValue(dir, angle); + angle = Base::toDegrees(angle); + + QLocale loc; + QString data = QString::fromUtf8("Axis: (%1 %2 %3)\n" + "Angle: %4") + .arg(loc.toString(dir.x,'f',decimals()), + loc.toString(dir.y,'f',decimals()), + loc.toString(dir.z,'f',decimals()), + Base::Quantity(angle, Base::Unit::Angle).getUserString()); + return QVariant(data); +} + +QVariant PropertyRotationItem::toString(const QVariant& prop) const +{ + const Base::Rotation& p = prop.value(); + double angle; + Base::Vector3d dir; + p.getRawValue(dir, angle); + angle = Base::toDegrees(angle); + + QLocale loc; + QString data = QString::fromUtf8("[(%1 %2 %3); %4]") + .arg(loc.toString(dir.x,'f',2), + loc.toString(dir.y,'f',2), + loc.toString(dir.z,'f',2), + Base::Quantity(angle, Base::Unit::Angle).getUserString()); + return QVariant(data); +} + +void PropertyRotationItem::setValue(const QVariant& value) +{ + if (!value.canConvert()) + return; + // Accept this only if the user changed the axis, angle or position but + // not if >this< item loses focus + if (!changed_value) + return; + changed_value = false; + + Base::QuantityFormat format(Base::QuantityFormat::Fixed, decimals()); + QString data = QString::fromLatin1("App.Rotation(App.Vector(%1,%2,%3),%4)") + .arg(Base::UnitsApi::toNumber(rot_axis.x, format)) + .arg(Base::UnitsApi::toNumber(rot_axis.y, format)) + .arg(Base::UnitsApi::toNumber(rot_axis.z, format)) + .arg(Base::UnitsApi::toNumber(rot_angle, format)); + setPropertyValue(data); +} + +QWidget* PropertyRotationItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const +{ + Q_UNUSED(parent) + Q_UNUSED(receiver) + Q_UNUSED(method) + return nullptr; +} + +void PropertyRotationItem::setEditorData(QWidget *editor, const QVariant& data) const +{ + Q_UNUSED(editor) + Q_UNUSED(data) +} + +QVariant PropertyRotationItem::editorData(QWidget *editor) const +{ + Q_UNUSED(editor) + return QVariant(); +} + +void PropertyRotationItem::propertyBound() +{ + if (isBound()) { + m_a->bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<) Q_DECLARE_METATYPE(Base::Matrix4D) Q_DECLARE_METATYPE(Base::Placement) +Q_DECLARE_METATYPE(Base::Rotation) Q_DECLARE_METATYPE(Base::Quantity) Q_DECLARE_METATYPE(QList) #endif @@ -656,6 +657,46 @@ private: PropertyFloatItem* m_a44; }; +/** + * Edit properties of rotation type. + * \author Werner Mayer + */ +class GuiExport PropertyRotationItem: public PropertyItem +{ + Q_OBJECT + Q_PROPERTY(Base::Quantity Angle READ getAngle WRITE setAngle DESIGNABLE true USER true) + Q_PROPERTY(Base::Vector3d Axis READ getAxis WRITE setAxis DESIGNABLE true USER true) + PROPERTYITEM_HEADER + + virtual QWidget* createEditor(QWidget* parent, const QObject* receiver, const char* method) const; + virtual void setEditorData(QWidget *editor, const QVariant& data) const; + virtual QVariant editorData(QWidget *editor) const; + + virtual void propertyBound(); + virtual void assignProperty(const App::Property*); + + Base::Quantity getAngle() const; + void setAngle(Base::Quantity); + Base::Vector3d getAxis() const; + void setAxis(const Base::Vector3d&); + +protected: + PropertyRotationItem(); + ~PropertyRotationItem(); + virtual QVariant toolTip(const App::Property*) const; + virtual QVariant toString(const QVariant&) const; + virtual QVariant value(const App::Property*) const; + virtual void setValue(const QVariant&); + +private: + bool init_axis; + bool changed_value; + double rot_angle; + Base::Vector3d rot_axis; + PropertyUnitItem * m_a; + PropertyVectorItem* m_d; +}; + class PlacementEditor : public Gui::LabelButton { Q_OBJECT