diff --git a/include/ignition/gazebo/gui/GuiEvents.hh b/include/ignition/gazebo/gui/GuiEvents.hh index aa22dbf0e2..15d4c5af77 100644 --- a/include/ignition/gazebo/gui/GuiEvents.hh +++ b/include/ignition/gazebo/gui/GuiEvents.hh @@ -18,6 +18,8 @@ #define IGNITION_GAZEBO_GUI_GUIEVENTS_HH_ #include +#include + #include #include #include @@ -158,6 +160,51 @@ namespace events /// \brief True if a transform mode is active. private: bool tranformModeActive; }; + + /// \brief Event that notifies an entity is to be added to the model editor + class ModelEditorAddEntity : public QEvent + { + /// \brief Constructor + /// \param[in] _tranformModeActive is the transform control mode active + public: explicit ModelEditorAddEntity(QString _entity, QString _type, + ignition::gazebo::Entity _parent, QString _uri) : + QEvent(kType), entity(_entity), type(_type), parent(_parent), uri(_uri) + { + } + + /// \brief Get the entity to add + public: QString Entity() const + { + return this->entity; + } + + /// \brief Get the URI, if any, associated with the entity to add + public: QString Uri() const + { + return this->uri; + } + + /// \brief Get the entity type + public: QString EntityType() const + { + return this->type; + } + + /// \brief Get the parent entity to add the entity to + public: ignition::gazebo::Entity ParentEntity() const + { + return this->parent; + } + + /// \brief Unique type for this event. + static const QEvent::Type kType = QEvent::Type(QEvent::User + 7); + + private: QString entity; + private: QString type; + private: ignition::gazebo::Entity parent; + private: QString uri; + }; + } // namespace events } } // namespace gui diff --git a/include/ignition/gazebo/rendering/RenderUtil.hh b/include/ignition/gazebo/rendering/RenderUtil.hh index 3d0361d955..49a4a5557e 100644 --- a/include/ignition/gazebo/rendering/RenderUtil.hh +++ b/include/ignition/gazebo/rendering/RenderUtil.hh @@ -18,6 +18,7 @@ #define IGNITION_GAZEBO_RENDERUTIL_HH_ #include +#include #include #include @@ -76,6 +77,14 @@ inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { public: void UpdateFromECM(const UpdateInfo &_info, const EntityComponentManager &_ecm); + /// \brief Helper function to create visuals for new entities created in + /// ECM. This function is intended to be used by other GUI plugins when + /// new entities are created on the GUI side. + /// \param[in] _ecm Const reference to the entity component manager + /// \param[in] _entities Entities to create visuals for. + public: void CreateVisualsForEntities(const EntityComponentManager &_ecm, + const std::set &_entities); + /// \brief Set the rendering engine to use /// \param[in] _engineName Name of the rendering engine. public: void SetEngineName(const std::string &_engineName); diff --git a/src/gui/plugins/component_inspector/CMakeLists.txt b/src/gui/plugins/component_inspector/CMakeLists.txt index 367278b24d..62200aecb6 100644 --- a/src/gui/plugins/component_inspector/CMakeLists.txt +++ b/src/gui/plugins/component_inspector/CMakeLists.txt @@ -1,4 +1,4 @@ gz_add_gui_plugin(ComponentInspector - SOURCES ComponentInspector.cc - QT_HEADERS ComponentInspector.hh + SOURCES ComponentInspector.cc ModelEditor.cc + QT_HEADERS ComponentInspector.hh ModelEditor.hh ) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index 081b3d5b4a..d5e2a58df4 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,7 @@ #include "ignition/gazebo/gui/GuiEvents.hh" #include "ComponentInspector.hh" +#include "ModelEditor.hh" namespace ignition::gazebo { @@ -101,6 +103,9 @@ namespace ignition::gazebo /// \brief Transport node for making command requests public: transport::Node node; + + /// \brief Transport node for making command requests + public: ModelEditor modelEditor; }; } @@ -405,10 +410,12 @@ void ComponentInspector::LoadConfig(const tinyxml2::XMLElement *) // Connect model this->Context()->setContextProperty( "ComponentsModel", &this->dataPtr->componentsModel); + + this->dataPtr->modelEditor.Load(); } ////////////////////////////////////////////////// -void ComponentInspector::Update(const UpdateInfo &, +void ComponentInspector::Update(const UpdateInfo &_info, EntityComponentManager &_ecm) { IGN_PROFILE("ComponentInspector::Update"); @@ -789,6 +796,8 @@ void ComponentInspector::Update(const UpdateInfo &, Qt::QueuedConnection, Q_ARG(ignition::gazebo::ComponentTypeId, typeId)); } + + this->dataPtr->modelEditor.Update(_info, _ecm); } ///////////////////////////////////////////////// @@ -1029,6 +1038,45 @@ bool ComponentInspector::NestedModel() const return this->dataPtr->nestedModel; } +///////////////////////////////////////////////// +void ComponentInspector::OnAddEntity(const QString &_entity, + const QString &_type) +{ + // currently just assumes parent is the model + // todo(anyone) support adding visuals / collisions / sensors to links + ignition::gazebo::gui::events::ModelEditorAddEntity addEntityEvent( + _entity, _type, this->dataPtr->entity, QString("")); + + ignition::gui::App()->sendEvent( + ignition::gui::App()->findChild(), + &addEntityEvent); +} + +///////////////////////////////////////////////// +void ComponentInspector::OnLoadMesh(const QString &_entity, + const QString &_type, const QString &_mesh) +{ + std::string meshStr = _mesh.toStdString(); + if (QUrl(_mesh).isLocalFile()) + { + // mesh to sdf model + common::rtrim(meshStr); + + if (!common::MeshManager::Instance()->IsValidFilename(meshStr)) + { + QString errTxt = QString::fromStdString("Invalid URI: " + meshStr + + "\nOnly mesh file types DAE, OBJ, and STL are supported."); + return; + } + + ignition::gazebo::gui::events::ModelEditorAddEntity addEntityEvent( + _entity, _type, this->dataPtr->entity, QString(meshStr.c_str())); + ignition::gui::App()->sendEvent( + ignition::gui::App()->findChild(), + &addEntityEvent); + } +} + // Register this plugin IGNITION_ADD_PLUGIN(ignition::gazebo::ComponentInspector, ignition::gui::Plugin) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.hh b/src/gui/plugins/component_inspector/ComponentInspector.hh index 18b0ef7d66..1790c3b521 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.hh +++ b/src/gui/plugins/component_inspector/ComponentInspector.hh @@ -331,6 +331,19 @@ namespace gazebo /// \brief Notify that paused has changed. signals: void PausedChanged(); + /// \brief Callback in Qt thread when an entity is to be added + /// \param[in] _entity Entity to add, e.g. box, sphere, cylinder, etc + /// \param[in] _type Entity type, e.g. link, visual, collision, etc + public: Q_INVOKABLE void OnAddEntity(const QString &_entity, + const QString &_type); + + /// \brief Callback to insert a new entity + /// \param[in] _entity Entity to add, e.g. box, sphere, cylinder, etc + /// \param[in] _type Entity type, e.g. link, visual, collision, etc + /// \param[in] _mesh Mesh file to load. + public: Q_INVOKABLE void OnLoadMesh(const QString &_entity, + const QString &_type, const QString &_mesh); + /// \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..e433714fe1 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -18,10 +18,12 @@ 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 IgnGazebo 1.0 as IgnGazebo + Rectangle { id: componentInspector color: lightGrey @@ -124,6 +126,20 @@ Rectangle { _heading); } + // The component for a menu section header + Component { + id: menuSectionHeading + Rectangle { + height: childrenRect.height + + Text { + text: sectionText + font.pointSize: 10 + padding: 5 + } + } + } + Rectangle { id: header height: lockButton.height @@ -198,6 +214,159 @@ Rectangle { } } + ToolButton { + id: addButton + checkable: false + text: "Add entity" + visible: entityType == "model" + contentItem: Image { + fillMode: Image.Pad + horizontalAlignment: Image.AlignHCenter + verticalAlignment: Image.AlignVCenter + source: "qrc:/Gazebo/images/plus.png" + sourceSize.width: 18; + sourceSize.height: 18; + } + ToolTip.text: "Add an entity to a model" + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + onClicked: { + addLinkMenu.open() + } + + FileDialog { + id: loadFileDialog + title: "Load mesh" + folder: shortcuts.home + nameFilters: [ "Collada files (*.dae)", "(*.stl)", "(*.obj)" ] + selectMultiple: false + selectExisting: true + onAccepted: { + ComponentInspector.OnLoadMesh("mesh", "link", fileUrl) + } + } + + Menu { + id: addLinkMenu + + Item { + Layout.fillWidth: true + height: childrenRect.height + Loader { + property string sectionText: "Link" + sourceComponent: menuSectionHeading + } + } + + MenuItem { + id: boxLink + text: "Box" + onClicked: { + ComponentInspector.OnAddEntity("box", "link"); + addLinkMenu.close() + } + } + + MenuItem { + id: capsuleLink + text: "Capsule" + onClicked: { + ComponentInspector.OnAddEntity("capsule", "link"); + addLinkMenu.close() + } + } + + MenuItem { + id: cylinderLink + text: "Cylinder" + onClicked: { + ComponentInspector.OnAddEntity("cylinder", "link"); + } + } + + MenuItem { + id: ellipsoidLink + text: "Ellipsoid" + onClicked: { + ComponentInspector.OnAddEntity("ellipsoid", "link"); + } + } + + MenuItem { + id: emptyLink + text: "Empty" + onClicked: { + ComponentInspector.OnAddEntity("empty", "link"); + } + } + + MenuItem { + id: meshLink + text: "Mesh" + onClicked: { + loadFileDialog.open() + } + } + + MenuItem { + id: sphereLink + text: "Sphere" + onClicked: { + ComponentInspector.OnAddEntity("sphere", "link"); + } + } + + MenuSeparator { + padding: 0 + topPadding: 12 + bottomPadding: 12 + contentItem: Rectangle { + implicitWidth: 200 + implicitHeight: 1 + color: "#1E000000" + } + } + + Item { + Layout.fillWidth: true + height: childrenRect.height + Loader { + property string sectionText: "Light" + sourceComponent: menuSectionHeading + } + } + + MenuItem { + id: directionalLink + text: "Directional" + onClicked: { + ComponentInspector.OnAddEntity("directional", "link"); + addLinkMenu.close() + } + } + + MenuItem { + id: pointLink + text: "Point" + onClicked: { + ComponentInspector.OnAddEntity("point", "link"); + addLinkMenu.close() + } + } + + MenuItem { + id: spotLink + text: "Spot" + onClicked: { + ComponentInspector.OnAddEntity("spot", "link"); + addLinkMenu.close() + } + } + + // \todo(anyone) Add joints + } + } + Label { id: entityLabel text: 'Entity ' + ComponentInspector.entity @@ -209,6 +378,7 @@ Rectangle { } } + ListView { anchors.top: header.bottom anchors.bottom: parent.bottom diff --git a/src/gui/plugins/component_inspector/ModelEditor.cc b/src/gui/plugins/component_inspector/ModelEditor.cc new file mode 100644 index 0000000000..d40cae6e1a --- /dev/null +++ b/src/gui/plugins/component_inspector/ModelEditor.cc @@ -0,0 +1,409 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#include "ignition/gazebo/components/Model.hh" +#include "ignition/gazebo/components/Name.hh" +#include "ignition/gazebo/components/ParentEntity.hh" +#include "ignition/gazebo/EntityComponentManager.hh" +#include "ignition/gazebo/SdfEntityCreator.hh" + +#include "ignition/gazebo/gui/GuiEvents.hh" + +#include "ModelEditor.hh" + +namespace ignition::gazebo +{ + class EntityToAdd + { + /// \brief Entity to add to the model editor + public: std::string geomOrLightType; + + /// \brief Type of entity to add + public: std::string entityType; + + /// \brief Parent entity to add the entity to + public: Entity parentEntity; + + /// \brief Entity URI, such as a URI for a mesh. + public: std::string uri; + }; + + class ModelEditorPrivate + { + /// \brief Handle entity addition + /// \param[in] _geomOrLightType Geometry or light type, e.g. sphere, + /// directional, etc + /// \param[in] _entityType Type of entity: link, visual, collision, etc + /// \param[in] _parentEntity Name of parent entity + /// \param[in] _uri URI associated with the entity, needed for mesh + /// types. + public: void HandleAddEntity(const std::string &_geomOrLightType, + const std::string &_entityType, Entity _parentEntity, + const std::string &_uri); + + /// \brief Get a SDF string of a geometry + /// \param[in] _eta Entity to add. + public: std::string GeomSDFString(const EntityToAdd &_eta) const; + + /// \brief Get a SDF string of a light + /// \param[in] _eta Entity to add. + public: std::string LightSDFString(const EntityToAdd &_eta) const; + + /// \brief Get a SDF string of a link + /// \param[in] _eta Entity to add. + public: std::string LinkSDFString(const EntityToAdd &_eta) const; + + /// \brief Entity Creator API. + public: std::unique_ptr entityCreator{nullptr}; + + /// \brief A record of the ids in the editor + /// for easy deletion of visuals later + public: std::vector entityIds; + + /// \brief Mutex to protect the entity sdf list + public: std::mutex mutex; + + /// \brief A map of links to add to the ECM and the parent entity names + // public: std::vector> linksToAdd; + public: std::vector entitiesToAdd; + + /// \brief Event Manager + public: EventManager eventMgr; + }; +} + +using namespace ignition; +using namespace gazebo; + +///////////////////////////////////////////////// +ModelEditor::ModelEditor() + : dataPtr(std::make_unique()) +{ +} + +///////////////////////////////////////////////// +ModelEditor::~ModelEditor() = default; + +///////////////////////////////////////////////// +void ModelEditor::Load() +{ + ignition::gui::App()->findChild< + ignition::gui::MainWindow *>()->installEventFilter(this); +} + +////////////////////////////////////////////////// +void ModelEditor::Update(const UpdateInfo &, + EntityComponentManager &_ecm) +{ + IGN_PROFILE("ModelEditor::Update"); + + if (!this->dataPtr->entityCreator) + { + this->dataPtr->entityCreator = std::make_unique( + _ecm, this->dataPtr->eventMgr); + } + + std::lock_guard lock(this->dataPtr->mutex); + // add link entities to the ECM + std::set newEntities; + for (const auto &eta : this->dataPtr->entitiesToAdd) + { + sdf::Link linkSdf; + if (eta.entityType == "link") + { + // create an sdf::Link to it can be added to the ECM throught the + // CreateEntities call + std::string linkSDFStr = this->dataPtr->LinkSDFString(eta); + if (!linkSDFStr.empty()) + { + linkSDFStr = std::string("" + + linkSDFStr + ""; + + sdf::ElementPtr linkElem(new sdf::Element); + sdf::initFile("link.sdf", linkElem); + sdf::readString(linkSDFStr, linkElem); + linkSdf.Load(linkElem); + } + else + { + continue; + } + if (eta.parentEntity == kNullEntity) + { + ignerr << "Parent entity not defined." << std::endl; + continue; + } + + // generate unique link name + // note passing components::Link() as arg to EntityByComponents causes + // a crash on exit, see issue #1158 + std::string linkName = "link"; + Entity linkEnt = _ecm.EntityByComponents( + components::ParentEntity(eta.parentEntity), + components::Name(linkName)); + int64_t counter = 0; + while (linkEnt) + { + linkName = std::string("link") + "_" + std::to_string(++counter); + linkEnt = _ecm.EntityByComponents( + components::ParentEntity(eta.parentEntity), + components::Name(linkName)); + } + + linkSdf.SetName(linkName); + auto entity = this->dataPtr->entityCreator->CreateEntities(&linkSdf); + this->dataPtr->entityCreator->SetParent(entity, eta.parentEntity); + + // traverse the tree and add all new entities created by the entity + // creator to the set + std::list entities; + entities.push_back(entity); + while (!entities.empty()) + { + Entity ent = entities.front(); + entities.pop_front(); + + // add new entity created + newEntities.insert(ent); + + auto childEntities = _ecm.EntitiesByComponents( + components::ParentEntity(ent)); + for (const auto &child : childEntities) + entities.push_back(child); + } + } + } + + // use tmp AddedRemovedEntities event to update other gui plugins + // note this event will be removed in Ignition Garden + std::set removedEntities; + ignition::gazebo::gui::events::AddedRemovedEntities event( + newEntities, removedEntities); + ignition::gui::App()->sendEvent( + ignition::gui::App()->findChild(), + &event); + + this->dataPtr->entitiesToAdd.clear(); +} + +///////////////////////////////////////////////// +bool ModelEditor::eventFilter(QObject *_obj, QEvent *_event) +{ + if (_event->type() == gazebo::gui::events::ModelEditorAddEntity::kType) + { + auto event = reinterpret_cast(_event); + if (event) + { + this->dataPtr->HandleAddEntity(event->Entity().toStdString(), + event->EntityType().toStdString(), + event->ParentEntity(), + event->Uri().toStdString()); + } + } + + // Standard event processing + return QObject::eventFilter(_obj, _event); +} + +///////////////////////////////////////////////// +std::string ModelEditorPrivate::LightSDFString(const EntityToAdd &_eta) const +{ + std::stringstream lightStr; + lightStr << ""; + + if (_eta.geomOrLightType == "directional") + { + lightStr + << "false" + << "1.0 1.0 1.0 1" + << "0.5 0.5 0.5 1"; + } + else if (_eta.geomOrLightType == "spot") + { + lightStr + << "false" + << "1.0 1.0 1.0 1" + << "0.5 0.5 0.5 1" + << "" + << "4" + << "0.2" + << "0.5" + << "0.01" + << "" + << "0 0 -1" + << "" + << "0.1" + << "0.5" + << "0.8" + << ""; + } + else if (_eta.geomOrLightType == "point") + { + lightStr + << "false" + << "1.0 1.0 1.0 1" + << "0.5 0.5 0.5 1" + << "" + << "4" + << "0.2" + << "0.5" + << "0.01" + << ""; + } + else + { + ignwarn << "Light type not supported: " + << _eta.geomOrLightType << std::endl; + return std::string(); + } + + lightStr << ""; + return lightStr.str(); +} + +///////////////////////////////////////////////// +std::string ModelEditorPrivate::GeomSDFString(const EntityToAdd &_eta) const +{ + math::Vector3d size = math::Vector3d::One; + std::stringstream geomStr; + geomStr << ""; + if (_eta.geomOrLightType == "box") + { + geomStr + << "" + << " " << size << "" + << ""; + } + else if (_eta.geomOrLightType == "sphere") + { + geomStr + << "" + << " " << size.X() * 0.5 << "" + << ""; + } + else if (_eta.geomOrLightType == "cylinder") + { + geomStr + << "" + << " " << size.X() * 0.5 << "" + << " " << size.Z() << "" + << ""; + } + else if (_eta.geomOrLightType == "capsule") + { + geomStr + << "" + << " " << size.X() * 0.5 << "" + << " " << size.Z() << "" + << ""; + } + else if (_eta.geomOrLightType == "ellipsoid") + { + geomStr + << "" + << " " << size * 0.5 << "" + << ""; + } + else if (_eta.geomOrLightType == "mesh") + { + geomStr + << "" + << " " << _eta.uri << "" + << ""; + } + else + { + ignwarn << "Geometry type not supported: " + << _eta.geomOrLightType << std::endl; + return std::string(); + } + + + geomStr << ""; + return geomStr.str(); +} + +///////////////////////////////////////////////// +std::string ModelEditorPrivate::LinkSDFString(const EntityToAdd &_eta) const +{ + std::stringstream linkStr; + if (_eta.geomOrLightType == "empty") + { + linkStr << ""; + return linkStr.str(); + } + + std::string geomOrLightStr; + if (_eta.geomOrLightType == "spot" || _eta.geomOrLightType == "directional" || + _eta.geomOrLightType == "point") + { + geomOrLightStr = this->LightSDFString(_eta); + linkStr + << "" + << geomOrLightStr + << ""; + } + else + { + geomOrLightStr = this->GeomSDFString(_eta); + linkStr + << "" + << " " + << geomOrLightStr + << " " + << " " + << geomOrLightStr + << " " + << ""; + } + + if (geomOrLightStr.empty()) + return std::string(); + + return linkStr.str(); +} + +///////////////////////////////////////////////// +void ModelEditorPrivate::HandleAddEntity(const std::string &_geomOrLightType, + const std::string &_type, Entity _parentEntity, + const std::string &_uri) +{ + std::lock_guard lock(this->mutex); + std::string entType = common::lowercase(_type); + std::string geomLightType = common::lowercase(_geomOrLightType); + + EntityToAdd eta; + eta.entityType = entType; + eta.geomOrLightType = geomLightType; + eta.parentEntity = _parentEntity; + eta.uri = _uri; + this->entitiesToAdd.push_back(eta); +} diff --git a/src/gui/plugins/component_inspector/ModelEditor.hh b/src/gui/plugins/component_inspector/ModelEditor.hh new file mode 100644 index 0000000000..bfc09df897 --- /dev/null +++ b/src/gui/plugins/component_inspector/ModelEditor.hh @@ -0,0 +1,62 @@ +/* + * 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_MODELEDITOR_HH_ +#define IGNITION_GAZEBO_GUI_MODELEDITOR_HH_ + +#include + +#include + +#include + +namespace ignition +{ +namespace gazebo +{ + class ModelEditorPrivate; + + /// \brief Model Editor + class ModelEditor : public QObject + { + Q_OBJECT + + /// \brief Constructor + public: ModelEditor(); + + /// \brief Destructor + public: ~ModelEditor(); + + /// \brief Load the model editor + public: void Load(); + + /// \brief Update the model editor with data from ECM + /// \param[in] _info Simulator update info + /// \param[in] _ecm Reference to Entity Component Manager + public: void Update(const UpdateInfo &_info, EntityComponentManager &_ecm); + + // Documentation inherited + protected: bool eventFilter(QObject *_obj, QEvent *_event) override; + + /// \internal + /// \brief Pointer to private data. + private: std::unique_ptr dataPtr; + }; +} +} + +#endif diff --git a/src/gui/plugins/entity_tree/EntityTree.cc b/src/gui/plugins/entity_tree/EntityTree.cc index de909caa38..20aa36916a 100644 --- a/src/gui/plugins/entity_tree/EntityTree.cc +++ b/src/gui/plugins/entity_tree/EntityTree.cc @@ -129,6 +129,12 @@ void TreeModel::AddEntity(Entity _entity, const QString &_entityName, IGN_PROFILE("TreeModel::AddEntity"); QStandardItem *parentItem{nullptr}; + // check if entity has already been added or not. + // This could happen because we get new and removed entity updates from both + // the ECM and GUI events. + if (this->entityItems.find(_entity) != this->entityItems.end()) + return; + // Root if (_parentEntity == kNullEntity) { @@ -151,13 +157,6 @@ void TreeModel::AddEntity(Entity _entity, const QString &_entityName, return; } - if (this->entityItems.find(_entity) != this->entityItems.end()) - { - ignwarn << "Internal error: Trying to create item for entity [" << _entity - << "], but entity already has an item." << std::endl; - return; - } - // New entity item auto entityItem = new QStandardItem(_entityName); entityItem->setData(_entityName, this->roleNames().key("entityName")); diff --git a/src/gui/plugins/entity_tree/EntityTree.hh b/src/gui/plugins/entity_tree/EntityTree.hh index b899c12980..e4fc238943 100644 --- a/src/gui/plugins/entity_tree/EntityTree.hh +++ b/src/gui/plugins/entity_tree/EntityTree.hh @@ -135,8 +135,8 @@ namespace gazebo public: Q_INVOKABLE void OnInsertEntity(const QString &_type); /// \brief Callback to insert a new entity - /// \param[in] _type Type of entity to insert - public: Q_INVOKABLE void OnLoadMesh(const QString &_type); + /// \param[in] _mesh Mesh file to create a model from. + public: Q_INVOKABLE void OnLoadMesh(const QString &_mesh); // Documentation inherited protected: bool eventFilter(QObject *_obj, QEvent *_event) override; diff --git a/src/gui/plugins/entity_tree/EntityTree.qml b/src/gui/plugins/entity_tree/EntityTree.qml index b0cf0d55a3..ae1d8fec19 100644 --- a/src/gui/plugins/entity_tree/EntityTree.qml +++ b/src/gui/plugins/entity_tree/EntityTree.qml @@ -101,6 +101,20 @@ Rectangle { } } + // The component for a menu section header + Component { + id: menuSectionHeading + Rectangle { + height: childrenRect.height + + Text { + text: sectionText + font.pointSize: 10 + padding: 5 + } + } + } + Rectangle { id: header visible: true @@ -126,7 +140,7 @@ Rectangle { ToolButton { anchors.right: parent.right id: addEntity - ToolTip.text: "Add Entity" + ToolTip.text: "Add an entity to the world" ToolTip.visible: hovered contentItem: Image { fillMode: Image.Pad @@ -153,6 +167,15 @@ Rectangle { Menu { id: addEntityMenu + Item { + Layout.fillWidth: true + height: childrenRect.height + Loader { + property string sectionText: "Model" + sourceComponent: menuSectionHeading + } + } + MenuItem { id: box @@ -218,6 +241,15 @@ Rectangle { } } + Item { + Layout.fillWidth: true + height: childrenRect.height + Loader { + property string sectionText: "Light" + sourceComponent: menuSectionHeading + } + } + MenuItem { id: directionalLight diff --git a/src/gui/plugins/scene_manager/GzSceneManager.cc b/src/gui/plugins/scene_manager/GzSceneManager.cc index f7ec59fbac..a5c1d68cde 100644 --- a/src/gui/plugins/scene_manager/GzSceneManager.cc +++ b/src/gui/plugins/scene_manager/GzSceneManager.cc @@ -14,6 +14,7 @@ * limitations under the License. * */ +#include #include "GzSceneManager.hh" @@ -28,6 +29,7 @@ #include "ignition/gazebo/EntityComponentManager.hh" #include "ignition/gazebo/components/Name.hh" #include "ignition/gazebo/components/World.hh" +#include "ignition/gazebo/gui/GuiEvents.hh" #include "ignition/gazebo/rendering/RenderUtil.hh" namespace ignition @@ -46,6 +48,16 @@ inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { /// \brief Rendering utility public: RenderUtil renderUtil; + + /// \brief List of new entities from a gui event + public: std::set newEntities; + + /// \brief List of removed entities from a gui event + public: std::set removedEntities; + + /// \brief Mutex to protect gui event and system upate call race conditions + /// for newEntities and removedEntities + public: std::mutex newRemovedEntityMutex; }; } } @@ -80,16 +92,39 @@ void GzSceneManager::Update(const UpdateInfo &_info, IGN_PROFILE("GzSceneManager::Update"); this->dataPtr->renderUtil.UpdateECM(_info, _ecm); + + std::lock_guard lock(this->dataPtr->newRemovedEntityMutex); + { + this->dataPtr->renderUtil.CreateVisualsForEntities(_ecm, + this->dataPtr->newEntities); + this->dataPtr->newEntities.clear(); + } + this->dataPtr->renderUtil.UpdateFromECM(_info, _ecm); } ///////////////////////////////////////////////// bool GzSceneManager::eventFilter(QObject *_obj, QEvent *_event) { - if (_event->type() == gui::events::Render::kType) + if (_event->type() == ignition::gui::events::Render::kType) { this->dataPtr->OnRender(); } + else if (_event->type() == + ignition::gazebo::gui::events::AddedRemovedEntities::kType) + { + std::lock_guard lock(this->dataPtr->newRemovedEntityMutex); + auto addedRemovedEvent = + reinterpret_cast(_event); + if (addedRemovedEvent) + { + for (auto entity : addedRemovedEvent->NewEntities()) + this->dataPtr->newEntities.insert(entity); + + for (auto entity : addedRemovedEvent->RemovedEntities()) + this->dataPtr->removedEntities.insert(entity); + } + } // Standard event processing return QObject::eventFilter(_obj, _event); diff --git a/src/rendering/RenderUtil.cc b/src/rendering/RenderUtil.cc index 431373b129..4a70ac7b36 100644 --- a/src/rendering/RenderUtil.cc +++ b/src/rendering/RenderUtil.cc @@ -145,6 +145,53 @@ class ignition::gazebo::RenderUtilPrivate const sdf::Sensor &_sdfData, Entity _parent, const std::string &_topicSuffix); + /// \brief Helper function to create a visual for a link entity + /// \param[in] _ecm The entity-component manager + /// \param[in] _entity Entity to create the visual for + /// \param[in] _name Name component + /// \param[in] _pose Pose component + /// \param[in] _parent ParentEntity component + public: void CreateLink( + const EntityComponentManager &_ecm, + const Entity &_entity, + const components::Name *_name, + const components::Pose *_pose, + const components::ParentEntity *_parent); + + /// \brief Helper function to create a visual for a visual entity + /// \param[in] _ecm The entity-component manager + /// \param[in] _entity Entity to create the visual for + /// \param[in] _name Name component + /// \param[in] _pose Pose component + /// \param[in] _geom Geometry component + /// \param[in] _castShadows CastShadows component + /// \param[in] _transparency Transparency component + /// \param[in] _visibilityFlags VisibilityFlags component + /// \param[in] _parent ParentEntity component + public: void CreateVisual( + const EntityComponentManager &_ecm, + const Entity &_entity, + const components::Name *_name, + const components::Pose *_pose, + const components::Geometry *_geom, + const components::CastShadows *_castShadows, + const components::Transparency *_transparency, + const components::VisibilityFlags *_visibilityFlags, + const components::ParentEntity *_parent); + + /// \brief Helper function to create a visual for a light entity + /// \param[in] _ecm The entity-component manager + /// \param[in] _entity Entity to create the visual for + /// \param[in] _light Light component + /// \param[in] _name Name component + /// \param[in] _parent ParentEntity component + public: void CreateLight( + const EntityComponentManager &_ecm, + const Entity &_entity, + const components::Light *_light, + const components::Name *_name, + const components::ParentEntity *_parent); + /// \brief Total time elapsed in simulation. This will not increase while /// paused. public: std::chrono::steady_clock::duration simTime{0}; @@ -1499,16 +1546,7 @@ void RenderUtilPrivate::CreateEntitiesFirstUpdate( const components::Pose *_pose, const components::ParentEntity *_parent)->bool { - sdf::Link link; - link.SetName(_name->Data()); - link.SetRawPose(_pose->Data()); - this->newLinks.push_back( - std::make_tuple(_entity, link, _parent->Data())); - // used for collsions - this->modelToLinkEntities[_parent->Data()].push_back(_entity); - // used for joints - this->matchLinksWithEntities[_parent->Data()][_name->Data()] = - _entity; + this->CreateLink(_ecm, _entity, _name, _pose, _parent); return true; }); @@ -1529,62 +1567,8 @@ void RenderUtilPrivate::CreateEntitiesFirstUpdate( const components::VisibilityFlags *_visibilityFlags, const components::ParentEntity *_parent)->bool { - sdf::Visual visual; - visual.SetName(_name->Data()); - visual.SetRawPose(_pose->Data()); - visual.SetGeom(_geom->Data()); - visual.SetCastShadows(_castShadows->Data()); - visual.SetTransparency(_transparency->Data()); - visual.SetVisibilityFlags(_visibilityFlags->Data()); - - // Optional components - auto material = _ecm.Component(_entity); - if (material != nullptr) - { - visual.SetMaterial(material->Data()); - } - - auto laserRetro = _ecm.Component(_entity); - if (laserRetro != nullptr) - { - visual.SetLaserRetro(laserRetro->Data()); - } - - // set label - auto label = _ecm.Component(_entity); - if (label != nullptr) - { - this->entityLabel[_entity] = label->Data(); - } - - if (auto temp = _ecm.Component(_entity)) - { - // get the uniform temperature for the entity - this->entityTemp[_entity] = std::make_tuple - (temp->Data().Kelvin(), 0.0, ""); - } - else - { - // entity doesn't have a uniform temperature. Check if it has - // a heat signature with an associated temperature range - auto heatSignature = - _ecm.Component(_entity); - auto tempRange = - _ecm.Component(_entity); - if (heatSignature && tempRange) - { - this->entityTemp[_entity] = - std::make_tuple( - tempRange->Data().min.Kelvin(), - tempRange->Data().max.Kelvin(), - std::string(heatSignature->Data())); - } - } - - this->newVisuals.push_back( - std::make_tuple(_entity, visual, _parent->Data())); - - this->linkToVisualEntities[_parent->Data()].push_back(_entity); + this->CreateVisual(_ecm, _entity, _name, _pose, _geom, _castShadows, + _transparency, _visibilityFlags, _parent); return true; }); @@ -1615,8 +1599,7 @@ void RenderUtilPrivate::CreateEntitiesFirstUpdate( const components::Name *_name, const components::ParentEntity *_parent) -> bool { - this->newLights.push_back(std::make_tuple(_entity, _light->Data(), - _name->Data(), _parent->Data())); + this->CreateLight(_ecm, _entity, _light, _name, _parent); return true; }); @@ -1816,16 +1799,7 @@ void RenderUtilPrivate::CreateEntitiesRuntime( const components::Pose *_pose, const components::ParentEntity *_parent)->bool { - sdf::Link link; - link.SetName(_name->Data()); - link.SetRawPose(_pose->Data()); - this->newLinks.push_back( - std::make_tuple(_entity, link, _parent->Data())); - // used for collsions - this->modelToLinkEntities[_parent->Data()].push_back(_entity); - // used for joints - this->matchLinksWithEntities[_parent->Data()][_name->Data()] = - _entity; + this->CreateLink(_ecm, _entity, _name, _pose, _parent); return true; }); @@ -1846,62 +1820,8 @@ void RenderUtilPrivate::CreateEntitiesRuntime( const components::VisibilityFlags *_visibilityFlags, const components::ParentEntity *_parent)->bool { - sdf::Visual visual; - visual.SetName(_name->Data()); - visual.SetRawPose(_pose->Data()); - visual.SetGeom(_geom->Data()); - visual.SetCastShadows(_castShadows->Data()); - visual.SetTransparency(_transparency->Data()); - visual.SetVisibilityFlags(_visibilityFlags->Data()); - - // Optional components - auto material = _ecm.Component(_entity); - if (material != nullptr) - { - visual.SetMaterial(material->Data()); - } - - auto laserRetro = _ecm.Component(_entity); - if (laserRetro != nullptr) - { - visual.SetLaserRetro(laserRetro->Data()); - } - - // set label - auto label = _ecm.Component(_entity); - if (label != nullptr) - { - this->entityLabel[_entity] = label->Data(); - } - - if (auto temp = _ecm.Component(_entity)) - { - // get the uniform temperature for the entity - this->entityTemp[_entity] = std::make_tuple - (temp->Data().Kelvin(), 0.0, ""); - } - else - { - // entity doesn't have a uniform temperature. Check if it has - // a heat signature with an associated temperature range - auto heatSignature = - _ecm.Component(_entity); - auto tempRange = - _ecm.Component(_entity); - if (heatSignature && tempRange) - { - this->entityTemp[_entity] = - std::make_tuple( - tempRange->Data().min.Kelvin(), - tempRange->Data().max.Kelvin(), - std::string(heatSignature->Data())); - } - } - - this->newVisuals.push_back( - std::make_tuple(_entity, visual, _parent->Data())); - - this->linkToVisualEntities[_parent->Data()].push_back(_entity); + this->CreateVisual(_ecm, _entity, _name, _pose, _geom, _castShadows, + _transparency, _visibilityFlags, _parent); return true; }); @@ -1933,8 +1853,7 @@ void RenderUtilPrivate::CreateEntitiesRuntime( const components::Name *_name, const components::ParentEntity *_parent) -> bool { - this->newLights.push_back(std::make_tuple(_entity, _light->Data(), - _name->Data(), _parent->Data())); + this->CreateLight(_ecm, _entity, _light, _name, _parent); return true; }); @@ -3436,3 +3355,147 @@ void RenderUtil::ViewCollisions(const Entity &_entity) } } } + +///////////////////////////////////////////////// +void RenderUtil::CreateVisualsForEntities( + const EntityComponentManager &_ecm, + const std::set &_entities) +{ + for (auto const &ent : _entities) + { + auto linkComp = _ecm.Component(ent); + if (linkComp) + { + this->dataPtr->CreateLink(_ecm, ent, + _ecm.Component(ent), + _ecm.Component(ent), + _ecm.Component(ent)); + continue; + } + + auto visualComp = _ecm.Component(ent); + if (visualComp) + { + this->dataPtr->CreateVisual(_ecm, ent, + _ecm.Component(ent), + _ecm.Component(ent), + _ecm.Component(ent), + _ecm.Component(ent), + _ecm.Component(ent), + _ecm.Component(ent), + _ecm.Component(ent)); + continue; + } + auto lightComp = _ecm.Component(ent); + if (lightComp) + { + this->dataPtr->CreateLight(_ecm, ent, + _ecm.Component(ent), + _ecm.Component(ent), + _ecm.Component(ent)); + continue; + } + } +} + +///////////////////////////////////////////////// +void RenderUtilPrivate::CreateLink( + const EntityComponentManager &/*_ecm*/, + const Entity &_entity, + const components::Name *_name, + const components::Pose *_pose, + const components::ParentEntity *_parent) +{ + sdf::Link link; + link.SetName(_name->Data()); + link.SetRawPose(_pose->Data()); + this->newLinks.push_back( + std::make_tuple(_entity, link, _parent->Data())); + // used for collsions + this->modelToLinkEntities[_parent->Data()].push_back(_entity); + // used for joints + this->matchLinksWithEntities[_parent->Data()][_name->Data()] = + _entity; +} + +///////////////////////////////////////////////// +void RenderUtilPrivate::CreateVisual( + const EntityComponentManager &_ecm, + const Entity &_entity, + const components::Name *_name, + const components::Pose *_pose, + const components::Geometry *_geom, + const components::CastShadows *_castShadows, + const components::Transparency *_transparency, + const components::VisibilityFlags *_visibilityFlags, + const components::ParentEntity *_parent) +{ + sdf::Visual visual; + visual.SetName(_name->Data()); + visual.SetRawPose(_pose->Data()); + visual.SetGeom(_geom->Data()); + visual.SetCastShadows(_castShadows->Data()); + visual.SetTransparency(_transparency->Data()); + visual.SetVisibilityFlags(_visibilityFlags->Data()); + + // Optional components + auto material = _ecm.Component(_entity); + if (material != nullptr) + { + visual.SetMaterial(material->Data()); + } + + auto laserRetro = _ecm.Component(_entity); + if (laserRetro != nullptr) + { + visual.SetLaserRetro(laserRetro->Data()); + } + + // set label + auto label = _ecm.Component(_entity); + if (label != nullptr) + { + this->entityLabel[_entity] = label->Data(); + } + + if (auto temp = _ecm.Component(_entity)) + { + // get the uniform temperature for the entity + this->entityTemp[_entity] = std::make_tuple + (temp->Data().Kelvin(), 0.0, ""); + } + else + { + // entity doesn't have a uniform temperature. Check if it has + // a heat signature with an associated temperature range + auto heatSignature = + _ecm.Component(_entity); + auto tempRange = + _ecm.Component(_entity); + if (heatSignature && tempRange) + { + this->entityTemp[_entity] = + std::make_tuple( + tempRange->Data().min.Kelvin(), + tempRange->Data().max.Kelvin(), + std::string(heatSignature->Data())); + } + } + + this->newVisuals.push_back( + std::make_tuple(_entity, visual, _parent->Data())); + + this->linkToVisualEntities[_parent->Data()].push_back(_entity); +} + +///////////////////////////////////////////////// +void RenderUtilPrivate::CreateLight( + const EntityComponentManager &/*_ecm*/, + const Entity &_entity, + const components::Light *_light, + const components::Name *_name, + const components::ParentEntity *_parent) +{ + this->newLights.push_back(std::make_tuple(_entity, _light->Data(), + _name->Data(), _parent->Data())); +}