diff --git a/CMakeLists.txt b/CMakeLists.txt index ded63d5e79..406ee1b308 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,7 @@ set(IGN_FUEL_TOOLS_VER ${ignition-fuel_tools7_VERSION_MAJOR}) #-------------------------------------- # Find ignition-gui -ign_find_package(ignition-gui6 REQUIRED) +ign_find_package(ignition-gui6 REQUIRED VERSION 6.1) set(IGN_GUI_VER ${ignition-gui6_VERSION_MAJOR}) ign_find_package (Qt5 COMPONENTS diff --git a/examples/plugin/rendering_plugins/rendering_plugins.sdf b/examples/plugin/rendering_plugins/rendering_plugins.sdf index eb56737121..68f77eb736 100644 --- a/examples/plugin/rendering_plugins/rendering_plugins.sdf +++ b/examples/plugin/rendering_plugins/rendering_plugins.sdf @@ -92,6 +92,7 @@ true true true + true diff --git a/examples/scripts/log_video_recorder/log_video_recorder.sdf b/examples/scripts/log_video_recorder/log_video_recorder.sdf index e2cdc72a9d..e333935ddf 100644 --- a/examples/scripts/log_video_recorder/log_video_recorder.sdf +++ b/examples/scripts/log_video_recorder/log_video_recorder.sdf @@ -88,6 +88,7 @@ true /world/default/control /world/default/stats + true diff --git a/examples/worlds/camera_sensor.sdf b/examples/worlds/camera_sensor.sdf index 48d052d355..8e8c090728 100644 --- a/examples/worlds/camera_sensor.sdf +++ b/examples/worlds/camera_sensor.sdf @@ -105,6 +105,7 @@ true true true + true diff --git a/examples/worlds/fuel_textured_mesh.sdf b/examples/worlds/fuel_textured_mesh.sdf index 8b9e9147b2..2f42cf7608 100644 --- a/examples/worlds/fuel_textured_mesh.sdf +++ b/examples/worlds/fuel_textured_mesh.sdf @@ -108,6 +108,7 @@ true true true + true diff --git a/examples/worlds/grid.sdf b/examples/worlds/grid.sdf index efab4d2549..affe914178 100644 --- a/examples/worlds/grid.sdf +++ b/examples/worlds/grid.sdf @@ -80,6 +80,7 @@ true true true + true diff --git a/examples/worlds/minimal_scene.sdf b/examples/worlds/minimal_scene.sdf index 0221e08050..90f85eac6d 100644 --- a/examples/worlds/minimal_scene.sdf +++ b/examples/worlds/minimal_scene.sdf @@ -147,6 +147,7 @@ Features: true true true + true diff --git a/examples/worlds/optical_tactile_sensor_plugin.sdf b/examples/worlds/optical_tactile_sensor_plugin.sdf index d458079a15..06262adb00 100644 --- a/examples/worlds/optical_tactile_sensor_plugin.sdf +++ b/examples/worlds/optical_tactile_sensor_plugin.sdf @@ -110,6 +110,7 @@ true true true + true diff --git a/examples/worlds/plot_3d.sdf b/examples/worlds/plot_3d.sdf index 59b7c09c6a..ed66fbf3a6 100644 --- a/examples/worlds/plot_3d.sdf +++ b/examples/worlds/plot_3d.sdf @@ -84,6 +84,7 @@ true true true + true diff --git a/examples/worlds/segmentation_camera.sdf b/examples/worlds/segmentation_camera.sdf index 4e0840a37e..30d515cb4c 100644 --- a/examples/worlds/segmentation_camera.sdf +++ b/examples/worlds/segmentation_camera.sdf @@ -98,6 +98,7 @@ true true true + true diff --git a/examples/worlds/sensors_demo.sdf b/examples/worlds/sensors_demo.sdf index c679e2ebeb..31374a1a3e 100644 --- a/examples/worlds/sensors_demo.sdf +++ b/examples/worlds/sensors_demo.sdf @@ -100,6 +100,7 @@ true true true + true diff --git a/examples/worlds/sky.sdf b/examples/worlds/sky.sdf index c22442bac2..be14b395c5 100644 --- a/examples/worlds/sky.sdf +++ b/examples/worlds/sky.sdf @@ -83,6 +83,7 @@ Currently only supported using ogre2 rendering engine plugin. true true true + true diff --git a/examples/worlds/thermal_camera.sdf b/examples/worlds/thermal_camera.sdf index 03be5f2d64..cb37b61619 100644 --- a/examples/worlds/thermal_camera.sdf +++ b/examples/worlds/thermal_camera.sdf @@ -101,6 +101,7 @@ true true true + true diff --git a/examples/worlds/tunnel.sdf b/examples/worlds/tunnel.sdf index 496c2a1781..583b313641 100644 --- a/examples/worlds/tunnel.sdf +++ b/examples/worlds/tunnel.sdf @@ -129,6 +129,7 @@ true true true + true diff --git a/examples/worlds/video_record_dbl_pendulum.sdf b/examples/worlds/video_record_dbl_pendulum.sdf index d7cf86abbf..5f0775c046 100644 --- a/examples/worlds/video_record_dbl_pendulum.sdf +++ b/examples/worlds/video_record_dbl_pendulum.sdf @@ -180,6 +180,7 @@ true true true + true diff --git a/examples/worlds/visibility.sdf b/examples/worlds/visibility.sdf index 33480d9de5..31bec366b3 100644 --- a/examples/worlds/visibility.sdf +++ b/examples/worlds/visibility.sdf @@ -112,6 +112,7 @@ true true true + true diff --git a/examples/worlds/visualize_contacts.sdf b/examples/worlds/visualize_contacts.sdf index 357e4b5adb..f39994dced 100644 --- a/examples/worlds/visualize_contacts.sdf +++ b/examples/worlds/visualize_contacts.sdf @@ -102,6 +102,7 @@ Contacts will be visualized as blue spheres and green cylinders. true true true + true diff --git a/examples/worlds/visualize_lidar.sdf b/examples/worlds/visualize_lidar.sdf index 7d01bf2a76..d38fe690ce 100644 --- a/examples/worlds/visualize_lidar.sdf +++ b/examples/worlds/visualize_lidar.sdf @@ -96,6 +96,7 @@ true true true + true diff --git a/src/SimulationRunner.cc b/src/SimulationRunner.cc index 250d0f2fb2..57041d66ad 100644 --- a/src/SimulationRunner.cc +++ b/src/SimulationRunner.cc @@ -195,12 +195,14 @@ SimulationRunner::SimulationRunner(const sdf::World *_world, // TODO(louise) Combine both messages into one. this->node->Advertise("control", &SimulationRunner::OnWorldControl, this); + this->node->Advertise("control/state", &SimulationRunner::OnWorldControlState, + this); this->node->Advertise("playback/control", &SimulationRunner::OnPlaybackControl, this); ignmsg << "Serving world controls on [" << opts.NameSpace() - << "/control] and [" << opts.NameSpace() << "/playback/control]" - << std::endl; + << "/control], [" << opts.NameSpace() << "/control/state] and [" + << opts.NameSpace() << "/playback/control]" << std::endl; // Publish empty GUI messages for worlds that have no GUI in the beginning. // In the future, support modifying GUI from the server at runtime. @@ -410,6 +412,12 @@ void SimulationRunner::PublishStats() msg.set_paused(this->currentInfo.paused); + if (this->Stepping()) + { + auto headerData = msg.mutable_header()->add_data(); + headerData->set_key("step"); + } + // Publish the stats message. The stats message is throttled. this->statsPub.Publish(msg); @@ -1079,6 +1087,18 @@ void SimulationRunner::SetPaused(const bool _paused) this->currentInfo.paused = _paused; } +///////////////////////////////////////////////// +void SimulationRunner::SetStepping(bool _stepping) +{ + this->stepping = _stepping; +} + +///////////////////////////////////////////////// +bool SimulationRunner::Stepping() const +{ + return this->stepping; +} + ///////////////////////////////////////////////// void SimulationRunner::SetRunToSimTime( const std::chrono::steady_clock::duration &_time) @@ -1097,36 +1117,54 @@ void SimulationRunner::SetRunToSimTime( ///////////////////////////////////////////////// bool SimulationRunner::OnWorldControl(const msgs::WorldControl &_req, msgs::Boolean &_res) +{ + msgs::WorldControlState req; + req.mutable_world_control()->CopyFrom(_req); + + return this->OnWorldControlState(req, _res); +} + +///////////////////////////////////////////////// +bool SimulationRunner::OnWorldControlState(const msgs::WorldControlState &_req, + msgs::Boolean &_res) { std::lock_guard lock(this->msgBufferMutex); + // update the server ECM if the request contains SerializedState information + if (_req.has_state()) + this->entityCompMgr.SetState(_req.state()); + // TODO(anyone) notify server systems of changes made to the ECM, if there + // were any? + WorldControl control; - control.pause = _req.pause(); + control.pause = _req.world_control().pause(); - if (_req.multi_step() != 0) - control.multiStep = _req.multi_step(); - else if (_req.step()) + if (_req.world_control().multi_step() != 0) + control.multiStep = _req.world_control().multi_step(); + else if (_req.world_control().step()) control.multiStep = 1; - if (_req.has_reset()) + if (_req.world_control().has_reset()) { - control.rewind = _req.reset().all() || _req.reset().time_only(); + control.rewind = _req.world_control().reset().all() || + _req.world_control().reset().time_only(); - if (_req.reset().model_only()) + if (_req.world_control().reset().model_only()) { ignwarn << "Model only reset is not supported." << std::endl; } } - if (_req.seed() != 0) + if (_req.world_control().seed() != 0) { ignwarn << "Changing seed is not supported." << std::endl; } - if (_req.has_run_to_sim_time()) + if (_req.world_control().has_run_to_sim_time()) { - control.runToSimTime = std::chrono::seconds(_req.run_to_sim_time().sec()) + - std::chrono::nanoseconds(_req.run_to_sim_time().nsec()); + control.runToSimTime = std::chrono::seconds( + _req.world_control().run_to_sim_time().sec()) + + std::chrono::nanoseconds(_req.world_control().run_to_sim_time().nsec()); } this->worldControls.push_back(control); @@ -1175,6 +1213,10 @@ void SimulationRunner::ProcessMessages() void SimulationRunner::ProcessWorldControl() { IGN_PROFILE("SimulationRunner::ProcessWorldControl"); + + // assume no stepping unless WorldControl msgs say otherwise + this->SetStepping(false); + for (const auto &control : this->worldControls) { // Play / pause @@ -1186,6 +1228,7 @@ void SimulationRunner::ProcessWorldControl() this->pendingSimIterations += control.multiStep; // Unpause so that stepping can occur. this->SetPaused(false); + this->SetStepping(true); } // Rewind / reset diff --git a/src/SimulationRunner.hh b/src/SimulationRunner.hh index 36754d3388..9f17d59b6b 100644 --- a/src/SimulationRunner.hh +++ b/src/SimulationRunner.hh @@ -38,6 +38,7 @@ #include #include #include +#include #include #include "ignition/gazebo/config.hh" @@ -291,6 +292,17 @@ namespace ignition /// \return True if the simulation runner is paused, false otherwise. public: bool Paused() const; + /// \brief Set if the simulation runner is stepping based on WorldControl + /// info + /// \param[in] _step True if stepping based on WorldControl info, false + /// otherwise + public: void SetStepping(bool _step); + + /// \brief Get if the simulation runner is stepping based on WorldControl + /// info + /// \return True if stepping based on WorldControl info, false otherwise + public: bool Stepping() const; + /// \brief Set the run to simulation time. /// \param[in] _time A simulation time in the future to run to and then /// pause. A negative number or a time less than the current simulation @@ -370,6 +382,16 @@ namespace ignition private: bool OnWorldControl(const msgs::WorldControl &_req, msgs::Boolean &_res); + /// \brief World control state service callback. This function stores the + /// the request which will then be processed by the ProcessMessages + /// function. + /// \param[in] _req Request from client, currently handling play / pause + /// and multistep. This also may contain SerializedState information. + /// \param[out] _res Response to client, true if successful. + /// \return True for success + private: bool OnWorldControlState(const msgs::WorldControlState &_req, + msgs::Boolean &_res); + /// \brief World control service callback. This function stores the /// the request which will then be processed by the ProcessMessages /// function. @@ -601,6 +623,10 @@ namespace ignition /// \brief True if Server::RunOnce triggered a blocking paused step private: bool blockingPausedStepPending{false}; + /// \brief Whether the simulation runner is currently stepping based on + /// WorldControl info (true) or not (false) + private: bool stepping{false}; + friend class LevelManager; }; } diff --git a/src/gui/GuiRunner.cc b/src/gui/GuiRunner.cc index 862e8188f0..50a2c3f075 100644 --- a/src/gui/GuiRunner.cc +++ b/src/gui/GuiRunner.cc @@ -19,7 +19,9 @@ #include #include #include +#include #include +#include #include // Include all components so they have first-class support @@ -63,6 +65,9 @@ class ignition::gazebo::GuiRunner::Implementation /// \brief True if the initial state has been received and processed. public: bool receivedInitialState{false}; + + /// \brief Name of WorldControl service + public: std::string controlService; }; ///////////////////////////////////////////////// @@ -115,11 +120,53 @@ GuiRunner::GuiRunner(const std::string &_worldName) QPointer timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &GuiRunner::UpdatePlugins); timer->start(33); + + this->dataPtr->controlService = "/world/" + _worldName + "/control/state"; + + ignition::gui::App()->findChild< + ignition::gui::MainWindow *>()->installEventFilter(this); } ///////////////////////////////////////////////// GuiRunner::~GuiRunner() = default; +///////////////////////////////////////////////// +bool GuiRunner::eventFilter(QObject *_obj, QEvent *_event) +{ + if (_event->type() == ignition::gui::events::WorldControl::kType) + { + auto worldControlEvent = + reinterpret_cast(_event); + if (worldControlEvent) + { + msgs::WorldControlState req; + req.mutable_world_control()->CopyFrom( + worldControlEvent->WorldControlInfo()); + + // share the GUI's ECM with the server if: + // 1. Play was pressed + // 2. Step was pressed while paused + const auto &info = worldControlEvent->WorldControlInfo(); + const bool pressedStep = info.multi_step() > 0u; + const bool pressedPlay = !info.pause() && !pressedStep; + const bool pressedStepWhilePaused = info.pause() && pressedStep; + if (pressedPlay || pressedStepWhilePaused) + req.mutable_state()->CopyFrom(this->dataPtr->ecm.State()); + + std::function cb = + [](const ignition::msgs::Boolean &/*_rep*/, const bool _result) + { + if (!_result) + ignerr << "Error sharing WorldControl info with the server.\n"; + }; + this->dataPtr->node.Request(this->dataPtr->controlService, req, cb); + } + } + + // Standard event processing + return QObject::eventFilter(_obj, _event); +} + ///////////////////////////////////////////////// void GuiRunner::RequestState() { diff --git a/src/gui/GuiRunner.hh b/src/gui/GuiRunner.hh index b2bceaf0e9..1a06098161 100644 --- a/src/gui/GuiRunner.hh +++ b/src/gui/GuiRunner.hh @@ -46,6 +46,9 @@ class IGNITION_GAZEBO_GUI_VISIBLE GuiRunner : public QObject /// \brief Destructor public: ~GuiRunner() override; + // Documentation inherited + protected: bool eventFilter(QObject *_obj, QEvent *_event) override; + /// \brief Callback when a plugin has been added. /// This function has no effect and is left here for ABI compatibility. /// \param[in] _objectName Plugin's object name. diff --git a/src/gui/gui.config b/src/gui/gui.config index 36166061f8..36782b6b9d 100644 --- a/src/gui/gui.config +++ b/src/gui/gui.config @@ -136,6 +136,7 @@ true true true + true diff --git a/src/gui/playback_gui.config b/src/gui/playback_gui.config index 50cffa6992..4e6d386447 100644 --- a/src/gui/playback_gui.config +++ b/src/gui/playback_gui.config @@ -60,6 +60,7 @@ true true true + true