diff --git a/src/gui/plugins/CMakeLists.txt b/src/gui/plugins/CMakeLists.txt index 3eed9fc486..0ab4babed3 100644 --- a/src/gui/plugins/CMakeLists.txt +++ b/src/gui/plugins/CMakeLists.txt @@ -85,6 +85,7 @@ add_subdirectory(align_tool) add_subdirectory(component_inspector) add_subdirectory(entity_tree) add_subdirectory(grid_config) +add_subdirectory(plotting) add_subdirectory(resource_spawner) add_subdirectory(scene3d) add_subdirectory(shapes) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index e452444718..5ef105ece8 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -256,7 +256,8 @@ QHash ComponentsModel::RoleNames() std::pair(102, "shortName"), std::pair(103, "dataType"), std::pair(104, "unit"), - std::pair(105, "data")}; + std::pair(105, "data"), + std::pair(106, "entity")}; } ///////////////////////////////////////////////// @@ -383,6 +384,9 @@ void ComponentInspector::Update(const UpdateInfo &, Q_ARG(ignition::gazebo::ComponentTypeId, typeId)); } + item->setData(QString::number(this->dataPtr->entity), + ComponentsModel::RoleNames().key("entity")); + if (nullptr == item) { ignerr << "Failed to get item for component type [" << typeId << "]" diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qrc b/src/gui/plugins/component_inspector/ComponentInspector.qrc index 73b599baf0..43efec19d1 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qrc +++ b/src/gui/plugins/component_inspector/ComponentInspector.qrc @@ -7,5 +7,6 @@ String.qml TypeHeader.qml Vector3d.qml + plottable_icon.svg diff --git a/src/gui/plugins/component_inspector/Pose3d.qml b/src/gui/plugins/component_inspector/Pose3d.qml index e57fba90e3..5e3a17b872 100644 --- a/src/gui/plugins/component_inspector/Pose3d.qml +++ b/src/gui/plugins/component_inspector/Pose3d.qml @@ -44,6 +44,9 @@ Rectangle { return !(isModel) } + property int iconWidth: 20 + property int iconHeight: 20 + // Loaded item for X property var xItem: {} @@ -177,6 +180,7 @@ Rectangle { } } + GridLayout { id: grid width: parent.width @@ -188,12 +192,58 @@ Rectangle { width: margin + indentation } - Text { - text: 'X (m)' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + + Component { + id: plotIcon + Image { + property string componentInfo: "" + source: "plottable_icon.svg" + anchors.top: parent.top + anchors.left: parent.left + + Drag.mimeData: { "text/plain" : (model === null) ? "" : + "Component," + model.entity + "," + model.typeId + "," + + model.dataType + "," + componentInfo + "," + model.shortName + } + Drag.dragType: Drag.Automatic + Drag.supportedActions : Qt.CopyAction + Drag.active: dragMouse.drag.active + // a point to drag from + Drag.hotSpot.x: 0 + Drag.hotSpot.y: y + MouseArea { + id: dragMouse + anchors.fill: parent + drag.target: (model === null) ? null : parent + onPressed: parent.grabToImage(function(result) {parent.Drag.imageSource = result.url }) + onReleased: parent.Drag.drop(); + cursorShape: Qt.DragCopyCursor + } } + } + + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: xText.width + indentation*3 + Loader { + id: loaderX + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderX.item.componentInfo = "x" + + Text { + id : xText + text: ' X (m)' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } + } Item { Layout.fillWidth: true @@ -209,11 +259,27 @@ Rectangle { } } - Text { - text: 'Roll (rad)' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: rollText.width + indentation*3 + Loader { + id: loaderRoll + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderRoll.item.componentInfo = "roll" + + Text { + id: rollText + text: ' Roll (rad)' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } } Item { @@ -233,14 +299,30 @@ Rectangle { // Right spacer Item { Layout.rowSpan: 3 - width: margin + width: margin + indentation } - Text { - text: 'Y (m)' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: yText.width + indentation*3 + Loader { + id: loaderY + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderY.item.componentInfo = "y" + Text { + id: yText + text: ' Y (m)' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } } Item { @@ -257,11 +339,26 @@ Rectangle { } } - Text { - text: 'Pitch (rad)' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: pitchText.width + indentation*3 + Loader { + id: loaderPitch + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderPitch.item.componentInfo = "pitch"; + Text { + id: pitchText + text: ' Pitch (rad)' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } } Item { @@ -278,11 +375,26 @@ Rectangle { } } - Text { - text: 'Z (m)' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: zText.width + indentation*3 + Loader { + id: loaderZ + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderZ.item.componentInfo = "z"; + Text { + id: zText + text: ' Z (m)' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } } Item { @@ -299,11 +411,26 @@ Rectangle { } } - Text { - text: 'Yaw (rad)' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: yawText.width + indentation*3 + Loader { + id: loaderYaw + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderYaw.item.componentInfo = "yaw"; + Text { + id: yawText + text: ' Yaw (rad)' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } } Item { diff --git a/src/gui/plugins/component_inspector/Vector3d.qml b/src/gui/plugins/component_inspector/Vector3d.qml index 900543701c..40e8129d23 100644 --- a/src/gui/plugins/component_inspector/Vector3d.qml +++ b/src/gui/plugins/component_inspector/Vector3d.qml @@ -32,6 +32,10 @@ Rectangle { // Left indentation property int indentation: 10 + // icon size + property int iconWidth: 20 + property int iconHeight: 20 + // Horizontal margins property int margin: 5 @@ -74,6 +78,34 @@ Rectangle { } } } + Component { + id: plotIcon + Image { + property string componentInfo: "" + source: "plottable_icon.svg" + anchors.top: parent.top + anchors.left: parent.left + + Drag.mimeData: { "text/plain" : (model === null) ? "" : + "Component," + model.entity + "," + model.typeId + "," + + model.dataType + "," + componentInfo + "," + model.shortName + } + Drag.dragType: Drag.Automatic + Drag.supportedActions : Qt.CopyAction + Drag.active: dragMouse.drag.active + // a point to drag from + Drag.hotSpot.x: 0 + Drag.hotSpot.y: y + MouseArea { + id: dragMouse + anchors.fill: parent + drag.target: (model === null) ? null : parent + onPressed: parent.grabToImage(function(result) {parent.Drag.imageSource = result.url }) + onReleased: parent.Drag.drop(); + cursorShape: Qt.DragCopyCursor + } + } + } Column { anchors.fill: parent @@ -146,16 +178,31 @@ Rectangle { // Left spacer Item { Layout.rowSpan: 3 - width: indentation + margin + width: margin + indentation } - Text { - text: 'X (' + unit + ')' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 - } + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: xText.width + indentation*3 + Loader { + id: loaderX + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderX.item.componentInfo = "x" + Text { + id: xText + text: ' X (' + unit + ')' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } + } Item { Layout.fillWidth: true height: 40 @@ -169,14 +216,29 @@ Rectangle { // Right spacer Item { Layout.rowSpan: 3 - width: margin + width: margin + indentation } - Text { - text: 'Y (' + unit + ')' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: xText.width + indentation*3 + Loader { + id: loaderY + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderY.item.componentInfo = "y" + + Text { + text: ' Y (' + unit + ')' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } } Item { @@ -189,11 +251,26 @@ Rectangle { } } - Text { - text: 'Z (' + unit + ')' - leftPadding: 5 - color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" - font.pointSize: 12 + Rectangle { + color: "transparent" + height: 40 + Layout.preferredWidth: xText.width + indentation*3 + Loader { + id: loaderZ + width: iconWidth + height: iconHeight + y:10 + sourceComponent: plotIcon + } + Component.onCompleted: loaderZ.item.componentInfo = "z" + + Text { + text: ' Z (' + unit + ')' + leftPadding: 5 + color: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + font.pointSize: 12 + anchors.centerIn: parent + } } Item { diff --git a/src/gui/plugins/component_inspector/plottable_icon.svg b/src/gui/plugins/component_inspector/plottable_icon.svg new file mode 100644 index 0000000000..bcac9afc2e --- /dev/null +++ b/src/gui/plugins/component_inspector/plottable_icon.svg @@ -0,0 +1,76 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/src/gui/plugins/plotting/CMakeLists.txt b/src/gui/plugins/plotting/CMakeLists.txt new file mode 100644 index 0000000000..dc26ac4083 --- /dev/null +++ b/src/gui/plugins/plotting/CMakeLists.txt @@ -0,0 +1,4 @@ +gz_add_gui_plugin(Plotting + SOURCES Plotting.cc + QT_HEADERS Plotting.hh +) diff --git a/src/gui/plugins/plotting/Plotting.cc b/src/gui/plugins/plotting/Plotting.cc new file mode 100644 index 0000000000..d470f2e3d3 --- /dev/null +++ b/src/gui/plugins/plotting/Plotting.cc @@ -0,0 +1,405 @@ + /* + * Copyright (C) 2020 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 "Plotting.hh" + +#include +#include "ignition/gazebo/components/AngularAcceleration.hh" +#include "ignition/gazebo/components/AngularVelocity.hh" +#include "ignition/gazebo/components/CastShadows.hh" +#include "ignition/gazebo/components/Factory.hh" +#include "ignition/gazebo/components/Gravity.hh" +#include "ignition/gazebo/components/LinearAcceleration.hh" +#include "ignition/gazebo/components/LinearVelocity.hh" +#include "ignition/gazebo/components/LinearVelocitySeed.hh" +#include "ignition/gazebo/components/MagneticField.hh" +#include "ignition/gazebo/components/ParentEntity.hh" +#include "ignition/gazebo/components/Pose.hh" +#include "ignition/gazebo/components/PoseCmd.hh" +#include "ignition/gazebo/components/Static.hh" +#include "ignition/gazebo/components/WindMode.hh" +#include "ignition/gazebo/components/World.hh" +#include "ignition/gazebo/EntityComponentManager.hh" + +namespace ignition::gazebo +{ + class PlottingPrivate + { + /// \brief Interface to communicate with Qml + public: std::unique_ptr plottingIface{nullptr}; + + /// \brief registered components for plotting + /// map key: string contains EntityID + "," + ComponentID + public: std::map> components; + }; + + class PlotComponentPrivate + { + /// \brief entity id in the simulation + public: Entity entity; + + /// \brief type identifier unique to each component type + public: ComponentTypeId typeId; + + /// \brief component data type (Pose3d, Vector3d, double) + public: std::string type; + + /// \brief attributes of the components, + /// ex: x,y,z attributes in Vector3d type component + public: std::map> data; + }; +} + +using namespace ignition::gazebo; +using namespace ignition::gui; + +////////////////////////////////////////////////// +PlotComponent::PlotComponent(const std::string &_type, + ignition::gazebo::Entity _entity, + ComponentTypeId _typeId) : + dataPtr(std::make_unique()) +{ + this->dataPtr->entity = _entity; + this->dataPtr->typeId = _typeId; + this->dataPtr->type = _type; + + if (_type == "Vector3d") + { + this->dataPtr->data["x"] = std::make_shared(); + this->dataPtr->data["y"] = std::make_shared(); + this->dataPtr->data["z"] = std::make_shared(); + } + else if (_type == "Pose3d") + { + this->dataPtr->data["x"] = std::make_shared(); + this->dataPtr->data["y"] = std::make_shared(); + this->dataPtr->data["z"] = std::make_shared(); + this->dataPtr->data["roll"] = std::make_shared(); + this->dataPtr->data["pitch"] = std::make_shared(); + this->dataPtr->data["yaw"] = std::make_shared(); + } + else if (_type == "double") + this->dataPtr->data["value"] = std::make_shared(); + else + ignwarn << "Invalid Plot Component Type:" << _type << std::endl; +} + +////////////////////////////////////////////////// +PlotComponent::~PlotComponent() +{ +} + +////////////////////////////////////////////////// +void PlotComponent::RegisterChart(std::string _attribute, int _chart) +{ + if (this->dataPtr->data.count(_attribute) == 0) + { + ignwarn << "Invalid Plot Component Attribute" << std::endl; + return; + } + this->dataPtr->data[_attribute]->AddChart(_chart); +} + +////////////////////////////////////////////////// +void PlotComponent::UnRegisterChart(std::string _attribute, int _chart) +{ + if (this->dataPtr->data.count(_attribute) == 0) + { + ignwarn << "Invalid Plot Component Attribute" << std::endl; + return; + } + this->dataPtr->data[_attribute]->RemoveChart(_chart); +} + +////////////////////////////////////////////////// +bool PlotComponent::HasCharts() +{ + for (auto field : this->dataPtr->data) + if (field.second->ChartCount() > 0) + return true; + return false; +} + +////////////////////////////////////////////////// +void PlotComponent::SetAttributeValue(std::string _attribute, + const double &_value) +{ + if (this->dataPtr->data.count(_attribute) > 0) + this->dataPtr->data[_attribute]->SetValue(_value); +} + +////////////////////////////////////////////////// +std::map> PlotComponent::Data() const +{ + return this->dataPtr->data; +} + +////////////////////////////////////////////////// +Entity PlotComponent::Entity() +{ + return this->dataPtr->entity; +} + +////////////////////////////////////////////////// +ComponentTypeId PlotComponent::TypeId() +{ + return this->dataPtr->typeId; +} + +////////////////////////////////////////////////// +Plotting ::Plotting() : GuiSystem(), + dataPtr(std::make_unique()) +{ + this->dataPtr->plottingIface = std::make_unique(); + + // PlottingInterface connecting + this->connect(this->dataPtr->plottingIface.get(), SIGNAL(ComponentSubscribe + (uint64_t, uint64_t, std::string, std::string, int)), + this, SLOT(RegisterChartToComponent + (uint64_t, uint64_t, std::string, std::string, int))); + + this->connect(this->dataPtr->plottingIface.get(), SIGNAL(ComponentUnSubscribe + (uint64_t, uint64_t, std::string, int)), + this, SLOT(UnRegisterChartToComponent + (uint64_t, uint64_t, std::string, int))); + + this->connect(this->dataPtr->plottingIface.get(), + SIGNAL(ComponentName(uint64_t)), this, SLOT(ComponentName(uint64_t))); +} + +////////////////////////////////////////////////// +Plotting ::~Plotting() +{ +} + +////////////////////////////////////////// +void Plotting::LoadConfig(const tinyxml2::XMLElement *) +{ + if (this->title.empty()) + this->title = "Plotting"; +} + +////////////////////////////////////////////////// +void Plotting::SetData(std::string _Id, const ignition::math::Vector3d &_vector) +{ + this->dataPtr->components[_Id]->SetAttributeValue("x", _vector.X()); + this->dataPtr->components[_Id]->SetAttributeValue("y", _vector.Y()); + this->dataPtr->components[_Id]->SetAttributeValue("z", _vector.Z()); +} + +////////////////////////////////////////////////// +void Plotting::SetData(std::string _Id, const ignition::math::Pose3d &_pose) +{ + this->dataPtr->components[_Id]->SetAttributeValue("x", _pose.Pos().X()); + this->dataPtr->components[_Id]->SetAttributeValue("y", _pose.Pos().Y()); + this->dataPtr->components[_Id]->SetAttributeValue("z", _pose.Pos().Z()); + this->dataPtr->components[_Id]->SetAttributeValue("roll", _pose.Rot().Roll()); + this->dataPtr->components[_Id]->SetAttributeValue("pitch", + _pose.Rot().Pitch()); + this->dataPtr->components[_Id]->SetAttributeValue("yaw", _pose.Rot().Yaw()); +} + +////////////////////////////////////////////////// +void Plotting::SetData(std::string _Id, const double &_value) +{ + this->dataPtr->components[_Id]->SetAttributeValue("value", _value); +} + + +////////////////////////////////////////////////// +void Plotting::RegisterChartToComponent(uint64_t _entity, uint64_t _typeId, + std::string _type, + std::string _attribute, + int _chart) +{ + std::string Id = std::to_string(_entity) + "," + std::to_string(_typeId); + + if (this->dataPtr->components.count(Id) == 0) + { + this->dataPtr->components[Id] = std::make_shared( + _type, _entity, _typeId); + } + + this->dataPtr->components[Id]->RegisterChart(_attribute, _chart); +} + +////////////////////////////////////////////////// +void Plotting::UnRegisterChartFromComponent(uint64_t _entity, uint64_t _typeId, + std::string _attribute, int _chart) +{ + std::string id = std::to_string(_entity) + "," + std::to_string(_typeId); + igndbg << "UnRegister [" << id << "]" << std::endl; + + if (this->dataPtr->components.count(id) == 0) + return; + + this->dataPtr->components[id]->UnRegisterChart(_attribute, _chart); + + if (!this->dataPtr->components[id]->HasCharts()) + this->dataPtr->components.erase(id); +} + +////////////////////////////////////////////////// +std::string Plotting::ComponentName(const uint64_t &_typeId) +{ + std::string name = components::Factory::Instance()->Name(_typeId); + + auto pos = name.find("ign.gazebo.components."); + + if (pos != std::string::npos) + name.erase(pos, 22); + + return name; +} + +////////////////////////////////////////////////// +void Plotting ::Update(const ignition::gazebo::UpdateInfo &_info, + ignition::gazebo::EntityComponentManager &_ecm) +{ + for (auto component : this->dataPtr->components) + { + auto entity = component.second->Entity(); + auto typeId = component.second->TypeId(); + + if (typeId == components::AngularAcceleration::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::AngularVelocity::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::CastShadows::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::Gravity::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::LinearAcceleration::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::LinearVelocity::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::MagneticField::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::ParentEntity::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::Pose::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::Static::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::TrajectoryPose::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::WindMode::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::WorldAngularAcceleration::typeId) + { + auto comp = _ecm.Component( + entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::WorldLinearVelocity::typeId) + { + auto comp = _ecm.Component( + entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::WorldLinearVelocitySeed::typeId) + { + auto comp = _ecm.Component( + entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::WorldPose::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + else if (typeId == components::WorldPoseCmd::typeId) + { + auto comp = _ecm.Component(entity); + if (comp) + this->SetData(component.first, comp->Data()); + } + + for (auto attribute : component.second->Data()) + { + for (auto chart : attribute.second->Charts()) + { + QString attributeName = QString::fromStdString( + component.first + "," + attribute.first); + double y = attribute.second->Value(); + + double x = _info.simTime.count() * std::pow(10, -9); + + emit this->dataPtr->plottingIface->plot(chart, attributeName, x, y); + } + } + } +} + +// Register this plugin +IGNITION_ADD_PLUGIN(ignition::gazebo::Plotting , + ignition::gazebo::GuiSystem, + ignition::gui::Plugin) diff --git a/src/gui/plugins/plotting/Plotting.hh b/src/gui/plugins/plotting/Plotting.hh new file mode 100644 index 0000000000..bf9c54c69b --- /dev/null +++ b/src/gui/plugins/plotting/Plotting.hh @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 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_GUI_PLUGINS_PLOTTING_HH_ +#define IGNITION_GUI_PLUGINS_PLOTTING_HH_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace ignition { + +namespace gazebo { + +class PlotComponentPrivate; + +/// \brief A container of the component data that keeps track of the registered +/// attributes and update their values and their registered charts +class PlotComponent +{ + /// \brief Constructor + /// \param[in] _type component data type (Pose3d, Vector3d, double) + /// \param [in] _entity entity id of that component + /// \param [in] _typeId type identifier unique to each component type + public: PlotComponent(const std::string &_type, + ignition::gazebo::Entity _entity, + ComponentTypeId _typeId); + + /// \brief Destructor + public: ~PlotComponent(); + + /// \brief Add a registered chart to the attribute + /// \param[in] _attribute component attribute to add the chart to it + /// \param[in] _chart chart ID to be added to the attribute + public: void RegisterChart(std::string _attribute, int _chart); + + /// \brief Remove a registered chart from the attribute + /// \param[in] _attribute component attribute to remove the chart from it + /// \param[in] _chart chart ID to be removed from the attribute + public: void UnRegisterChart(std::string _attribute, int _chart); + + /// \brief Check if any of the component attributes has any chart + /// \return true if any attribute has a chart + /// false if all attributes are empty from the charts + public: bool HasCharts(); + + /// \brief Set a value of specefic component attribute + /// \param[in] _attribute component attribute to set its value + /// ex : yaw attribute in Pose3d type Component + /// \param[in] _value value to be set to the attribute + public: void SetAttributeValue(std::string _attribute, const double &_value); + + /// \brief Get all attributes of the component + /// \return component attributes + public: std::map> + Data() const; + + /// \brief Get the Component entity ID + /// \return Entity ID + public: ignition::gazebo::Entity Entity(); + + /// \brief Get the Component type ID + /// \return component type ID + public: ComponentTypeId TypeId(); + + /// \brief dataPtr holds Abstraction data of PlottingPrivate + private: std::unique_ptr dataPtr; +}; + +class PlottingPrivate; + +/// \brief Physics data plotting handler that keeps track of the +/// registered components, update them and update the plot +class Plotting : public ignition::gazebo::GuiSystem +{ + Q_OBJECT + /// \brief Constructor + public: Plotting(); + + /// \brief Destructor + public: ~Plotting(); + + // Documentation inherited + public: void LoadConfig(const tinyxml2::XMLElement *) override; + + // Documentation inherited + public: void Update(const ignition::gazebo::UpdateInfo &_info, + ignition::gazebo::EntityComponentManager &_ecm) override; + + /// \brief Set the Component data of giving id to the giving vector + /// \param [in] _Id Component Key of the components map + /// \param [in] _vector Vector Data to be set to the component + public: void SetData(std::string _Id, + const ignition::math::Vector3d &_vector); + + /// \brief Set the Component data of giving id to the giving pose + /// \param [in] _Id Component Key of the components map + /// \param [in] _pose Position Data to be set to the component + public: void SetData(std::string _Id, + const ignition::math::Pose3d &_pose); + + /// \brief Set the Component data of giving id to the giving number + /// \param [in] _Id Component Key of the components map + /// \param [in] _value double Data to be set to the component + /// valid for types (double, float, int, bool) + public: void SetData(std::string _Id, const double &_value); + + /// \brief Add a chart to a specefic component attribute + /// \param[in] _entity entity id in the simulation + /// \param[in] _typeId type identifier unique to each component type + /// \param[in] _type Component Datatype ("Pose3d","Vector3d","double") + /// \param[in] _attribute component attribute to add the chart to it + /// ex: x attribute in Pose3d Component will be "x" + /// \param [in] _chart chart ID to be registered + public slots: void RegisterChartToComponent(uint64_t _entity, + uint64_t _typeId, + std::string _type, + std::string _attribute, + int _chart); + + /// \brief Remove a chart from a specefic component attribute + /// \param[in] _entity entity id in the simulation + /// \param[in] _typeId type identifier unique to each component type + /// \param[in] _attribute component attribute to remove the chart from it + /// ex: x attribute in Pose3d Component will be "x" + /// \param[in] _chart chart ID to be unregistered + public slots: void UnRegisterChartFromComponent(uint64_t _entity, + uint64_t _typeId, + std::string _attribute, + int _chart); + + /// \brief Get Component Name based on its type Id + /// \param[in] _typeId type Id of the component + /// \return Component name + public slots: std::string ComponentName(const uint64_t &_typeId); + + /// \brief dataPtr holds Abstraction data of PlottingPrivate + private: std::unique_ptr dataPtr; +}; +} +} + +#endif diff --git a/src/gui/plugins/plotting/Plotting.qml b/src/gui/plugins/plotting/Plotting.qml new file mode 100644 index 0000000000..cfafbba101 --- /dev/null +++ b/src/gui/plugins/plotting/Plotting.qml @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 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.0 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "qrc:/qml" + +PlottingInterface { + Layout.minimumWidth: 600 + Layout.minimumHeight: 600 +} diff --git a/src/gui/plugins/plotting/Plotting.qrc b/src/gui/plugins/plotting/Plotting.qrc new file mode 100644 index 0000000000..e520bf6c03 --- /dev/null +++ b/src/gui/plugins/plotting/Plotting.qrc @@ -0,0 +1,5 @@ + + + Plotting.qml + +