From 18b26c2e1354521b2ec0068de364d854b3d1ed2d Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Fri, 5 Nov 2021 17:44:12 -0700 Subject: [PATCH 01/15] Support editing air pressure sensor in the GUI Signed-off-by: Nate Koenig --- .../component_inspector/AirPressure.cc | 120 +++++ .../component_inspector/AirPressure.hh | 60 +++ .../component_inspector/AirPressure.qml | 136 ++++++ .../component_inspector/CMakeLists.txt | 10 +- .../component_inspector/ComponentInspector.cc | 40 +- .../component_inspector/ComponentInspector.hh | 71 +-- .../ComponentInspector.qml | 17 + .../ComponentInspector.qrc | 1 + .../ExpandingTypeHeader.qml | 5 + src/gui/plugins/component_inspector/Noise.qml | 412 ++++++++++++++++++ .../component_inspector/TypeHeader.qml | 20 +- src/gui/plugins/component_inspector/Types.cc | 33 ++ src/gui/plugins/component_inspector/Types.hh | 117 +++++ src/gui/plugins/modules/TypeIcon.qml | 1 + src/gui/resources/gazebo.qrc | 1 + src/gui/resources/images/sensor.png | Bin 0 -> 27876 bytes 16 files changed, 979 insertions(+), 65 deletions(-) create mode 100644 src/gui/plugins/component_inspector/AirPressure.cc create mode 100644 src/gui/plugins/component_inspector/AirPressure.hh create mode 100644 src/gui/plugins/component_inspector/AirPressure.qml create mode 100644 src/gui/plugins/component_inspector/Noise.qml create mode 100644 src/gui/plugins/component_inspector/Types.cc create mode 100644 src/gui/plugins/component_inspector/Types.hh create mode 100644 src/gui/resources/images/sensor.png diff --git a/src/gui/plugins/component_inspector/AirPressure.cc b/src/gui/plugins/component_inspector/AirPressure.cc new file mode 100644 index 0000000000..2bc3267e4a --- /dev/null +++ b/src/gui/plugins/component_inspector/AirPressure.cc @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#include + +#include +#include +#include + +#include "ComponentInspector.hh" +#include "Types.hh" +#include "AirPressure.hh" + +using namespace ignition; +using namespace gazebo; + +///////////////////////////////////////////////// +AirPressure::AirPressure(ComponentInspector *_inspector) +{ + _inspector->Context()->setContextProperty("AirPressure", this); + this->inspector = _inspector; + + ComponentCreator creator = + [=](EntityComponentManager &_ecm, Entity _entity, QStandardItem *_item) + { + auto comp = _ecm.Component(_entity); + if (nullptr == _item || nullptr == comp) + return; + const sdf::AirPressure *air = comp->Data().AirPressureSensor(); + + _item->setData(QString("AirPressure"), + ComponentsModel::RoleNames().key("dataType")); + _item->setData(QList({ + QVariant(air->ReferenceAltitude()), + QVariant(air->PressureNoise().Mean()), + QVariant(air->PressureNoise().BiasMean()), + QVariant(air->PressureNoise().StdDev()), + QVariant(air->PressureNoise().BiasStdDev()), + QVariant(air->PressureNoise().DynamicBiasStdDev()), + QVariant(air->PressureNoise().DynamicBiasCorrelationTime()), + }), ComponentsModel::RoleNames().key("data")); + }; + + this->inspector->RegisterComponentCreator( + components::AirPressureSensor::typeId, creator); +} + +///////////////////////////////////////////////// +Q_INVOKABLE void AirPressure::OnAirPressureNoise( + double _mean, double _meanBias, double _stdDev, + double _stdDevBias, double _dynamicBiasStdDev, + double _dynamicBiasCorrelationTime) +{ + ignition::gazebo::UpdateCallback cb = + [=](EntityComponentManager &_ecm) + { + auto comp = _ecm.Component( + this->inspector->Entity()); + if (comp) + { + sdf::AirPressure *airpressure = comp->Data().AirPressureSensor(); + if (airpressure) + { + sdf::Noise noise = airpressure->PressureNoise(); + + setNoise(noise, _mean, _meanBias, _stdDev, _stdDevBias, + _dynamicBiasStdDev, _dynamicBiasCorrelationTime); + + airpressure->SetPressureNoise(noise); + } + else + ignerr << "Unable to get the air pressure data.\n"; + } + else + { + ignerr << "Unable to get the air pressure component.\n"; + } + }; + this->inspector->AddUpdateCallback(cb); +} + +///////////////////////////////////////////////// +Q_INVOKABLE void AirPressure::OnAirPressureReferenceAltitude( + double _referenceAltitude) +{ + ignition::gazebo::UpdateCallback cb = + [=](EntityComponentManager &_ecm) + { + auto comp = _ecm.Component( + this->inspector->Entity()); + if (comp) + { + sdf::AirPressure *airpressure = comp->Data().AirPressureSensor(); + if (airpressure) + { + airpressure->SetReferenceAltitude(_referenceAltitude); + } + else + ignerr << "Unable to get the air pressure data.\n"; + } + else + { + ignerr << "Unable to get the air pressure component.\n"; + } + }; + this->inspector->AddUpdateCallback(cb); +} diff --git a/src/gui/plugins/component_inspector/AirPressure.hh b/src/gui/plugins/component_inspector/AirPressure.hh new file mode 100644 index 0000000000..7d00c2aefa --- /dev/null +++ b/src/gui/plugins/component_inspector/AirPressure.hh @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#ifndef IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_AIRPRESSURE_HH_ +#define IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_AIRPRESSURE_HH_ + +#include + +namespace ignition +{ +namespace gazebo +{ + class ComponentInspector; + + /// \brief A class that handles air pressure changes. + class AirPressure : public QObject + { + Q_OBJECT + + /// \brief Constructor + /// \param[in] _inspector The component inspector. + public: AirPressure(ComponentInspector *_inspector); + + /// \brief This function is called when a user changes values in the + /// air pressure sensor. + /// \param[in] _mean Mean value + /// \param[in] _meanBias Bias mean value + /// \param[in] _stdDev Standard deviation value + /// \param[in] _stdDevBias Bias standard deviation value + /// \param[in] _dynamicBiasStdDev Dynamic bias standard deviation value + /// \param[in] _dynamicBiasCorrelationTime Dynamic bias correlation time + /// value + public: Q_INVOKABLE void OnAirPressureNoise( + double _mean, double _meanBias, double _stdDev, + double _stdDevBias, double _dynamicBiasStdDev, + double _dynamicBiasCorrelationTime); + + public: Q_INVOKABLE void OnAirPressureReferenceAltitude( + double _referenceAltitude); + + /// \brief Pointer to the component inspector. This is used to add + /// update callbacks that modify the ECM. + private: ComponentInspector *inspector{nullptr}; + }; +} +} +#endif diff --git a/src/gui/plugins/component_inspector/AirPressure.qml b/src/gui/plugins/component_inspector/AirPressure.qml new file mode 100644 index 0000000000..23a89b3b60 --- /dev/null +++ b/src/gui/plugins/component_inspector/AirPressure.qml @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Dialogs 1.0 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 +import "qrc:/ComponentInspector" +import "qrc:/qml" + +// Item displaying air pressure noise information. +Rectangle { + height: header.height + content.height + width: componentInspector.width + color: index % 2 == 0 ? lightGrey : darkGrey + + Column { + anchors.fill: parent + + // The expanding header. Make sure that the content to expand has an id set + // to the value "content". + ExpandingTypeHeader { + id: header + + // Set the 'expandingHeaderText' value to override the default header + // values, which is based on the model. + expandingHeaderText: "Air pressure" + expandingHeaderToolTip: "Air pressure properties" + } + + // This is the content that will be expanded/contracted using the + // ExpandingHeader above. Note the "id: content" attribute. + Rectangle { + id: content + property bool show: false + width: parent.width + height: show ? layout.height : 0 + clip: true + color: "transparent" + Layout.leftMargin: 4 + + Behavior on height { + NumberAnimation { + duration: 200; + easing.type: Easing.InOutQuad + } + } + + ColumnLayout { + id: layout + width: parent.width + + RowLayout { + id: rowLayout + width: layout.width + + Text { + leftPadding: 10 + id: referenceAltitudeText + text: "Reference altitude (m)" + color: "dimgrey" + font.pointSize: 12 + } + IgnSpinBox { + id: referenceAltitudeSpin + Layout.fillWidth: true + height: 40 + property double refAltitude: model.data[0] + value: referenceAltitudeSpin.activeFocus ? referenceAltitudeSpin.value : refAltitude + + minimumValue: 0 + maximumValue: 100000 + decimals:4 + stepSize: 0.1 + onEditingFinished: { + refAltitude = referenceAltitudeSpin.value + componentInspector.onAirPressureReferenceAltitude(refAltitude); + } + } + } + + // Space out the section header. + Rectangle { + id: noiseTextRect + width: parent.width + height: childrenRect.height + Layout.leftMargin: 10 + Layout.topMargin: 10 + color: "transparent" + + Text { + text: "Pressure Noise" + color: "dimgrey" + font.pointSize: 12 + } + } + + // Show the pressure noise values. + Noise { + id: pressureNoise + Layout.fillWidth: true + Layout.leftMargin: 20 + + meanValue: model.data[1] + meanBias: model.data[2] + stdDevValue: model.data[3] + stdDevBias: model.data[4] + dynamicBiasStdDev: model.data[5] + dynamicBiasCorrelationTime: model.data[6] + + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + pressureNoise.onNoiseUpdate.connect( + componentInspector.onAirPressureNoise) + } + } + } + } + } +} diff --git a/src/gui/plugins/component_inspector/CMakeLists.txt b/src/gui/plugins/component_inspector/CMakeLists.txt index 367278b24d..65f1172a97 100644 --- a/src/gui/plugins/component_inspector/CMakeLists.txt +++ b/src/gui/plugins/component_inspector/CMakeLists.txt @@ -1,4 +1,10 @@ gz_add_gui_plugin(ComponentInspector - SOURCES ComponentInspector.cc - QT_HEADERS ComponentInspector.hh + SOURCES + AirPressure.cc + ComponentInspector.cc + Types.cc + QT_HEADERS + AirPressure.hh + ComponentInspector.hh + Types.hh ) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index e789c91516..1fcc4f8b85 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -65,6 +65,7 @@ #include "ignition/gazebo/EntityComponentManager.hh" #include "ignition/gazebo/gui/GuiEvents.hh" +#include "AirPressure.hh" #include "ComponentInspector.hh" namespace ignition::gazebo @@ -100,6 +101,16 @@ namespace ignition::gazebo /// \brief Transport node for making command requests public: transport::Node node; + + /// \brief Air pressure sensor inspector elements + public: std::unique_ptr airPressure; + + /// \brief Set of callbacks to execture during the Update function. + public: std::vector< + std::function> updateCallbacks; + + /// \brief A map of component type to creation functions. + public: std::map componentCreators; }; } @@ -404,6 +415,9 @@ void ComponentInspector::LoadConfig(const tinyxml2::XMLElement *) // Connect model this->Context()->setContextProperty( "ComponentsModel", &this->dataPtr->componentsModel); + + // Create air pressure + this->dataPtr->airPressure = std::make_unique(this); } ////////////////////////////////////////////////// @@ -412,9 +426,6 @@ void ComponentInspector::Update(const UpdateInfo &, { IGN_PROFILE("ComponentInspector::Update"); - if (this->dataPtr->paused) - return; - auto componentTypes = _ecm.ComponentTypes(this->dataPtr->entity); // List all components @@ -767,6 +778,12 @@ void ComponentInspector::Update(const UpdateInfo &, if (comp) setData(item, comp->Data()); } + else if (this->dataPtr->componentCreators.find(typeId) != + this->dataPtr->componentCreators.end()) + { + this->dataPtr->componentCreators[typeId]( + _ecm, this->dataPtr->entity, item); + } } // Remove components no longer present @@ -781,6 +798,23 @@ void ComponentInspector::Update(const UpdateInfo &, Q_ARG(ignition::gazebo::ComponentTypeId, typeId)); } } + + // Process all of the update callbacks + for (auto cb : this->dataPtr->updateCallbacks) + cb(_ecm); + this->dataPtr->updateCallbacks.clear(); +} + +///////////////////////////////////////////////// +void ComponentInspector::AddUpdateCallback(UpdateCallback _cb) +{ + this->dataPtr->updateCallbacks.push_back(_cb); +} +///////////////////////////////////////////////// +void ComponentInspector::RegisterComponentCreator(ComponentTypeId _id, + ComponentCreator _creatorFn) +{ + this->dataPtr->componentCreators[_id] = _creatorFn; } ///////////////////////////////////////////////// diff --git a/src/gui/plugins/component_inspector/ComponentInspector.hh b/src/gui/plugins/component_inspector/ComponentInspector.hh index 18b0ef7d66..b304f00ae9 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.hh +++ b/src/gui/plugins/component_inspector/ComponentInspector.hh @@ -33,7 +33,7 @@ #include -Q_DECLARE_METATYPE(ignition::gazebo::ComponentTypeId) +#include "Types.hh" namespace ignition { @@ -41,27 +41,6 @@ namespace gazebo { class ComponentInspectorPrivate; - /// \brief Generic function to set data. - /// \param[in] _item Item whose data will be set. - /// \param[in] _data Data to set. - template - void setData(QStandardItem *_item, const DataType &_data) - { - // cppcheck-suppress syntaxError - // cppcheck-suppress unmatchedSuppression - if constexpr (traits::IsOutStreamable::value) - { - std::stringstream ss; - ss << _data; - setData(_item, ss.str()); - } - else - { - ignwarn << "Attempting to set unsupported data type to item [" - << _item->text().toStdString() << "]" << std::endl; - } - } - /// \brief Specialized to set string data. /// \param[in] _item Item whose data will be set. /// \param[in] _data Data to set. @@ -127,40 +106,6 @@ namespace gazebo /// \param[in] _unit Unit to be displayed, such as 'm' for meters. void setUnit(QStandardItem *_item, const std::string &_unit); - /// \brief Model holding information about components, such as their type - /// and data. - class ComponentsModel : public QStandardItemModel - { - Q_OBJECT - - /// \brief Constructor - public: explicit ComponentsModel(); - - /// \brief Destructor - public: ~ComponentsModel() override = default; - - // Documentation inherited - public: QHash roleNames() const override; - - /// \brief Static version of roleNames - /// \return A hash connecting a unique identifier to a role name. - public: static QHash RoleNames(); - - /// \brief Add a component type to the inspector. - /// \param[in] _typeId Type of component to be added. - /// \return Newly created item. - public slots: QStandardItem *AddComponentType( - ignition::gazebo::ComponentTypeId _typeId); - - /// \brief Remove a component type from the inspector. - /// \param[in] _typeId Type of component to be removed. - public slots: void RemoveComponentType( - ignition::gazebo::ComponentTypeId _typeId); - - /// \brief Keep track of items in the tree, according to type ID. - public: std::map items; - }; - /// \brief Displays a tree view with all the entities in the world. /// /// ## Configuration @@ -331,6 +276,20 @@ namespace gazebo /// \brief Notify that paused has changed. signals: void PausedChanged(); + /// \brief Add a callback that will be executed during the next Update. + /// \param[in] _cb The callback to run. + public: void AddUpdateCallback(UpdateCallback _cb); + + /// \brief Register a component creator. A component creator is + /// responsible for selecting the correct QML and setting the + /// appropriate data for a ComponentTypeId. + /// \param[in] _id The component type id to associate with the creation + /// function. + /// \param[in] _creatorFn Function to call in order to create the QML + /// component. + public: void RegisterComponentCreator(ComponentTypeId _id, + ComponentCreator _creatorFn); + /// \internal /// \brief Pointer to private data. private: std::unique_ptr dataPtr; diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 86ad73a023..66cff0065b 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -87,6 +87,23 @@ Rectangle { return 6; } + /** + * Forward air pressure noise changes to C++ + */ + function onAirPressureNoise(_mean, _meanBias, _stdDev, _stdDevBias, + _dynamicBiasStdDev, _dynamicBiasCorrelationTime) { + AirPressure.OnAirPressureNoise( + _mean, _meanBias, _stdDev, _stdDevBias, + _dynamicBiasStdDev, _dynamicBiasCorrelationTime); + } + + /** + * Forward air pressure reference altitude changes to C++ + */ + function onAirPressureReferenceAltitude(_referenceAltitude) { + AirPressure.OnAirPressureReferenceAltitude(_referenceAltitude); + } + /** * Forward pose changes to C++ */ diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qrc b/src/gui/plugins/component_inspector/ComponentInspector.qrc index fde9cbbba7..417bbdedaf 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qrc +++ b/src/gui/plugins/component_inspector/ComponentInspector.qrc @@ -1,5 +1,6 @@ + AirPressure.qml Boolean.qml ComponentInspector.qml ExpandingTypeHeader.qml diff --git a/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml b/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml index c233ecf775..7f8126801d 100644 --- a/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml +++ b/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml @@ -34,6 +34,9 @@ Rectangle { // Left indentation property int indentation: 10 + property string expandingHeaderText: "" + property string expandingHeaderToolTip: "" + RowLayout { anchors.fill: parent Item { @@ -50,6 +53,8 @@ Rectangle { } TypeHeader { id: typeHeader + headerText: expandingHeaderText + headerToolTip: expandingHeaderToolTip } Item { Layout.fillWidth: true diff --git a/src/gui/plugins/component_inspector/Noise.qml b/src/gui/plugins/component_inspector/Noise.qml new file mode 100644 index 0000000000..cab75f0638 --- /dev/null +++ b/src/gui/plugins/component_inspector/Noise.qml @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Dialogs 1.0 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 +import "qrc:/ComponentInspector" +import "qrc:/qml" + +// Item displaying noise information. +Rectangle { + id: noise + height: noiseContent.height + color: "transparent" + objectName: "NoiseQML" + + // Mean value + property double meanValue: 0.0 + + // Mean bias + property double meanBias: 0.0 + + // Standard deviation value + property double stdDevValue: 0.0 + + // Standard deviation bias + property double stdDevBias: 0.0 + + // Dynamic bias standard deviation + property double dynamicBiasStdDev: 0.0 + + // Dynamic bias correlation time + property double dynamicBiasCorrelationTime: 0.0 + + // Signal that a user of this component can connect to in order to receive + // noise updates + signal onNoiseUpdate(double _mean, double _meanBias, double _stdDev, + double _stdDevBias, double _dynamicBiasStdDev, + double _dynamicBiasCorrelationTime) + + // Display the main content + Column { + anchors.fill: parent + + // Content + Rectangle { + id: noiseContent + width: parent.width + height: grid.height + clip: true + color: "transparent" + + Behavior on height { + NumberAnimation { + duration: 200; + easing.type: Easing.InOutQuad + } + } + + GridLayout { + id: grid + width: parent.width + columns: 4 + + // Padding + Rectangle { + Layout.columnSpan: 4 + height: 4 + } + + // Mean text label + Text { + Layout.columnSpan: 4 + text: "Mean" + color: "dimgrey" + font.pointSize: 10 + font.bold: true + } + + // Mean + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: meanText.width + + Text { + id : meanText + text: 'Value' + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + ToolTip { + visible: meanMa.containsMouse + delay: tooltipDelay + text: "Mean value" + y: noise.y - 30 + enter: null + exit: null + } + MouseArea { + id: meanMa + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.RightButton + } + } + } + IgnSpinBox { + id: meanSpin + Layout.fillWidth: true + height: 40 + value: meanSpin.activeFocus ? meanSpin.value : meanValue + + minimumValue: 0 + maximumValue: 100000 + decimals:4 + stepSize: 0.1 + onEditingFinished: { + meanValue = meanSpin.value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + } + // End of mean + + // Mean Bias + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: meanBiasText.width + + Text { + id : meanBiasText + text: 'Bias' + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + ToolTip { + visible: meanBiasMa.containsMouse + delay: tooltipDelay + text: "Mean bias" + y: noise.y - 30 + enter: null + exit: null + } + MouseArea { + id: meanBiasMa + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.RightButton + } + } + } + IgnSpinBox { + id: meanBiasSpin + Layout.fillWidth: true + height: 40 + property double numberValue: meanBias + value: meanBiasSpin.activeFocus ? meanBiasSpin.value : numberValue + + minimumValue: 0 + maximumValue: 100000 + decimals:4 + stepSize: 0.1 + onEditingFinished: { + meanBias = meanBiasSpin.value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + } + // End of mean bias + + // Padding + Rectangle { + Layout.columnSpan: 4 + height: 4 + } + + // Std Dev text label + Text { + Layout.columnSpan: 4 + text: "Standard deviation" + color: "dimgrey" + font.pointSize: 10 + font.bold: true + } + + // Std Dev + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: stddevText.width + + Text { + id : stddevText + text: 'Value' + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + ToolTip { + visible: stdDevMa.containsMouse + delay: tooltipDelay + text: "Standard deviation value" + y: noise.y - 30 + enter: null + exit: null + } + MouseArea { + id: stdDevMa + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.RightButton + } + } + } + IgnSpinBox { + id: stddevSpin + Layout.fillWidth: true + height: 40 + property double numberValue: stdDevValue + value: stddevSpin.activeFocus ? stddevSpin.value : numberValue + + minimumValue: 0 + maximumValue: 100000 + decimals:4 + stepSize: 0.1 + onEditingFinished: { + stdDevValue = stddevSpin.value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + } + // End of stddev + + // Std Dev bias + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: stddevBiasText.width + + Text { + id : stddevBiasText + text: 'Bias' + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + ToolTip { + visible: stddevBiasTextMa.containsMouse + delay: tooltipDelay + text: "Standard deviation bias" + y: noise.y - 30 + enter: null + exit: null + } + MouseArea { + id: stddevBiasTextMa + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.RightButton + } + } + } + IgnSpinBox { + id: stddevBiasSpin + Layout.fillWidth: true + height: 40 + property double numberValue: stdDevBias + value: stddevBiasSpin.activeFocus ? stddevBiasSpin.value : numberValue + + minimumValue: 0 + maximumValue: 100000 + decimals:4 + stepSize: 0.1 + onEditingFinished: { + stdDevBias = stddevBiasSpin.value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + } + // End of stddev bias + + // Padding + Rectangle { + Layout.columnSpan: 4 + height: 4 + } + + // Dynamic bias text label + Text { + Layout.columnSpan: 4 + text: "Dynamic bias" + color: "dimgrey" + font.pointSize: 10 + font.bold: true + } + + // Dynamic bias std Dev + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: dynamicBiasStddevText.width + + Text { + id : dynamicBiasStddevText + text: 'Std. Dev.' + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + ToolTip { + visible: dynamicBiasStddevTextMa.containsMouse + delay: tooltipDelay + text: "Standard deviation" + y: noise.y - 30 + enter: null + exit: null + } + MouseArea { + id: dynamicBiasStddevTextMa + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.RightButton + } + + } + } + IgnSpinBox { + id: dynamicBiasStdDevSpin + Layout.fillWidth: true + height: 40 + property double numberValue: dynamicBiasStdDev + value: dynamicBiasStdDevSpin.activeFocus ? dynamicBiasStdDevSpin.value : numberValue + + minimumValue: 0 + maximumValue: 100000 + decimals:4 + stepSize: 0.1 + onEditingFinished: { + dynamicBiasStdDev = dynamicBiasStdDevSpin.value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + } + // End of dynamic bias stddev + + // Dynamic bias correlation time + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: dynamicBiasCorrelationTimeText.width + + Text { + id : dynamicBiasCorrelationTimeText + text: 'Cor. Time (s)' + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + + ToolTip { + visible: dynamicBiasCorrelationTimeTextMa.containsMouse + delay: tooltipDelay + text: "Correlation time in seconds." + y: noise.y - 30 + enter: null + exit: null + } + MouseArea { + id: dynamicBiasCorrelationTimeTextMa + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.RightButton + } + } + } + IgnSpinBox { + id: dynamicBiasCorrelationTimeSpin + Layout.fillWidth: true + height: 40 + property double numberValue: dynamicBiasCorrelationTime + value: dynamicBiasCorrelationTimeSpin.activeFocus ? dynamicBiasCorrelationTimeSpin.value : numberValue + + minimumValue: 0 + maximumValue: 100000 + decimals:4 + stepSize: 0.1 + onEditingFinished: { + dynamicBiasCorrelationTime = dynamicBiasCorrelationTimeSpin.value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + } + // End of dynamic bias correlation time stddev bias + } + } + } +} diff --git a/src/gui/plugins/component_inspector/TypeHeader.qml b/src/gui/plugins/component_inspector/TypeHeader.qml index 5cb0b39e2b..80eb283cc3 100644 --- a/src/gui/plugins/component_inspector/TypeHeader.qml +++ b/src/gui/plugins/component_inspector/TypeHeader.qml @@ -23,11 +23,17 @@ import QtQuick.Controls.Styles 1.4 Rectangle { id: typeHeader - height: headerText.height - width: headerText.width + height: headerTextId.height + width: headerTextId.width color: "transparent" + property string headerText: "" + property string headerToolTip: "" function tooltipText(_model) { + if (headerToolTip !== undefined && headerToolTip !== "" ) { + return headerToolTip + } + if (model === null) return "Unknown component" @@ -42,8 +48,14 @@ Rectangle { } Text { - id: headerText - text: model && model.shortName ? model.shortName : '' + id: headerTextId + text: { + if (headerText === undefined || headerText === "") { + model && model.shortName ? model.shortName : '' + } else { + headerText + } + } color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" font.pointSize: 12 diff --git a/src/gui/plugins/component_inspector/Types.cc b/src/gui/plugins/component_inspector/Types.cc new file mode 100644 index 0000000000..fdef409818 --- /dev/null +++ b/src/gui/plugins/component_inspector/Types.cc @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "Types.hh" + +void ignition::gazebo::setNoise(sdf::Noise &_noise, + double _mean, double _meanBias, double _stdDev, + double _stdDevBias, double _dynamicBiasStdDev, + double _dynamicBiasCorrelationTime) +{ + _noise.SetMean(_mean); + _noise.SetBiasMean(_meanBias); + + _noise.SetStdDev(_stdDev); + _noise.SetBiasStdDev(_stdDevBias); + + _noise.SetDynamicBiasStdDev(_dynamicBiasStdDev); + _noise.SetDynamicBiasCorrelationTime(_dynamicBiasCorrelationTime); +} diff --git a/src/gui/plugins/component_inspector/Types.hh b/src/gui/plugins/component_inspector/Types.hh new file mode 100644 index 0000000000..9c871a7990 --- /dev/null +++ b/src/gui/plugins/component_inspector/Types.hh @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_TYPES_HH_ +#define IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_TYPES_HH_ + +#include + +#include + +#include +#include + +Q_DECLARE_METATYPE(ignition::gazebo::ComponentTypeId) + +namespace ignition +{ +namespace gazebo +{ + /// \brief UpdateCallback is a function defition that is used by a + /// component to manage ECM changes. + /// \sa void ComponentInspector::AddUpdateCallback(UpdateCallback _cb) + using UpdateCallback = std::function; + + // ComponentCreator is a function definition that a component can use + // to create the appropriate UI elements for an Entity based on + // a ComponentTypeId. + /// \sa void ComponentInspector::RegisterComponentCreator(UpdateCallback _cb) + using ComponentCreator = std::function; + + /// \brief Helper function that will set all noise properties. + /// \param[out] _noise Noise to set + /// \param[in] _mean Mean value + /// \param[in] _meanBias Bias mean value + /// \param[in] _stdDev Standard deviation value + /// \param[in] _stdDevBias Bias standard deviation value + /// \param[in] _dynamicBiasStdDev Dynamic bias standard deviation value + /// \param[in] _dynamicBiasCorrelationTime Dynamic bias correlation time + /// value + void setNoise(sdf::Noise &_noise, + double _mean, double _meanBias, double _stdDev, + double _stdDevBias, double _dynamicBiasStdDev, + double _dynamicBiasCorrelationTime); + + /// \brief Generic function to set data. + /// \param[in] _item Item whose data will be set. + /// \param[in] _data Data to set. + template + void setData(QStandardItem *_item, const DataType &_data) + { + // cppcheck-suppress syntaxError + // cppcheck-suppress unmatchedSuppression + if constexpr (traits::IsOutStreamable::value) + { + std::stringstream ss; + ss << _data; + setData(_item, ss.str()); + } + else + { + ignwarn << "Attempting to set unsupported data type to item [" + << _item->text().toStdString() << "]" << std::endl; + } + } + + /// \brief Model holding information about components, such as their type + /// and data. + class ComponentsModel : public QStandardItemModel + { + Q_OBJECT + + /// \brief Constructor + public: explicit ComponentsModel(); + + /// \brief Destructor + public: ~ComponentsModel() override = default; + + // Documentation inherited + public: QHash roleNames() const override; + + /// \brief Static version of roleNames + /// \return A hash connecting a unique identifier to a role name. + public: static QHash RoleNames(); + + /// \brief Add a component type to the inspector. + /// \param[in] _typeId Type of component to be added. + /// \return Newly created item. + public slots: QStandardItem *AddComponentType( + ignition::gazebo::ComponentTypeId _typeId); + + /// \brief Remove a component type from the inspector. + /// \param[in] _typeId Type of component to be removed. + public slots: void RemoveComponentType( + ignition::gazebo::ComponentTypeId _typeId); + + /// \brief Keep track of items in the tree, according to type ID. + public: std::map items; + }; + +} +} +#endif diff --git a/src/gui/plugins/modules/TypeIcon.qml b/src/gui/plugins/modules/TypeIcon.qml index 4e8c1a308e..16820d0461 100644 --- a/src/gui/plugins/modules/TypeIcon.qml +++ b/src/gui/plugins/modules/TypeIcon.qml @@ -44,6 +44,7 @@ Image { entityType == 'joint' || entityType == 'light' || entityType == 'link' || + entityType == 'sensor' || entityType == 'model') { entityImage = entityType diff --git a/src/gui/resources/gazebo.qrc b/src/gui/resources/gazebo.qrc index 44c2823540..0845b98594 100644 --- a/src/gui/resources/gazebo.qrc +++ b/src/gui/resources/gazebo.qrc @@ -10,6 +10,7 @@ images/minus.png images/model.png images/plus.png + images/sensor.png images/unlock.svg images/visual.png diff --git a/src/gui/resources/images/sensor.png b/src/gui/resources/images/sensor.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf4d003e1498fc3df454318dad2c0c1fa798d8f GIT binary patch literal 27876 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 V+?FI_N2 zkev+36Ox$SsFDizdxt6cM2qciG2y7hKKuFwIEX%7?^?{jKN?CC(LCHmp1;2H;W?KI zJ~wb^Kp{{`gn>66>fi$-VM5DEW(SQBwUB?}2kDEx#-{Sdh|1m0U6Btk%fs%cGVlEI zSduqKEivt)<|^aRXqF$mp7LtI)#nGIZhxh^=KCbtijc~oOE?!G$Mp@ahzbKqBtT{s zngs+y<}3`*FYVG4(kJRb&*Qh+F~aP4N5}*eaZ}U0*a92YoUQ(%Yl|U;w7q`w0kGFq z`Btt+@^o_l3on0wyR2j?OsH z@woBwN?Y+V975jU@rUzf!y;29XHj@_C)U__%kZ0Gx|~v_CQca|wXo(+Xh6 zMGUCc$cPTemH+4m90M4XKxtf=L@4m_F&fU}z#zU3)UJ3LoQ~%JZH^oug_;Z|ft8cQ z9DwWzxlB+&mj_U~&>@DoU4MB9_z@U zcj=(zh_ZluVlk9)Iu5?k?|w&PP(c4WgO)?b2$-X4J_qSyDN|b%3Uu(oI|VwlH@zN< zK|oPTD09d){*vGS28DxgW^ta_Qd@u@fv(L$YCplIgoMx>YtGP5oiVKYs=%L)CGuu) z3ZA|r(Go?A7)QxX)X|E1^F;sYigqSI3TW(u#!6vySEoq_)xae9q!Bkly=6*kk8Q*r!`{CtEuc|C=D0p}NNYt(TUp+evo zOz^}99Q-3xjNnPR6i_<&j=xwol=1`ULUZsjR*i1r5$PHqLw2V94PVYCOHJ$t><;!I zOPBHfej6BL*)5N*BvB?y2O9xsMmEql$53ZqNk*y<? zn<*KCK1J8vhvrvxDoHfchkhJ)*CL`rX2BSX9k&i61(4gS*$+DsU%PG40(${1&pN!N zzdUd^RsdBL+3m1yFshpuo(jfrpV3iPF+AT!yI9N@D;tDgslwhrQ6P%T#9wC~=@Qut z4`h+SS+OF|504`G-I6`pNO6FblrP2#@Vk1 zz=`X=k^wN=H4?M^eNy3kT>ax>CRSVD+hVGVgJMe2_jBm+D@Zg92JHF4f6u#Qz3A}b zAnq8*^ZRT`K-^(i+1NIE&-58c7sc)%no6`GxjOtt|EVV4hkUpfFNB4hPwld@`LGc; zdq5T-Fc|Qa>f9q`ZK-;c?JLzQl1B{e*o|Pa} zEFhbO-6=ckIDI48am>o42T+0DT}+sQ=E^p2mhNxsWwK*cn|wKF%b+1oMd^y@<# zN#cajqw|^InPsimqj5VjbDhm?S6y2>4?=uTp)zjft0KZbCTn8Y$y!%oe_YvHSk}>Y z$^5iP-K-gZ(Q@@?Nb7A`Jn!8z=M=G@Pv|JG2WHeEOV$)XWX~n>$CXBuXXgO!_~TM} zw?gG5oDI5q$2^Yw1=}p{VQ%P&Do61!udm_LZ}pC{MD$)s(u*!V2Cez^Ve~(ePJ1u* z=s9-5?z3Lh_G{&-TUNUD=|te={FzH_$D}dPAZ7(767}=r6;EWhx&OE{`r69L{PO&D z8*>)EUHma1lnS`NbNCQyC%I5*k%w`7K}&Ec&Xk~vN_ z-23VbsBAPEF?SC^M$I4$JMlqd7Bz*L=RutgplRU0XUY)oS!kh`cB$hFFpez-M*b3Dm{SIqc!*h25uo zC(D8A$q*(~WVCQ&xy4+DvO8Q{u1MqQO#W z?25n`#mgWj;86ux2P#S?oGyG(^|!)-F4oA9y7|7pv;g=V6)95? z9JU&e8q<97Hy-9GKn?URfRe3L^=Tt3nI8A|vnp_7RQ{DZNnvXmo38Kx7?J;FzWbrN z>9pzh-fP+%GK;^7ISXAm@o#3Uat^P%+G^$9{Li!9ym>Kq^rLWroyJXLgIQCls*8m^S zyD7Gb{inFfrEyW(&789@?e9i)+J!r+&1dzNCy@ODN7dx`-itGp!=BPrgK$6XHYfA+ z)j%qPML92p4Ad*dYgv<)B$8IlwHEJ zSLpC&(h8ddb5i|!)e=Wa7^kUlI;+wsFkJtQpn7s5u_>4^8~lCvcHgy$rn|!ZgJqoL zL{=7DO1NeEl1^gm?P*J^O_lJNN?=*YAQOTtt8EH^uLd1U4)KCE&__kIV64sEE^4W%N%~SAj2=={9fZGsvhd zx>l4V<3javH*<_^`&O#VNq_ifDkB$@rUgcMy9!v~*{wh&@^#+KNF7;uG5?{UnY2q4 zJXfo4R5W@Ap+#>cBq2i!*54U>Z-y25=>m+kICt|ZE*p%}E+))7986{*w?L~x+Kg-2 zN@zD|7h+#Uk7vz3Veq@Gh2&(mylCOj=nOF7n2J%ar1?4u&OptL&RWX{dhJP*9e!5a z%n>*`!_~bXuhG82*?MJ@b%dG|U0_`v(D-a*zT2vaFSPvn+u&8u8q$~3m=sFt_+s|P zJWee8f+UAH@mb{fyw=G6*aFWu7sc|t1`%IXL=O1x-XmFAlmD;@q4(gtZXOQ2NX{gS3^1HV)+;g)piU7{Y%~Yx zv`+go%>@w?P1K%At)EltO1^C3J~c5`+-fQ>troF+r)(*|7{b^MvJxag%~W{>4m_1i zXnhD&VkM2Br_jhyEYnwhV-F_&s)y4nT7C0b<~yXh@UfN{{Df}+&-`bCwDS(=;YE?Z zjK7B}zkb$r1)e4C_R`Yi)e+)(x>d2|GXG71YLiQ(^Pau|1t7Kbf3EgeJWQr4s^DLk&#`5FjUhFXxVN z@7p<#cR%chwKLXOW3H9C*Pe6!e`~afZhM1DV3^|TXoJm*vepQjJKiaa~&xbw4L2C>EVOjXYB@DtMB7|gG^eU4u7n^t2XF}h_7 zuOwK;*}l$C5`a1K1@l{4OzClE)H7hjR?@_=W3+zrG>4dH=psd1e#@RL_yQ$XdRGeP z-5FuN8nUW#7&oZYwKueQ^|f;`OuY@xLF`WZ#3%@Ck%`|p2#ShE1|vtL6dDtgnlPDC zzOWfsFp`qx%?lRfdGmpr%|*k^dAL1}4lpIU*hBcGvB~R-0gZPuTw_`Rd%d_f-Fb_M z`x3vi2i~uxl2uhZ3#qqbPM{WTMFLhXF@mt;J9Iv6Pp!M&GabrMJLl~scirhuku{14 zsoXrX&CNT^&c5a1o{%QNWbZy|wiZ!MP-5s+A3gu@A*x!t&*rJwwPORoJ?$H!L;$yB z#{s^msfcC^e*pX?0*MdSGFGXl5hcXj`-#F;me)9hDG((jx@Y}8=uHovf4h)9D^qTx z6{J%GSuCWtm+KB~iOrHO?knt#9ns^0tY4Phzlhc>&aZrafv#U@2&Rjktv$u9T>#UbauuI z1V0e|qm|Oin#7q(_?&|Pn^GXmQ5JhP{mQ(yb;xi9w4w-ihx0&N>6h21N4(wG{jd^} z(+a-S_|!*B*Xh_c-G20&ddh_wYk29~wlJ$R%?k)pS=JB{cb8&P7;u@fb95U{%7BAkjhT z`k-lSWzLU$#ZUk<0Ns4Arz|M7wYSt0iMU2D^<8KAlV8|qIyU?#Biq;1AM$F_FPlZUpISWIn>3j=@?~Kls>Y5j&JPGx=K0NZd}5w zs2ykb%&nG{s6{GIhqqGF+$NMA;$>&caR(0*Z<>_wN?P)kgk%nTL+6xmtDs>?yDyuL z62=W-m_(%%kitk!xsE1Db9%O<?Jj04r%=}qApa#t02X4`dNF<)L(^zYn)W8*8-mgfzWujgr4uwd!Vc&t5 zSV|&$GGNl_Bsd4*+Tq}>!UQ}`qA%Aq={+@R*UY4@byConE*PZ7|7AGl=C%(wD1*|@ z_a@%E!yFx26rXl^sS8R0EsFOTyyQ8&rR))d>Mvb82BUtvY1!M^C@SRYVm#u(Ecj`+ zQ2YnejgiL2_m^Qchl7z?S2*L<)n^_dy~PCU`+G!t=g~+Vs{mRA)sN}5D8s{LVmgL8 zg1%FnZcKn0MaxY$_B^hV z$Y3fZ1+*ZrJ?5pVN;Dnv)^<>s%R!DH_FirbZc!g*Rt{?v21WXQKO|Y$viMlqhpCH- z{#oEZmKi7@*{_PY8kV9$oHH`3))OM4Q`*{gR&_+W~q)$4MTKOIeykGV* zNxNRF*ai64gxWS+mY*bk`#Y{6j%yMWns!S^QxAJbK7*=7)s)=`J1IH5^cXprfWWwa zXkqNi!FxeDaXAh|qj@07C(Us-sEG8#?n}Lqf((xal$E^3dR--s5E@40GHeZL5A1$t zZBML7#o9xor#Bf1b=nLd!SByz@22$h6pu=x$|W5*KiN(0E-KnPZNAqC%F0+%b_-hx z`0G3uI6HH+4q|}1vo2OjLB<Kpeq^_Zc3x&_> zsuJB1;9I_D2XoSuzg}fV%}{8T)}Oulc|6I0yyd>FMQ&_4EajkC)OgxB`oYKKt3*iE zDc-um&nOKmb6efMV8z{RoTGGSZ&}r0bT%Qyw8UW;74d*Sy=`Ynd0JtEE5kl9DdPYi zLKNx5CD|idcmrlcx0jG6Lk8-;v@lNl&&uQb_ zwMg$i1EbmHdVA|qwuyaXnhLY^Jp}hWTt0d1SyH8!#9?TcXRq2~ zpWRjOzTz^I$rY&)aid$3n9L6~KxCu!s_VT8?t9-HO49M7Fff>hg7Bv0G`y-wSx3X9 zi?ToRomi)l(dtSdA8>B{`6%c5yKapesAI_;_t7l8!!AIoNA9~68!7MIFl3$+C$B=aez3)*@hRH6INEY*fT z2~iEIefV}>ZJ5Oh9s@sZ3M{Z+zekYEsB%IP$)Be)>~(*u@lASJ`f{Q-dwnZIWb789 zua55C?!Y9WNe_v9ON~1AJFxZ1MdUx8y^C|Ti`p4ZNJ2z3Cd6LB4_P$_ov8<2wmPgq zY9U?Hhh^%c*rmbaJ(J)s%Ew}fsP?wXFehr~dWUlh_B##y?`4g05Z>`cvbDPIXRBE| zHwn>UH0bn4HmB4poWZtthoEAG&GaQCD`mSZx2DGMx?`4tnUHNI{hAGrA}Bpbw5Bg} zaQD@ablt5x74E#%$7J8`9^Wu=H#HMp+P)Dq;{IxvOLR|eD*wVQm%h-V>F(I5N-OOS zzk_`V`tS5HPODV7L=$nNej; z?N|H#R(mhkxu00cjLptKZB|UJp;&bamg1T@_Ly1VV!)k&Pi`cjF)XQ1x^C!=!n5nx za0*SqEYa9v4mK%%jmxMDPdu{VQ1-;D{ca_P$ljV%csuK#pezod^_KK5ETeel zbdM=-4fM5Btz~Co-f4;LiWj@a0w<7MBWMNXO|Q-X7q&ZVFWO>ZB6wsy3!=zczqHK+l7&&HQ?T(ftNHea-w2QQ)~(0h5Gt-*VcG$44+ zmKsn4CKy#}FOiEUQ8}C59T{lDZO`Y1!O!n)JLnyG=QSa4k=~VJ6t-n>(--3%^7BaB zSJ@GaTI70a9pQarOtkvVVgncHl_w)mzepLuSq^g+t?Gsp#pQX`# zC|rRb4EayDKw3py(9WlJ*>{>#5s(cgi!1rMJK!co)I^jM?|yxdW5U}9`mNlz>l0#X zR?EE09Z?!lOT8A<$bTVjmnr{&Vkg8)B*!m=1GAzZq`|wbC?=D>s<{|SzpLGB5nbKD zzOMNo`pQ|U)e!?L+clT^iu(Pb#v0vpw#A6NWU5}1FeE5~|0aufX!KT!RAoY`^bU%D zPic|nJ+dzn3>5t&4?j~YD0DC^eA8Tcsee#BWnf6Wz)yw1lpf~7dP6i3@~5um+!jX- z{zzD-v%+7GkKzXQ-`_f=Pl9I_$p-L^g+GpV-P#29UNy8hD^<=ar(|y_3R;;BU=Fhb zSn;TK1@GVpY2~BCq#I_i!m>>hBxfnj0X4`Ld! zoKpzb9+vNHJ6t^c@u`UKxf1xJP=gxU`7j$7nDmzjr(mKuou~An#|GWs!st?=a;JfJ zYI$>KIR>2GJ$y>~yiHtUEF-*FXf-+ZT}*%}&eSc9AO7u71o- z-ifzYM}_e5&xXrZ`GFwctJg|?o_WquU8re7h_0Wv#jcA8{m9%mM`F#8o1G`ZWN-Vv zVcPSaldW>kW`(Y^z~lLC=E0l34gxd}=1cwYVx;}pYOFIG`7Z0PC!5ZV<%e-jyY_SZ zG9fgBcv_&1)Iq60D-5{>bBVbecW5AmD5+Xkoe){&`47Egk6#!wHG?8vfl$F|TC??4cu2T8^NKmc_gv=Ty<7MMXN)pLF9>uf z9_neFXqe{f=Sb@us~1r8^CSx+h`O2yjxo>;RN5!HNC7M3i(ac7`8B0$7oIQ>Z`0ig zu;yoRGIx|Wxg8kl3R{yVQ`@a0QNKmtpk`yw+MX)&a4pLrSjq=PSyg&*>?k~+9P zRON)T-R09R{ftGI=O`?rv32gX?6cN=+orZ0-3qm-M`uoJufaWAg@IrQ^K*jIRcw0R z<;_A6cSHJK%iMe8?2?ZQVx`s7pYk(aNgO4?@wIx@pC1pS@L+qkbW&OgpYG@A$7!#D zjkiHSm#IK&B`!2CMxW_68&$}+Z68p4&ozN|40JQ3mTL614vBBts{>cDpNhh7+4%M* zH!z)P1&Twk8RyJ`P@@%WK;s~0YG%b@ujHUrImzw*vx*mM5bh16<7?#8Hk`Lz^{DA^ zL|a_-PdUN#`V#NoRKzjoXo4!aMOJokN}vDlxqFQ$b7nk_3q*VOx~+A0C2RZm@eW^o z(K`;On4)*}51-7Ug9T*e5;P{`vpT#)e349+{41uvGy!`!UC zmVts zRLpQj)s}e2qotqJ^+Pnxq)wxP!jgV@CZnrs!@l#+(+T}L2USn*T+sm!wnul>4g^;M z*vr}GD?dAh5Ov4Z-!Ln9-Ub1%*Ar6T$s)9;KgM8hq-S?bMB@0H`Z3g=5-347PZU7P zamaOt*zqnq*kQ$t^^TO&PUx?@A#G|fhn|u0*M&18l|v4{Ce*GJ8L(sQ);;3d?q3nV z+A+#kd=1V2JQ+EM^HteSs@nAlmG2oZELSNSGh@rd#un4UVd{Ks zE|p#i+SJb=)^3?gNlDLP(-^o7q=g-+ZI6;KjnhNZ)U5`#dR}>o_=rwiSlcdh{hciJ zes;CfRP9|E)N?nanB^-ch_+6!L+Dk(`(l$Yt2FwaIkOFQ^(%Vrpkk@CK&jh(!yCrE zHAQjZjl|#7Zyq(54`1Brzv&wJ>MI+oHGA%kVF>lr$1Y5}wMQu@&FWSA;3{&M;2J&z zP>EiDY19elq$)Zg(u=q9&JUnNmXsk!0XR9EI%;-xnz`OM{uTU9{)4=U`@bB%i8y;b zG2E>Tczi}lD7d6HORKY9wf1@N&ZaQf^I=v}5P|W4j~bXHhYIMCS{XV~;{=Eeo>KUF zBnQwp-2Stpsz4E-(pXBu?mrFKJ;KdN_Jtk=_!58fF&(htsS&a6J8cxQ`3er6lA5^U z1-2AjJk-V>fws(0e2S{Vd*)kk{T}+9>s6U!1q%3lodT^+5P7X-gU?o9;raTb$-7?+ zzc?jcctrELWEIO9y`u~Jep6KP%wK>|dGqt3?@kUl|M1mM6k`*gQAC0?SnyiEfB{=t zIU6?*ZLXLpcReZd_S|H+F}1xlmyy3oZv4dU>Vfh|EG#mD3hXArm zf*JOeL0tS5sF(<;9svo*6{6Fot7-8bnI`2W`CYjT0Uwp;UF>0o$c$yXr)>ifQ_P88 zNhFR0Pxy3Ily(0ef}+F$?W`e6F#-3 zDCE@n@jQgc)#J1sY6?R#X z#sLEw!5`ZkVm`zfeI+&uN}o4eA4bA5WB+;vy)R8mY_i?rg%^>MONW007x&ser3HeT zHg|Gs{VrmOCu`O>uf6R3lUW$pdqHp5wF2>?v6vmc_at2vs&Q8e^AElQP!+(sWDfS) zy))hhzqRu~^7i8Sej8LbkrktRF_gOd{_hZcv zP{Ytq{i2jacbhjspa5>q8vNYd5OV^La$i6+WDHTM8#op!%XFkyJ*_ny^i*s2D$D^d z|MQfk)c4lEU6eEncdPAr^KdzfZ2>zy`ft#RAgScT^W&4U_#tsHw{k&D zNvW0KI`_1w8nViv!3tq-NS7Nwr;^se!w8H(_S)-*qiJK?b(1W?J^@4YIV>SQzODrA za&1W>n(_7{K-GfD%lahZ`o^^?=?aC+!~oJ*4$3Oe0W47}Z$U~#g~3ft0!1oxMciQh zO(qx5ddB5hcxQk&fqD3?*P8x{=_MAgX-`B<-&A*r16DIa&<(KFszHt*fuA#G#SAg_oLb3gqAmlc||nz2S+9;iE4f@ z=*bfZkYmguYq2Ad{vj$aDy*&o*3ZCjW!G89H%Zh7(~{worqOp5o09BFxv#!HYn8q~ zkj1RS^1GJoMi`PYQ6DSUm0R15$sz8g1#Xzp-~3ILyAn9gFf}5)UZCQY80j1z@s?ZU z_QE|BI*`Ta@0g=gg#U1a^ip{TtlrtFqOdikTK2|v%iS;Kf{-bL>%pHCo2Of0Rh5#r$$^b+qK+$N{-ql!mm40frwHjD z2HB(HV&P&0N@1x0uv2rE;(EMb_|K_9COP&eR27^Ai7+x}9L4!P@gA1J8Jo&mvfn#i zVI3`VFs1aupZ|C+#QoMG|OTAJ86vN)PmlT3r59Lvf@$Xgm1Z>sFB&2rlukltpA@Y0gP;OKpwmc;lL-O_u}>r^-={2o zTEEKxyp-Kva|)*va#NT_2p54sz=Eg*ZhYzhFQ_V{3hzGYuVyclHrw*~Bpe24OA~fr zN0zQnANq^kQ->uT`Ez{rALJ-(;Jl^lgxREDQcGYXa!cX7;euXBM~}O@Fun&idLYnA zdQs|B#ub4+rV?u(T?38btF{DTf6B6T&iZOxkuYp)(%=ovy=ae@5U6%xAgOABOhBel5mRl4sF&y-dxhvrLW;(t1tH>+!T=#px;dVA{q=55y;_1_^}AH^ z(}ODmY295h^-1;WqTma-IMoTM9ej0^U9=js}N|Q!c_B$ zOuSUTc*gvHSt_CF<##D=@>Ai?j4A&nG-G;p~eVw~f@Gy6%7unkeXU3oEyegV*Off>Q)L6q~hqZcJ3VGW+xZIk7)6t;2?D}_EvK@wmhYGX zHR7@-_J>4(qsW1^e$V9{KasM6l|`Gh*4qa(pfS@FS6rlAZ+o=Hvt)dt#R-Xs02AO? z4tZwbu2XVE9;*0h>SQUjCgMdW>DH0aEMPPsDyLBWdM3$tk#6y0dq~fEa#Ft}EkzK>NMkdYT2YnZ)gHb&h; z0#IW)ZQ|}HiZ)~lzU4FbYX~cDO#S!BCuhl@CgHT=i z-lQmE)!#WYZ*+;8F2^hIxb9DnW8whlt>st#Z|07wbExhT?I2_e7>iWS2vlwk?>d=3 zwAB>4#5)(q%k4()=2R6C1i*C;?S{ahP9fZS{kOc{r~?1`2NobVC;*R)79sz&Ddv%1 zw)^b!OI{+NCY^NdJv%z{*B*0kXD<2e$=`fdKI&5OLz>PuX_9hJoSkm#`ss1dbpOOC- z<+i9jqptix98f*_fwy$Rv}e~)wBdAIWxq?pn@I8q^FI1n{}+s z#6AU1(Ish0+adbyt zT-g&hEAB)g&)UHaFOuedRN)ANwxzSB`F&q*i8pa3TbC#l<=c4Wt_cA5(nMaQH|_BP zS-WRi1hP6a!E6&=%gx%-%1ep-rh(<0(5Li;MwH}Zsr*m^vfYsmkf+&9v`>k`@dCM{ z+OIvmX9+y+d$Z31n%l#8?8Ha_Rg-r>7BHjhu3D}QmB(*@30&xi?n^qr2u(5nbubX4 z$G>9wrN4O+eS;#?h?LN?6O>JGzL@zq0qpVNh}j%j>qkMu&rRqa24Hc_IwYn#*C$|a za13opRYVdB(85|S0~Cgs?Ol& zvVw#c(d!?0&=+}{jyb4UtiBd9Az`9DJtn?M&E0^)5vpjPO^Gxy7cQGfgF z6e4yr#i2c103COOZH%!0wF!9m<(_j-Z=CM#MKu>W&|m$nkK(l*g^kXAf+5eWGpe-0 zMYZlr*#*>}nw*n$2y|YxeNDY~rEJ%>sr+tYHi4|C&>UH#1CE1UssgHSyZ_F&e-O~q z%gCxK>OK=Fl}8b!umBSRolQrYQRSy^W)cPt_wq{idF9jz2MTmV&o8L=R$j@H`2woU z$e+E0ox%->z)Y!tFZ|1odz%1Mhv#+Ou1k+U!DaylE?Yhz?$}+SDkNck2^Z17+yBau z5C8vU3V`IFF}eTU2zsLV$Hallwot m{I^B^@DLjQkH)Z%c&^w!e=fN-35S0_)qZ5~utLrD&3^&zB>3?F literal 0 HcmV?d00001 From 1477a68236521c68647bcfb688bbb5a572c704ff Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Fri, 5 Nov 2021 17:50:28 -0700 Subject: [PATCH 02/15] Add noise to qrc Signed-off-by: Nate Koenig --- src/gui/plugins/component_inspector/ComponentInspector.qrc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qrc b/src/gui/plugins/component_inspector/ComponentInspector.qrc index 417bbdedaf..19a0d1cddc 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qrc +++ b/src/gui/plugins/component_inspector/ComponentInspector.qrc @@ -6,6 +6,7 @@ ExpandingTypeHeader.qml Light.qml NoData.qml + Noise.qml Physics.qml Pose3d.qml SphericalCoordinates.qml From 92bed4e6d1ba07f8025a9dcf9d0e8a8fd91f7c6e Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Fri, 5 Nov 2021 17:56:53 -0700 Subject: [PATCH 03/15] Add noise to qrc Signed-off-by: Nate Koenig --- src/cmd/ModelCommandAPI.cc | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/cmd/ModelCommandAPI.cc b/src/cmd/ModelCommandAPI.cc index f467668e9f..77dc9e967c 100644 --- a/src/cmd/ModelCommandAPI.cc +++ b/src/cmd/ModelCommandAPI.cc @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -182,6 +184,31 @@ void printNoise(const sdf::Noise &_noise, int _spaces) << _noise.DynamicBiasCorrelationTime() << std::endl; } +////////////////////////////////////////////////// +/// \brief Print info about an air pressure sensor. +/// \param[in] _entity Entity to print information for. Nothing is +/// printed if the entity is not an air pressure sensor. +/// \param[in] _ecm The entity component manager. +/// \param[in] _spaces Number of spaces to indent for every line +void printAirPressure(const uint64_t _entity, + const EntityComponentManager &_ecm, int _spaces) +{ + // Get the type and return if the _entity does not have the correct + // component. + auto comp = _ecm.Component(_entity); + if (!comp) + return; + + const sdf::Sensor &sensor = comp->Data(); + const sdf::AirPressure *air = sensor.AirPressureSensor(); + + std::cout << std::string(_spaces, ' ') << "- Reference altitude: " + << air->ReferenceAltitude() << "\n"; + + std::cout << std::string(_spaces, ' ') << "- Pressure noise:\n"; + printNoise(air->PressureNoise(), _spaces + 2); +} + ////////////////////////////////////////////////// /// \brief Print info about an altimeter sensor. /// \param[in] _entity Entity to print information for. Nothing is @@ -603,6 +630,7 @@ void printLinks(const uint64_t _modelEntity, // Run through all the sensor print statements. Each function will // exit early if the the sensor is the wrong type. + printAirPressure(sensor, _ecm, spaces + 2); printAltimeter(sensor, _ecm, spaces + 2); printCamera(sensor, _ecm, spaces + 2); printImu(sensor, _ecm, spaces + 2); From 103cdf020218c7c3b1e2ae9dc995ad7d57e6c446 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 8 Nov 2021 08:20:07 -0600 Subject: [PATCH 04/15] Fix lint Signed-off-by: Michael Carroll --- src/gui/plugins/component_inspector/ComponentInspector.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index 1fcc4f8b85..c34e2dd567 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -17,6 +17,8 @@ #include #include +#include + #include #include #include From 2029d2be38b993387aed347125759f849558898b Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Mon, 8 Nov 2021 10:51:26 -0800 Subject: [PATCH 05/15] Update sensor icon Signed-off-by: Nate Koenig --- src/gui/resources/images/sensor.png | Bin 27876 -> 7013 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/gui/resources/images/sensor.png b/src/gui/resources/images/sensor.png index dbf4d003e1498fc3df454318dad2c0c1fa798d8f..eeb2ff65c7484da760b1febda756ca0c46dbaf60 100644 GIT binary patch literal 7013 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 Qx>sPE(@w`Cd)oQ`0+WQe<^TWy literal 27876 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 V+?FI_N2 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 From 2d8d50fd9e53479a56ba6640256454c67e2afaac Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Tue, 9 Nov 2021 08:09:35 -0800 Subject: [PATCH 06/15] Move AirPressure functions out of ComponentInspector (#1179) Signed-off-by: Louise Poubel --- .../component_inspector/AirPressure.cc | 2 +- .../component_inspector/AirPressure.qml | 31 ++++++++++++++----- .../ComponentInspector.qml | 17 ---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/gui/plugins/component_inspector/AirPressure.cc b/src/gui/plugins/component_inspector/AirPressure.cc index 2bc3267e4a..e5c0b15753 100644 --- a/src/gui/plugins/component_inspector/AirPressure.cc +++ b/src/gui/plugins/component_inspector/AirPressure.cc @@ -30,7 +30,7 @@ using namespace gazebo; ///////////////////////////////////////////////// AirPressure::AirPressure(ComponentInspector *_inspector) { - _inspector->Context()->setContextProperty("AirPressure", this); + _inspector->Context()->setContextProperty("AirPressureImpl", this); this->inspector = _inspector; ComponentCreator creator = diff --git a/src/gui/plugins/component_inspector/AirPressure.qml b/src/gui/plugins/component_inspector/AirPressure.qml index 23a89b3b60..ea089e1f25 100644 --- a/src/gui/plugins/component_inspector/AirPressure.qml +++ b/src/gui/plugins/component_inspector/AirPressure.qml @@ -30,6 +30,23 @@ Rectangle { width: componentInspector.width color: index % 2 == 0 ? lightGrey : darkGrey + /** + * Forward air pressure noise changes to C++ + */ + function onAirPressureNoise(_mean, _meanBias, _stdDev, _stdDevBias, + _dynamicBiasStdDev, _dynamicBiasCorrelationTime) { + AirPressureImpl.OnAirPressureNoise( + _mean, _meanBias, _stdDev, _stdDevBias, + _dynamicBiasStdDev, _dynamicBiasCorrelationTime); + } + + /** + * Forward air pressure reference altitude changes to C++ + */ + function onAirPressureReferenceAltitude(_referenceAltitude) { + AirPressureImpl.OnAirPressureReferenceAltitude(_referenceAltitude); + } + Column { anchors.fill: parent @@ -41,7 +58,7 @@ Rectangle { // Set the 'expandingHeaderText' value to override the default header // values, which is based on the model. expandingHeaderText: "Air pressure" - expandingHeaderToolTip: "Air pressure properties" + expandingHeaderToolTip: "Air pressure properties" } // This is the content that will be expanded/contracted using the @@ -82,15 +99,15 @@ Rectangle { Layout.fillWidth: true height: 40 property double refAltitude: model.data[0] - value: referenceAltitudeSpin.activeFocus ? referenceAltitudeSpin.value : refAltitude + value: referenceAltitudeSpin.activeFocus ? referenceAltitudeSpin.value : refAltitude - minimumValue: 0 - maximumValue: 100000 - decimals:4 + minimumValue: 0 + maximumValue: 100000 + decimals:4 stepSize: 0.1 onEditingFinished: { refAltitude = referenceAltitudeSpin.value - componentInspector.onAirPressureReferenceAltitude(refAltitude); + onAirPressureReferenceAltitude(refAltitude); } } } @@ -127,7 +144,7 @@ Rectangle { // Connect to the onNoiseUpdate signal in Noise.qml Component.onCompleted: { pressureNoise.onNoiseUpdate.connect( - componentInspector.onAirPressureNoise) + onAirPressureNoise) } } } diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 66cff0065b..86ad73a023 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -87,23 +87,6 @@ Rectangle { return 6; } - /** - * Forward air pressure noise changes to C++ - */ - function onAirPressureNoise(_mean, _meanBias, _stdDev, _stdDevBias, - _dynamicBiasStdDev, _dynamicBiasCorrelationTime) { - AirPressure.OnAirPressureNoise( - _mean, _meanBias, _stdDev, _stdDevBias, - _dynamicBiasStdDev, _dynamicBiasCorrelationTime); - } - - /** - * Forward air pressure reference altitude changes to C++ - */ - function onAirPressureReferenceAltitude(_referenceAltitude) { - AirPressure.OnAirPressureReferenceAltitude(_referenceAltitude); - } - /** * Forward pose changes to C++ */ From 1a3b5ee68c656505bba5f602e7d9a30e347c25f9 Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Tue, 9 Nov 2021 10:43:10 -0800 Subject: [PATCH 07/15] Fix get decimals, and address comments Signed-off-by: Nate Koenig --- .../component_inspector/AirPressure.cc | 2 +- .../component_inspector/AirPressure.hh | 5 +++- .../component_inspector/AirPressure.qml | 2 +- .../component_inspector/ComponentInspector.cc | 2 +- .../ComponentInspector.qml | 27 ++++++++++++++++--- src/gui/plugins/component_inspector/Noise.qml | 13 ++++----- .../plugins/component_inspector/Pose3d.qml | 2 +- .../plugins/component_inspector/Vector3d.qml | 4 +-- 8 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/gui/plugins/component_inspector/AirPressure.cc b/src/gui/plugins/component_inspector/AirPressure.cc index e5c0b15753..d9b9656598 100644 --- a/src/gui/plugins/component_inspector/AirPressure.cc +++ b/src/gui/plugins/component_inspector/AirPressure.cc @@ -20,9 +20,9 @@ #include #include +#include "AirPressure.hh" #include "ComponentInspector.hh" #include "Types.hh" -#include "AirPressure.hh" using namespace ignition; using namespace gazebo; diff --git a/src/gui/plugins/component_inspector/AirPressure.hh b/src/gui/plugins/component_inspector/AirPressure.hh index 7d00c2aefa..9999b0068d 100644 --- a/src/gui/plugins/component_inspector/AirPressure.hh +++ b/src/gui/plugins/component_inspector/AirPressure.hh @@ -32,7 +32,7 @@ namespace gazebo /// \brief Constructor /// \param[in] _inspector The component inspector. - public: AirPressure(ComponentInspector *_inspector); + public: explicit AirPressure(ComponentInspector *_inspector); /// \brief This function is called when a user changes values in the /// air pressure sensor. @@ -48,6 +48,9 @@ namespace gazebo double _stdDevBias, double _dynamicBiasStdDev, double _dynamicBiasCorrelationTime); + /// \brief This function is called when a user changes the air pressure + /// reference altitude. + /// \param[in] _referenceAltitude New reference altitude value. public: Q_INVOKABLE void OnAirPressureReferenceAltitude( double _referenceAltitude); diff --git a/src/gui/plugins/component_inspector/AirPressure.qml b/src/gui/plugins/component_inspector/AirPressure.qml index ea089e1f25..d7490140b1 100644 --- a/src/gui/plugins/component_inspector/AirPressure.qml +++ b/src/gui/plugins/component_inspector/AirPressure.qml @@ -103,7 +103,7 @@ Rectangle { minimumValue: 0 maximumValue: 100000 - decimals:4 + decimals: componentInspector.getDecimalsAdjustValue(referenceAltitudeSpin, refAltitude) stepSize: 0.1 onEditingFinished: { refAltitude = referenceAltitudeSpin.value diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index c34e2dd567..b9c6568e5c 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -107,7 +107,7 @@ namespace ignition::gazebo /// \brief Air pressure sensor inspector elements public: std::unique_ptr airPressure; - /// \brief Set of callbacks to execture during the Update function. + /// \brief Set of callbacks to execute during the Update function. public: std::vector< std::function> updateCallbacks; diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 86ad73a023..41c0f9b4dc 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -78,13 +78,32 @@ Rectangle { return _model.dataType + '.qml' } - // Get number of decimal digits based on a widget's width + // Get number of decimal digits based on a width value + // \param[in] _width Pixel width + // \return Number of decimals that fit with the provided width. function getDecimals(_width) { + // Use full decimals if the width is <= 0, which allows the value + // to appear correctly. + if (_width <= 0 || _width > 110) + return 6; + if (_width <= 80) return 2; - else if (_width <= 100) - return 4; - return 6; + + return 4; + } + + // Get number of decimal digits based on a widget's width, and adjust the + // widget's value to prevent zero padding. + // \param[in, out] _widgetId The widget id that will display the value. This + // widget's width attribute is used to determine the number of decimals. + // \param[in] _value The value that should be used to set the _widgetId's + // value attribute. + function getDecimalsAdjustValue(_widgetId, _value) { + // Make sure to update the value, otherwise zeros are used intead of + // the actual values. + _widgetId.value = _widgetId.activeFocus ? _widgetId.value : _value; + return getDecimals(_widgetId.width); } /** diff --git a/src/gui/plugins/component_inspector/Noise.qml b/src/gui/plugins/component_inspector/Noise.qml index cab75f0638..028cf9e5bb 100644 --- a/src/gui/plugins/component_inspector/Noise.qml +++ b/src/gui/plugins/component_inspector/Noise.qml @@ -130,9 +130,10 @@ Rectangle { minimumValue: 0 maximumValue: 100000 - decimals:4 + decimals: componentInspector.getDecimalsAdjustValue(meanSpin, meanValue) stepSize: 0.1 onEditingFinished: { + console.log("Width", meanSpin.width); meanValue = meanSpin.value; onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, dynamicBiasStdDev, dynamicBiasCorrelationTime); @@ -177,7 +178,7 @@ Rectangle { minimumValue: 0 maximumValue: 100000 - decimals:4 + decimals: componentInspector.getDecimalsAdjustValue(meanBiasSpin, meanBias) stepSize: 0.1 onEditingFinished: { meanBias = meanBiasSpin.value; @@ -239,7 +240,7 @@ Rectangle { minimumValue: 0 maximumValue: 100000 - decimals:4 + decimals: componentInspector.getDecimalsAdjustValue(stddevSpin, stdDevValue) stepSize: 0.1 onEditingFinished: { stdDevValue = stddevSpin.value; @@ -286,7 +287,7 @@ Rectangle { minimumValue: 0 maximumValue: 100000 - decimals:4 + decimals: componentInspector.getDecimalsAdjustValue(stddevBiasSpin, stdDevBias) stepSize: 0.1 onEditingFinished: { stdDevBias = stddevBiasSpin.value; @@ -349,7 +350,7 @@ Rectangle { minimumValue: 0 maximumValue: 100000 - decimals:4 + decimals: componentInspector.getDecimalsAdjustValue(dynamicBiasStdDevSpin, dynamicBiasStdDev) stepSize: 0.1 onEditingFinished: { dynamicBiasStdDev = dynamicBiasStdDevSpin.value; @@ -397,7 +398,7 @@ Rectangle { minimumValue: 0 maximumValue: 100000 - decimals:4 + decimals: componentInspector.getDecimalsAdjustValue(dynamicBiasCorrelationTimeSpin, dynamicBiasCorrelationTime) stepSize: 0.1 onEditingFinished: { dynamicBiasCorrelationTime = dynamicBiasCorrelationTimeSpin.value; diff --git a/src/gui/plugins/component_inspector/Pose3d.qml b/src/gui/plugins/component_inspector/Pose3d.qml index e9806463e4..45784f9ac9 100644 --- a/src/gui/plugins/component_inspector/Pose3d.qml +++ b/src/gui/plugins/component_inspector/Pose3d.qml @@ -93,7 +93,7 @@ Rectangle { value: writableSpin.activeFocus ? writableSpin.value : numberValue minimumValue: -spinMax maximumValue: spinMax - decimals: getDecimals(writableSpin.width) + decimals: getDecimalsAdjustValue(writableSpin, numberValue) onEditingFinished: { sendPose() } diff --git a/src/gui/plugins/component_inspector/Vector3d.qml b/src/gui/plugins/component_inspector/Vector3d.qml index 1cc4f42ca6..5a14072926 100644 --- a/src/gui/plugins/component_inspector/Vector3d.qml +++ b/src/gui/plugins/component_inspector/Vector3d.qml @@ -58,7 +58,7 @@ Rectangle { value: numberValue minimumValue: -spinMax maximumValue: spinMax - decimals: getDecimals(writableSpin.width) + decimals: getDecimalsAdjustValue(writableSpin, numberValue) } } @@ -73,7 +73,7 @@ Rectangle { horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter text: { - var decimals = getDecimals(numberText.width) + var decimals = getDecimals(numberText) return numberValue.toFixed(decimals) } } From 5c0a4c0cc04e85ae1997397c05a4de3bc6c77741 Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Tue, 9 Nov 2021 11:44:57 -0800 Subject: [PATCH 08/15] cleanup and simplification Signed-off-by: Nate Koenig --- .../component_inspector/AirPressure.qml | 21 ++----------------- src/gui/plugins/component_inspector/Noise.qml | 1 - 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/gui/plugins/component_inspector/AirPressure.qml b/src/gui/plugins/component_inspector/AirPressure.qml index d7490140b1..3a71f14a97 100644 --- a/src/gui/plugins/component_inspector/AirPressure.qml +++ b/src/gui/plugins/component_inspector/AirPressure.qml @@ -30,23 +30,6 @@ Rectangle { width: componentInspector.width color: index % 2 == 0 ? lightGrey : darkGrey - /** - * Forward air pressure noise changes to C++ - */ - function onAirPressureNoise(_mean, _meanBias, _stdDev, _stdDevBias, - _dynamicBiasStdDev, _dynamicBiasCorrelationTime) { - AirPressureImpl.OnAirPressureNoise( - _mean, _meanBias, _stdDev, _stdDevBias, - _dynamicBiasStdDev, _dynamicBiasCorrelationTime); - } - - /** - * Forward air pressure reference altitude changes to C++ - */ - function onAirPressureReferenceAltitude(_referenceAltitude) { - AirPressureImpl.OnAirPressureReferenceAltitude(_referenceAltitude); - } - Column { anchors.fill: parent @@ -107,7 +90,7 @@ Rectangle { stepSize: 0.1 onEditingFinished: { refAltitude = referenceAltitudeSpin.value - onAirPressureReferenceAltitude(refAltitude); + AirPressureImpl.OnAirPressureReferenceAltitude(refAltitude); } } } @@ -144,7 +127,7 @@ Rectangle { // Connect to the onNoiseUpdate signal in Noise.qml Component.onCompleted: { pressureNoise.onNoiseUpdate.connect( - onAirPressureNoise) + AirPressureImpl.OnAirPressureNoise) } } } diff --git a/src/gui/plugins/component_inspector/Noise.qml b/src/gui/plugins/component_inspector/Noise.qml index 028cf9e5bb..847d301d5e 100644 --- a/src/gui/plugins/component_inspector/Noise.qml +++ b/src/gui/plugins/component_inspector/Noise.qml @@ -133,7 +133,6 @@ Rectangle { decimals: componentInspector.getDecimalsAdjustValue(meanSpin, meanValue) stepSize: 0.1 onEditingFinished: { - console.log("Width", meanSpin.width); meanValue = meanSpin.value; onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, dynamicBiasStdDev, dynamicBiasCorrelationTime); From 9e7250d111f72d725238b0cf9c5fd9593f5c199c Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Tue, 9 Nov 2021 13:35:45 -0800 Subject: [PATCH 09/15] Require sdf 12.1.0 Signed-off-by: Nate Koenig --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 406ee1b308..4357ef5828 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # as protobuf could be find transitively by any dependency set(protobuf_MODULE_COMPATIBLE TRUE) -ign_find_package(sdformat12 REQUIRED) +ign_find_package(sdformat12 REQUIRED VERSION 12.1) set(SDF_VER ${sdformat12_VERSION_MAJOR}) #-------------------------------------- From d5526034f651e8a8825eb9bf5b555fe692df7c17 Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Tue, 9 Nov 2021 15:51:25 -0800 Subject: [PATCH 10/15] missign width Signed-off-by: Nate Koenig --- src/gui/plugins/component_inspector/Vector3d.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/plugins/component_inspector/Vector3d.qml b/src/gui/plugins/component_inspector/Vector3d.qml index 5a14072926..61e2dadc07 100644 --- a/src/gui/plugins/component_inspector/Vector3d.qml +++ b/src/gui/plugins/component_inspector/Vector3d.qml @@ -73,7 +73,7 @@ Rectangle { horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter text: { - var decimals = getDecimals(numberText) + var decimals = getDecimals(numberText.width) return numberValue.toFixed(decimals) } } From fbfbe499bee264be85e3f3cbcada0274da43ba4e Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Wed, 10 Nov 2021 15:26:30 -0800 Subject: [PATCH 11/15] Added simulation state aware spin box Signed-off-by: Nate Koenig --- .../component_inspector/AirPressure.qml | 20 +-- .../component_inspector/ComponentInspector.cc | 23 ++- .../component_inspector/ComponentInspector.hh | 18 ++ .../ComponentInspector.qml | 15 +- .../ComponentInspector.qrc | 1 + src/gui/plugins/component_inspector/Noise.qml | 156 ++++++++++-------- .../component_inspector/StateAwareSpin.qml | 109 ++++++++++++ .../scene_broadcaster/SceneBroadcaster.cc | 23 ++- 8 files changed, 272 insertions(+), 93 deletions(-) create mode 100644 src/gui/plugins/component_inspector/StateAwareSpin.qml diff --git a/src/gui/plugins/component_inspector/AirPressure.qml b/src/gui/plugins/component_inspector/AirPressure.qml index 3a71f14a97..21294ef1aa 100644 --- a/src/gui/plugins/component_inspector/AirPressure.qml +++ b/src/gui/plugins/component_inspector/AirPressure.qml @@ -77,20 +77,18 @@ Rectangle { color: "dimgrey" font.pointSize: 12 } - IgnSpinBox { + StateAwareSpin { id: referenceAltitudeSpin Layout.fillWidth: true height: 40 - property double refAltitude: model.data[0] - value: referenceAltitudeSpin.activeFocus ? referenceAltitudeSpin.value : refAltitude - - minimumValue: 0 - maximumValue: 100000 - decimals: componentInspector.getDecimalsAdjustValue(referenceAltitudeSpin, refAltitude) - stepSize: 0.1 - onEditingFinished: { - refAltitude = referenceAltitudeSpin.value - AirPressureImpl.OnAirPressureReferenceAltitude(refAltitude); + numberValue: model.data[0] + minValue: 0 + maxValue: 100000 + stepValue: 0.1 + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + referenceAltitudeSpin.onChange.connect( + AirPressureImpl.OnAirPressureReferenceAltitude) } } } diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index 3b07ee8fd9..ef710e776c 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -103,6 +103,9 @@ namespace ignition::gazebo /// \brief Whether updates are currently paused. public: bool paused{false}; + /// \brief Whether simulation is currently paused. + public: bool simPaused{true}; + /// \brief Transport node for making command requests public: transport::Node node; @@ -425,11 +428,13 @@ void ComponentInspector::LoadConfig(const tinyxml2::XMLElement *) } ////////////////////////////////////////////////// -void ComponentInspector::Update(const UpdateInfo &, +void ComponentInspector::Update(const UpdateInfo &_info, EntityComponentManager &_ecm) { IGN_PROFILE("ComponentInspector::Update"); + this->SetSimPaused(_info.paused); + auto componentTypes = _ecm.ComponentTypes(this->dataPtr->entity); // List all components @@ -905,6 +910,22 @@ void ComponentInspector::SetLocked(bool _locked) this->LockedChanged(); } +///////////////////////////////////////////////// +bool ComponentInspector::SimPaused() const +{ + return this->dataPtr->simPaused; +} + +///////////////////////////////////////////////// +void ComponentInspector::SetSimPaused(bool _paused) +{ + if (_paused != this->dataPtr->simPaused) + { + this->dataPtr->simPaused = _paused; + this->SimPausedChanged(); + } +} + ///////////////////////////////////////////////// bool ComponentInspector::Paused() const { diff --git a/src/gui/plugins/component_inspector/ComponentInspector.hh b/src/gui/plugins/component_inspector/ComponentInspector.hh index b304f00ae9..86cddbe837 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.hh +++ b/src/gui/plugins/component_inspector/ComponentInspector.hh @@ -146,6 +146,13 @@ namespace gazebo NOTIFY PausedChanged ) + /// \brief Simulation paused + Q_PROPERTY( + bool simPaused + READ SimPaused + NOTIFY SimPausedChanged + ) + /// \brief Nested Model Q_PROPERTY( bool nestedModel @@ -265,6 +272,17 @@ namespace gazebo /// \brief Notify that locked has changed. signals: void LockedChanged(); + /// \brief Get whether simulation is currently paused. + /// \return True for paused. + public: Q_INVOKABLE bool SimPaused() const; + + /// \brief Notify that simulation paused state has changed. + signals: void SimPausedChanged(); + + /// \brief Set whether simulation is currently paused. + /// \param[in] _paused True for paused. + public: void SetSimPaused(bool _paused); + /// \brief Get whether the inspector is currently paused for updates. /// \return True for paused. public: Q_INVOKABLE bool Paused() const; diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 41c0f9b4dc..44dbd59b4a 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -78,6 +78,11 @@ Rectangle { return _model.dataType + '.qml' } + function getSimPaused() { + console.log("Is paused:",ComponentInspector.simPaused) + return ComponentInspector.simPaused + } + // Get number of decimal digits based on a width value // \param[in] _width Pixel width // \return Number of decimals that fit with the provided width. @@ -85,12 +90,12 @@ Rectangle { // Use full decimals if the width is <= 0, which allows the value // to appear correctly. if (_width <= 0 || _width > 110) - return 6; + return 6 if (_width <= 80) - return 2; + return 2 - return 4; + return 4 } // Get number of decimal digits based on a widget's width, and adjust the @@ -102,8 +107,8 @@ Rectangle { function getDecimalsAdjustValue(_widgetId, _value) { // Make sure to update the value, otherwise zeros are used intead of // the actual values. - _widgetId.value = _widgetId.activeFocus ? _widgetId.value : _value; - return getDecimals(_widgetId.width); + _widgetId.value = _widgetId.activeFocus ? _widgetId.value : _value + return getDecimals(_widgetId.width) } /** diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qrc b/src/gui/plugins/component_inspector/ComponentInspector.qrc index 19a0d1cddc..7b83df0784 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qrc +++ b/src/gui/plugins/component_inspector/ComponentInspector.qrc @@ -13,6 +13,7 @@ String.qml TypeHeader.qml Vector3d.qml + StateAwareSpin.qml plottable_icon.svg diff --git a/src/gui/plugins/component_inspector/Noise.qml b/src/gui/plugins/component_inspector/Noise.qml index 847d301d5e..f2a39d74de 100644 --- a/src/gui/plugins/component_inspector/Noise.qml +++ b/src/gui/plugins/component_inspector/Noise.qml @@ -29,7 +29,6 @@ Rectangle { id: noise height: noiseContent.height color: "transparent" - objectName: "NoiseQML" // Mean value property double meanValue: 0.0 @@ -55,6 +54,42 @@ Rectangle { double _stdDevBias, double _dynamicBiasStdDev, double _dynamicBiasCorrelationTime) + function onMean(_value) { + meanValue = _value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + + function onMeanBias(_value) { + meanBias = _value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + + function onStdDev(_value) { + stdDevValue = _value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + + function onStdDevBias(_value) { + stdDevBias = _value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + + function onDynamicBiasStdDev(_value) { + dynamicBiasStdDev = _value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + + function onDynamicBiasCorrelationTime(_value) { + dynamicBiasCorrelationTime = _value; + onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, + dynamicBiasStdDev, dynamicBiasCorrelationTime); + } + // Display the main content Column { anchors.fill: parent @@ -122,20 +157,17 @@ Rectangle { } } } - IgnSpinBox { + StateAwareSpin { id: meanSpin Layout.fillWidth: true height: 40 - value: meanSpin.activeFocus ? meanSpin.value : meanValue - - minimumValue: 0 - maximumValue: 100000 - decimals: componentInspector.getDecimalsAdjustValue(meanSpin, meanValue) - stepSize: 0.1 - onEditingFinished: { - meanValue = meanSpin.value; - onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, - dynamicBiasStdDev, dynamicBiasCorrelationTime); + numberValue: meanValue + minValue: -100000 + maxValue: 100000 + stepValue: 0.1 + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + meanSpin.onChange.connect(onMean) } } // End of mean @@ -168,21 +200,17 @@ Rectangle { } } } - IgnSpinBox { + StateAwareSpin { id: meanBiasSpin Layout.fillWidth: true height: 40 - property double numberValue: meanBias - value: meanBiasSpin.activeFocus ? meanBiasSpin.value : numberValue - - minimumValue: 0 - maximumValue: 100000 - decimals: componentInspector.getDecimalsAdjustValue(meanBiasSpin, meanBias) - stepSize: 0.1 - onEditingFinished: { - meanBias = meanBiasSpin.value; - onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, - dynamicBiasStdDev, dynamicBiasCorrelationTime); + numberValue: meanBias + minValue: -100000 + maxValue: 100000 + stepValue: 0.1 + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + meanBiasSpin.onChange.connect(onMeanBias) } } // End of mean bias @@ -230,21 +258,17 @@ Rectangle { } } } - IgnSpinBox { + StateAwareSpin { id: stddevSpin Layout.fillWidth: true height: 40 - property double numberValue: stdDevValue - value: stddevSpin.activeFocus ? stddevSpin.value : numberValue - - minimumValue: 0 - maximumValue: 100000 - decimals: componentInspector.getDecimalsAdjustValue(stddevSpin, stdDevValue) - stepSize: 0.1 - onEditingFinished: { - stdDevValue = stddevSpin.value; - onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, - dynamicBiasStdDev, dynamicBiasCorrelationTime); + numberValue: stdDevValue + minValue: 0 + maxValue: 100000 + stepValue: 0.1 + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + stddevSpin.onChange.connect(onStdDev) } } // End of stddev @@ -277,21 +301,17 @@ Rectangle { } } } - IgnSpinBox { + StateAwareSpin { id: stddevBiasSpin Layout.fillWidth: true height: 40 - property double numberValue: stdDevBias - value: stddevBiasSpin.activeFocus ? stddevBiasSpin.value : numberValue - - minimumValue: 0 - maximumValue: 100000 - decimals: componentInspector.getDecimalsAdjustValue(stddevBiasSpin, stdDevBias) - stepSize: 0.1 - onEditingFinished: { - stdDevBias = stddevBiasSpin.value; - onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, - dynamicBiasStdDev, dynamicBiasCorrelationTime); + numberValue: stdDevBias + minValue: 0 + maxValue: 100000 + stepValue: 0.1 + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + stddevBiasSpin.onChange.connect(onStdDevBias) } } // End of stddev bias @@ -340,21 +360,17 @@ Rectangle { } } - IgnSpinBox { + StateAwareSpin { id: dynamicBiasStdDevSpin Layout.fillWidth: true height: 40 - property double numberValue: dynamicBiasStdDev - value: dynamicBiasStdDevSpin.activeFocus ? dynamicBiasStdDevSpin.value : numberValue - - minimumValue: 0 - maximumValue: 100000 - decimals: componentInspector.getDecimalsAdjustValue(dynamicBiasStdDevSpin, dynamicBiasStdDev) - stepSize: 0.1 - onEditingFinished: { - dynamicBiasStdDev = dynamicBiasStdDevSpin.value; - onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, - dynamicBiasStdDev, dynamicBiasCorrelationTime); + numberValue: dynamicBiasStdDev + minValue: 0 + maxValue: 100000 + stepValue: 0.1 + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + dynamicBiasStdDevSpin.onChange.connect(onDynamicBiasStdDev) } } // End of dynamic bias stddev @@ -388,21 +404,17 @@ Rectangle { } } } - IgnSpinBox { + StateAwareSpin { id: dynamicBiasCorrelationTimeSpin Layout.fillWidth: true height: 40 - property double numberValue: dynamicBiasCorrelationTime - value: dynamicBiasCorrelationTimeSpin.activeFocus ? dynamicBiasCorrelationTimeSpin.value : numberValue - - minimumValue: 0 - maximumValue: 100000 - decimals: componentInspector.getDecimalsAdjustValue(dynamicBiasCorrelationTimeSpin, dynamicBiasCorrelationTime) - stepSize: 0.1 - onEditingFinished: { - dynamicBiasCorrelationTime = dynamicBiasCorrelationTimeSpin.value; - onNoiseUpdate(meanValue, meanBias, stdDevValue, stdDevBias, - dynamicBiasStdDev, dynamicBiasCorrelationTime); + numberValue: dynamicBiasCorrelationTime + minValue: 0 + maxValue: 100000 + stepValue: 0.1 + // Connect to the onNoiseUpdate signal in Noise.qml + Component.onCompleted: { + dynamicBiasCorrelationTimeSpin.onChange.connect(onDynamicBiasCorrelationTime) } } // End of dynamic bias correlation time stddev bias diff --git a/src/gui/plugins/component_inspector/StateAwareSpin.qml b/src/gui/plugins/component_inspector/StateAwareSpin.qml new file mode 100644 index 0000000000..33db4d3e73 --- /dev/null +++ b/src/gui/plugins/component_inspector/StateAwareSpin.qml @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Dialogs 1.0 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 +import "qrc:/ComponentInspector" +import "qrc:/qml" + +Rectangle { + id: stateAwareSpin + height: stateAwareSpinContent.height + color: "transparent" + + // step size + property double stepValue: 0.1 + + // min value + property double minValue: 0 + + // max value + property double maxValue: 1 + + // value of the spin number value + property double numberValue: 0.0 + + signal onChange(double _value) + + /** + * Used to create a spin box + */ + Component { + id: writableNumber + IgnSpinBox { + id: writableSpin + value: writableSpin.activeFocus ? writableSpin.value : numberValue + minimumValue: minValue + maximumValue: maxValue + stepSize: stepValue + decimals: { + writableSpin.value = writableSpin.activeFocus ? writableSpin.value : numberValue + componentInspector.getDecimals(writableSpin.width) + } + onEditingFinished: { + numberValue = writableSpin.value + onChange(writableSpin.value) + } + } + } + + /** + * Used to create a read-only number + */ + Component { + id: readOnlyNumber + Rectangle { + border.width: 1 + color: index % 2 == 0 ? lightGrey : darkGrey + implicitHeight: 40 + Text { + padding: 10 + id: numberText + anchors.fill: parent + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + text: { + return numberValue.toFixed(componentInspector.getDecimals(numberText.width)) + } + } + } + } + + Column { + anchors.fill: parent + + // Content + Rectangle { + id: stateAwareSpinContent + width: parent.width + height: stateAwareSpinContentLoader.height + clip: true + color: "transparent" + + Loader { + id: stateAwareSpinContentLoader + width: parent.width + Layout.fillWidth: true + sourceComponent: componentInspector.getSimPaused() ? writableNumber : readOnlyNumber + } + } + } +} diff --git a/src/systems/scene_broadcaster/SceneBroadcaster.cc b/src/systems/scene_broadcaster/SceneBroadcaster.cc index c299ca50f0..cec3166616 100644 --- a/src/systems/scene_broadcaster/SceneBroadcaster.cc +++ b/src/systems/scene_broadcaster/SceneBroadcaster.cc @@ -199,9 +199,18 @@ class ignition::gazebo::systems::SceneBroadcasterPrivate public: std::chrono::time_point lastStatePubTime{std::chrono::system_clock::now()}; + /// \brief Period to publish state while paused and running. The key for + /// the map is used to store the publish period when paused and not + /// paused. The not-paused (a.ka. running) period has a key=false and a + /// default update rate of 60Hz. The paused period has a key=true and a + /// default update rate of 30Hz. + public: std::map>> + statePublishPeriod{{false, std::chrono::milliseconds(1000/60)}, + {true, std::chrono::milliseconds(1000/30)}}; + /// \brief Period to publish state, defaults to 60 Hz. public: std::chrono::duration> - statePublishPeriod{std::chrono::milliseconds(1000/60)}; + statePublishPeriodPaused{std::chrono::milliseconds(1000/30)}; /// \brief Flag used to indicate if the state service was called. public: bool stateServiceRequest{false}; @@ -237,10 +246,16 @@ void SceneBroadcaster::Configure( this->dataPtr->dyPoseHertz = readHertz.first; auto stateHerz = _sdf->Get("state_hertz", 60); - this->dataPtr->statePublishPeriod = + this->dataPtr->statePublishPeriod[false] = std::chrono::duration>( std::chrono::milliseconds(1000/stateHerz.first)); + // Set the paused update rate to half of the running update rate. + this->dataPtr->statePublishPeriod[true] = + std::chrono::duration>( + std::chrono::milliseconds(1000 / + static_cast(std::max(stateHerz.first * 0.5, 1.0)))); + // Add to graph { std::lock_guard lock(this->dataPtr->graphMutex); @@ -286,8 +301,8 @@ void SceneBroadcaster::PostUpdate(const UpdateInfo &_info, _manager.HasNewEntities() || _manager.HasOneTimeComponentChanges() || jumpBackInTime; auto now = std::chrono::system_clock::now(); - bool itsPubTime = !_info.paused && (now - this->dataPtr->lastStatePubTime > - this->dataPtr->statePublishPeriod); + bool itsPubTime = (now - this->dataPtr->lastStatePubTime > + this->dataPtr->statePublishPeriod[_info.paused]); auto shouldPublish = this->dataPtr->statePub.HasConnections() && (changeEvent || itsPubTime); From 8e4c87b752a43251535b41386196a3113e2d0edb Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Wed, 10 Nov 2021 16:11:11 -0800 Subject: [PATCH 12/15] Remove console output Signed-off-by: Nate Koenig --- src/gui/plugins/component_inspector/ComponentInspector.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 31a6d66307..245c057752 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -80,8 +80,8 @@ Rectangle { return _model.dataType + '.qml' } + /// \brief Get whether simulation is paused function getSimPaused() { - console.log("Is paused:",ComponentInspector.simPaused) return ComponentInspector.simPaused } From 3cdffb452c29edf820a4f53bed19188781529a38 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 12 Nov 2021 16:06:07 -0600 Subject: [PATCH 13/15] Allow user to modify joint type Signed-off-by: Michael Carroll --- .../component_inspector/ComponentInspector.cc | 77 ++++++++++ .../component_inspector/ComponentInspector.hh | 11 ++ .../ComponentInspector.qml | 8 + .../ComponentInspector.qrc | 1 + .../plugins/component_inspector/JointType.qml | 140 ++++++++++++++++++ 5 files changed, 237 insertions(+) create mode 100644 src/gui/plugins/component_inspector/JointType.qml diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index f30794d833..4bb8d1bbfb 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -38,6 +38,7 @@ #include "ignition/gazebo/components/Factory.hh" #include "ignition/gazebo/components/Gravity.hh" #include "ignition/gazebo/components/Joint.hh" +#include "ignition/gazebo/components/JointType.hh" #include "ignition/gazebo/components/Level.hh" #include "ignition/gazebo/components/Light.hh" #include "ignition/gazebo/components/LightCmd.hh" @@ -276,6 +277,39 @@ void ignition::gazebo::setData(QStandardItem *_item, const double &_data) _item->setData(_data, ComponentsModel::RoleNames().key("data")); } +////////////////////////////////////////////////// +template<> +void ignition::gazebo::setData(QStandardItem *_item, const sdf::JointType &_data) +{ + if (nullptr == _item) + return; + + _item->setData(QString("JointType"), + ComponentsModel::RoleNames().key("dataType")); + + QString jointType; + if (_data == sdf::JointType::BALL) + jointType = "Ball"; + else if (_data == sdf::JointType::CONTINUOUS) + jointType = "Continuous"; + else if (_data == sdf::JointType::FIXED) + jointType = "Fixed"; + else if (_data == sdf::JointType::GEARBOX) + jointType = "Gearbox"; + else if (_data == sdf::JointType::PRISMATIC) + jointType = "Prismatic"; + else if (_data == sdf::JointType::REVOLUTE) + jointType = "Revolute"; + else if (_data == sdf::JointType::REVOLUTE2) + jointType = "Revolute2"; + else if (_data == sdf::JointType::SCREW) + jointType = "Screw"; + else if (_data == sdf::JointType::UNIVERSAL) + jointType = "Univeral"; + + _item->setData(jointType, ComponentsModel::RoleNames().key("data")); +} + ////////////////////////////////////////////////// template<> void ignition::gazebo::setData(QStandardItem *_item, const sdf::Physics &_data) @@ -594,6 +628,13 @@ void ComponentInspector::Update(const UpdateInfo &_info, setUnit(item, "m/s\u00B2"); } } + else if (typeId == components::JointType::typeId) + { + auto comp = _ecm.Component( + this->dataPtr->entity); + if (comp) + setData(item, comp->Data()); + } else if (typeId == components::LinearAcceleration::typeId) { auto comp = _ecm.Component( @@ -1091,6 +1132,42 @@ void ComponentInspector::OnSphericalCoordinates(QString _surface, this->dataPtr->node.Request(sphericalCoordsCmdService, req, cb); } +///////////////////////////////////////////////// +void ComponentInspector::OnJointType(QString _jointType) +{ + auto entity = this->Entity(); + + ignition::gazebo::UpdateCallback cb = + [=](EntityComponentManager &_ecm) + { + components::JointType *comp = + _ecm.Component(entity); + + if (comp) + { + if (_jointType == "Ball") + comp->Data() = sdf::JointType::BALL; + else if (_jointType == "Continuous") + comp->Data() = sdf::JointType::CONTINUOUS; + else if (_jointType == "Fixed") + comp->Data() = sdf::JointType::FIXED; + else if (_jointType == "Gearbox") + comp->Data() = sdf::JointType::GEARBOX; + else if (_jointType == "Prismatic") + comp->Data() = sdf::JointType::PRISMATIC; + else if (_jointType == "Revolute") + comp->Data() = sdf::JointType::REVOLUTE; + else if (_jointType == "Revolute2") + comp->Data() = sdf::JointType::REVOLUTE2; + else if (_jointType == "Screw") + comp->Data() = sdf::JointType::SCREW; + else if (_jointType == "Universal") + comp->Data() = sdf::JointType::UNIVERSAL; + } + }; + this->AddUpdateCallback(cb); +} + ///////////////////////////////////////////////// bool ComponentInspector::NestedModel() const { diff --git a/src/gui/plugins/component_inspector/ComponentInspector.hh b/src/gui/plugins/component_inspector/ComponentInspector.hh index d51312850d..c1b90c5c88 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.hh +++ b/src/gui/plugins/component_inspector/ComponentInspector.hh @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -65,6 +66,12 @@ namespace gazebo template<> void setData(QStandardItem *_item, const math::Vector3d &_data); + /// \brief Specialized to set Joint Type data. + /// \param[in] _item Item whose data will be set. + /// \param[in] _data Data to set. + template<> + void setData(QStandardItem *_item, const sdf::JointType &_data); + /// \brief Specialized to set Physics data. /// \param[in] _item Item whose data will be set. /// \param[in] _data Data to set. @@ -229,6 +236,10 @@ namespace gazebo double _latitude, double _longitude, double _elevation, double _heading); + /// \brief Callback in Qt thread when joint type changes. + /// \param[in] _surface Surface model + public: Q_INVOKABLE void OnJointType(QString _jointType); + /// \brief Get whether the entity is a nested model or not /// \return True if the entity is a nested model, false otherwise public: Q_INVOKABLE bool NestedModel() const; diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 245c057752..277d37bf87 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -120,6 +120,14 @@ Rectangle { ComponentInspector.OnPose(_x, _y, _z, _roll, _pitch, _yaw) } + /** + * Forward pose changes to C++ + */ + function onJointType(_jointType) { + console.log(_jointType) + ComponentInspector.OnJointType(_jointType) + } + /** * Forward light changes to C++ */ diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qrc b/src/gui/plugins/component_inspector/ComponentInspector.qrc index 7b83df0784..8c3ccbd9d5 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qrc +++ b/src/gui/plugins/component_inspector/ComponentInspector.qrc @@ -4,6 +4,7 @@ Boolean.qml ComponentInspector.qml ExpandingTypeHeader.qml + JointType.qml Light.qml NoData.qml Noise.qml diff --git a/src/gui/plugins/component_inspector/JointType.qml b/src/gui/plugins/component_inspector/JointType.qml new file mode 100644 index 0000000000..8af85ac1ff --- /dev/null +++ b/src/gui/plugins/component_inspector/JointType.qml @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 +import "qrc:/ComponentInspector" +import "qrc:/qml" + +// Item displaying 3D pose information. +Rectangle { + id: jointTypeComponent + height: jointType.height + width: componentInspector.width + color: index % 2 == 0 ? lightGrey : darkGrey + + // Left indentation + property int indentation: 0 + + // Horizontal margins + property int margin: 5 + + ListModel { + id: jointTypes + ListElement { type: "Ball" } + ListElement { type: "Continuous" } + ListElement { type: "Fixed" } + ListElement { type: "Gearbox" } + ListElement { type: "Prismatic" } + ListElement { type: "Revolute" } + ListElement { type: "Revolute2" } + ListElement { type: "Screw" } + ListElement { type: "Universal" } + } + + function indexFromModel(_model) { + if (_model && _model.data !== undefined) + { + for (var i = 0; i < jointTypes.count; i++) + { + if (jointTypes.get(i).type === _model.data) + { + return i; + } + } + } + return -1 + } + + FontMetrics { + id: fontMetrics + font.family: "Roboto" + } + + Column { + anchors.fill: parent + + ExpandingTypeHeader { + id: header + // Using the default header text values. + } + + // Content + Rectangle { + id: content + property bool show: false + width: parent.width + height: show ? grid.height : 0 + clip: true + color: "transparent" + + Behavior on height { + NumberAnimation { + duration: 200; + easing.type: Easing.InOutQuad + } + } + + RowLayout { + id: grid + width: parent.width + + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: jointTypeText.width + indentation * 3 + + Text { + id : jointTypeText + text: ' Joint Type' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } + } + + QtObject{ + // Workaround to keep from using the ListModel as model + id: indexObj + property int index: indexFromModel(model) + } + + Rectangle { + Layout.fillWidth: true + Layout.columnSpan: 4 + Layout.alignment: Qt.AlignRight + + height: 40 + ComboBox { + id: jointType + textRole: "type" + model: jointTypes + currentIndex: indexObj.index + enabled: componentInspector.getSimPaused() + onActivated: (index) => { + componentInspector.onJointType(currentText); + } + } + } + } + } + } +} From c90ff379ab74f10fc7e503f00291f754cb1d4011 Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Tue, 30 Nov 2021 17:19:03 -0800 Subject: [PATCH 14/15] Updated to use a separate class, and consolidate the look Signed-off-by: Nate Koenig --- .../component_inspector/CMakeLists.txt | 2 + .../component_inspector/ComponentInspector.cc | 84 ++----------- .../component_inspector/ComponentInspector.hh | 10 -- .../ComponentInspector.qml | 8 -- .../plugins/component_inspector/JointType.cc | 110 ++++++++++++++++++ .../plugins/component_inspector/JointType.hh | 47 ++++++++ .../plugins/component_inspector/JointType.qml | 94 +++++---------- 7 files changed, 197 insertions(+), 158 deletions(-) create mode 100644 src/gui/plugins/component_inspector/JointType.cc create mode 100644 src/gui/plugins/component_inspector/JointType.hh diff --git a/src/gui/plugins/component_inspector/CMakeLists.txt b/src/gui/plugins/component_inspector/CMakeLists.txt index 8a0f463e63..20908c4b24 100644 --- a/src/gui/plugins/component_inspector/CMakeLists.txt +++ b/src/gui/plugins/component_inspector/CMakeLists.txt @@ -4,6 +4,7 @@ gz_add_gui_plugin(ComponentInspector Altimeter.cc ComponentInspector.cc Imu.cc + JointType.cc Magnetometer.cc ModelEditor.cc Types.cc @@ -12,6 +13,7 @@ gz_add_gui_plugin(ComponentInspector Altimeter.hh ComponentInspector.hh Imu.hh + JointType.hh Magnetometer.hh ModelEditor.hh Types.hh diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index 3cab4c210a..3239d634ff 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -38,7 +38,6 @@ #include "ignition/gazebo/components/Factory.hh" #include "ignition/gazebo/components/Gravity.hh" #include "ignition/gazebo/components/Joint.hh" -#include "ignition/gazebo/components/JointType.hh" #include "ignition/gazebo/components/Level.hh" #include "ignition/gazebo/components/Light.hh" #include "ignition/gazebo/components/LightCmd.hh" @@ -75,6 +74,7 @@ #include "Altimeter.hh" #include "ComponentInspector.hh" #include "Imu.hh" +#include "JointType.hh" #include "Magnetometer.hh" #include "ModelEditor.hh" @@ -127,6 +127,9 @@ namespace ignition::gazebo /// \brief Imu inspector elements public: std::unique_ptr imu; + /// \brief Joint inspector elements + public: std::unique_ptr joint; + /// \brief Magnetometer inspector elements public: std::unique_ptr magnetometer; @@ -289,39 +292,6 @@ void ignition::gazebo::setData(QStandardItem *_item, const double &_data) _item->setData(_data, ComponentsModel::RoleNames().key("data")); } -////////////////////////////////////////////////// -template<> -void ignition::gazebo::setData(QStandardItem *_item, const sdf::JointType &_data) -{ - if (nullptr == _item) - return; - - _item->setData(QString("JointType"), - ComponentsModel::RoleNames().key("dataType")); - - QString jointType; - if (_data == sdf::JointType::BALL) - jointType = "Ball"; - else if (_data == sdf::JointType::CONTINUOUS) - jointType = "Continuous"; - else if (_data == sdf::JointType::FIXED) - jointType = "Fixed"; - else if (_data == sdf::JointType::GEARBOX) - jointType = "Gearbox"; - else if (_data == sdf::JointType::PRISMATIC) - jointType = "Prismatic"; - else if (_data == sdf::JointType::REVOLUTE) - jointType = "Revolute"; - else if (_data == sdf::JointType::REVOLUTE2) - jointType = "Revolute2"; - else if (_data == sdf::JointType::SCREW) - jointType = "Screw"; - else if (_data == sdf::JointType::UNIVERSAL) - jointType = "Univeral"; - - _item->setData(jointType, ComponentsModel::RoleNames().key("data")); -} - ////////////////////////////////////////////////// template<> void ignition::gazebo::setData(QStandardItem *_item, const sdf::Physics &_data) @@ -485,6 +455,9 @@ void ComponentInspector::LoadConfig(const tinyxml2::XMLElement *) // Create the imu this->dataPtr->imu = std::make_unique(this); + // Create the joint + this->dataPtr->joint = std::make_unique(this); + // Create the magnetometer this->dataPtr->magnetometer = std::make_unique(this); } @@ -649,13 +622,6 @@ void ComponentInspector::Update(const UpdateInfo &_info, setUnit(item, "m/s\u00B2"); } } - else if (typeId == components::JointType::typeId) - { - auto comp = _ecm.Component( - this->dataPtr->entity); - if (comp) - setData(item, comp->Data()); - } else if (typeId == components::LinearAcceleration::typeId) { auto comp = _ecm.Component( @@ -1153,42 +1119,6 @@ void ComponentInspector::OnSphericalCoordinates(QString _surface, this->dataPtr->node.Request(sphericalCoordsCmdService, req, cb); } -///////////////////////////////////////////////// -void ComponentInspector::OnJointType(QString _jointType) -{ - auto entity = this->Entity(); - - ignition::gazebo::UpdateCallback cb = - [=](EntityComponentManager &_ecm) - { - components::JointType *comp = - _ecm.Component(entity); - - if (comp) - { - if (_jointType == "Ball") - comp->Data() = sdf::JointType::BALL; - else if (_jointType == "Continuous") - comp->Data() = sdf::JointType::CONTINUOUS; - else if (_jointType == "Fixed") - comp->Data() = sdf::JointType::FIXED; - else if (_jointType == "Gearbox") - comp->Data() = sdf::JointType::GEARBOX; - else if (_jointType == "Prismatic") - comp->Data() = sdf::JointType::PRISMATIC; - else if (_jointType == "Revolute") - comp->Data() = sdf::JointType::REVOLUTE; - else if (_jointType == "Revolute2") - comp->Data() = sdf::JointType::REVOLUTE2; - else if (_jointType == "Screw") - comp->Data() = sdf::JointType::SCREW; - else if (_jointType == "Universal") - comp->Data() = sdf::JointType::UNIVERSAL; - } - }; - this->AddUpdateCallback(cb); -} - ///////////////////////////////////////////////// bool ComponentInspector::NestedModel() const { diff --git a/src/gui/plugins/component_inspector/ComponentInspector.hh b/src/gui/plugins/component_inspector/ComponentInspector.hh index d9424ee852..501c490905 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.hh +++ b/src/gui/plugins/component_inspector/ComponentInspector.hh @@ -87,12 +87,6 @@ namespace gazebo template<> void setData(QStandardItem *_item, const math::Vector3d &_data); - /// \brief Specialized to set Joint Type data. - /// \param[in] _item Item whose data will be set. - /// \param[in] _data Data to set. - template<> - void setData(QStandardItem *_item, const sdf::JointType &_data); - /// \brief Specialized to set Physics data. /// \param[in] _item Item whose data will be set. /// \param[in] _data Data to set. @@ -291,10 +285,6 @@ namespace gazebo double _latitude, double _longitude, double _elevation, double _heading); - /// \brief Callback in Qt thread when joint type changes. - /// \param[in] _surface Surface model - public: Q_INVOKABLE void OnJointType(QString _jointType); - /// \brief Get whether the entity is a nested model or not /// \return True if the entity is a nested model, false otherwise public: Q_INVOKABLE bool NestedModel() const; diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 9c1d83a869..4d6d8b41b4 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -120,14 +120,6 @@ Rectangle { ComponentInspector.OnPose(_x, _y, _z, _roll, _pitch, _yaw) } - /** - * Forward pose changes to C++ - */ - function onJointType(_jointType) { - console.log(_jointType) - ComponentInspector.OnJointType(_jointType) - } - /** * Forward light changes to C++ */ diff --git a/src/gui/plugins/component_inspector/JointType.cc b/src/gui/plugins/component_inspector/JointType.cc new file mode 100644 index 0000000000..73d5d5352f --- /dev/null +++ b/src/gui/plugins/component_inspector/JointType.cc @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#include + +#include +#include "ignition/gazebo/components/JointType.hh" +#include + +#include "JointType.hh" +#include "ComponentInspector.hh" +#include "Types.hh" + +using namespace ignition; +using namespace gazebo; + +///////////////////////////////////////////////// +JointType::JointType(ComponentInspector *_inspector) +{ + _inspector->Context()->setContextProperty("JointTypeImpl", this); + this->inspector = _inspector; + + ComponentCreator creator = + [=](EntityComponentManager &_ecm, Entity _entity, QStandardItem *_item) + { + auto comp = _ecm.Component(_entity); + if (nullptr == _item || nullptr == comp) + return; + + const sdf::JointType joint = comp->Data(); + + _item->setData(QString("JointType"), + ComponentsModel::RoleNames().key("dataType")); + + QString jointType; + if (joint == sdf::JointType::BALL) + jointType = "Ball"; + else if (joint == sdf::JointType::CONTINUOUS) + jointType = "Continuous"; + else if (joint == sdf::JointType::FIXED) + jointType = "Fixed"; + else if (joint == sdf::JointType::GEARBOX) + jointType = "Gearbox"; + else if (joint == sdf::JointType::PRISMATIC) + jointType = "Prismatic"; + else if (joint == sdf::JointType::REVOLUTE) + jointType = "Revolute"; + else if (joint == sdf::JointType::REVOLUTE2) + jointType = "Revolute2"; + else if (joint == sdf::JointType::SCREW) + jointType = "Screw"; + else if (joint == sdf::JointType::UNIVERSAL) + jointType = "Universal"; + + _item->setData(jointType, ComponentsModel::RoleNames().key("data")); + }; + + this->inspector->RegisterComponentCreator( + components::JointType::typeId, creator); +} + +///////////////////////////////////////////////// +Q_INVOKABLE void JointType::OnJointType(QString _jointType) +{ + ignition::gazebo::UpdateCallback cb = + [=](EntityComponentManager &_ecm) + { + components::JointType *comp = + _ecm.Component(this->inspector->Entity()); + if (comp) + { + if (_jointType == "Ball") + comp->Data() = sdf::JointType::BALL; + else if (_jointType == "Continuous") + comp->Data() = sdf::JointType::CONTINUOUS; + else if (_jointType == "Fixed") + comp->Data() = sdf::JointType::FIXED; + else if (_jointType == "Gearbox") + comp->Data() = sdf::JointType::GEARBOX; + else if (_jointType == "Prismatic") + comp->Data() = sdf::JointType::PRISMATIC; + else if (_jointType == "Revolute") + comp->Data() = sdf::JointType::REVOLUTE; + else if (_jointType == "Revolute2") + comp->Data() = sdf::JointType::REVOLUTE2; + else if (_jointType == "Screw") + comp->Data() = sdf::JointType::SCREW; + else if (_jointType == "Universal") + comp->Data() = sdf::JointType::UNIVERSAL; + } + else + { + ignerr << "Unable to get the joint type component.\n"; + } + }; + this->inspector->AddUpdateCallback(cb); +} diff --git a/src/gui/plugins/component_inspector/JointType.hh b/src/gui/plugins/component_inspector/JointType.hh new file mode 100644 index 0000000000..690efe1b7c --- /dev/null +++ b/src/gui/plugins/component_inspector/JointType.hh @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#ifndef IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_JOINT_HH_ +#define IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_JOINT_HH_ + +#include + +namespace ignition +{ +namespace gazebo +{ + class ComponentInspector; + + /// \brief A class that handles joint changes. + class JointType : public QObject + { + Q_OBJECT + + /// \brief Constructor + /// \param[in] _inspector The component inspector. + public: explicit JointType(ComponentInspector *_inspector); + + /// \brief Callback in Qt thread when joint type changes. + /// \param[in] _surface Surface model + public: Q_INVOKABLE void OnJointType(QString _jointType); + + /// \brief Pointer to the component inspector. This is used to add + /// update callbacks that modify the ECM. + private: ComponentInspector *inspector{nullptr}; + }; +} +} +#endif diff --git a/src/gui/plugins/component_inspector/JointType.qml b/src/gui/plugins/component_inspector/JointType.qml index 8af85ac1ff..a0e59e6ab5 100644 --- a/src/gui/plugins/component_inspector/JointType.qml +++ b/src/gui/plugins/component_inspector/JointType.qml @@ -23,7 +23,7 @@ import QtQuick.Controls.Styles 1.4 import "qrc:/ComponentInspector" import "qrc:/qml" -// Item displaying 3D pose information. +// Item displaying joint type information. Rectangle { id: jointTypeComponent height: jointType.height @@ -31,7 +31,7 @@ Rectangle { color: index % 2 == 0 ? lightGrey : darkGrey // Left indentation - property int indentation: 0 + property int indentation: 10 // Horizontal margins property int margin: 5 @@ -56,7 +56,7 @@ Rectangle { { if (jointTypes.get(i).type === _model.data) { - return i; + return i } } } @@ -68,72 +68,40 @@ Rectangle { font.family: "Roboto" } - Column { + RowLayout { anchors.fill: parent - ExpandingTypeHeader { - id: header - // Using the default header text values. + Item { + height: parent.height + width: margin + indentation } - // Content - Rectangle { - id: content - property bool show: false - width: parent.width - height: show ? grid.height : 0 - clip: true - color: "transparent" - - Behavior on height { - NumberAnimation { - duration: 200; - easing.type: Easing.InOutQuad - } - } - - RowLayout { - id: grid - width: parent.width - - Rectangle { - color: "transparent" - height: 40 - Layout.preferredWidth: jointTypeText.width + indentation * 3 - - Text { - id : jointTypeText - text: ' Joint Type' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - anchors.centerIn: parent - } - } - - QtObject{ - // Workaround to keep from using the ListModel as model - id: indexObj - property int index: indexFromModel(model) - } + TypeHeader { + id: typeHeader + } - Rectangle { - Layout.fillWidth: true - Layout.columnSpan: 4 - Layout.alignment: Qt.AlignRight + QtObject{ + // Workaround to keep from using the ListModel as model + id: indexObj + property int index: indexFromModel(model) + } - height: 40 - ComboBox { - id: jointType - textRole: "type" - model: jointTypes - currentIndex: indexObj.index - enabled: componentInspector.getSimPaused() - onActivated: (index) => { - componentInspector.onJointType(currentText); - } - } - } + ComboBox { + id: jointType + padding: 0 + textRole: "type" + model: jointTypes + currentIndex: indexObj.index + Layout.alignment: Qt.AlignRight + background: Rectangle { + color: "transparent" + implicitWidth: 140 + border.width: 1 + border.color: "#dedede" + } + enabled: componentInspector.getSimPaused() + onActivated: { + JointTypeImpl.OnJointType(currentText) } } } From 6cb59a57f839bee1afdedcf226616c9dff06c793 Mon Sep 17 00:00:00 2001 From: Nate Koenig Date: Wed, 1 Dec 2021 08:44:53 -0800 Subject: [PATCH 15/15] Added recreate to joint add Signed-off-by: Nate Koenig --- src/gui/plugins/component_inspector/ModelEditor.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/plugins/component_inspector/ModelEditor.cc b/src/gui/plugins/component_inspector/ModelEditor.cc index 2fec07dbc5..c6547cac73 100644 --- a/src/gui/plugins/component_inspector/ModelEditor.cc +++ b/src/gui/plugins/component_inspector/ModelEditor.cc @@ -225,6 +225,12 @@ void ModelEditor::Update(const UpdateInfo &, auto entity = this->dataPtr->entityCreator->CreateEntities(&jointSdf, true); this->dataPtr->entityCreator->SetParent(entity, eta.parentEntity); + // Make sure to mark the parent as needing recreation. This will + // tell the server to rebuild the model with the new link. + _ecm.CreateComponent(eta.parentEntity, components::Recreate()); + + // traverse the tree and add all new entities created by the entity + // creator to the set entities.push_back(entity); } }