Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[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 14 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
36 changes: 33 additions & 3 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 @@ -402,9 +405,36 @@ 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";
std::string downloadedDir = common::FuelModelDatabase::Instance()->ModelPath(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(common::find_file(filename), sdf))
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
{
gzerr << "Unable to read sdf file[" << filename << "]\n";
return false;
}

Expand Down
10 changes: 9 additions & 1 deletion 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,7 +162,12 @@ 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;
}

/////////////////////////////////////////////////
chapulina marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
80 changes: 64 additions & 16 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,77 @@ 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();
}
ahcorde marked this conversation as resolved.
Show resolved Hide resolved

std::string path;

if (!_forceDownload)
std::string fileUrl;
ignition::fuel_tools::ModelIdentifier model;
ignition::fuel_tools::WorldIdentifier world;
using namespace ignition::fuel_tools;

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
{
return path;
gzmsg << "Downloaded model URL: " << std::endl
<< " " << _uri << std::endl
<< " to: " << std::endl
<< " " << path << std::endl;
}
}

if (!this->dataPtr->fuelClient->DownloadModel(fuelUri, path))
{
gzerr << "Unable to download model[" << _uri << "]" << std::endl;
return std::string();
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)
{
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;
}
}
}
else
{
gzmsg << "Downloaded model URL: " << std::endl
<< " " << _uri << std::endl
<< " to: " << std::endl
<< " " << path << std::endl;

// ig path is still empty then try to download a world
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
if (path.empty()) {
if (this->dataPtr->fuelClient->ParseWorldUrl(fuelUri, world) &&
!this->dataPtr->fuelClient->CachedWorld(fuelUri, path))
{
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))
{
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
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
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
39 changes: 39 additions & 0 deletions worlds/fuel_models.world
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" ?>
<sdf version="1.5">
<world name="default">
<!-- A global light source -->
<include>
<uri>model://sun</uri>
</include>
<!-- A ground plane -->
<include>
<uri>model://ground_plane</uri>
</include>

<include>
<pose>0 1 3 0.0 0.0 1.57</pose>
<uri>https://fuel.ignitionrobotics.org/1.0/openrobotics/models/Construction Cone</uri>
</include>

<model name="Radio">
<pose>3 -1.5 0 0 0 0</pose>
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<mesh>
<uri>https://fuel.ignitionrobotics.org/1.0/openrobotics/models/Radio/4/files/meshes/Radio.dae</uri>
</mesh>
</geometry>
</collision>
<visual name="visual">
<geometry>
<mesh>
<uri>https://fuel.ignitionrobotics.org/1.0/openrobotics/models/Radio/4/files/meshes/Radio.dae</uri>
</mesh>
</geometry>
</visual>
</link>
</model>
</world>
</sdf>