Skip to content

Commit 39a0ce3

Browse files
nkoenigNate KoenigMichael Carrollahcorde
authored
ServerConfig accepts an sdf::Root DOM object (#1333)
Signed-off-by: Nate Koenig <nate@openrobotics.org> Co-authored-by: Nate Koenig <nate@openrobotics.org> Co-authored-by: Michael Carroll <michael@openrobotics.org> Co-authored-by: Alejandro Hernández Cordero <ahcorde@gmail.com>
1 parent 32bc612 commit 39a0ce3

File tree

8 files changed

+318
-83
lines changed

8 files changed

+318
-83
lines changed

include/ignition/gazebo/ServerConfig.hh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <string>
2525
#include <vector>
2626
#include <sdf/Element.hh>
27+
#include <sdf/Root.hh>
2728
#include <ignition/gazebo/config.hh>
2829
#include <ignition/gazebo/Export.hh>
2930

@@ -42,6 +43,23 @@ namespace ignition
4243
/// configuration.
4344
class IGNITION_GAZEBO_VISIBLE ServerConfig
4445
{
46+
/// \brief Type of SDF source.
47+
public: enum class SourceType
48+
{
49+
// No source specified.
50+
kNone,
51+
52+
// The source is an SDF Root object.
53+
kSdfRoot,
54+
55+
// The source is an SDF file.
56+
kSdfFile,
57+
58+
// The source is an SDF string.
59+
kSdfString,
60+
};
61+
62+
4563
class PluginInfoPrivate;
4664
/// \brief Information about a plugin that should be loaded by the
4765
/// server.
@@ -175,6 +193,17 @@ namespace ignition
175193
/// \return The full contents of the SDF string, or empty string.
176194
public: std::string SdfString() const;
177195

196+
/// \brief Set the SDF Root DOM object. The sdf::Root object will take
197+
/// precendence over ServerConfig::SdfString() and
198+
/// ServerConfig::SdfFile().
199+
/// \param[in] _root SDF Root object to use.
200+
public: void SetSdfRoot(const sdf::Root &_root) const;
201+
202+
/// \brief Get the SDF Root DOM object.
203+
/// \return SDF Root object to use, or std::nullopt if the sdf::Root
204+
/// has not been set via ServerConfig::SetSdfRoot().
205+
public: std::optional<sdf::Root> &SdfRoot() const;
206+
178207
/// \brief Set the update rate in Hertz. Value <=0 are ignored.
179208
/// \param[in] _hz The desired update rate of the server in Hertz.
180209
public: void SetUpdateRate(const double &_hz);
@@ -383,6 +412,10 @@ namespace ignition
383412
public: const std::chrono::time_point<std::chrono::system_clock> &
384413
Timestamp() const;
385414

415+
/// \brief Get the type of source
416+
/// \return The source type.
417+
public: SourceType Source() const;
418+
386419
/// \brief Private data pointer
387420
private: std::unique_ptr<ServerConfigPrivate> dataPtr;
388421
};

include/ignition/gazebo/Util.hh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,25 @@ namespace ignition
210210
const EntityComponentManager &_ecm,
211211
bool _excludeWorld = true);
212212

213+
/// \brief Convert an SDF world filename string, such as "shapes.sdf", to
214+
/// full system file path.
215+
/// The provided SDF filename may be a Fuel URI, relative path, name
216+
/// of an installed Gazebo world filename, or an absolute path.
217+
/// \param[in] _sdfFile An SDF world filename such as:
218+
/// 1. "shapes.sdf" - This is referencing an installed world file.
219+
/// 2. "../shapes.sdf" - This is referencing a relative world file.
220+
/// 3. "/home/user/shapes.sdf" - This is reference an absolute world
221+
/// file.
222+
/// 4. "https://fuel.ignitionrobotics.org/1.0/openrobotics/worlds/shapes.sdf"
223+
/// This is referencing a Fuel URI. This will download the world file.
224+
/// \param[in] _fuelResourceCache Path to a Fuel resource cache, if
225+
/// known.
226+
/// \return Full path to the SDF world file. An empty string is returned
227+
/// if the file could not be found.
228+
std::string IGNITION_GAZEBO_VISIBLE resolveSdfWorldFile(
229+
const std::string &_sdfFilename,
230+
const std::string &_fuelResourceCache = "");
231+
213232
/// \brief Helper function to "enable" a component (i.e. create it if it
214233
/// doesn't exist) or "disable" a component (i.e. remove it if it exists).
215234
/// \param[in] _ecm Mutable reference to the ECM

src/Server.cc

Lines changed: 38 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,6 @@
3333
using namespace ignition;
3434
using namespace gazebo;
3535

36-
//////////////////////////////////////////////////
37-
// Getting the first .sdf file in the path
38-
std::string findFuelResourceSdf(const std::string &_path)
39-
{
40-
if (!common::exists(_path))
41-
return "";
42-
43-
for (common::DirIter file(_path); file != common::DirIter(); ++file)
44-
{
45-
std::string current(*file);
46-
if (!common::isFile(current))
47-
continue;
48-
49-
auto fileName = common::basename(current);
50-
auto fileExtensionIndex = fileName.rfind(".");
51-
auto fileExtension = fileName.substr(fileExtensionIndex + 1);
52-
53-
if (fileExtension == "sdf")
54-
{
55-
return current;
56-
}
57-
}
58-
return "";
59-
}
60-
6136
/// \brief This struct provides access to the default world.
6237
struct DefaultWorld
6338
{
@@ -98,83 +73,64 @@ Server::Server(const ServerConfig &_config)
9873

9974
sdf::Errors errors;
10075

101-
// Load a world if specified. Check SDF string first, then SDF file
102-
if (!_config.SdfString().empty())
76+
switch (_config.Source())
10377
{
104-
std::string msg = "Loading SDF string. ";
105-
if (_config.SdfFile().empty())
78+
// Load a world if specified. Check SDF string first, then SDF file
79+
case ServerConfig::SourceType::kSdfRoot:
10680
{
107-
msg += "File path not available.\n";
81+
this->dataPtr->sdfRoot = _config.SdfRoot()->Clone();
82+
ignmsg << "Loading SDF world from SDF DOM.\n";
83+
break;
10884
}
109-
else
110-
{
111-
msg += "File path [" + _config.SdfFile() + "].\n";
112-
}
113-
ignmsg << msg;
114-
errors = this->dataPtr->sdfRoot.LoadSdfString(_config.SdfString());
115-
}
116-
else if (!_config.SdfFile().empty())
117-
{
118-
std::string filePath;
11985

120-
// Check Fuel if it's a URL
121-
auto sdfUri = common::URI(_config.SdfFile());
122-
if (sdfUri.Scheme() == "http" || sdfUri.Scheme() == "https")
86+
case ServerConfig::SourceType::kSdfString:
12387
{
124-
std::string fuelCachePath;
125-
if (this->dataPtr->fuelClient->CachedWorld(common::URI(_config.SdfFile()),
126-
fuelCachePath))
88+
std::string msg = "Loading SDF string. ";
89+
if (_config.SdfFile().empty())
12790
{
128-
filePath = findFuelResourceSdf(fuelCachePath);
129-
}
130-
else if (auto result = this->dataPtr->fuelClient->DownloadWorld(
131-
common::URI(_config.SdfFile()), fuelCachePath))
132-
{
133-
filePath = findFuelResourceSdf(fuelCachePath);
91+
msg += "File path not available.\n";
13492
}
13593
else
13694
{
137-
ignwarn << "Fuel couldn't download URL [" << _config.SdfFile()
138-
<< "], error: [" << result.ReadableResult() << "]"
139-
<< std::endl;
95+
msg += "File path [" + _config.SdfFile() + "].\n";
14096
}
97+
ignmsg << msg;
98+
errors = this->dataPtr->sdfRoot.LoadSdfString(_config.SdfString());
99+
break;
141100
}
142101

143-
if (filePath.empty())
102+
case ServerConfig::SourceType::kSdfFile:
144103
{
145-
common::SystemPaths systemPaths;
104+
std::string filePath = resolveSdfWorldFile(_config.SdfFile(),
105+
_config.ResourceCache());
146106

147-
// Worlds from environment variable
148-
systemPaths.SetFilePathEnv(kResourcePathEnv);
107+
if (filePath.empty())
108+
{
109+
ignerr << "Failed to find world [" << _config.SdfFile() << "]"
110+
<< std::endl;
111+
return;
112+
}
149113

150-
// Worlds installed with ign-gazebo
151-
systemPaths.AddFilePaths(IGN_GAZEBO_WORLD_INSTALL_DIR);
114+
ignmsg << "Loading SDF world file[" << filePath << "].\n";
152115

153-
filePath = systemPaths.FindFile(_config.SdfFile());
116+
// \todo(nkoenig) Async resource download.
117+
// This call can block for a long period of time while
118+
// resources are downloaded. Blocking here causes the GUI to block with
119+
// a black screen (search for "Async resource download" in
120+
// 'src/gui_main.cc'.
121+
errors = this->dataPtr->sdfRoot.Load(filePath);
122+
break;
154123
}
155124

156-
if (filePath.empty())
125+
case ServerConfig::SourceType::kNone:
126+
default:
157127
{
158-
ignerr << "Failed to find world [" << _config.SdfFile() << "]"
159-
<< std::endl;
160-
return;
128+
ignmsg << "Loading default world.\n";
129+
// Load an empty world.
130+
/// \todo(nkoenig) Add a "AddWorld" function to sdf::Root.
131+
errors = this->dataPtr->sdfRoot.LoadSdfString(DefaultWorld::World());
132+
break;
161133
}
162-
163-
ignmsg << "Loading SDF world file[" << filePath << "].\n";
164-
165-
// \todo(nkoenig) Async resource download.
166-
// This call can block for a long period of time while
167-
// resources are downloaded. Blocking here causes the GUI to block with
168-
// a black screen (search for "Async resource download" in
169-
// 'src/gui_main.cc'.
170-
errors = this->dataPtr->sdfRoot.Load(filePath);
171-
}
172-
else
173-
{
174-
ignmsg << "Loading default world.\n";
175-
// Load an empty world.
176-
/// \todo(nkoenig) Add a "AddWorld" function to sdf::Root.
177-
errors = this->dataPtr->sdfRoot.LoadSdfString(DefaultWorld::World());
178134
}
179135

180136
if (!errors.empty())

src/ServerConfig.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,12 @@ class ignition::gazebo::ServerConfigPrivate
301301

302302
/// \brief is the headless mode active.
303303
public: bool isHeadlessRendering{false};
304+
305+
/// \brief Optional SDF root object.
306+
public: std::optional<sdf::Root> sdfRoot;
307+
308+
/// \brief Type of source used.
309+
public: ServerConfig::SourceType source{ServerConfig::SourceType::kNone};
304310
};
305311

306312
//////////////////////////////////////////////////
@@ -321,8 +327,10 @@ ServerConfig::~ServerConfig() = default;
321327
//////////////////////////////////////////////////
322328
bool ServerConfig::SetSdfFile(const std::string &_file)
323329
{
330+
this->dataPtr->source = ServerConfig::SourceType::kSdfFile;
324331
this->dataPtr->sdfFile = _file;
325332
this->dataPtr->sdfString = "";
333+
this->dataPtr->sdfRoot = std::nullopt;
326334
return true;
327335
}
328336

@@ -335,8 +343,10 @@ std::string ServerConfig::SdfFile() const
335343
//////////////////////////////////////////////////
336344
bool ServerConfig::SetSdfString(const std::string &_sdfString)
337345
{
346+
this->dataPtr->source = ServerConfig::SourceType::kSdfString;
338347
this->dataPtr->sdfFile = "";
339348
this->dataPtr->sdfString = _sdfString;
349+
this->dataPtr->sdfRoot = std::nullopt;
340350
return true;
341351
}
342352

@@ -697,6 +707,35 @@ const std::vector<std::string> &ServerConfig::LogRecordTopics() const
697707
return this->dataPtr->logRecordTopics;
698708
}
699709

710+
/////////////////////////////////////////////////
711+
void ServerConfig::SetSdfRoot(const sdf::Root &_root) const
712+
{
713+
this->dataPtr->source = ServerConfig::SourceType::kSdfRoot;
714+
this->dataPtr->sdfRoot.emplace();
715+
716+
for (uint64_t i = 0; i < _root.WorldCount(); ++i)
717+
{
718+
const sdf::World *world = _root.WorldByIndex(i);
719+
if (world)
720+
this->dataPtr->sdfRoot->AddWorld(*world);
721+
}
722+
723+
this->dataPtr->sdfFile = "";
724+
this->dataPtr->sdfString = "";
725+
}
726+
727+
/////////////////////////////////////////////////
728+
std::optional<sdf::Root> &ServerConfig::SdfRoot() const
729+
{
730+
return this->dataPtr->sdfRoot;
731+
}
732+
733+
/////////////////////////////////////////////////
734+
ServerConfig::SourceType ServerConfig::Source() const
735+
{
736+
return this->dataPtr->source;
737+
}
738+
700739
/////////////////////////////////////////////////
701740
void copyElement(sdf::ElementPtr _sdf, const tinyxml2::XMLElement *_xml)
702741
{

src/ServerConfig_TEST.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,31 @@ TEST(ServerConfig, GenerateRecordPlugin)
228228
EXPECT_EQ(plugin.Name(), "ignition::gazebo::systems::LogRecord");
229229
}
230230

231+
//////////////////////////////////////////////////
232+
TEST(ServerConfig, SdfRoot)
233+
{
234+
ServerConfig config;
235+
EXPECT_FALSE(config.SdfRoot());
236+
EXPECT_TRUE(config.SdfFile().empty());
237+
EXPECT_TRUE(config.SdfString().empty());
238+
EXPECT_EQ(ServerConfig::SourceType::kNone, config.Source());
239+
240+
config.SetSdfString("string");
241+
EXPECT_FALSE(config.SdfRoot());
242+
EXPECT_TRUE(config.SdfFile().empty());
243+
EXPECT_FALSE(config.SdfString().empty());
244+
EXPECT_EQ(ServerConfig::SourceType::kSdfString, config.Source());
245+
246+
config.SetSdfFile("file");
247+
EXPECT_FALSE(config.SdfRoot());
248+
EXPECT_FALSE(config.SdfFile().empty());
249+
EXPECT_TRUE(config.SdfString().empty());
250+
EXPECT_EQ(ServerConfig::SourceType::kSdfFile, config.Source());
251+
252+
sdf::Root root;
253+
config.SetSdfRoot(root);
254+
EXPECT_TRUE(config.SdfRoot());
255+
EXPECT_TRUE(config.SdfFile().empty());
256+
EXPECT_TRUE(config.SdfString().empty());
257+
EXPECT_EQ(ServerConfig::SourceType::kSdfRoot, config.Source());
258+
}

src/Server_TEST.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,49 @@ TEST_P(ServerFixture, IGN_UTILS_TEST_DISABLED_ON_WIN32(SdfServerConfig))
295295
EXPECT_FALSE(server.HasEntity("bad", 1));
296296
}
297297

298+
/////////////////////////////////////////////////
299+
TEST_P(ServerFixture, IGN_UTILS_TEST_DISABLED_ON_WIN32(SdfRootServerConfig))
300+
{
301+
ignition::gazebo::ServerConfig serverConfig;
302+
303+
serverConfig.SetSdfString(TestWorldSansPhysics::World());
304+
EXPECT_TRUE(serverConfig.SdfFile().empty());
305+
EXPECT_FALSE(serverConfig.SdfString().empty());
306+
307+
serverConfig.SetSdfFile(common::joinPaths(PROJECT_SOURCE_PATH,
308+
"test", "worlds", "air_pressure.sdf"));
309+
EXPECT_FALSE(serverConfig.SdfFile().empty());
310+
EXPECT_TRUE(serverConfig.SdfString().empty());
311+
312+
sdf::Root root;
313+
root.Load(common::joinPaths(PROJECT_SOURCE_PATH,
314+
"test", "worlds", "shapes.sdf"));
315+
316+
// Setting the SDF Root should override the string and file.
317+
serverConfig.SetSdfRoot(root);
318+
319+
EXPECT_TRUE(serverConfig.SdfRoot());
320+
EXPECT_TRUE(serverConfig.SdfFile().empty());
321+
EXPECT_TRUE(serverConfig.SdfString().empty());
322+
323+
gazebo::Server server(serverConfig);
324+
EXPECT_FALSE(server.Running());
325+
EXPECT_FALSE(*server.Running(0));
326+
EXPECT_TRUE(*server.Paused());
327+
EXPECT_EQ(0u, *server.IterationCount());
328+
EXPECT_EQ(24u, *server.EntityCount());
329+
EXPECT_EQ(3u, *server.SystemCount());
330+
331+
EXPECT_TRUE(server.HasEntity("box"));
332+
EXPECT_FALSE(server.HasEntity("box", 1));
333+
EXPECT_TRUE(server.HasEntity("sphere"));
334+
EXPECT_TRUE(server.HasEntity("cylinder"));
335+
EXPECT_TRUE(server.HasEntity("capsule"));
336+
EXPECT_TRUE(server.HasEntity("ellipsoid"));
337+
EXPECT_FALSE(server.HasEntity("bad", 0));
338+
EXPECT_FALSE(server.HasEntity("bad", 1));
339+
}
340+
298341
/////////////////////////////////////////////////
299342
TEST_P(ServerFixture, IGN_UTILS_TEST_DISABLED_ON_WIN32(ServerConfigLogRecord))
300343
{

0 commit comments

Comments
 (0)