diff --git a/src/plugins/scene3d/Scene3D.cc b/src/plugins/scene3d/Scene3D.cc index 1b2bcc9fd..828cf266a 100644 --- a/src/plugins/scene3d/Scene3D.cc +++ b/src/plugins/scene3d/Scene3D.cc @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -231,9 +230,6 @@ namespace plugins /// \brief View control focus target public: math::Vector3d target; - - /// \brief Path to save the screenshot - public: std::string screenshotSavePath; }; /// \brief Private data class for RenderWindowItem @@ -252,11 +248,6 @@ namespace plugins /// \brief Private data class for Scene3D class Scene3DPrivate { - /// \brief Transport node - public: transport::Node node; - - /// \brief Screenshot service - public: std::string screenshotService; }; } } @@ -861,25 +852,6 @@ void IgnRenderer::Render() // update and render to texture this->dataPtr->camera->Update(); - // Save screenshot - { - if (!this->dataPtr->screenshotSavePath.empty()) - { - unsigned int width = this->dataPtr->camera->ImageWidth(); - unsigned int height = this->dataPtr->camera->ImageHeight(); - - auto cameraImage = this->dataPtr->camera->CreateImage(); - this->dataPtr->camera->Copy(cameraImage); - - common::Image image; - image.SetFromData(cameraImage.Data(), width, height, - common::Image::RGB_INT8); - image.SavePNG(this->dataPtr->screenshotSavePath); - - this->dataPtr->screenshotSavePath.clear(); - } - } - if (ignition::gui::App()) { ignition::gui::App()->sendEvent( @@ -888,13 +860,6 @@ void IgnRenderer::Render() } } -///////////////////////////////////////////////// -void IgnRenderer::SaveScreenshot(const std::string &_filename) -{ - std::lock_guard lock(this->dataPtr->mutex); - this->dataPtr->screenshotSavePath = _filename; -} - ///////////////////////////////////////////////// void IgnRenderer::HandleMouseEvent() { @@ -1467,26 +1432,8 @@ void Scene3D::LoadConfig(const tinyxml2::XMLElement *_pluginElem) renderWindow->SetSceneTopic(topic); } } - - // Screenshot service - this->dataPtr->screenshotService = "/gui/screenshot"; - this->dataPtr->node.Advertise(this->dataPtr->screenshotService, - &Scene3D::OnScreenshot, this); - ignmsg << "Screenshot service on [" - << this->dataPtr->screenshotService << "]" << std::endl; } -///////////////////////////////////////////////// -bool Scene3D::OnScreenshot(const msgs::StringMsg &_msg, - msgs::Boolean &_res) -{ - auto renderWindow = this->PluginItem()->findChild(); - renderWindow->SaveScreenshot(_msg.data()); - _res.set_data(true); - return true; -} - - ///////////////////////////////////////////////// void RenderWindowItem::mousePressEvent(QMouseEvent *_e) { @@ -1537,13 +1484,6 @@ void RenderWindowItem::wheelEvent(QWheelEvent *_e) this->dataPtr->mouseEvent, math::Vector2d(scroll, scroll)); } -///////////////////////////////////////////////// -void RenderWindowItem::SaveScreenshot(const std::string &_filename) -{ - this->dataPtr->renderThread->ignRenderer.SaveScreenshot(_filename); -} - - /////////////////////////////////////////////////// // void Scene3D::resizeEvent(QResizeEvent *_e) // { diff --git a/src/plugins/scene3d/Scene3D.hh b/src/plugins/scene3d/Scene3D.hh index d9a46016c..96cb81a1d 100644 --- a/src/plugins/scene3d/Scene3D.hh +++ b/src/plugins/scene3d/Scene3D.hh @@ -75,13 +75,6 @@ namespace plugins public: virtual void LoadConfig(const tinyxml2::XMLElement *_pluginElem) override; - /// \brief Callback for saving a screenshot (from the user camera) request - /// \param[in] _msg Request message of the saved file path - /// \param[in] _res Response data - /// \return True if the request is received - private: bool OnScreenshot(const msgs::StringMsg &_msg, - msgs::Boolean &_res); - /// \internal /// \brief Pointer to private data. private: std::unique_ptr dataPtr; @@ -126,10 +119,6 @@ namespace plugins private: math::Vector3d ScreenToScene(const math::Vector2i &_screenPos) const; - /// \brief Save a screenshot from the user camera - /// \param[in] _filename The filename (including path) of the screenshot - public: void SaveScreenshot(const std::string &_filename); - /// \brief Render texture id public: GLuint textureId = 0u; @@ -271,10 +260,6 @@ namespace plugins /// \brief Slot called when thread is ready to be started public Q_SLOTS: void Ready(); - /// \brief Save a screenshot from the user camera - /// \param[in] _filename The filename (including path) of the screenshot - public: void SaveScreenshot(const std::string &_filename); - // Documentation inherited protected: virtual void mousePressEvent(QMouseEvent *_e) override; diff --git a/src/plugins/screenshot/CMakeLists.txt b/src/plugins/screenshot/CMakeLists.txt index 224f72b59..df4d87d88 100644 --- a/src/plugins/screenshot/CMakeLists.txt +++ b/src/plugins/screenshot/CMakeLists.txt @@ -3,4 +3,6 @@ ign_gui_add_plugin(Screenshot Screenshot.cc QT_HEADERS Screenshot.hh + PUBLIC_LINK_LIBS + ignition-rendering${IGN_RENDERING_VER}::ignition-rendering${IGN_RENDERING_VER} ) diff --git a/src/plugins/screenshot/Screenshot.cc b/src/plugins/screenshot/Screenshot.cc index f0b46442c..640eca9e6 100644 --- a/src/plugins/screenshot/Screenshot.cc +++ b/src/plugins/screenshot/Screenshot.cc @@ -23,10 +23,17 @@ #include #include +#include #include +#include +#include +#include +#include #include #include "ignition/gui/Application.hh" +#include "ignition/gui/GuiEvents.hh" +#include "ignition/gui/MainWindow.hh" namespace ignition { @@ -44,6 +51,12 @@ namespace plugins /// \brief Directory to save screenshots public: std::string directory; + + /// \brief Whether a screenshot has been requested but not processed yet. + public: bool dirty{false}; + + /// \brief Pointer to the user camera. + public: ignition::rendering::CameraPtr userCamera{nullptr}; }; } } @@ -58,21 +71,18 @@ Screenshot::Screenshot() : ignition::gui::Plugin(), dataPtr(std::make_unique()) { - // for screenshot requests - this->dataPtr->screenshotService = "/gui/screenshot"; - std::string home; common::env(IGN_HOMEDIR, home); // default directory this->dataPtr->directory = - common::joinPaths(home, ".ignition", "gazebo", "pictures"); + common::joinPaths(home, ".ignition", "gui", "pictures"); if (!common::exists(this->dataPtr->directory)) { - if (!common::createDirectory(this->dataPtr->directory)) + if (!common::createDirectories(this->dataPtr->directory)) { - std::string defaultDir = common::joinPaths(home, ".ignition", "gazebo"); + std::string defaultDir = common::joinPaths(home, ".ignition", "gui"); ignerr << "Unable to create directory [" << this->dataPtr->directory << "]. Changing default directory to: " << defaultDir << std::endl; @@ -94,24 +104,138 @@ void Screenshot::LoadConfig(const tinyxml2::XMLElement *) { if (this->title.empty()) this->title = "Screenshot"; + + // Screenshot service + this->dataPtr->screenshotService = "/gui/screenshot"; + this->dataPtr->node.Advertise(this->dataPtr->screenshotService, + &Screenshot::ScreenshotService, this); + ignmsg << "Screenshot service on [" + << this->dataPtr->screenshotService << "]" << std::endl; + + App()->findChild()->installEventFilter(this); } ///////////////////////////////////////////////// -void Screenshot::OnScreenshot() +bool Screenshot::eventFilter(QObject *_obj, QEvent *_event) { - std::function cb = - [](const ignition::msgs::Boolean &/*_rep*/, const bool _result) + if (_event->type() == events::Render::kType && this->dataPtr->dirty) { - if (!_result) - ignerr << "Error sending move to request" << std::endl; - }; + this->SaveScreenshot(); + } + + // Standard event processing + return QObject::eventFilter(_obj, _event); +} + +///////////////////////////////////////////////// +bool Screenshot::ScreenshotService(const msgs::StringMsg &_msg, + msgs::Boolean &_res) +{ + if (!_msg.data().empty()) + this->dataPtr->directory = _msg.data(); + this->dataPtr->dirty = true; + _res.set_data(true); + return true; +} + +///////////////////////////////////////////////// +void Screenshot::SaveScreenshot() +{ + this->FindUserCamera(); + + if (nullptr == this->dataPtr->userCamera) + return; + + unsigned int width = this->dataPtr->userCamera->ImageWidth(); + unsigned int height = this->dataPtr->userCamera->ImageHeight(); + + auto cameraImage = this->dataPtr->userCamera->CreateImage(); + this->dataPtr->userCamera->Copy(cameraImage); std::string time = common::systemTimeISO() + ".png"; - std::string savedPath = common::joinPaths(this->dataPtr->directory, time); + std::string savePath = common::joinPaths(this->dataPtr->directory, time); + + common::Image image; + image.SetFromData(cameraImage.Data(), width, height, + common::Image::RGB_INT8); + image.SavePNG(savePath); + + igndbg << "Saved image to [" << savePath << "]" << std::endl; + + this->dataPtr->dirty = false; +} + +///////////////////////////////////////////////// +void Screenshot::FindUserCamera() +{ + if (nullptr != this->dataPtr->userCamera) + return; - ignition::msgs::StringMsg req; - req.set_data(savedPath); - this->dataPtr->node.Request(this->dataPtr->screenshotService, req, cb); + auto loadedEngNames = ignition::rendering::loadedEngines(); + if (loadedEngNames.empty()) + { + igndbg << "No rendering engine is loaded yet" << std::endl; + return; + } + + // assume there is only one engine loaded + auto engineName = loadedEngNames[0]; + if (loadedEngNames.size() > 1) + { + igndbg << "More than one engine is available. " + << "Using engine [" << engineName << "]" << std::endl; + } + auto engine = ignition::rendering::engine(engineName); + if (!engine) + { + ignerr << "Internal error: failed to load engine [" << engineName + << "]. Grid plugin won't work." << std::endl; + return; + } + + if (engine->SceneCount() == 0) + { + igndbg << "No scene has been created yet" << std::endl; + return; + } + + // Get first scene + auto scene = engine->SceneByIndex(0); + if (nullptr == scene) + { + ignerr << "Internal error: scene is null." << std::endl; + return; + } + + if (engine->SceneCount() > 1) + { + igndbg << "More than one scene is available. " + << "Using scene [" << scene->Name() << "]" << std::endl; + } + + if (!scene->IsInitialized() || nullptr == scene->RootVisual()) + { + return; + } + + for (unsigned int i = 0; i < scene->NodeCount(); ++i) + { + auto cam = std::dynamic_pointer_cast( + scene->NodeByIndex(i)); + if (nullptr != cam) + { + this->dataPtr->userCamera = cam; + igndbg << "Screnshot plugin taking pictures of camera [" + << this->dataPtr->userCamera->Name() << "]" << std::endl; + break; + } + } +} + +///////////////////////////////////////////////// +void Screenshot::OnScreenshot() +{ + this->dataPtr->dirty = true; } ///////////////////////////////////////////////// diff --git a/src/plugins/screenshot/Screenshot.hh b/src/plugins/screenshot/Screenshot.hh index 507368b7a..6864c45b1 100644 --- a/src/plugins/screenshot/Screenshot.hh +++ b/src/plugins/screenshot/Screenshot.hh @@ -17,6 +17,9 @@ #ifndef IGNITION_GUI_PLUGINS_SCREENSHOT_HH_ #define IGNITION_GUI_PLUGINS_SCREENSHOT_HH_ +#include +#include + #include #include "ignition/gui/qt.h" @@ -30,7 +33,12 @@ namespace plugins { class ScreenshotPrivate; - /// \brief Provides a button for taking a screenshot of current 3D scene + /// \brief Provides a button and a transport service for taking a screenshot + /// of current 3D scene. + /// + /// /gui/screenshot service: + /// Data: Path to save to, leave empty to save to latest path. + /// Response: True if screenshot has been queued succesfully. class Screenshot : public Plugin { Q_OBJECT @@ -44,12 +52,31 @@ namespace plugins // Documentation inherited public: void LoadConfig(const tinyxml2::XMLElement *_pluginElem) override; - /// \brief Callback when screenshot is requested + /// \brief Callback when screenshot is requested from the GUI. public slots: void OnScreenshot(); /// \brief Callback for changing the directory where screenshots are saved public slots: void OnChangeDirectory(const QString &_dirUrl); + /// \brief Callback for all installed event filders. + /// \param[in] _obj Object that received the event + /// \param[in] _event Event + private: bool eventFilter(QObject *_obj, QEvent *_event) override; + + /// \brief Callback for saving a screenshot (from the user camera) request + /// \param[in] _msg Request message of the saved file path + /// \param[in] _res Response data + /// \return True if the request is received + private: bool ScreenshotService(const msgs::StringMsg &_msg, + msgs::Boolean &_res); + + /// \brief Encapsulates the logic to find the user camera through the + /// render engine singleton. + private: void FindUserCamera(); + + /// \brief Save a screenshot from the user camera + private: void SaveScreenshot(); + /// \internal /// \brief Pointer to private data. private: std::unique_ptr dataPtr;