diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3caa9b7dcc..ce24763e77 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,7 +75,7 @@ set(IGN_FUEL_TOOLS_VER ${ignition-fuel_tools5_VERSION_MAJOR})
#--------------------------------------
# Find ignition-gui
-ign_find_package(ignition-gui4 REQUIRED)
+ign_find_package(ignition-gui4 REQUIRED VERSION 4.1)
set(IGN_GUI_VER ${ignition-gui4_VERSION_MAJOR})
ign_find_package (Qt5
COMPONENTS
diff --git a/examples/worlds/logical_audio_sensor_plugin.sdf b/examples/worlds/logical_audio_sensor_plugin.sdf
new file mode 100644
index 0000000000..9b93d11f99
--- /dev/null
+++ b/examples/worlds/logical_audio_sensor_plugin.sdf
@@ -0,0 +1,277 @@
+
+
+
+
+
+ 0.001
+ 1.0
+
+
+
+
+
+
+
+
+
+ 1.0 1.0 1.0
+ 0.8 0.8 0.8
+
+
+
+ true
+ 0 0 10 0 0 0
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+ 1000
+ 0.9
+ 0.01
+ 0.001
+
+ -0.5 0.1 -0.9
+
+
+
+ true
+
+
+
+
+ 0 0 1
+
+
+
+
+
+
+ 0 0 1
+ 100 100
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+
+
+
+ 0 0 0.5 0 0 0
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 0
+ 1
+
+ 1.0
+
+
+
+
+ 1 1 1
+
+
+
+
+
+
+ 1 1 1
+
+
+
+ 1 0 0 1
+ 1 0 0 1
+ 1 0 0 1
+
+
+
+
+
+
+
+
+
+ 3 3 0.5 0 0 0
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 0
+ 1
+
+ 1.0
+
+
+
+
+ 1 1 1
+
+
+
+
+
+
+ 1 1 1
+
+
+
+ 0 0 1 1
+ 0 0 1 1
+ 0 0 1 1
+
+
+
+
+
+
+
+
+
+ 0 2 0.5 0 0 0
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 0
+ 1
+
+ 1.0
+
+
+
+
+ 1 1 1
+
+
+
+
+
+
+ 1 1 1
+
+
+
+ 0 1 0 1
+ 0 1 0 1
+ 0 1 0 1
+
+
+
+
+
+ 1
+ 0 .5 0 0 0 0
+ .4
+
+
+
+
+
+ -5 4 0.5 0 0 0
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 0
+ 1
+
+ 1.0
+
+
+
+
+ 1 1 1
+
+
+
+
+
+
+ 1 1 1
+
+
+
+ 1 1 0 1
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+ 1
+ 0.5 0.5 0.5 0 0 0
+ .3
+
+
+
+
+
diff --git a/include/ignition/gazebo/components/LogicalAudio.hh b/include/ignition/gazebo/components/LogicalAudio.hh
new file mode 100644
index 0000000000..7b465e6178
--- /dev/null
+++ b/include/ignition/gazebo/components/LogicalAudio.hh
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2020 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+*/
+#ifndef IGNITION_GAZEBO_COMPONENTS_LOGICALAUDIO_HH_
+#define IGNITION_GAZEBO_COMPONENTS_LOGICALAUDIO_HH_
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering.
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE {
+namespace logical_audio
+{
+ /// \brief Audio source attenuation functions.
+ /// AttenuationFunction::Undefined is used to indicate that an
+ /// attenuation function has not been defined yet.
+ enum class AttenuationFunction { LINEAR, UNDEFINED };
+
+ /// \brief Audio source attenuation shapes.
+ /// AttenuationShape::Undefined is used to indicate that an
+ /// attenuation shape has not been defined yet.
+ enum class AttenuationShape { SPHERE, UNDEFINED };
+
+ /// \brief Properties of a logical audio source.
+ /// A source also has a pose, which can be stored as a component of a
+ /// source entity via ignition::gazebo::components::Pose
+ struct Source
+ {
+ /// \brief The source's id
+ unsigned int id;
+
+ /// \brief The source's attenuation function
+ logical_audio::AttenuationFunction attFunc;
+
+ /// \brief The source's attenuation shape
+ logical_audio::AttenuationShape attShape;
+
+ /// \brief The source's inner radius, which should be >= 0
+ double innerRadius;
+
+ /// \brief The source's falloff distance, which should be
+ /// greater than Source::innerRadius
+ double falloffDistance;
+
+ /// \brief The source's emission volume, which should be
+ /// between 0.0 (0% volume) and 1.0 (100% volume)
+ double emissionVolume;
+
+ public: bool operator==(const Source &_source) const
+ {
+ return this->id == _source.id;
+ }
+
+ public: bool operator!=(const Source &_source) const
+ {
+ return !(*this == _source);
+ }
+ };
+
+ /// \brief A source's playing information.
+ /// Useful for keeping track of when to start/stop playing a source.
+ struct SourcePlayInfo
+ {
+ /// \brief Constructor
+ SourcePlayInfo() : startTime()
+ {
+ }
+
+ /// \brief Whether the source is currently playing or not
+ bool playing{false};
+
+ /// \brief How long the source should play for, in seconds.
+ /// Setting this to 0 means the source has a play duration of infinity
+ std::chrono::seconds playDuration;
+
+ /// \brief The time at which the source most recently started playing
+ std::chrono::steady_clock::duration startTime;
+
+ public: bool operator==(const SourcePlayInfo &_sourcePlayInfo) const
+ {
+ return (this->playing == _sourcePlayInfo.playing) &&
+ (this->playDuration == _sourcePlayInfo.playDuration) &&
+ (this->startTime == _sourcePlayInfo.startTime);
+ }
+
+ public: bool operator!=(const SourcePlayInfo &_sourcePlayInfo) const
+ {
+ return !(*this == _sourcePlayInfo);
+ }
+ };
+
+ /// \brief Properties of a logical audio microphone.
+ /// A microphone also has a pose, which can be stored as a component of a
+ /// microphone entity via ignition::gazebo::components::Pose
+ struct Microphone
+ {
+ /// \brief The microphone's id
+ unsigned int id;
+
+ /// \brief The minimum volume this microphone can detect.
+ /// This should be a value between 0.0 (0% volume) and 1.0 (100% volume)
+ double volumeDetectionThreshold;
+
+ public: bool operator==(const Microphone &_microphone) const
+ {
+ return this->id == _microphone.id;
+ }
+
+ public: bool operator!=(const Microphone &_microphone) const
+ {
+ return !(*this == _microphone);
+ }
+ };
+}
+
+namespace serializers
+{
+ /// \brief Output stream overload for logical_audio::AttenuationFunction
+ inline std::ostream &operator<<(std::ostream &_out,
+ const logical_audio::AttenuationFunction &_func)
+ {
+ auto temp = static_cast(_func);
+ _out << temp;
+ return _out;
+ }
+
+ /// \brief Input stream overload for logical_audio::AttenuationFunction
+ inline std::istream &operator>>(std::istream &_in,
+ logical_audio::AttenuationFunction &_func)
+ {
+ unsigned int temp = 0u;
+ if (_in >> temp)
+ _func = static_cast(temp);
+ return _in;
+ }
+
+ /// \brief Output stream overload for logical_audio::AttenuationShape
+ inline std::ostream &operator<<(std::ostream &_out,
+ const logical_audio::AttenuationShape &_shape)
+ {
+ auto temp = static_cast(_shape);
+ _out << temp;
+ return _out;
+ }
+
+ /// \brief Input stream overload for logical_audio::AttenuationShape
+ inline std::istream &operator>>(std::istream &_in,
+ logical_audio::AttenuationShape &_shape)
+ {
+ unsigned int temp = 0u;
+ if (_in >> temp)
+ _shape = static_cast(temp);
+ return _in;
+ }
+
+ /// \brief Output stream overload for std::chrono::steady_clock::duration
+ inline std::ostream &operator<<(std::ostream &_out,
+ const std::chrono::steady_clock::duration &_dur)
+ {
+ _out << std::chrono::duration_cast(
+ _dur).count();
+ return _out;
+ }
+
+ /// \brief Input stream overload for std::chrono::steady_clock::duration
+ inline std::istream &operator>>(std::istream &_in,
+ std::chrono::steady_clock::duration &_dur)
+ {
+ int64_t time;
+ _in >> time;
+ _dur = std::chrono::duration(time);
+ return _in;
+ }
+
+ /// \brief Serializer for components::LogicalAudioSource object
+ class LogicalAudioSourceSerializer
+ {
+ /// \brief Serialization for logical_audio::Source
+ /// \param[out] _out Output stream
+ /// \param[in] _source Object for the stream
+ /// \return The stream
+ public: static std::ostream &Serialize(std::ostream &_out,
+ const logical_audio::Source &_source)
+ {
+ _out << _source.id << " " << _source.attFunc << " " << _source.attShape
+ << " " << _source.innerRadius << " " << _source.falloffDistance
+ << " " << _source.emissionVolume;
+ return _out;
+ }
+
+ /// \brief Deserialization for logical_audio::Source
+ /// \param[in] _in Input stream
+ /// \param[out] _source The object to populate
+ /// \return The stream
+ public: static std::istream &Deserialize(std::istream &_in,
+ logical_audio::Source &_source)
+ {
+ _in >> _source.id >> _source.attFunc >> _source.attShape
+ >> _source.innerRadius >> _source.falloffDistance
+ >> _source.emissionVolume;
+ return _in;
+ }
+ };
+
+ /// \brief Serializer for components::LogicalAudioSourcePlayInfo object
+ class LogicalAudioSourcePlayInfoSerializer
+ {
+ /// \brief Serialization for logical_audio::SourcePlayInfo
+ /// \param[out] _out Output stream
+ /// \param[in] _playInfo Object for the stream
+ /// \return The stream
+ public: static std::ostream &Serialize(std::ostream &_out,
+ const logical_audio::SourcePlayInfo &_playInfo)
+ {
+ _out << _playInfo.playing << " " << _playInfo.playDuration.count() << " "
+ << _playInfo.startTime;
+ return _out;
+ }
+
+ /// \brief Deserialization for logical_audio::SourcePlayInfo
+ /// \param[in] _in Input stream
+ /// \param[out] _playInfo The object to populate
+ /// \return The stream
+ public: static std::istream &Deserialize(std::istream &_in,
+ logical_audio::SourcePlayInfo &_playInfo)
+ {
+ uint64_t count;
+ _in >> _playInfo.playing >> count >> _playInfo.startTime;
+ _playInfo.playDuration = std::chrono::seconds(count);
+ return _in;
+ }
+ };
+
+ /// \brief Serializer for components::LogicalMicrophone object
+ class LogicalMicrophoneSerializer
+ {
+ /// \brief Serialization for logical_audio::Microphone
+ /// \param[out] _out Output stream
+ /// \param[in] _mic Object for the stream
+ /// \return The stream
+ public: static std::ostream &Serialize(std::ostream &_out,
+ const logical_audio::Microphone &_mic)
+ {
+ _out << _mic.id << " " << _mic.volumeDetectionThreshold;
+ return _out;
+ }
+
+ /// \brief Deserialization for logical_audio::Microphone
+ /// \param[in] _in Inout stream
+ /// \param[out] _mic The object to populate
+ public: static std::istream &Deserialize(std::istream &_in,
+ logical_audio::Microphone &_mic)
+ {
+ _in >> _mic.id >> _mic.volumeDetectionThreshold;
+ return _in;
+ }
+ };
+}
+
+// using separate namespace blocks so all components appear in Doxygen
+// (appears as if Doxygen can't parse multiple components in a single
+// namespace block since IGN_GAZEBO_REGISTER_COMPONENT doesn't have a
+// trailing semicolon)
+namespace components
+{
+ /// \brief A component that contains a logical audio source, which is
+ /// represented by logical_audio::Source
+ using LogicalAudioSource = Component;
+ IGN_GAZEBO_REGISTER_COMPONENT("ign_gazebo_components.LogicalAudioSource",
+ LogicalAudioSource)
+}
+
+namespace components
+{
+ /// \brief A component that contains a logical audio source's playing
+ /// information, which is represented by logical_audio::SourcePlayInfo
+ using LogicalAudioSourcePlayInfo = Component;
+ IGN_GAZEBO_REGISTER_COMPONENT(
+ "ign_gazebo_components.LogicalAudioSourcePlayInfo",
+ LogicalAudioSourcePlayInfo)
+}
+
+namespace components
+{
+ /// \brief A component that contains a logical microphone, which is
+ /// represented by logical_audio::Microphone
+ using LogicalMicrophone = Component;
+ IGN_GAZEBO_REGISTER_COMPONENT("ign_gazebo_components.LogicalMicrophone",
+ LogicalMicrophone)
+}
+}
+}
+}
+
+#endif
diff --git a/include/ignition/gazebo/detail/EntityComponentManager.hh b/include/ignition/gazebo/detail/EntityComponentManager.hh
index ee87528162..027baf355c 100644
--- a/include/ignition/gazebo/detail/EntityComponentManager.hh
+++ b/include/ignition/gazebo/detail/EntityComponentManager.hh
@@ -63,6 +63,7 @@ namespace traits
template
auto CompareData = [](const DataType &_a, const DataType &_b) -> bool
{
+ // cppcheck-suppress syntaxError
if constexpr (std::is_same::value)
{
return math::equal(_a, _b);
diff --git a/include/ignition/gazebo/gui/GuiRunner.hh b/include/ignition/gazebo/gui/GuiRunner.hh
index 564ac183f0..a60f77274c 100644
--- a/include/ignition/gazebo/gui/GuiRunner.hh
+++ b/include/ignition/gazebo/gui/GuiRunner.hh
@@ -53,11 +53,9 @@ class IGNITION_GAZEBO_VISIBLE GuiRunner : public QObject
/// \brief Make a new state request to the server.
public slots: void RequestState();
- /// \brief Callback for the state service.
+ /// \brief Callback for the async state service.
/// \param[in] _res Response containing new state.
- /// \param[in] _result True if successful.
- private: void OnStateService(const msgs::SerializedStepMap &_res,
- const bool _result);
+ private: void OnStateAsyncService(const msgs::SerializedStepMap &_res);
/// \brief Callback when a new state is received from the server.
/// \param[in] _msg New state message.
diff --git a/src/SimulationRunner.hh b/src/SimulationRunner.hh
index e7f4c0b15f..775487f4fa 100644
--- a/src/SimulationRunner.hh
+++ b/src/SimulationRunner.hh
@@ -70,16 +70,16 @@ namespace ignition
struct WorldControl
{
/// \brief True to pause simulation.
- /// cppcheck-suppress unusedStructMember
+ // cppcheck-suppress unusedStructMember
bool pause{false}; // NOLINT
/// \biref Run a given number of simulation iterations.
- /// cppcheck-suppress unusedStructMember
+ // cppcheck-suppress unusedStructMember
uint64_t multiStep{0u}; // NOLINT
/// \brief Reset simulation back to time zero. Rewinding resets sim time,
/// real time and iterations.
- /// cppcheck-suppress unusedStructMember
+ // cppcheck-suppress unusedStructMember
bool rewind{false}; // NOLINT
/// \brief Sim time to jump to. A negative value means don't seek.
diff --git a/src/gui/GuiRunner.cc b/src/gui/GuiRunner.cc
index 4456f3c39e..565838a06c 100644
--- a/src/gui/GuiRunner.cc
+++ b/src/gui/GuiRunner.cc
@@ -59,7 +59,16 @@ GuiRunner::~GuiRunner() = default;
/////////////////////////////////////////////////
void GuiRunner::RequestState()
{
- this->node.Request(this->stateTopic, &GuiRunner::OnStateService, this);
+ // set up service for async state response callback
+ std::string id = std::to_string(gui::App()->applicationPid());
+ std::string reqSrv =
+ this->node.Options().NameSpace() + "/" + id + "/state_async";
+ this->node.Advertise(reqSrv, &GuiRunner::OnStateAsyncService, this);
+ ignition::msgs::StringMsg req;
+ req.set_data(reqSrv);
+
+ // send async state request
+ this->node.Request(this->stateTopic + "_async", req);
}
/////////////////////////////////////////////////
@@ -77,17 +86,17 @@ void GuiRunner::OnPluginAdded(const QString &_objectName)
}
/////////////////////////////////////////////////
-void GuiRunner::OnStateService(const msgs::SerializedStepMap &_res,
- const bool _result)
+void GuiRunner::OnStateAsyncService(const msgs::SerializedStepMap &_res)
{
- if (!_result)
- {
- ignerr << "Service call failed for [" << this->stateTopic << "]"
- << std::endl;
- return;
- }
this->OnState(_res);
+ // todo(anyone) store reqSrv string in a member variable and use it here
+ // and in RequestState()
+ std::string id = std::to_string(gui::App()->applicationPid());
+ std::string reqSrv =
+ this->node.Options().NameSpace() + "/" + id + "/state_async";
+ this->node.UnadvertiseSrv(reqSrv);
+
// Only subscribe to periodic updates after receiving initial state
if (this->node.SubscribedTopics().empty())
this->node.Subscribe(this->stateTopic, &GuiRunner::OnState, this);
diff --git a/src/gui/plugins/CMakeLists.txt b/src/gui/plugins/CMakeLists.txt
index 2da32427c6..56bb1b13f3 100644
--- a/src/gui/plugins/CMakeLists.txt
+++ b/src/gui/plugins/CMakeLists.txt
@@ -90,6 +90,7 @@ add_subdirectory(plotting)
add_subdirectory(resource_spawner)
add_subdirectory(scene3d)
add_subdirectory(shapes)
+add_subdirectory(tape_measure)
add_subdirectory(transform_control)
add_subdirectory(video_recorder)
add_subdirectory(view_angle)
diff --git a/src/gui/plugins/align_tool/AlignTool.cc b/src/gui/plugins/align_tool/AlignTool.cc
index 20c56f0cb5..e2b7084e8a 100644
--- a/src/gui/plugins/align_tool/AlignTool.cc
+++ b/src/gui/plugins/align_tool/AlignTool.cc
@@ -18,7 +18,12 @@
#include
#include
+#include
#include
+#include