Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

[Gazebo 11] Allow gazebo to download models from Fuel in the sdf files #2822

Merged
merged 20 commits into from
Sep 22, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 50 additions & 13 deletions gazebo/Server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
#include <sdf/sdf.hh>

#include <ignition/math/Rand.hh>
#include "ignition/common/Profiler.hh"
#include <ignition/common/Profiler.hh>
#include <ignition/common/Filesystem.hh>
#include <ignition/common/URI.hh>
#include <ignition/fuel_tools/Interface.hh>

#include "gazebo/gazebo.hh"
#include "gazebo/transport/transport.hh"
Expand Down Expand Up @@ -385,15 +388,6 @@ bool Server::GetInitialized() const
bool Server::LoadFile(const std::string &_filename,
const std::string &_physics)
{
// Quick test for a valid file
FILE *test = fopen(common::find_file(_filename).c_str(), "r");
if (!test)
{
gzerr << "Could not open file[" << _filename << "]\n";
return false;
}
fclose(test);

// Load the world file
sdf::SDFPtr sdf(new sdf::SDF);
if (!sdf::init(sdf))
Expand All @@ -402,10 +396,53 @@ bool Server::LoadFile(const std::string &_filename,
return false;
}

if (!sdf::readFile(common::find_file(_filename), sdf))
std::string filename = _filename;
auto filenameUri = ignition::common::URI(filename);

if (filenameUri.Scheme() == "http" || filenameUri.Scheme() == "https")
{
gzerr << "Unable to read sdf file[" << _filename << "]\n";
return false;
std::string downloadedDir = common::FuelModelDatabase::Instance()->WorldPath(filename);
// Find the first sdf file in the world path for now, the later intention is
// to load an optional world config file first and if that does not exist,
// continue to load the first sdf file found as done below
for (ignition::common::DirIter file(downloadedDir);
file != ignition::common::DirIter(); ++file)
{
std::string current(*file);
if (ignition::common::isFile(current))
{
std::string fileName = ignition::common::basename(current);
std::string::size_type fileExtensionIndex = fileName.rfind(".");
std::string fileExtension = fileName.substr(fileExtensionIndex + 1);

if (fileExtension == "sdf" || fileExtension == "world")
{
filename = current.c_str();
if (!sdf::readFile(filename, sdf))
{
gzerr << "Unable to read SDF from URL[" << filename << "]\n";
return false;
}
}
}
}
}
else
{
// Quick test for a valid file
FILE *test = fopen(common::find_file(filename).c_str(), "r");
if (!test)
{
gzerr << "Could not open file[" << filename << "]\n";
return false;
}
fclose(test);

if (!sdf::readFile(common::find_file(filename), sdf))
{
gzerr << "Unable to read sdf file[" << filename << "]\n";
return false;
}
}

return this->LoadImpl(sdf->Root(), _physics);
Expand Down
17 changes: 15 additions & 2 deletions gazebo/common/CommonIface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@
#include "gazebo/common/Console.hh"
#include "gazebo/common/CommonIface.hh"
#include "gazebo/common/Exception.hh"
#include "gazebo/common/FuelModelDatabase.hh"
#include "gazebo/common/SystemPaths.hh"

#include "ignition/fuel_tools/Interface.hh"

using namespace gazebo;

#ifdef _WIN32
Expand Down Expand Up @@ -159,13 +162,23 @@ void common::add_search_path_suffix(const std::string &_suffix)
/////////////////////////////////////////////////
std::string common::find_file(const std::string &_file)
{
return common::SystemPaths::Instance()->FindFile(_file, true);
std::string path = common::FuelModelDatabase::Instance()->ModelPath(_file);
if (path.empty())
{
path = common::SystemPaths::Instance()->FindFile(_file, true);
}
return path;
}

/////////////////////////////////////////////////
std::string common::find_file(const std::string &_file, bool _searchLocalPath)
{
return common::SystemPaths::Instance()->FindFile(_file, _searchLocalPath);
std::string path = common::FuelModelDatabase::Instance()->ModelPath(_file);
if (path.empty())
{
path = common::SystemPaths::Instance()->FindFile(_file, _searchLocalPath);
}
return path;
}

/////////////////////////////////////////////////
Expand Down
83 changes: 72 additions & 11 deletions gazebo/common/FuelModelDatabase.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@

#include <ignition/common/Console.hh>
#include <ignition/common/Filesystem.hh>
#include <ignition/common/URI.hh>
#include <ignition/fuel_tools/FuelClient.hh>
#include <ignition/fuel_tools/WorldIdentifier.hh>
#include <sdf/sdf.hh>

#include "gazebo/gazebo_config.h"
Expand Down Expand Up @@ -208,31 +210,90 @@ std::string FuelModelDatabase::ModelPath(const std::string &_uri,

if (fuelUri.Scheme() != "http" && fuelUri.Scheme() != "https")
{
gzwarn << "URI not supported by Fuel [" << _uri << "]" << std::endl;
return std::string();
}

std::string path;
std::string fileUrl;
ignition::fuel_tools::ModelIdentifier model;
using namespace ignition::fuel_tools;

if (!_forceDownload)
if ((this->dataPtr->fuelClient->ParseModelUrl(fuelUri, model) &&
!this->dataPtr->fuelClient->CachedModel(fuelUri, path))
|| _forceDownload)
{
if (this->dataPtr->fuelClient->CachedModel(fuelUri, path))
Result result_download =
this->dataPtr->fuelClient->DownloadModel(fuelUri, path);
if (result_download != Result(ResultType::FETCH) ||
result_download != Result(ResultType::FETCH_ALREADY_EXISTS))
{
gzerr << "Unable to download model[" << _uri << "]" << std::endl;
return std::string();
}
else
{
gzmsg << "Downloaded model URL: " << std::endl
<< " " << _uri << std::endl
<< " to: " << std::endl
<< " " << path << std::endl;
}
}
if (path.empty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (path.empty()) {
if (path.empty())
{

if ((this->dataPtr->fuelClient->ParseModelFileUrl(fuelUri, model, fileUrl) &&
!this->dataPtr->fuelClient->CachedModelFile(fuelUri, path))
|| _forceDownload)
{
return path;
auto modelUri = _uri.substr(0,
_uri.find("files", model.UniqueName().size())-1);
Result result_download =
this->dataPtr->fuelClient->DownloadModel(ignition::common::URI(modelUri), path);
if (result_download != Result(ResultType::FETCH) ||
result_download != Result(ResultType::FETCH_ALREADY_EXISTS))
{
gzerr << "Unable to download model[" << _uri << "]" << std::endl;
return std::string();
}
else
{
gzmsg << "Downloaded model URL: " << std::endl
<< " " << _uri << std::endl
<< " to: " << std::endl
<< " " << path << std::endl;
path += "/" + fileUrl;
}
}
}

if (!this->dataPtr->fuelClient->DownloadModel(fuelUri, path))
return path;
}

/////////////////////////////////////////////////
std::string FuelModelDatabase::WorldPath(const std::string &_uri, const bool _forceDownload)
{
auto fuelUri = ignition::common::URI(_uri);

if (fuelUri.Scheme() != "http" && fuelUri.Scheme() != "https")
{
gzerr << "Unable to download model[" << _uri << "]" << std::endl;
return std::string();
}
else

std::string path;
std::string fileUrl;
ignition::fuel_tools::WorldIdentifier world;

if ((this->dataPtr->fuelClient->ParseWorldUrl(fuelUri, world) &&
!this->dataPtr->fuelClient->CachedWorld(fuelUri, path)) || _forceDownload)
{
this->dataPtr->fuelClient->DownloadWorld(fuelUri, path);
}
// Download the world, if it's a world file URI
else if (this->dataPtr->fuelClient->ParseWorldFileUrl(fuelUri, world, fileUrl) &&
!this->dataPtr->fuelClient->CachedWorldFile(fuelUri, path))
{
gzmsg << "Downloaded model URL: " << std::endl
<< " " << _uri << std::endl
<< " to: " << std::endl
<< " " << path << std::endl;
auto worldUri = _uri.substr(0,
_uri.find("files", world.UniqueName().size())-1);
this->dataPtr->fuelClient->DownloadWorld(ignition::common::URI(worldUri), path);
path += "/" + fileUrl;
}

return path;
Expand Down
10 changes: 10 additions & 0 deletions gazebo/common/FuelModelDatabase.hh
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ namespace gazebo
public: std::string ModelPath(const std::string &_uri,
const bool _forceDownload = false);

/// \brief Get the local path to a world.
///
/// Get the path to a world based on a URI. If the world is on
/// a remote server, then the model fetched and installed locally.
/// \param[in] _uri the model uri
/// \param[in] _forceDownload True to skip searching local cache.
/// \return Local path to a world directory
public: std::string WorldPath(const std::string &_uri,
const bool _forceDownload = false);

/// \brief Get the full local path to a cached file based on its URI.
/// \param[in] _uri The file's URI
/// \return Local path to the file
Expand Down
21 changes: 0 additions & 21 deletions gazebo/gui/InsertModelWidget.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,6 @@ using namespace gui;

static bool gInsertModelWidgetDeleted = false;

/////////////////////////////////////////////////
// TODO: Remove this once Fuel support is fully functional
bool usingFuel()
{
auto useFuel = std::getenv("USE_IGNITION_FUEL");
if (!useFuel || *useFuel == '\0')
return false;

std::string useFuelStr(useFuel);
std::transform(useFuelStr.begin(), useFuelStr.end(),
useFuelStr.begin(), ::tolower);

return useFuelStr != "false" && useFuelStr != "0";
}

/////////////////////////////////////////////////
InsertModelWidget::InsertModelWidget(QWidget *_parent)
: QWidget(_parent), dataPtr(new InsertModelWidgetPrivate)
Expand Down Expand Up @@ -523,9 +508,6 @@ bool InsertModelWidget::IsPathAccessible(const boost::filesystem::path &_path)
/////////////////////////////////////////////////
void InsertModelWidget::InitializeFuelServers()
{
if (!usingFuel())
return;

// Get the list of Ignition Fuel servers.
auto servers = common::FuelModelDatabase::Instance()->Servers();

Expand All @@ -550,9 +532,6 @@ void InsertModelWidget::InitializeFuelServers()
/////////////////////////////////////////////////
void InsertModelWidget::PopulateFuelServers()
{
if (!usingFuel())
return;

// Get the list of Ignition Fuel servers.
auto servers = common::FuelModelDatabase::Instance()->Servers();

Expand Down
2 changes: 1 addition & 1 deletion gazebo/rendering/RenderEngine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ void RenderEngine::LoadPlugins()
/////////////////////////////////////////////////
void RenderEngine::AddResourcePath(const std::string &_uri)
{
if (_uri == "__default__" || _uri.empty())
if (_uri.find("__default__") != std::string::npos || _uri.empty())
return;

std::string path = common::find_file_path(_uri);
Expand Down
69 changes: 66 additions & 3 deletions test/integration/factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1222,7 +1222,6 @@ TEST_F(FactoryTest, FilenameModelDatabaseRelativePaths)
}

//////////////////////////////////////////////////
#ifdef HAVE_IGNITION_FUEL_TOOLS
TEST_F(FactoryTest, FilenameFuelURL)
{
this->Load("worlds/empty.world", true);
Expand All @@ -1233,7 +1232,7 @@ TEST_F(FactoryTest, FilenameFuelURL)

msgs::Factory msg;
msg.set_sdf_filename(
"https://api.ignitionfuel.org/1.0/chapulina/models/Test box");
"https://fuel.ignitionrobotics.org/1.0/chapulina/models/Test box");

auto pub = this->node->Advertise<msgs::Factory>("~/factory");
pub->Publish(msg);
Expand All @@ -1249,7 +1248,71 @@ TEST_F(FactoryTest, FilenameFuelURL)
// Check model was spawned
ASSERT_NE(nullptr, world->ModelByName("test_box"));
}
#endif

TEST_F(FactoryTest, WorldWithFuelModels)
{
this->Load("worlds/fuel_models.world", true);

// World
auto world = physics::get_world("default");
ASSERT_NE(nullptr, world);

// Wait for it to be spawned
int sleep = 0;
int maxSleep = 50;
while (!world->ModelByName("Radio") && sleep++ < maxSleep)
{
common::Time::MSleep(100);
}

// Check model was spawned
ASSERT_NE(nullptr, world->ModelByName("Radio"));
}

//////////////////////////////////////////////////
TEST_F(FactoryTest, FuelURIAsWorldArgument)
{
// ServerFixture::LoadArgs will split whitespaces, so we use a world name
// without spaces
this->Load(
"https://fuel.ignitionrobotics.org/1.0/OpenRobotics/worlds/Test_shapes",
true);

// World
auto world = physics::get_world("default");
ASSERT_NE(nullptr, world);

// Wait for it to be spawned
int sleep = 0;
int maxSleep = 50;
while (!world->ModelByName("box") && sleep++ < maxSleep)
{
common::Time::MSleep(100);
}

// Check model was spawned
ASSERT_NE(nullptr, world->ModelByName("box"));

sleep = 0;
maxSleep = 50;
while (!world->ModelByName("cylinder") && sleep++ < maxSleep)
{
common::Time::MSleep(100);
}

// Check model was spawned
ASSERT_NE(nullptr, world->ModelByName("cylinder"));

sleep = 0;
maxSleep = 50;
while (!world->ModelByName("sphere") && sleep++ < maxSleep)
{
common::Time::MSleep(100);
}

// Check model was spawned
ASSERT_NE(nullptr, world->ModelByName("sphere"));
}

//////////////////////////////////////////////////
TEST_P(FactoryTest, InvalidMeshInsertion)
Expand Down
Loading