diff --git a/CMakeLists.txt b/CMakeLists.txt index 41bed03ae9..77729eb948 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.2) set(SDF_VER ${sdformat12_VERSION_MAJOR}) #-------------------------------------- diff --git a/Changelog.md b/Changelog.md index b0d80c0a43..53aebf90bc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,105 @@ ## Ignition Gazebo 6.x +### Ignition Gazebo 6.X.X (202X-XX-XX) + +### Ignition Gazebo 6.2.0 (2021-11-16) + +1. Configurable joint state publisher's topic + * [Pull request #1076](https://github.com/ignitionrobotics/ign-gazebo/pull/1076) + +1. Thruster plugin: add tests and velocity control + * [Pull request #1190](https://github.com/ignitionrobotics/ign-gazebo/pull/1190) + +1. Prevent creation of spurious `` elements when saving worlds + * [Pull request #1192](https://github.com/ignitionrobotics/ign-gazebo/pull/1192) + +1. Add `sdfString` to `ServerConfig`'s copy constructor. + * [Pull request #1185](https://github.com/ignitionrobotics/ign-gazebo/pull/1185) + +1. Added support for tracked vehicles + * [Pull request #869](https://github.com/ignitionrobotics/ign-gazebo/pull/869) + +1. Add components to dynamically set joint limits + * [Pull request #847](https://github.com/ignitionrobotics/ign-gazebo/pull/847) + +1. Remove bounding box when entities are removed + * [Pull request #1053](https://github.com/ignitionrobotics/ign-gazebo/pull/1053) + * [Pull request #1213](https://github.com/ignitionrobotics/ign-gazebo/pull/1213) + +1. Fix updating component from state + * [Pull request #1181](https://github.com/ignitionrobotics/ign-gazebo/pull/1181) + +1. Extend odom publisher to allow 3D + * [Pull request #1180](https://github.com/ignitionrobotics/ign-gazebo/pull/1180) + +1. Support copy/paste + * [Pull request #1013](https://github.com/ignitionrobotics/ign-gazebo/pull/1013) + +1. Tweaks install instructions + * [Pull request #1078](https://github.com/ignitionrobotics/ign-gazebo/pull/1078) + +1. Publish 10 world stats msgs/sec instead of 5 + * [Pull request #1163](https://github.com/ignitionrobotics/ign-gazebo/pull/1163) + +1. Add functionality to add entities via the entity tree + * [Pull request #1101](https://github.com/ignitionrobotics/ign-gazebo/pull/1101) + +1. Get updated GUI ECM info when a user presses 'play' + * [Pull request #1109](https://github.com/ignitionrobotics/ign-gazebo/pull/1109) + +1. Create expanding type header to reduce code duplication + * [Pull request #1169](https://github.com/ignitionrobotics/ign-gazebo/pull/1169) + +1. `minimal_scene.sdf` example: add `camera_clip` params + * [Pull request #1166](https://github.com/ignitionrobotics/ign-gazebo/pull/1166) + +1. Sensor systems work if loaded after sensors + * [Pull request #1104](https://github.com/ignitionrobotics/ign-gazebo/pull/1104) + +1. Support printing sensors using `ign model` + * [Pull request #1157](https://github.com/ignitionrobotics/ign-gazebo/pull/1157) + +1. Set camera clipping plane distances from the GUI + * [Pull request #1162](https://github.com/ignitionrobotics/ign-gazebo/pull/1162) + +1. Fix generation of systems library symlinks in build directory + * [Pull request #1160](https://github.com/ignitionrobotics/ign-gazebo/pull/1160) + +1. Add a default value for `isHeadlessRendering`. + * [Pull request #1151](https://github.com/ignitionrobotics/ign-gazebo/pull/1151) + +1. Component inspector + + 1. Edit material colors + * [Pull request #1123](https://github.com/ignitionrobotics/ign-gazebo/pull/1123) + * [Pull request #1186](https://github.com/ignitionrobotics/ign-gazebo/pull/1186) + + 1. Fix integers and floats + * [Pull request #1143](https://github.com/ignitionrobotics/ign-gazebo/pull/1143) + + 1. Prevent a segfault when updating + * [Pull request #1167](https://github.com/ignitionrobotics/ign-gazebo/pull/1167) + + 1. Use `uint64_t` for Entity IDs + * [Pull request #1144](https://github.com/ignitionrobotics/ign-gazebo/pull/1144) + +1. Support setting the background color for sensors + * [Pull request #1147](https://github.com/ignitionrobotics/ign-gazebo/pull/1147) + +1. Select top level entity not visual + * [Pull request #1150](https://github.com/ignitionrobotics/ign-gazebo/pull/1150) + +1. Update create entity offset on GUI side + * [Pull request #1145](https://github.com/ignitionrobotics/ign-gazebo/pull/1145) + +1. Update Select Entities GUI plugin to use Entity type + * [Pull request #1146](https://github.com/ignitionrobotics/ign-gazebo/pull/1146) + +1. Notify other GUI plugins of added/removed entities via GUI events + * [Pull request #1138](https://github.com/ignitionrobotics/ign-gazebo/pull/1138) + * [Pull request #1213](https://github.com/ignitionrobotics/ign-gazebo/pull/1213) + ### Ignition Gazebo 6.1.0 (2021-10-25) 1. Updates to camera video record from subt diff --git a/examples/worlds/plane_propeller_demo.sdf b/examples/worlds/plane_propeller_demo.sdf index d7f104cbf0..3604385a09 100644 --- a/examples/worlds/plane_propeller_demo.sdf +++ b/examples/worlds/plane_propeller_demo.sdf @@ -24,6 +24,10 @@ filename="ignition-gazebo-scene-broadcaster-system" name="ignition::gazebo::systems::SceneBroadcaster"> + + true diff --git a/examples/worlds/sensors.sdf b/examples/worlds/sensors.sdf index eefd6a8ee8..b07bd89925 100644 --- a/examples/worlds/sensors.sdf +++ b/examples/worlds/sensors.sdf @@ -35,6 +35,10 @@ filename="ignition-gazebo-magnetometer-system" name="ignition::gazebo::systems::Magnetometer"> + + @@ -186,5 +190,71 @@ + + + true + + + + 0 0 2.0 0 0 0 + + + 0.100000 + 0.000000 + 0.000000 + 0.100000 + 0.000000 + 0.100000 + + 10.000000 + + + + + 0.100000 + + + + + 0 0 -0.75 0 0 0 + + + 0.0100000 + 1.5 + + + + + + + 0.100000 + + + + + + world + base_plate + + + base_plate + link_1 + 0 0 -1.5 0 0 0 + + + -1.57079 + 1.57079 + + + 0.000000 + 0.000000 + + 1.000000 0.000000 0.000000 + + + 10 + + + diff --git a/include/ignition/gazebo/gui/GuiEvents.hh b/include/ignition/gazebo/gui/GuiEvents.hh index d05645a49e..8bac275993 100644 --- a/include/ignition/gazebo/gui/GuiEvents.hh +++ b/include/ignition/gazebo/gui/GuiEvents.hh @@ -23,6 +23,8 @@ #include #include #include +#include +#include "ignition/gazebo/gui/Export.hh" #include "ignition/gazebo/Entity.hh" #include "ignition/gazebo/config.hh" @@ -38,7 +40,7 @@ inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { namespace events { /// \brief Event that notifies when new entities have been selected. - class EntitiesSelected : public QEvent + class IGNITION_GAZEBO_GUI_VISIBLE EntitiesSelected : public QEvent { /// \brief Constructor /// \param[in] _entities All the selected entities @@ -76,7 +78,7 @@ namespace events }; /// \brief Event that notifies when all entities have been deselected. - class DeselectAllEntities : public QEvent + class IGNITION_GAZEBO_GUI_VISIBLE DeselectAllEntities : public QEvent { /// \brief Constructor /// \param[in] _fromUser True if the event was directly generated by the @@ -100,68 +102,60 @@ namespace events private: bool fromUser{false}; }; - /// \brief Event that contains newly created and removed entities - class AddedRemovedEntities : public QEvent + /// \brief Event that contains entities newly created or removed from the + /// GUI, but that aren't present on the server yet. + /// \sa NewRemovedEntities + class IGNITION_GAZEBO_GUI_VISIBLE GuiNewRemovedEntities : public QEvent { /// \brief Constructor /// \param[in] _newEntities Set of newly created entities /// \param[in] _removedEntities Set of recently removed entities - public: AddedRemovedEntities(const std::set &_newEntities, - const std::set &_removedEntities) - : QEvent(kType), newEntities(_newEntities), - removedEntities(_removedEntities) - { - } + public: GuiNewRemovedEntities(const std::set &_newEntities, + const std::set &_removedEntities); /// \brief Get the set of newly created entities - public: const std::set &NewEntities() const - { - return this->newEntities; - } + public: const std::set &NewEntities() const; /// \brief Get the set of recently removed entities - public: const std::set &RemovedEntities() const - { - return this->removedEntities; - } + public: const std::set &RemovedEntities() const; /// \brief Unique type for this event. static const QEvent::Type kType = QEvent::Type(QEvent::User + 3); - /// \brief Set of newly created entities - private: std::set newEntities; - - /// \brief Set of recently removed entities - private: std::set removedEntities; + /// \internal + /// \brief Private data pointer + IGN_UTILS_IMPL_PTR(dataPtr) }; - /// \brief Event that notifies when new entities have been removed. - class RemovedEntities : public QEvent + /// \brief Event that notifies when new entities have been created or removed + /// on the server. This is a duplication of what `GuiSystem`s would get from + /// `EachNew` / `EachRemoved` ECM calls. + /// \sa GuiNewRemovedEntities + class IGNITION_GAZEBO_GUI_VISIBLE NewRemovedEntities : public QEvent { /// \brief Constructor - /// \param[in] _entities All the removed entities - public: explicit RemovedEntities(const std::vector &_entities) - : QEvent(kType), entities(_entities) - { - } + /// \param[in] _newEntities Set of newly created entities + /// \param[in] _removedEntities Set of recently removed entities + public: NewRemovedEntities(const std::set &_newEntities, + const std::set &_removedEntities); - /// \brief Get the data sent with the event. - /// \return The entities being removed. - public: std::vector Data() const - { - return this->entities; - } + /// \brief Get the set of newly created entities + public: const std::set &NewEntities() const; + + /// \brief Get the set of recently removed entities + public: const std::set &RemovedEntities() const; /// \brief Unique type for this event. static const QEvent::Type kType = QEvent::Type(QEvent::User + 4); - /// \brief The removed entities. - private: std::vector entities; + /// \internal + /// \brief Private data pointer + IGN_UTILS_IMPL_PTR(dataPtr) }; /// \brief True if a transform control is currently active (translate / /// rotate / scale). False if we're in selection mode. - class TransformControlModeActive : public QEvent + class IGNITION_GAZEBO_GUI_VISIBLE TransformControlModeActive : public QEvent { /// \brief Constructor /// \param[in] _tranformModeActive is the transform control mode active diff --git a/src/SdfGenerator.cc b/src/SdfGenerator.cc index e2ca63bfc1..9f8b9286c2 100644 --- a/src/SdfGenerator.cc +++ b/src/SdfGenerator.cc @@ -26,12 +26,37 @@ #include #include "ignition/gazebo/Util.hh" +#include "ignition/gazebo/components/AirPressureSensor.hh" +#include "ignition/gazebo/components/Altimeter.hh" +#include "ignition/gazebo/components/Camera.hh" +#include "ignition/gazebo/components/ChildLinkName.hh" +#include "ignition/gazebo/components/ContactSensor.hh" +#include "ignition/gazebo/components/DepthCamera.hh" +#include "ignition/gazebo/components/ForceTorque.hh" +#include "ignition/gazebo/components/GpuLidar.hh" +#include "ignition/gazebo/components/Imu.hh" +#include "ignition/gazebo/components/Inertial.hh" +#include "ignition/gazebo/components/Joint.hh" +#include "ignition/gazebo/components/JointAxis.hh" +#include "ignition/gazebo/components/JointType.hh" #include "ignition/gazebo/components/Light.hh" +#include "ignition/gazebo/components/Link.hh" +#include "ignition/gazebo/components/LogicalCamera.hh" +#include "ignition/gazebo/components/Magnetometer.hh" #include "ignition/gazebo/components/Model.hh" #include "ignition/gazebo/components/Name.hh" #include "ignition/gazebo/components/ParentEntity.hh" +#include "ignition/gazebo/components/ParentLinkName.hh" #include "ignition/gazebo/components/Pose.hh" +#include "ignition/gazebo/components/RgbdCamera.hh" +#include "ignition/gazebo/components/SelfCollide.hh" +#include "ignition/gazebo/components/Sensor.hh" #include "ignition/gazebo/components/SourceFilePath.hh" +#include "ignition/gazebo/components/SegmentationCamera.hh" +#include "ignition/gazebo/components/Static.hh" +#include "ignition/gazebo/components/ThermalCamera.hh" +#include "ignition/gazebo/components/ThreadPitch.hh" +#include "ignition/gazebo/components/WindMode.hh" #include "ignition/gazebo/components/World.hh" @@ -277,7 +302,7 @@ namespace sdf_generator // First remove child entities of whose names can be changed during // simulation (eg. models). Then we add them back from the data in the // ECM. - // TODO(addisu) Remove actors and lights + // TODO(addisu) Remove actors std::vector toRemove; if (_elem->HasElement("model")) { @@ -287,6 +312,15 @@ namespace sdf_generator toRemove.push_back(modelElem); } } + if (_elem->HasElement("light")) + { + for (auto lightElem = _elem->GetElement("light"); lightElem; + lightElem = lightElem->GetNextElement("light")) + { + toRemove.push_back(lightElem); + } + } + for (const auto &e : toRemove) { _elem->RemoveChild(e); @@ -294,6 +328,7 @@ namespace sdf_generator auto worldDir = common::parentPath(worldSdf->Data().Element()->FilePath()); + // models _ecm.Each( [&](const Entity &_modelEntity, const components::Model *, const components::ModelSdf *_modelSdf) @@ -386,6 +421,21 @@ namespace sdf_generator return true; }); + // lights + _ecm.Each( + [&](const Entity &_lightEntity, + const components::Light *, + const components::ParentEntity *_parent) -> bool + { + if (_parent->Data() != _entity) + return true; + + auto lightElem = _elem->AddElement("light"); + updateLightElement(lightElem, _ecm, _lightEntity); + + return true; + }); + return true; } @@ -397,10 +447,12 @@ namespace sdf_generator if (!copySdf(_ecm.Component(_entity), _elem)) return false; - // Update sdf based current components. Here are the list of components to - // be updated: + // Update sdf based on current components. Here are the list of components + // to be updated: // - Name // - Pose + // - Static + // - SelfCollide // This list is to be updated as other components become updateable during // simulation auto *nameComp = _ecm.Component(_entity); @@ -421,19 +473,463 @@ namespace sdf_generator } poseElem->Set(poseComp->Data()); + // static + auto *staticComp = _ecm.Component(_entity); + if (staticComp) + _elem->GetElement("static")->Set(staticComp->Data()); + + // self collide + auto *selfCollideComp = _ecm.Component(_entity); + if (selfCollideComp) + _elem->GetElement("self_collide")->Set(selfCollideComp->Data()); + const auto *pathComp = _ecm.Component(_entity); - if (_elem->HasElement("link") && nullptr != pathComp) + if (_elem->HasElement("link")) + { + if (nullptr != pathComp) + { + // Update relative URIs to use absolute paths. Relative URIs work fine + // in included models, but they have to be converted to absolute URIs + // when the included model is expanded. + relativeToAbsoluteUri(_elem, common::parentPath(pathComp->Data())); + } + + // update links + sdf::ElementPtr linkElem = _elem->GetElement("link"); + while (linkElem) + { + std::string linkName = linkElem->Get("name"); + auto linkEnt = _ecm.EntityByComponents( + components::ParentEntity(_entity), components::Name(linkName)); + if (linkEnt != kNullEntity) + updateLinkElement(linkElem, _ecm, linkEnt); + linkElem = linkElem->GetNextElement("link"); + } + } + + if (_elem->HasElement("joint")) + { + // update joints + sdf::ElementPtr jointElem = _elem->GetElement("joint"); + while (jointElem) + { + std::string jointName = jointElem->Get("name"); + auto jointEnt = _ecm.EntityByComponents( + components::ParentEntity(_entity), components::Name(jointName)); + if (jointEnt != kNullEntity) + updateJointElement(jointElem, _ecm, jointEnt); + jointElem = jointElem->GetNextElement("joint"); + } + } + + return true; + } + + ///////////////////////////////////////////////// + bool updateLinkElement(const sdf::ElementPtr &_elem, + const EntityComponentManager &_ecm, + const Entity &_entity) + { + // Update sdf based on current components. Here are the list of components + // to be updated: + // - Name + // - Pose + // - Inertial + // - WindMode + // This list is to be updated as other components become updateable during + // simulation + auto *nameComp = _ecm.Component(_entity); + _elem->GetAttribute("name")->Set(nameComp->Data()); + + auto *poseComp = _ecm.Component(_entity); + + auto poseElem = _elem->GetElement("pose"); + + // Remove all attributes of poseElem + for (const auto *attrName : {"relative_to", "degrees", "rotation_format"}) + { + sdf::ParamPtr attr = poseElem->GetAttribute(attrName); + if (nullptr != attr) + { + attr->Reset(); + } + } + poseElem->Set(poseComp->Data()); + + // inertial + auto inertialComp = _ecm.Component(_entity); + if (inertialComp) { - // Update relative URIs to use absolute paths. Relative URIs work fine in - // included models, but they have to be converted to absolute URIs when - // the included model is expanded. - relativeToAbsoluteUri(_elem, common::parentPath(pathComp->Data())); + math::Inertiald inertial = inertialComp->Data(); + sdf::ElementPtr inertialElem = _elem->GetElement("inertial"); + inertialElem->GetElement("pose")->Set(inertial.Pose()); + const math::MassMatrix3d &massMatrix = inertial.MassMatrix(); + inertialElem->GetElement("mass")->Set(massMatrix.Mass()); + sdf::ElementPtr inertiaElem = inertialElem->GetElement("inertia"); + inertiaElem->GetElement("ixx")->Set(massMatrix.Ixx()); + inertiaElem->GetElement("ixy")->Set(massMatrix.Ixy()); + inertiaElem->GetElement("ixz")->Set(massMatrix.Ixz()); + inertiaElem->GetElement("iyy")->Set(massMatrix.Iyy()); + inertiaElem->GetElement("iyz")->Set(massMatrix.Iyz()); + inertiaElem->GetElement("izz")->Set(massMatrix.Izz()); } + + // wind mode + auto windModeComp = _ecm.Component(_entity); + if (windModeComp) + { + bool windMode = windModeComp->Data(); + _elem->GetElement("enable_wind")->Set(windMode); + } + + // update sensors + if (_elem->HasElement("sensor")) + { + sdf::ElementPtr sensorElem = _elem->GetElement("sensor"); + while (sensorElem) + { + std::string sensorName = sensorElem->Get("name"); + auto sensorEnt = _ecm.EntityByComponents( + components::ParentEntity(_entity), components::Name(sensorName)); + if (sensorEnt != kNullEntity) + updateSensorElement(sensorElem, _ecm, sensorEnt); + sensorElem = sensorElem->GetNextElement("sensor"); + } + } + + // update lights + if (_elem->HasElement("light")) + { + sdf::ElementPtr lightElem = _elem->GetElement("light"); + while (lightElem) + { + std::string lightName = lightElem->Get("name"); + auto lightEnt = _ecm.EntityByComponents( + components::ParentEntity(_entity), components::Name(lightName)); + if (lightEnt != kNullEntity) + updateLightElement(lightElem, _ecm, lightEnt); + lightElem = lightElem->GetNextElement("light"); + } + } + return true; } + ///////////////////////////////////////////////// + bool updateSensorElement(sdf::ElementPtr _elem, + const EntityComponentManager &_ecm, + const Entity &_entity) + { + // Update sdf based on current components. + // This list is to be updated as other components become updateable during + // simulation + auto updateSensorNameAndPose = [&] + { + // override name and pose sdf element using values from ECM + auto *nameComp = _ecm.Component(_entity); + _elem->GetAttribute("name")->Set(nameComp->Data()); + + auto *poseComp = _ecm.Component(_entity); + auto poseElem = _elem->GetElement("pose"); + + // Remove all attributes of poseElem + for (const auto *attrName : {"relative_to", "degrees", "rotation_format"}) + { + sdf::ParamPtr attr = poseElem->GetAttribute(attrName); + if (nullptr != attr) + { + attr->Reset(); + } + } + poseElem->Set(poseComp->Data()); + return true; + }; + + // camera + auto camComp = _ecm.Component(_entity); + if (camComp) + { + const sdf::Sensor &sensor = camComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // depth camera + auto depthCamComp = _ecm.Component(_entity); + if (depthCamComp) + { + const sdf::Sensor &sensor = depthCamComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // thermal camera + auto thermalCamComp = _ecm.Component(_entity); + if (thermalCamComp) + { + const sdf::Sensor &sensor = thermalCamComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // logical camera + auto logicalCamComp = _ecm.Component(_entity); + if (logicalCamComp) + { + // components::LogicalCamera holds an sdf::ElementPtr instead of an + // sdf::Sensor + _elem = logicalCamComp->Data(); + return updateSensorNameAndPose(); + } + // segmentation camera + auto segmentationCamComp = + _ecm.Component(_entity); + if (segmentationCamComp) + { + const sdf::Sensor &sensor = segmentationCamComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + + // gpu lidar + auto gpuLidarComp = _ecm.Component(_entity); + if (gpuLidarComp) + { + const sdf::Sensor &sensor = gpuLidarComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // altimeter + auto altimeterComp = _ecm.Component(_entity); + if (altimeterComp) + { + const sdf::Sensor &sensor = altimeterComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // contact + auto contactComp = _ecm.Component(_entity); + if (contactComp) + { + // components::ContactSensor holds an sdf::ElementPtr instead of an + // sdf::Sensor + _elem = contactComp->Data(); + return updateSensorNameAndPose(); + } + // air pressure + auto airPressureComp = + _ecm.Component(_entity); + if (airPressureComp) + { + const sdf::Sensor &sensor = airPressureComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // force torque + auto forceTorqueComp = _ecm.Component(_entity); + if (forceTorqueComp) + { + const sdf::Sensor &sensor = forceTorqueComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // imu + auto imuComp = _ecm.Component(_entity); + if (imuComp) + { + const sdf::Sensor &sensor = imuComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + // magnetometer + auto magnetometerComp = + _ecm.Component(_entity); + if (magnetometerComp) + { + const sdf::Sensor &sensor = magnetometerComp->Data(); + _elem->Copy(sensor.ToElement()); + return updateSensorNameAndPose(); + } + + return true; + } + + ///////////////////////////////////////////////// + bool updateLightElement(sdf::ElementPtr _elem, + const EntityComponentManager &_ecm, + const Entity &_entity) + { + // Update sdf based on the light component + auto updateLightNameAndPose = [&] + { + // override name and pose sdf element using values from ECM + auto *nameComp = _ecm.Component(_entity); + _elem->GetAttribute("name")->Set(nameComp->Data()); + + auto *poseComp = _ecm.Component(_entity); + auto poseElem = _elem->GetElement("pose"); + + // Remove all attributes of poseElem + for (const auto *attrName : {"relative_to", "degrees", "rotation_format"}) + { + sdf::ParamPtr attr = poseElem->GetAttribute(attrName); + if (nullptr != attr) + { + attr->Reset(); + } + } + poseElem->Set(poseComp->Data()); + return true; + }; + + // light + auto lightComp = _ecm.Component(_entity); + if (lightComp) + { + const sdf::Light &light = lightComp->Data(); + _elem->Copy(light.ToElement()); + return updateLightNameAndPose(); + } + return true; + } + + ///////////////////////////////////////////////// + bool updateJointElement(sdf::ElementPtr _elem, + const EntityComponentManager &_ecm, + const Entity &_entity) + { + // Update sdf based on the joint component + auto updateJointNameAndPose = [&] + { + // override name and pose sdf element using values from ECM + auto *nameComp = _ecm.Component(_entity); + _elem->GetAttribute("name")->Set(nameComp->Data()); + + auto *poseComp = _ecm.Component(_entity); + auto poseElem = _elem->GetElement("pose"); + + // Remove all attributes of poseElem + for (const auto *attrName : {"relative_to", "degrees", "rotation_format"}) + { + sdf::ParamPtr attr = poseElem->GetAttribute(attrName); + if (nullptr != attr) + { + attr->Reset(); + } + } + poseElem->Set(poseComp->Data()); + return true; + }; + + // joint + auto jointComp = _ecm.Component(_entity); + if (!jointComp) + { + return false; + } + + // joint type + auto jointTypeComp = _ecm.Component(_entity); + sdf::JointType jointType = jointTypeComp->Data(); + if (jointTypeComp) + { + std::string jointTypeStr = "invalid"; + switch (jointType) + { + case sdf::JointType::BALL: + jointTypeStr = "ball"; + break; + case sdf::JointType::CONTINUOUS: + jointTypeStr = "continuous"; + break; + case sdf::JointType::FIXED: + jointTypeStr = "fixed"; + break; + case sdf::JointType::PRISMATIC: + jointTypeStr = "prismatic"; + break; + case sdf::JointType::GEARBOX: + jointTypeStr = "gearbox"; + break; + case sdf::JointType::REVOLUTE: + jointTypeStr = "revolute"; + break; + case sdf::JointType::REVOLUTE2: + jointTypeStr = "revolute2"; + break; + case sdf::JointType::SCREW: + jointTypeStr = "screw"; + break; + case sdf::JointType::UNIVERSAL: + jointTypeStr = "universal"; + break; + default: + break; + } + _elem->GetAttribute("type")->Set(jointTypeStr); + } + + // parent + auto parentLinkNameComp = + _ecm.Component(_entity); + if (parentLinkNameComp) + { + _elem->GetElement("parent")->Set(parentLinkNameComp->Data()); + } + // child + auto childLinkNameComp = _ecm.Component(_entity); + if (childLinkNameComp) + { + _elem->GetElement("child")->Set(childLinkNameComp->Data()); + } + // thread pitch + auto threadPitchComp = _ecm.Component(_entity); + if (threadPitchComp && jointType == sdf::JointType::SCREW) + { + _elem->GetElement("thread_pitch")->Set(threadPitchComp->Data()); + } + // axis + auto jointAxisComp = _ecm.Component(_entity); + if (jointAxisComp) + { + const sdf::JointAxis axis = jointAxisComp->Data(); + _elem->GetElement("axis")->Copy(axis.ToElement()); + } + // axis2 + auto jointAxis2Comp = _ecm.Component(_entity); + if (jointAxis2Comp) + { + const sdf::JointAxis axis2 = jointAxis2Comp->Data(); + _elem->GetElement("axis2")->Copy(axis2.ToElement(1u)); + } + + // sensors + // remove existing ones in sdf element and add new ones from ECM. + std::vector toRemove; + if (_elem->HasElement("sensor")) + { + for (auto sensorElem = _elem->GetElement("sensor"); sensorElem; + sensorElem = sensorElem->GetNextElement("sensor")) + { + toRemove.push_back(sensorElem); + } + } + for (const auto &e : toRemove) + { + _elem->RemoveChild(e); + } + + auto sensorEntities = _ecm.EntitiesByComponents( + components::ParentEntity(_entity), components::Sensor()); + + for (const auto &sensorEnt : sensorEntities) + { + sdf::ElementPtr sensorElem = _elem->AddElement("sensor"); + updateSensorElement(sensorElem, _ecm, sensorEnt); + } + + return updateJointNameAndPose(); + } + ///////////////////////////////////////////////// /// \brief Checks if a string is a number /// \param[in] _str The string to check diff --git a/src/SdfGenerator.hh b/src/SdfGenerator.hh index e580fe53e8..83a7844c29 100644 --- a/src/SdfGenerator.hh +++ b/src/SdfGenerator.hh @@ -99,6 +99,49 @@ namespace sdf_generator const EntityComponentManager &_ecm, const Entity &_entity, const std::string &_uri); + /// \brief Update an sdf::Element of a link. + /// Intended for internal use. + /// \input[in, out] _elem sdf::Element to update + /// \input[in] _ecm Immutable reference to the Entity Component Manager + /// \input[in] _entity Link entity + /// \returns true if update succeeded. + IGNITION_GAZEBO_VISIBLE + bool updateLinkElement(const sdf::ElementPtr &_elem, + const EntityComponentManager &_ecm, + const Entity &_entity); + + /// \brief Update an sdf::Element of a sensor. + /// Intended for internal use. + /// \input[in, out] _elem sdf::Element to update + /// \input[in] _ecm Immutable reference to the Entity Component Manager + /// \input[in] _entity Sensor entity + /// \returns true if update succeeded. + IGNITION_GAZEBO_VISIBLE + bool updateSensorElement(sdf::ElementPtr _elem, + const EntityComponentManager &_ecm, + const Entity &_entity); + + /// \brief Update an sdf::Element of a light. + /// Intended for internal use. + /// \input[in, out] _elem sdf::Element to update + /// \input[in] _ecm Immutable reference to the Entity Component Manager + /// \input[in] _entity Light entity + /// \returns true if update succeeded. + IGNITION_GAZEBO_VISIBLE + bool updateLightElement(sdf::ElementPtr _elem, + const EntityComponentManager &_ecm, + const Entity &_entity); + + /// \brief Update an sdf::Element of a joint. + /// Intended for internal use. + /// \input[in, out] _elem sdf::Element to update + /// \input[in] _ecm Immutable reference to the Entity Component Manager + /// \input[in] _entity joint entity + /// \returns true if update succeeded. + IGNITION_GAZEBO_VISIBLE + bool updateJointElement(sdf::ElementPtr _elem, + const EntityComponentManager &_ecm, + const Entity &_entity); } // namespace sdf_generator } // namespace IGNITION_GAZEBO_VERSION_NAMESPACE } // namespace gazebo diff --git a/src/SdfGenerator_TEST.cc b/src/SdfGenerator_TEST.cc index aace52588f..3599fda3ef 100644 --- a/src/SdfGenerator_TEST.cc +++ b/src/SdfGenerator_TEST.cc @@ -117,6 +117,43 @@ static testing::AssertionResult isSubset(const sdf::ElementPtr &_elemA, << "'"; } } + else if (valA->GetTypeName() == "double") + { + double dblA, dblB; + valA->Get(dblA); + valB->Get(dblB); + if (!math::equal(dblA, dblB)) + { + return testing::AssertionFailure() + << "Mismatch in value as double: '" << dblA << "' vs '" + << dblB << "'"; + } + } + else if (valA->GetTypeName() == "float") + { + float fltA, fltB; + valA->Get(fltA); + valB->Get(fltB); + if (!math::equal(fltA, fltB)) + { + return testing::AssertionFailure() + << "Mismatch in value as float: '" << fltA << "' vs '" + << fltB << "'"; + } + } + else if (valA->GetTypeName() == "bool") + { + bool boolA, boolB; + valA->Get(boolA); + valB->Get(boolB); + if (boolA != boolB) + { + return testing::AssertionFailure() + << "Mismatch in value as bool: '" << boolA << "' vs '" + << boolB << "'"; + } + + } else if (valA->GetAsString() != valB->GetAsString()) { return testing::AssertionFailure() @@ -148,6 +185,27 @@ static testing::AssertionResult isSubset(const sdf::ElementPtr &_elemA, if (!result) { + // Ignore missing pose values if the pose is zero. + sdf::ParamPtr childValA = childElemA->GetValue(); + if (childValA->GetTypeName() == "pose") + { + math::Pose3d childValPose; + childValA->Get(childValPose); + if (childValPose == math::Pose3d::Zero) + return testing::AssertionSuccess(); + } + else if (childValA->GetTypeName() == "bool") + { + bool childValBool; + childValA->Get(childValBool); + if (!childValBool && (childElemA->GetName() == "static" || + childElemA->GetName() == "self_collide" || + childElemA->GetName() == "enable_wind" )) + { + return testing::AssertionSuccess(); + } + } + return testing::AssertionFailure() << "No matching child element in element B for child element '" << childElemA->GetName() << "' in element A"; @@ -163,15 +221,15 @@ TEST(CompareElements, CompareWithDuplicateElements) const std::string m1Sdf = R"( - 0 0 0 0 0 0 + 1 0 0 0 0 0 )"; const std::string m1CompTestSdf = R"( - 0 0 0 0 0 0 - 0 0 0 0 0 0 + 1 0 0 0 0 0 + 0 1 0 0 0 0 )"; @@ -271,7 +329,7 @@ class ModelElementFixture : public ElementUpdateFixture auto elem = std::make_shared(); sdf::initFile("model.sdf", elem); - updateModelElement(elem, this->ecm, model); + EXPECT_TRUE(updateModelElement(elem, this->ecm, model)); return elem; } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index fa9d276b0f..ae0de7d3da 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,6 +1,7 @@ set (gui_sources AboutDialogHandler.cc Gui.cc + GuiEvents.cc GuiFileHandler.cc GuiRunner.cc PathManager.cc @@ -8,6 +9,7 @@ set (gui_sources set (gtest_sources Gui_TEST.cc + GuiEvents_TEST.cc ) add_subdirectory(plugins) diff --git a/src/gui/GuiEvents.cc b/src/gui/GuiEvents.cc new file mode 100644 index 0000000000..dd89de306f --- /dev/null +++ b/src/gui/GuiEvents.cc @@ -0,0 +1,85 @@ +/* + * 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 "ignition/gazebo/gui/GuiEvents.hh" + +class ignition::gazebo::gui::events::GuiNewRemovedEntities::Implementation +{ + /// \brief Set of newly created entities + public: std::set newEntities; + + /// \brief Set of recently removed entities + public: std::set removedEntities; +}; + +class ignition::gazebo::gui::events::NewRemovedEntities::Implementation +{ + /// \brief Set of newly created entities + public: std::set newEntities; + + /// \brief Set of recently removed entities + public: std::set removedEntities; +}; + +using namespace ignition; +using namespace gazebo; +using namespace gui; +using namespace events; + +///////////////////////////////////////////////// +GuiNewRemovedEntities::GuiNewRemovedEntities( + const std::set &_newEntities, + const std::set &_removedEntities) + : QEvent(kType), dataPtr(utils::MakeImpl()) +{ + this->dataPtr->newEntities = _newEntities; + this->dataPtr->removedEntities = _removedEntities; +} + +///////////////////////////////////////////////// +const std::set &GuiNewRemovedEntities::NewEntities() const +{ + return this->dataPtr->newEntities; +} + +///////////////////////////////////////////////// +const std::set &GuiNewRemovedEntities::RemovedEntities() const +{ + return this->dataPtr->removedEntities; +} + +///////////////////////////////////////////////// +NewRemovedEntities::NewRemovedEntities( + const std::set &_newEntities, + const std::set &_removedEntities) + : QEvent(kType), dataPtr(utils::MakeImpl()) +{ + this->dataPtr->newEntities = _newEntities; + this->dataPtr->removedEntities = _removedEntities; +} + +///////////////////////////////////////////////// +const std::set &NewRemovedEntities::NewEntities() const +{ + return this->dataPtr->newEntities; +} + +///////////////////////////////////////////////// +const std::set &NewRemovedEntities::RemovedEntities() const +{ + return this->dataPtr->removedEntities; +} diff --git a/src/gui/GuiEvents_TEST.cc b/src/gui/GuiEvents_TEST.cc new file mode 100644 index 0000000000..cb0bebc999 --- /dev/null +++ b/src/gui/GuiEvents_TEST.cc @@ -0,0 +1,67 @@ +/* + * 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 "ignition/gazebo/test_config.hh" +#include "ignition/gazebo/gui/GuiEvents.hh" + +using namespace ignition; +using namespace gazebo; +using namespace gui; + +///////////////////////////////////////////////// +TEST(GuiEventsTest, GuiNewRemovedEntities) +{ + events::GuiNewRemovedEntities event({1, 2, 3}, {4, 5}); + + EXPECT_LT(QEvent::User, event.type()); + + auto addedEntities = event.NewEntities(); + EXPECT_EQ(3u, addedEntities.size()); + EXPECT_NE(addedEntities.find(1), addedEntities.end()); + EXPECT_NE(addedEntities.find(2), addedEntities.end()); + EXPECT_NE(addedEntities.find(3), addedEntities.end()); + EXPECT_EQ(addedEntities.find(100), addedEntities.end()); + + auto removedEntities = event.RemovedEntities(); + EXPECT_EQ(2u, removedEntities.size()); + EXPECT_NE(removedEntities.find(4), removedEntities.end()); + EXPECT_NE(removedEntities.find(5), removedEntities.end()); + EXPECT_EQ(removedEntities.find(6), removedEntities.end()); +} + +///////////////////////////////////////////////// +TEST(GuiEventsTest, NewRemovedEntities) +{ + events::NewRemovedEntities event({1, 2, 3}, {4, 5}); + + EXPECT_LT(QEvent::User, event.type()); + + auto addedEntities = event.NewEntities(); + EXPECT_EQ(3u, addedEntities.size()); + EXPECT_NE(addedEntities.find(1), addedEntities.end()); + EXPECT_NE(addedEntities.find(2), addedEntities.end()); + EXPECT_NE(addedEntities.find(3), addedEntities.end()); + EXPECT_EQ(addedEntities.find(100), addedEntities.end()); + + auto removedEntities = event.RemovedEntities(); + EXPECT_EQ(2u, removedEntities.size()); + EXPECT_NE(removedEntities.find(4), removedEntities.end()); + EXPECT_NE(removedEntities.find(5), removedEntities.end()); + EXPECT_EQ(removedEntities.find(6), removedEntities.end()); +} diff --git a/src/gui/plugins/entity_tree/EntityTree.cc b/src/gui/plugins/entity_tree/EntityTree.cc index de909caa38..a1eb39a2e1 100644 --- a/src/gui/plugins/entity_tree/EntityTree.cc +++ b/src/gui/plugins/entity_tree/EntityTree.cc @@ -556,13 +556,15 @@ bool EntityTree::eventFilter(QObject *_obj, QEvent *_event) } } else if (_event->type() == - ignition::gazebo::gui::events::AddedRemovedEntities::kType) + ignition::gazebo::gui::events::GuiNewRemovedEntities::kType) { std::lock_guard lock(this->dataPtr->newRemovedEntityMutex); auto addedRemovedEvent = - reinterpret_cast(_event); + reinterpret_cast(_event); if (addedRemovedEvent) { + // TODO(chapulina) Make these entities visually different from entities + // created from the server. for (auto entity : addedRemovedEvent->NewEntities()) this->dataPtr->newEntities.insert(entity); diff --git a/src/gui/plugins/scene_manager/GzSceneManager.cc b/src/gui/plugins/scene_manager/GzSceneManager.cc index 802c350575..88710b0e9b 100644 --- a/src/gui/plugins/scene_manager/GzSceneManager.cc +++ b/src/gui/plugins/scene_manager/GzSceneManager.cc @@ -17,7 +17,7 @@ #include "GzSceneManager.hh" -#include +#include #include #include @@ -85,16 +85,25 @@ void GzSceneManager::Update(const UpdateInfo &_info, this->dataPtr->renderUtil.UpdateECM(_info, _ecm); this->dataPtr->renderUtil.UpdateFromECM(_info, _ecm); - // Emit entities removed event - std::vector removed; + // Emit entities created / removed event for gui::Plugins which don't have + // direct access to the ECM. + std::set created; + _ecm.EachNew( + [&](const Entity &_entity, const components::Name *)->bool + { + created.insert(_entity); + return true; + }); + std::set removed; _ecm.EachRemoved( [&](const Entity &_entity, const components::Name *)->bool { - removed.push_back(_entity); + removed.insert(_entity); return true; }); - ignition::gazebo::gui::events::RemovedEntities removedEvent(removed); + ignition::gazebo::gui::events::NewRemovedEntities removedEvent( + created, removed); ignition::gui::App()->sendEvent( ignition::gui::App()->findChild(), &removedEvent); diff --git a/src/gui/plugins/select_entities/SelectEntities.cc b/src/gui/plugins/select_entities/SelectEntities.cc index c08dc0963b..d3513888f7 100644 --- a/src/gui/plugins/select_entities/SelectEntities.cc +++ b/src/gui/plugins/select_entities/SelectEntities.cc @@ -574,13 +574,13 @@ bool SelectEntities::eventFilter(QObject *_obj, QEvent *_event) } } else if (_event->type() == - ignition::gazebo::gui::events::RemovedEntities::kType) + ignition::gazebo::gui::events::NewRemovedEntities::kType) { if (!this->dataPtr->wireBoxes.empty()) { - auto removedEvent = - reinterpret_cast(_event); - for (auto &entity : removedEvent->Data()) + auto event = + reinterpret_cast(_event); + for (auto &entity : event->RemovedEntities()) { auto wireBoxIt = this->dataPtr->wireBoxes.find(entity); if (wireBoxIt != this->dataPtr->wireBoxes.end()) diff --git a/src/gui/plugins/view_angle/ViewAngle.qml b/src/gui/plugins/view_angle/ViewAngle.qml index 9f7213f488..bdf494d73c 100644 --- a/src/gui/plugins/view_angle/ViewAngle.qml +++ b/src/gui/plugins/view_angle/ViewAngle.qml @@ -233,8 +233,8 @@ ColumnLayout { Layout.row: 0 Layout.column: 1 value: ViewAngle.camPose[0] - maximumValue: 1000000 - minimumValue: -1000000 + maximumValue: Number.MAX_VALUE + minimumValue: -Number.MAX_VALUE decimals: 6 stepSize: 0.01 onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) @@ -252,8 +252,8 @@ ColumnLayout { Layout.row: 1 Layout.column: 1 value: ViewAngle.camPose[1] - maximumValue: 1000000 - minimumValue: -1000000 + maximumValue: Number.MAX_VALUE + minimumValue: -Number.MAX_VALUE decimals: 6 stepSize: 0.01 onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) @@ -271,8 +271,8 @@ ColumnLayout { Layout.row: 2 Layout.column: 1 value: ViewAngle.camPose[2] - maximumValue: 1000000 - minimumValue: -1000000 + maximumValue: Number.MAX_VALUE + minimumValue: -Number.MAX_VALUE decimals: 6 stepSize: 0.01 onEditingFinished: ViewAngle.SetCamPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) @@ -383,7 +383,7 @@ ColumnLayout { Layout.row: 0 Layout.column: 3 value: ViewAngle.camClipDist[1] - maximumValue: 1000000 + maximumValue: Number.MAX_VALUE minimumValue: nearClip.value decimals: 6 stepSize: 0.01 diff --git a/src/systems/buoyancy/Buoyancy.cc b/src/systems/buoyancy/Buoyancy.cc index ff91c83471..fc544941b3 100644 --- a/src/systems/buoyancy/Buoyancy.cc +++ b/src/systems/buoyancy/Buoyancy.cc @@ -441,11 +441,6 @@ void Buoyancy::PreUpdate(const ignition::gazebo::UpdateInfo &_info, { auto newPose = enableComponent(_ecm, _entity); newPose |= enableComponent(_ecm, _entity); - if (newPose) - { - // Skip entity if WorldPose and inertial are not yet ready - return true; - } // World pose of the link. math::Pose3d linkWorldPose = worldPose(_entity, _ecm); @@ -477,6 +472,13 @@ void Buoyancy::PreUpdate(const ignition::gazebo::UpdateInfo &_info, else if (this->dataPtr->buoyancyType == BuoyancyPrivate::BuoyancyType::GRADED_BUOYANCY) { + if (newPose) + { + // Skip entity if WorldPose and inertial are not yet ready + // TODO(arjo): Find a way of disabling gravity effects for + // this first iteration. + return true; + } std::vector collisions = _ecm.ChildrenByComponents( _entity, components::Collision()); this->dataPtr->buoyancyForces.clear(); @@ -521,12 +523,13 @@ void Buoyancy::PreUpdate(const ignition::gazebo::UpdateInfo &_info, } } } - } - auto [force, torque] = this->dataPtr->ResolveForces( + auto [force, torque] = this->dataPtr->ResolveForces( link.WorldInertialPose(_ecm).value()); - // Apply the wrench to the link. This wrench is applied in the - // Physics System. - link.AddWorldWrench(_ecm, force, torque); + // Apply the wrench to the link. This wrench is applied in the + // Physics System. + link.AddWorldWrench(_ecm, force, torque); + } + return true; }); } diff --git a/src/systems/force_torque/ForceTorque.cc b/src/systems/force_torque/ForceTorque.cc index 56f78a4e21..e1668fbf71 100644 --- a/src/systems/force_torque/ForceTorque.cc +++ b/src/systems/force_torque/ForceTorque.cc @@ -136,8 +136,7 @@ void ForceTorque::PostUpdate(const UpdateInfo &_info, for (auto &it : this->dataPtr->entitySensorMap) { - // Update measurement time - it.second->Update(_info.simTime); + it.second.get()->sensors::Sensor::Update(_info.simTime, false); } } diff --git a/test/integration/buoyancy.cc b/test/integration/buoyancy.cc index 5ab6ba4ca2..481c7d29ee 100644 --- a/test/integration/buoyancy.cc +++ b/test/integration/buoyancy.cc @@ -57,7 +57,7 @@ TEST_F(BuoyancyTest, UniformWorldMovement) using namespace std::chrono_literals; server.SetUpdatePeriod(1ns); - std::size_t iterations = 1001; + std::size_t iterations = 1000; bool finished = false; test::Relay testSystem; diff --git a/test/integration/force_torque_system.cc b/test/integration/force_torque_system.cc index d3fce78236..a6f769e0ed 100644 --- a/test/integration/force_torque_system.cc +++ b/test/integration/force_torque_system.cc @@ -54,18 +54,21 @@ TEST_F(ForceTorqueTest, MeasureWeight) EXPECT_FALSE(*server.Running(0)); server.SetUpdatePeriod(1us); - size_t iters = 1000u; + // Having iters exactly in sync with update rate can lead to a race condition + // in the test between simulation and transport + size_t iters = 999u; + size_t updates = 100u; std::vector wrenches; - wrenches.reserve(iters); + wrenches.reserve(updates); std::mutex wrenchMutex; std::condition_variable cv; auto wrenchCb = std::function( - [&wrenchMutex, &wrenches, &cv, iters](const auto &_msg) + [&wrenchMutex, &wrenches, &cv, updates](const auto &_msg) { std::lock_guard lock(wrenchMutex); wrenches.push_back(_msg); - if (wrenches.size() >= iters) + if (wrenches.size() >= updates) { cv.notify_all(); } @@ -80,8 +83,8 @@ TEST_F(ForceTorqueTest, MeasureWeight) { std::unique_lock lock(wrenchMutex); - cv.wait_for(lock, 30s, [&] { return wrenches.size() >= iters; }); - ASSERT_EQ(iters, wrenches.size()); + cv.wait_for(lock, 30s, [&] { return wrenches.size() >= updates; }); + ASSERT_EQ(updates, wrenches.size()); const double kSensorMass = 0.2; const double kWeightMass = 10; @@ -109,18 +112,21 @@ TEST_F(ForceTorqueTest, SensorPoseOffset) EXPECT_FALSE(*server.Running(0)); server.SetUpdatePeriod(1us); - size_t iters = 1000u; + // Having iters exactly in sync with update rate can lead to a race condition + // in the test between simulation and transport + size_t iters = 999u; + size_t updates = 100u; std::vector wrenches; - wrenches.reserve(iters); + wrenches.reserve(updates); std::mutex wrenchMutex; std::condition_variable cv; auto wrenchCb = std::function( - [&wrenchMutex, &wrenches, &cv, iters](const auto &_msg) + [&wrenchMutex, &wrenches, &cv, updates](const auto &_msg) { std::lock_guard lock(wrenchMutex); wrenches.push_back(_msg); - if (wrenches.size() >= iters) + if (wrenches.size() >= updates) { cv.notify_all(); } @@ -138,8 +144,8 @@ TEST_F(ForceTorqueTest, SensorPoseOffset) const double kGravity = 9.8; { std::unique_lock lock(wrenchMutex); - cv.wait_for(lock, 30s, [&] { return wrenches.size() >= iters; }); - ASSERT_EQ(iters, wrenches.size()); + cv.wait_for(lock, 30s, [&] { return wrenches.size() >= updates; }); + ASSERT_EQ(updates, wrenches.size()); const double kMomentArm = 0.1; const auto &wrench = wrenches.back(); @@ -157,8 +163,8 @@ TEST_F(ForceTorqueTest, SensorPoseOffset) ASSERT_EQ(2 * iters, *server.IterationCount()); { std::unique_lock lock(wrenchMutex); - cv.wait_for(lock, 30s, [&] { return wrenches.size() >= iters; }); - ASSERT_EQ(iters, wrenches.size()); + cv.wait_for(lock, 30s, [&] { return wrenches.size() >= updates; }); + ASSERT_EQ(updates, wrenches.size()); const auto &wrench = wrenches.back(); diff --git a/test/integration/save_world.cc b/test/integration/save_world.cc index 7f9f7072e2..0e3964b052 100644 --- a/test/integration/save_world.cc +++ b/test/integration/save_world.cc @@ -20,10 +20,21 @@ #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include #include #include +#include #include #include @@ -428,6 +439,523 @@ TEST_F(SdfGeneratorFixture, ModelWithNestedIncludes) EXPECT_TRUE(err.empty()); } +///////////////////////////////////////////////// +TEST_F(SdfGeneratorFixture, WorldWithSensors) +{ + this->LoadWorld("test/worlds/non_rendering_sensors.sdf"); + + const std::string worldGenSdfRes = + this->RequestGeneratedSdf("non_rendering_sensors"); + + sdf::Root root; + sdf::Errors err = root.LoadSdfString(worldGenSdfRes); + EXPECT_TRUE(err.empty()); + auto *world = root.WorldByIndex(0); + ASSERT_NE(nullptr, world); + + EXPECT_TRUE(world->ModelNameExists("model")); + auto *model = world->ModelByName("model"); + ASSERT_NE(nullptr, model); + auto *link = model->LinkByName("link"); + ASSERT_NE(nullptr, link); + + // altimeter + { + auto *sensor = link->SensorByName("altimeter_sensor"); + ASSERT_NE(nullptr, sensor); + const sdf::Altimeter *altimeter = sensor->AltimeterSensor(); + ASSERT_NE(nullptr, altimeter); + const sdf::Noise &posNoise = altimeter->VerticalPositionNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, posNoise.Type()); + EXPECT_DOUBLE_EQ(0.1, posNoise.Mean()); + EXPECT_DOUBLE_EQ(0.2, posNoise.StdDev()); + + const sdf::Noise &velNoise = altimeter->VerticalVelocityNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, velNoise.Type()); + EXPECT_DOUBLE_EQ(2.3, velNoise.Mean()); + EXPECT_DOUBLE_EQ(4.5, velNoise.StdDev()); + } + + // contact sensor + { + // contact sensor does not have an SDF DOM class + auto *sensor = link->SensorByName("contact_sensor"); + ASSERT_NE(nullptr, sensor); + EXPECT_EQ(math::Pose3d(4, 5, 6, 0, 0, 0), sensor->RawPose()); + EXPECT_EQ(sdf::SensorType::CONTACT, sensor->Type()); + } + + // force torque + { + auto *sensor = link->SensorByName("force_torque_sensor"); + ASSERT_NE(nullptr, sensor); + EXPECT_EQ(math::Pose3d(10, 11, 12, 0, 0, 0), sensor->RawPose()); + const sdf::ForceTorque *forceTorque = sensor->ForceTorqueSensor(); + ASSERT_NE(nullptr, forceTorque); + EXPECT_EQ(sdf::ForceTorqueFrame::CHILD, forceTorque->Frame()); + EXPECT_EQ(sdf::ForceTorqueMeasureDirection::PARENT_TO_CHILD, + forceTorque->MeasureDirection()); + const sdf::Noise &forceXNoise = forceTorque->ForceXNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN_QUANTIZED, forceXNoise.Type()); + EXPECT_DOUBLE_EQ(0.02, forceXNoise.Mean()); + EXPECT_DOUBLE_EQ(0.0005, forceXNoise.StdDev()); + const sdf::Noise &torqueYNoise = forceTorque->TorqueYNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, torqueYNoise.Type()); + EXPECT_DOUBLE_EQ(0.009, torqueYNoise.Mean()); + EXPECT_DOUBLE_EQ(0.0000985, torqueYNoise.StdDev()); + } + + // imu + { + auto *sensor = link->SensorByName("imu_sensor"); + ASSERT_NE(nullptr, sensor); + EXPECT_EQ(math::Pose3d(4, 5, 6, 0, 0, 0), sensor->RawPose()); + const sdf::Imu *imu = sensor->ImuSensor(); + ASSERT_NE(nullptr, imu); + const sdf::Noise &linAccXNoise = imu->LinearAccelerationXNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, linAccXNoise.Type()); + EXPECT_DOUBLE_EQ(0.0, linAccXNoise.Mean()); + EXPECT_DOUBLE_EQ(0.1, linAccXNoise.StdDev()); + EXPECT_DOUBLE_EQ(0.2, linAccXNoise.DynamicBiasStdDev()); + EXPECT_DOUBLE_EQ(1.0, linAccXNoise.DynamicBiasCorrelationTime()); + const sdf::Noise &linAccYNoise = imu->LinearAccelerationYNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, linAccYNoise.Type()); + EXPECT_DOUBLE_EQ(1.0, linAccYNoise.Mean()); + EXPECT_DOUBLE_EQ(1.1, linAccYNoise.StdDev()); + EXPECT_DOUBLE_EQ(1.2, linAccYNoise.DynamicBiasStdDev()); + EXPECT_DOUBLE_EQ(2.0, linAccYNoise.DynamicBiasCorrelationTime()); + const sdf::Noise &linAccZNoise = imu->LinearAccelerationZNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, linAccZNoise.Type()); + EXPECT_DOUBLE_EQ(2.0, linAccZNoise.Mean()); + EXPECT_DOUBLE_EQ(2.1, linAccZNoise.StdDev()); + EXPECT_DOUBLE_EQ(2.2, linAccZNoise.DynamicBiasStdDev()); + EXPECT_DOUBLE_EQ(3.0, linAccZNoise.DynamicBiasCorrelationTime()); + const sdf::Noise &angVelXNoise = imu->AngularVelocityXNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, angVelXNoise.Type()); + EXPECT_DOUBLE_EQ(3.0, angVelXNoise.Mean()); + EXPECT_DOUBLE_EQ(3.1, angVelXNoise.StdDev()); + EXPECT_DOUBLE_EQ(4.2, angVelXNoise.DynamicBiasStdDev()); + EXPECT_DOUBLE_EQ(4.0, angVelXNoise.DynamicBiasCorrelationTime()); + const sdf::Noise &angVelYNoise = imu->AngularVelocityYNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, angVelYNoise.Type()); + EXPECT_DOUBLE_EQ(4.0, angVelYNoise.Mean()); + EXPECT_DOUBLE_EQ(4.1, angVelYNoise.StdDev()); + EXPECT_DOUBLE_EQ(5.2, angVelYNoise.DynamicBiasStdDev()); + EXPECT_DOUBLE_EQ(5.0, angVelYNoise.DynamicBiasCorrelationTime()); + const sdf::Noise &angVelZNoise = imu->AngularVelocityZNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, angVelZNoise.Type()); + EXPECT_DOUBLE_EQ(5.0, angVelZNoise.Mean()); + EXPECT_DOUBLE_EQ(5.1, angVelZNoise.StdDev()); + EXPECT_DOUBLE_EQ(6.2, angVelZNoise.DynamicBiasStdDev()); + EXPECT_DOUBLE_EQ(6.0, angVelZNoise.DynamicBiasCorrelationTime()); + + EXPECT_EQ("ENU", imu->Localization()); + EXPECT_EQ("linka", imu->CustomRpyParentFrame()); + EXPECT_EQ(math::Vector3d::UnitY, imu->CustomRpy()); + EXPECT_EQ("linkb", imu->GravityDirXParentFrame()); + EXPECT_EQ(math::Vector3d::UnitZ, imu->GravityDirX()); + EXPECT_FALSE(imu->OrientationEnabled()); + } + + // logical camera + { + // logical camera sensor does not have an SDF DOM class + auto *sensor = link->SensorByName("logical_camera_sensor"); + ASSERT_NE(nullptr, sensor); + EXPECT_EQ(math::Pose3d(7, 8, 9, 0, 0, 0), sensor->RawPose()); + EXPECT_EQ(sdf::SensorType::LOGICAL_CAMERA, sensor->Type()); + } + + // magnetometer + { + auto *sensor = link->SensorByName("magnetometer_sensor"); + ASSERT_NE(nullptr, sensor); + EXPECT_EQ(math::Pose3d(10, 11, 12, 0, 0, 0), sensor->RawPose()); + const sdf::Magnetometer *magnetometer = sensor->MagnetometerSensor(); + ASSERT_NE(nullptr, magnetometer); + const sdf::Noise &xNoise = magnetometer->XNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, xNoise.Type()); + EXPECT_DOUBLE_EQ(0.1, xNoise.Mean()); + EXPECT_DOUBLE_EQ(0.2, xNoise.StdDev()); + const sdf::Noise &yNoise = magnetometer->YNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, yNoise.Type()); + EXPECT_DOUBLE_EQ(1.2, yNoise.Mean()); + EXPECT_DOUBLE_EQ(2.3, yNoise.StdDev()); + const sdf::Noise &zNoise = magnetometer->ZNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, zNoise.Type()); + EXPECT_DOUBLE_EQ(3.4, zNoise.Mean()); + EXPECT_DOUBLE_EQ(5.6, zNoise.StdDev()); + } + + // air pressure + { + auto *sensor = link->SensorByName("air_pressure_sensor"); + ASSERT_NE(nullptr, sensor); + EXPECT_EQ(math::Pose3d(10, 20, 30, 0, 0, 0), sensor->RawPose()); + const sdf::AirPressure *airPressure = sensor->AirPressureSensor(); + ASSERT_NE(nullptr, airPressure); + EXPECT_DOUBLE_EQ(123.4, airPressure->ReferenceAltitude()); + const sdf::Noise &noise = airPressure->PressureNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN, noise.Type()); + EXPECT_DOUBLE_EQ(3.4, noise.Mean()); + EXPECT_DOUBLE_EQ(5.6, noise.StdDev()); + } + + tinyxml2::XMLDocument genSdfDoc; + genSdfDoc.Parse(worldGenSdfRes.c_str()); + ASSERT_NE(nullptr, genSdfDoc.RootElement()); + auto genWorld = genSdfDoc.RootElement()->FirstChildElement("world"); + ASSERT_NE(nullptr, genWorld); +} + +///////////////////////////////////////////////// +TEST_F(SdfGeneratorFixture, WorldWithRenderingSensors) +{ + this->LoadWorld("test/worlds/sensor.sdf"); + + const std::string worldGenSdfRes = + this->RequestGeneratedSdf("camera_sensor"); + + sdf::Root root; + sdf::Errors err = root.LoadSdfString(worldGenSdfRes); + EXPECT_TRUE(err.empty()); + auto *world = root.WorldByIndex(0); + ASSERT_NE(nullptr, world); + + // camera + { + EXPECT_TRUE(world->ModelNameExists("camera")); + auto *model = world->ModelByName("camera"); + ASSERT_NE(nullptr, model); + EXPECT_EQ(1u, model->LinkCount()); + + auto *link = model->LinkByName("link"); + ASSERT_NE(nullptr, link); + math::MassMatrix3d massMatrix(0.1, + math::Vector3d( 0.000166667, 0.000166667, 0.000166667), + math::Vector3d::Zero); + math::Inertiald inertial(massMatrix, math::Pose3d::Zero); + EXPECT_EQ(inertial, link->Inertial()); + + auto *cameraSensor = link->SensorByName("camera"); + ASSERT_NE(nullptr, cameraSensor); + EXPECT_EQ("camera", cameraSensor->Topic()); + const sdf::Camera *camera = cameraSensor->CameraSensor(); + ASSERT_NE(nullptr, camera); + EXPECT_DOUBLE_EQ(1.047, camera->HorizontalFov().Radian()); + EXPECT_EQ(320u, camera->ImageWidth()); + EXPECT_EQ(240u, camera->ImageHeight()); + EXPECT_DOUBLE_EQ(0.1, camera->NearClip()); + EXPECT_DOUBLE_EQ(100, camera->FarClip()); + const sdf::Noise &noise = camera->ImageNoise(); + EXPECT_EQ(sdf::NoiseType::GAUSSIAN_QUANTIZED, noise.Type()); + EXPECT_DOUBLE_EQ(0.01, noise.Mean()); + EXPECT_DOUBLE_EQ(0.0002, noise.StdDev()); + } + + EXPECT_TRUE(world->ModelNameExists("default_topics")); + auto *model = world->ModelByName("default_topics"); + ASSERT_NE(nullptr, model); + // gpu lidar + { + auto *gpuLidarLink = model->LinkByName("gpu_lidar_link"); + ASSERT_NE(nullptr, gpuLidarLink); + auto *gpuLidarSensor = gpuLidarLink->SensorByName("gpu_lidar"); + const sdf::Lidar *lidar = gpuLidarSensor->LidarSensor(); + EXPECT_EQ(640u, lidar->HorizontalScanSamples()); + EXPECT_DOUBLE_EQ(1.0, lidar->HorizontalScanResolution()); + EXPECT_NEAR(-1.396263, lidar->HorizontalScanMinAngle().Radian(), 1e-5); + EXPECT_NEAR(1.396263, lidar->HorizontalScanMaxAngle().Radian(), 1e-5); + EXPECT_EQ(1u, lidar->VerticalScanSamples()); + EXPECT_DOUBLE_EQ(0.01, lidar->VerticalScanResolution()); + EXPECT_DOUBLE_EQ(0.0, lidar->VerticalScanMinAngle().Radian()); + EXPECT_DOUBLE_EQ(0.0, lidar->VerticalScanMaxAngle().Radian()); + EXPECT_DOUBLE_EQ(0.08, lidar->RangeMin()); + EXPECT_DOUBLE_EQ(10.0, lidar->RangeMax()); + EXPECT_DOUBLE_EQ(0.01, lidar->RangeResolution()); + } + + // depth camera + { + auto *depthLink = model->LinkByName("depth_camera_link"); + ASSERT_NE(nullptr, depthLink); + auto *depthSensor = depthLink->SensorByName("depth_camera"); + ASSERT_NE(nullptr, depthSensor); + const sdf::Camera *camera = depthSensor->CameraSensor(); + ASSERT_NE(nullptr, camera); + EXPECT_DOUBLE_EQ(1.05, camera->HorizontalFov().Radian()); + EXPECT_EQ(256u, camera->ImageWidth()); + EXPECT_EQ(256u, camera->ImageHeight()); + EXPECT_EQ("R_FLOAT32", camera->PixelFormatStr()); + EXPECT_DOUBLE_EQ(0.1, camera->NearClip()); + EXPECT_DOUBLE_EQ(10, camera->FarClip()); + EXPECT_DOUBLE_EQ(0.05, camera->DepthNearClip()); + EXPECT_DOUBLE_EQ(9.0, camera->DepthFarClip()); + } + + // rgbd camera + { + auto *rgbdLink = model->LinkByName("rgbd_camera_link"); + ASSERT_NE(nullptr, rgbdLink); + auto *rgbdSensor = rgbdLink->SensorByName("rgbd_camera"); + ASSERT_NE(nullptr, rgbdSensor); + const sdf::Camera *camera = rgbdSensor->CameraSensor(); + ASSERT_NE(nullptr, camera); + EXPECT_DOUBLE_EQ(1.05, camera->HorizontalFov().Radian()); + EXPECT_EQ(256u, camera->ImageWidth()); + EXPECT_EQ(256u, camera->ImageHeight()); + EXPECT_DOUBLE_EQ(0.1, camera->NearClip()); + EXPECT_DOUBLE_EQ(10, camera->FarClip()); + } + + // thermal camera + { + auto *thermalLink = model->LinkByName("thermal_camera_link"); + ASSERT_NE(nullptr, thermalLink); + auto *thermalSensor = thermalLink->SensorByName("thermal_camera"); + ASSERT_NE(nullptr, thermalSensor); + const sdf::Camera *camera = thermalSensor->CameraSensor(); + ASSERT_NE(nullptr, camera); + EXPECT_DOUBLE_EQ(1.15, camera->HorizontalFov().Radian()); + EXPECT_EQ(300u, camera->ImageWidth()); + EXPECT_EQ(200u, camera->ImageHeight()); + EXPECT_DOUBLE_EQ(0.14, camera->NearClip()); + EXPECT_DOUBLE_EQ(120.0, camera->FarClip()); + } + + // segmentation camera + { + auto *segmentationLink = model->LinkByName("segmentation_camera_link"); + ASSERT_NE(nullptr, segmentationLink); + auto *segmentationSensor = + segmentationLink->SensorByName("segmentation_camera"); + ASSERT_NE(nullptr, segmentationSensor); + const sdf::Camera *camera = segmentationSensor->CameraSensor(); + ASSERT_NE(nullptr, camera); + EXPECT_DOUBLE_EQ(1.0, camera->HorizontalFov().Radian()); + EXPECT_EQ(320u, camera->ImageWidth()); + EXPECT_EQ(240u, camera->ImageHeight()); + EXPECT_DOUBLE_EQ(0.11, camera->NearClip()); + EXPECT_DOUBLE_EQ(20.0, camera->FarClip()); + EXPECT_EQ("panoptic", camera->SegmentationType()); + } + + tinyxml2::XMLDocument genSdfDoc; + genSdfDoc.Parse(worldGenSdfRes.c_str()); + ASSERT_NE(nullptr, genSdfDoc.RootElement()); + auto genWorld = genSdfDoc.RootElement()->FirstChildElement("world"); + ASSERT_NE(nullptr, genWorld); +} + +///////////////////////////////////////////////// +TEST_F(SdfGeneratorFixture, WorldWithLights) +{ + this->LoadWorld("test/worlds/lights.sdf"); + + const std::string worldGenSdfRes = + this->RequestGeneratedSdf("lights"); + + sdf::Root root; + sdf::Errors err = root.LoadSdfString(worldGenSdfRes); + EXPECT_TRUE(err.empty()); + auto *world = root.WorldByIndex(0); + ASSERT_NE(nullptr, world); + + // directional light in the world + { + const sdf::Light *light = world->LightByIndex(0u); + EXPECT_EQ("directional", light->Name()); + EXPECT_EQ(sdf::LightType::DIRECTIONAL, light->Type()); + EXPECT_EQ(ignition::math::Pose3d(0, 0, 10, 0, 0, 0), + light->RawPose()); + EXPECT_EQ(std::string(), light->PoseRelativeTo()); + EXPECT_TRUE(light->CastShadows()); + EXPECT_EQ(ignition::math::Color(0.8f, 0.8f, 0.8f, 1), + light->Diffuse()); + EXPECT_EQ(ignition::math::Color(0.2f, 0.2f, 0.2f, 1), + light->Specular()); + EXPECT_DOUBLE_EQ(100, light->AttenuationRange()); + EXPECT_DOUBLE_EQ(0.9, light->ConstantAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.01, light->LinearAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.001, light->QuadraticAttenuationFactor()); + EXPECT_EQ(ignition::math::Vector3d(0.5, 0.2, -0.9), + light->Direction()); + } + // point light in the world + { + const sdf::Light *light = world->LightByIndex(1u); + EXPECT_EQ("point", light->Name()); + EXPECT_EQ(sdf::LightType::POINT, light->Type()); + EXPECT_EQ(ignition::math::Pose3d(0, -1.5, 3, 0, 0, 0), + light->RawPose()); + EXPECT_FALSE(light->CastShadows()); + EXPECT_EQ(ignition::math::Color(1.0f, 0.0f, 0.0f, 1), + light->Diffuse()); + EXPECT_EQ(ignition::math::Color(0.1f, 0.1f, 0.1f, 1), + light->Specular()); + EXPECT_DOUBLE_EQ(4, light->AttenuationRange()); + EXPECT_DOUBLE_EQ(0.2, light->ConstantAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.5, light->LinearAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.01, light->QuadraticAttenuationFactor()); + } + // spot light in the world + { + const sdf::Light *light = world->LightByIndex(2u); + EXPECT_EQ("spot", light->Name()); + EXPECT_EQ(sdf::LightType::SPOT, light->Type()); + EXPECT_EQ(ignition::math::Pose3d(0, 1.5, 3, 0, 0, 0), + light->RawPose()); + EXPECT_EQ(std::string(), light->PoseRelativeTo()); + EXPECT_FALSE(light->CastShadows()); + EXPECT_EQ(ignition::math::Color(0.0f, 1.0f, 0.0f, 1), + light->Diffuse()); + EXPECT_EQ(ignition::math::Color(0.2f, 0.2f, 0.2f, 1), + light->Specular()); + EXPECT_DOUBLE_EQ(5, light->AttenuationRange()); + EXPECT_DOUBLE_EQ(0.3, light->ConstantAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.4, light->LinearAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.001, light->QuadraticAttenuationFactor()); + EXPECT_EQ(ignition::math::Vector3d(0.0, 0.0, -1.0), + light->Direction()); + EXPECT_DOUBLE_EQ(0.1, light->SpotInnerAngle().Radian()); + EXPECT_DOUBLE_EQ(0.5, light->SpotOuterAngle().Radian()); + EXPECT_DOUBLE_EQ(0.8, light->SpotFalloff()); + } + + // get model + EXPECT_TRUE(world->ModelNameExists("sphere")); + auto *model = world->ModelByName("sphere"); + ASSERT_NE(nullptr, model); + EXPECT_EQ(1u, model->LinkCount()); + + // get link + auto *link = model->LinkByName("sphere_link"); + ASSERT_NE(nullptr, link); + + // light attached to link + { + const sdf::Light *light = link->LightByName("link_light_point"); + EXPECT_EQ("link_light_point", light->Name()); + EXPECT_EQ(ignition::math::Pose3d(0.0, 0.0, 1.0, 0, 0, 0), + light->RawPose()); + EXPECT_EQ(sdf::LightType::POINT, light->Type()); + EXPECT_FALSE(light->CastShadows()); + EXPECT_EQ(ignition::math::Color(0.0f, 0.0f, 1.0f, 1), + light->Diffuse()); + EXPECT_EQ(ignition::math::Color(0.1f, 0.1f, 0.1f, 1), + light->Specular()); + EXPECT_DOUBLE_EQ(2, light->AttenuationRange()); + EXPECT_DOUBLE_EQ(0.05, light->ConstantAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.02, light->LinearAttenuationFactor()); + EXPECT_DOUBLE_EQ(0.01, light->QuadraticAttenuationFactor()); + } + + tinyxml2::XMLDocument genSdfDoc; + genSdfDoc.Parse(worldGenSdfRes.c_str()); + ASSERT_NE(nullptr, genSdfDoc.RootElement()); + auto genWorld = genSdfDoc.RootElement()->FirstChildElement("world"); + ASSERT_NE(nullptr, genWorld); +} + +///////////////////////////////////////////////// +TEST_F(SdfGeneratorFixture, ModelWithJoints) +{ + this->LoadWorld(ignition::common::joinPaths("test", "worlds", + "joint_sensor.sdf")); + + const std::string worldGenSdfRes = + this->RequestGeneratedSdf("joint_sensor"); + + sdf::Root root; + sdf::Errors err = root.LoadSdfString(worldGenSdfRes); + EXPECT_TRUE(err.empty()); + auto *world = root.WorldByIndex(0); + ASSERT_NE(nullptr, world); + EXPECT_EQ(1u, world->ModelCount()); + + EXPECT_TRUE(world->ModelNameExists("model")); + auto *model = world->ModelByName("model"); + ASSERT_NE(nullptr, model); + EXPECT_EQ(2u, model->LinkCount()); + auto *link1 = model->LinkByName("link1"); + ASSERT_NE(nullptr, link1); + auto *link2 = model->LinkByName("link2"); + ASSERT_NE(nullptr, link2); + EXPECT_EQ(1u, model->JointCount()); + auto *joint = model->JointByName("joint"); + ASSERT_NE(nullptr, joint); + + EXPECT_EQ("link1", joint->ParentLinkName()); + EXPECT_EQ("link2", joint->ChildLinkName()); + EXPECT_EQ(sdf::JointType::REVOLUTE2, joint->Type()); + + // Get the first axis + const sdf::JointAxis *axis = joint->Axis(); + ASSERT_NE(nullptr, axis); + ASSERT_NE(nullptr, axis->Element()); + + // Get the second axis + const sdf::JointAxis *axis2 = joint->Axis(1); + ASSERT_NE(nullptr, axis2); + + EXPECT_EQ(ignition::math::Vector3d::UnitZ, axis->Xyz()); + EXPECT_EQ(ignition::math::Vector3d::UnitY, axis2->Xyz()); + + EXPECT_EQ("__model__", axis->XyzExpressedIn()); + EXPECT_TRUE(axis2->XyzExpressedIn().empty()); + + EXPECT_DOUBLE_EQ(-0.5, axis->Lower()); + EXPECT_DOUBLE_EQ(0.5, axis->Upper()); + EXPECT_DOUBLE_EQ(-1.0, axis2->Lower()); + EXPECT_DOUBLE_EQ(1.0, axis2->Upper()); + + EXPECT_DOUBLE_EQ(123.4, axis->Effort()); + EXPECT_DOUBLE_EQ(0.5, axis2->Effort()); + + EXPECT_DOUBLE_EQ(12.0, axis->MaxVelocity()); + EXPECT_DOUBLE_EQ(200.0, axis2->MaxVelocity()); + + EXPECT_DOUBLE_EQ(0.1, axis->Damping()); + EXPECT_DOUBLE_EQ(0.0, axis2->Damping()); + + EXPECT_DOUBLE_EQ(0.2, axis->Friction()); + EXPECT_DOUBLE_EQ(0.0, axis2->Friction()); + + EXPECT_DOUBLE_EQ(1.3, axis->SpringReference()); + EXPECT_DOUBLE_EQ(0.0, axis2->SpringReference()); + + EXPECT_DOUBLE_EQ(10.6, axis->SpringStiffness()); + EXPECT_DOUBLE_EQ(0.0, axis2->SpringStiffness()); + + // sensor + const sdf::Sensor *forceTorqueSensor = + joint->SensorByName("force_torque_sensor"); + ASSERT_NE(nullptr, forceTorqueSensor); + EXPECT_EQ("force_torque_sensor", forceTorqueSensor->Name()); + EXPECT_EQ(sdf::SensorType::FORCE_TORQUE, forceTorqueSensor->Type()); + EXPECT_EQ(ignition::math::Pose3d(10, 11, 12, 0, 0, 0), + forceTorqueSensor->RawPose()); + auto forceTorqueSensorObj = forceTorqueSensor->ForceTorqueSensor(); + ASSERT_NE(nullptr, forceTorqueSensorObj); + EXPECT_EQ(sdf::ForceTorqueFrame::PARENT, forceTorqueSensorObj->Frame()); + EXPECT_EQ(sdf::ForceTorqueMeasureDirection::PARENT_TO_CHILD, + forceTorqueSensorObj->MeasureDirection()); + + EXPECT_DOUBLE_EQ(0.0, forceTorqueSensorObj->ForceXNoise().Mean()); + EXPECT_DOUBLE_EQ(0.1, forceTorqueSensorObj->ForceXNoise().StdDev()); + EXPECT_DOUBLE_EQ(1.0, forceTorqueSensorObj->ForceYNoise().Mean()); + EXPECT_DOUBLE_EQ(1.1, forceTorqueSensorObj->ForceYNoise().StdDev()); + EXPECT_DOUBLE_EQ(2.0, forceTorqueSensorObj->ForceZNoise().Mean()); + EXPECT_DOUBLE_EQ(2.1, forceTorqueSensorObj->ForceZNoise().StdDev()); + + EXPECT_DOUBLE_EQ(3.0, forceTorqueSensorObj->TorqueXNoise().Mean()); + EXPECT_DOUBLE_EQ(3.1, forceTorqueSensorObj->TorqueXNoise().StdDev()); + EXPECT_DOUBLE_EQ(4.0, forceTorqueSensorObj->TorqueYNoise().Mean()); + EXPECT_DOUBLE_EQ(4.1, forceTorqueSensorObj->TorqueYNoise().StdDev()); + EXPECT_DOUBLE_EQ(5.0, forceTorqueSensorObj->TorqueZNoise().Mean()); + EXPECT_DOUBLE_EQ(5.1, forceTorqueSensorObj->TorqueZNoise().StdDev()); +} + ///////////////////////////////////////////////// /// Main int main(int _argc, char **_argv) diff --git a/test/worlds/force_torque.sdf b/test/worlds/force_torque.sdf index fe9e177245..bc394430b2 100644 --- a/test/worlds/force_torque.sdf +++ b/test/worlds/force_torque.sdf @@ -132,7 +132,7 @@ base sensor_plate - 30 + 100 true true force_torque1 @@ -262,7 +262,7 @@ sensor_plate 0.1 0 0 0 0 0 - 30 + 100 true true force_torque2 @@ -392,7 +392,7 @@ sensor_plate 0 0 0.0 30 0 0 - 30 + 100 true true force_torque3 diff --git a/test/worlds/joint_sensor.sdf b/test/worlds/joint_sensor.sdf new file mode 100644 index 0000000000..f96cdeefe0 --- /dev/null +++ b/test/worlds/joint_sensor.sdf @@ -0,0 +1,107 @@ + + + + + + + + + + + + + link2 + link1 + + 0 0 1 + 0.5 + true + + -0.5 + 0.5 + 123.4 + 12 + + + 0.1 + 0.2 + 1.3 + 10.6 + + + + 0 1 0 + 1.5 + false + + -1 + 1 + 0.5 + 200 + + + + + 1 + + 0 + 0.2 + + + + + 10 11 12 0 0 0 + + parent + parent_to_child + + + + 0 + 0.1 + + + + + 1 + 1.1 + + + + + 2 + 2.1 + + + + + + + 3 + 3.1 + + + + + 4 + 4.1 + + + + + 5 + 5.1 + + + + + + + + + + diff --git a/test/worlds/non_rendering_sensors.sdf b/test/worlds/non_rendering_sensors.sdf new file mode 100644 index 0000000000..f5fac8c8aa --- /dev/null +++ b/test/worlds/non_rendering_sensors.sdf @@ -0,0 +1,176 @@ + + + + + ogre2 + + + + + + + 0 0 3 0 0 0 + + + + + 0.1 + 0.2 + + + + + 2.3 + 4.5 + + + + + + + 4 5 6 0 0 0 + true + + + + 10 11 12 0 0 0 + + child + parent_to_child + + + + 0.02 + 0.0005 + + + + + + + 0.009 + 0.0000985 + + + + + + + + 4 5 6 0 0 0 + + + + + 0 + 0.1 + 0.2 + 1 + + + + + 1 + 1.1 + 1.2 + 2 + + + + + 2 + 2.1 + 2.2 + 3 + + + + + + + 3 + 3.1 + 4.2 + 4 + + + + + 4 + 4.1 + 5.2 + 5 + + + + + 5 + 5.1 + 6.2 + 6 + + + + + ENU + 0 1 0 + 0 0 1 + + false + + + + + 7 8 9 0 0 0 + + 0.1 + 100.1 + 1.33 + 1.234 + + + + + 10 11 12 0 0 0 + + + + 0.1 + 0.2 + + + + + 1.2 + 2.3 + + + + + 3.4 + 5.6 + + + + + + + 10 20 30 0 0 0 + + 123.4 + + + 3.4 + 5.6 + + + + + + + + diff --git a/test/worlds/save_world.sdf b/test/worlds/save_world.sdf index e6a431d4e6..226ef45cb5 100644 --- a/test/worlds/save_world.sdf +++ b/test/worlds/save_world.sdf @@ -13,6 +13,21 @@ filename="ignition-gazebo-user-commands-system" name="ignition::gazebo::systems::UserCommands"> + 0 0 -9.8 + 5.5645e-6 22.8758e-6 -42.3884e-6 + + + + 0.4 0.4 0.4 1.0 + .7 .7 .7 1 + true + + + + 0.001 + 1.0 + 1000 + 10 0 0 0 0 0 diff --git a/test/worlds/sensor.sdf b/test/worlds/sensor.sdf index aebb32104c..1587a32736 100644 --- a/test/worlds/sensor.sdf +++ b/test/worlds/sensor.sdf @@ -64,6 +64,11 @@ 0.1 100 + + gaussian_quantized + 0.01 + 0.0002 + 1 30 @@ -131,6 +136,12 @@ 0.1 10.0 + + + 0.05 + 9.0 + + @@ -150,6 +161,40 @@ + + + + + 1.15 + + 300 + 200 + + + 0.14 + 120.0 + + + + + + + + + 1.0 + + 320 + 240 + + + 0.11 + 20.0 + + panoptic + + + +