From 284f866f7b6e9644b04e93c1f8212ce0c45e4435 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Thu, 28 May 2026 19:26:00 +0800
Subject: [PATCH 01/12] Add ns support to model & world
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/Model.hh | 9 ++++++++
include/sdf/World.hh | 9 ++++++++
sdf/1.12/model.sdf | 9 ++++++++
sdf/1.12/world.sdf | 9 ++++++++
src/Model.cc | 52 ++++++++++++++++++++++++++++++++++++++++++++
src/World.cc | 51 +++++++++++++++++++++++++++++++++++++++++++
6 files changed, 139 insertions(+)
diff --git a/include/sdf/Model.hh b/include/sdf/Model.hh
index 25687ec91..6833e2926 100644
--- a/include/sdf/Model.hh
+++ b/include/sdf/Model.hh
@@ -91,6 +91,15 @@ namespace sdf
/// \param[in] _name Name of the model.
public: void SetName(const std::string &_name);
+ /// \brief Get the namespace associated with the model.
+ /// \return Namespace of the model.
+ public: std::string Namespace() const;
+
+ /// \brief Set the namespace associated with the model.
+ /// \param[in] _ns Namespace of the model. If set to `"__name__"`, the
+ /// model name will be used as the namespace.
+ public: void SetNamespace(const std::string &_ns);
+
/// \brief Check if this model should be static.
/// A static model is one that is not subject to physical forces (in other
/// words, it's purely kinematic instead of dynamic).
diff --git a/include/sdf/World.hh b/include/sdf/World.hh
index 3e35222b7..992a3c8f0 100644
--- a/include/sdf/World.hh
+++ b/include/sdf/World.hh
@@ -91,6 +91,15 @@ namespace sdf
/// \param[in] _name Name of the world.
public: void SetName(const std::string &_name);
+ /// \brief Get the namespace associated with the world.
+ /// \return Namespace of the world.
+ public: std::string Namespace() const;
+
+ /// \brief Set the namespace associated with the world.
+ /// \param[in] _ns Namespace of the world. If set to `"__name__"`, the
+ /// world name will be used as the namespace.
+ public: void SetNamespace(const std::string &_ns);
+
/// \brief Get the audio device name. The audio device can be used to
/// playback audio files. A value of "default" or an empty string
/// indicates that the system's default audio device should be used.
diff --git a/sdf/1.12/model.sdf b/sdf/1.12/model.sdf
index 087f892e7..b26e5aff4 100644
--- a/sdf/1.12/model.sdf
+++ b/sdf/1.12/model.sdf
@@ -11,6 +11,15 @@
+
+
+ Optional namespace associated with the model. This namespace can be
+ used by downstream systems and applications to organize related entities
+ and communication interfaces. If set to "__name__", the model name will
+ be used as the namespace.
+
+
+
The name of the model's canonical link, to which the model's implicit
diff --git a/sdf/1.12/world.sdf b/sdf/1.12/world.sdf
index d39ad8819..bc5005364 100644
--- a/sdf/1.12/world.sdf
+++ b/sdf/1.12/world.sdf
@@ -5,6 +5,15 @@
Unique name of the world
+
+
+ Optional namespace associated with the world. This namespace can be
+ used by downstream systems and applications to organize related entities
+ and communication interfaces.If set to "__name__", the model name will
+ be used as the namespace.
+
+
+
Global audio properties.
diff --git a/src/Model.cc b/src/Model.cc
index 944e50b87..96deaad68 100644
--- a/src/Model.cc
+++ b/src/Model.cc
@@ -44,6 +44,12 @@ class sdf::Model::Implementation
/// \brief Name of the model.
public: std::string name = "";
+ /// \brief Namespace of the model.
+ public: std::string ns = "";
+
+ /// \brief True when namespace tracks the model name via "__name__".
+ public: bool nsFromName = false;
+
/// \brief True if this model is specified as static, false otherwise.
public: bool isStatic = false;
@@ -178,6 +184,18 @@ Errors Model::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
"] is reserved."});
}
+ // Read the model's namespace
+ if (_sdf->HasAttribute("namespace"))
+ {
+ auto ns = _sdf->Get("namespace", "").first;
+ if (ns == "__name__")
+ {
+ this->dataPtr->nsFromName = true;
+ ns = this->dataPtr->name;
+ }
+ this->dataPtr->ns = ns;
+ }
+
// Read the model's canonical_link attribute
if (_sdf->HasAttribute("canonical_link"))
{
@@ -534,6 +552,31 @@ std::string Model::Name() const
void Model::SetName(const std::string &_name)
{
this->dataPtr->name = _name;
+ if (this->dataPtr->nsFromName)
+ {
+ this->dataPtr->ns = _name;
+ }
+}
+
+/////////////////////////////////////////////////
+std::string Model::Namespace() const
+{
+ return this->dataPtr->ns;
+}
+
+/////////////////////////////////////////////////
+void Model::SetNamespace(const std::string &_ns)
+{
+ if (_ns == "__name__")
+ {
+ this->dataPtr->ns = this->name;
+ this->dataPtr->nsFromName = true;
+ }
+ else
+ {
+ this->dataPtr->ns = _ns;
+ this->dataPtr->nsFromName = false;
+ }
}
/////////////////////////////////////////////////
@@ -1094,6 +1137,7 @@ sdf::ElementPtr Model::ToElement(const OutputConfig &_config) const
sdf::ElementPtr includeElem = worldElem->AddElement("include");
includeElem->GetElement("uri")->Set(this->Uri());
includeElem->GetElement("name")->Set(this->Name());
+ // TODO(C88)
includeElem->GetElement("pose")->Set(this->RawPose());
if (!this->dataPtr->poseRelativeTo.empty())
{
@@ -1117,6 +1161,14 @@ sdf::ElementPtr Model::ToElement(const OutputConfig &_config) const
sdf::ElementPtr elem(new sdf::Element);
sdf::initFile("model.sdf", elem);
elem->GetAttribute("name")->Set(this->Name());
+ if (this->dataPtr->nsFromName)
+ {
+ elem->GetAttribute("namespace")->Set("__name__");
+ }
+ else
+ {
+ elem->GetAttribute("namespace")->Set(this->Namespace());
+ }
if (!this->dataPtr->canonicalLink.empty())
{
diff --git a/src/World.cc b/src/World.cc
index e17005c9f..d206dbb18 100644
--- a/src/World.cc
+++ b/src/World.cc
@@ -95,6 +95,12 @@ class sdf::World::Implementation
/// \brief Name of the world.
public: std::string name = "";
+ /// \brief Namespace of the world.
+ public: std::string ns = "";
+
+ /// \brief True when namespace tracks the world name via "__name__".
+ public: bool nsFromName = false;
+
/// \brief The physics profiles specified in this world.
public: std::vector physics;
@@ -163,6 +169,18 @@ Errors World::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
"] is reserved."});
}
+ // Read the world's namespace
+ if (_sdf->HasAttribute("namespace"))
+ {
+ auto ns = _sdf->Get("namespace", "").first;
+ if (ns == "__name__")
+ {
+ this->dataPtr->nsFromName = true;
+ ns = this->dataPtr->name;
+ }
+ this->dataPtr->ns = ns;
+ }
+
// Read the audio element
if (_sdf->HasElement("audio"))
{
@@ -428,6 +446,31 @@ std::string World::Name() const
void World::SetName(const std::string &_name)
{
this->dataPtr->name = _name;
+ if (this->dataPtr->nsFromName)
+ {
+ this->dataPtr->ns = _name;
+ }
+}
+
+/////////////////////////////////////////////////
+std::string World::Namespace() const
+{
+ return this->dataPtr->ns;
+}
+
+/////////////////////////////////////////////////
+void World::SetNamespace(const std::string &_ns)
+{
+ if (_ns == "__name__")
+ {
+ this->dataPtr->nsFromName = true;
+ this->dataPtr->ns = this->name;
+ }
+ else
+ {
+ this->dataPtr->nsFromName = false;
+ this->dataPtr->ns = _ns;
+ }
}
/////////////////////////////////////////////////
@@ -1083,6 +1126,14 @@ sdf::ElementPtr World::ToElement(const OutputConfig &_config) const
sdf::initFile("world.sdf", elem);
elem->GetAttribute("name")->Set(this->Name());
+ if (this->dataPtr->nsFromName)
+ {
+ elem->GetAttribute("namespace")->Set("__name__");
+ }
+ else
+ {
+ elem->GetAttribute("namespace")->Set(this->Namespace());
+ }
elem->GetElement("gravity")->Set(this->Gravity());
elem->GetElement("magnetic_field")->Set(this->MagneticField());
From c307cec472412c039e857d80aa34b7d059da7448 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Thu, 28 May 2026 21:18:27 +0800
Subject: [PATCH 02/12] Update
Signed-off-by: C88-YQ <1409947012@qq.com>
---
src/Model.cc | 6 +++++-
src/World.cc | 6 +++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/Model.cc b/src/Model.cc
index 96deaad68..803e9e5f5 100644
--- a/src/Model.cc
+++ b/src/Model.cc
@@ -193,6 +193,10 @@ Errors Model::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
this->dataPtr->nsFromName = true;
ns = this->dataPtr->name;
}
+ else
+ {
+ this->dataPtr->nsFromName = false;
+ }
this->dataPtr->ns = ns;
}
@@ -569,7 +573,7 @@ void Model::SetNamespace(const std::string &_ns)
{
if (_ns == "__name__")
{
- this->dataPtr->ns = this->name;
+ this->dataPtr->ns = this->dataPtr->name;
this->dataPtr->nsFromName = true;
}
else
diff --git a/src/World.cc b/src/World.cc
index d206dbb18..e5f32dae1 100644
--- a/src/World.cc
+++ b/src/World.cc
@@ -178,6 +178,10 @@ Errors World::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
this->dataPtr->nsFromName = true;
ns = this->dataPtr->name;
}
+ else
+ {
+ this->dataPtr->nsFromName = false;
+ }
this->dataPtr->ns = ns;
}
@@ -464,7 +468,7 @@ void World::SetNamespace(const std::string &_ns)
if (_ns == "__name__")
{
this->dataPtr->nsFromName = true;
- this->dataPtr->ns = this->name;
+ this->dataPtr->ns = this->dataPtr->name;
}
else
{
From c4a151a85ab1310831c02089f4f6caef39c07b24 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Fri, 29 May 2026 12:07:47 +0800
Subject: [PATCH 03/12] Add ns support to include
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/InterfaceElements.hh | 13 +++++++++++++
sdf/1.12/model.sdf | 4 ++++
sdf/1.12/world.sdf | 4 ++++
src/InterfaceElements.cc | 19 +++++++++++++++++++
src/Model.cc | 9 ++++++++-
src/Utils.cc | 4 ++++
src/parser.cc | 7 +++++++
7 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/include/sdf/InterfaceElements.hh b/include/sdf/InterfaceElements.hh
index e99bfbb83..02e68a93b 100644
--- a/include/sdf/InterfaceElements.hh
+++ b/include/sdf/InterfaceElements.hh
@@ -95,6 +95,19 @@ class SDFORMAT_VISIBLE NestedInclude
/// \param[in] _localModelName The local name
public: void SetLocalModelName(const std::string &_localModelName);
+ /// \brief Namespace relative to immediate parent as specified in
+ /// `//include/namespace`. This is nullopt if `//include/namespace` is not set. Then the
+ /// namespace of the model must be determined by the custom model parser from the
+ /// included model file.
+ /// Example: `my_new_model_namespace`
+ /// \return The local namespace. nullopt if `//include/namespace` is not set
+ public: const std::optional &LocalModelNamespace() const;
+
+ /// \brief Set the namespace relative to immediate parent as specified in
+ /// `//include/namespace`
+ /// \param[in] _localModelNs The local namespace
+ public: void SetLocalModelNamespace(const std::string &_localModelNs);
+
/// \brief Whether the model is static as defined by `//include/static`. This
/// is nullopt if `//include/static` is not set.
/// \return Whether the model is static. nullopt if `//include/static` is not
diff --git a/sdf/1.12/model.sdf b/sdf/1.12/model.sdf
index b26e5aff4..11da74e69 100644
--- a/sdf/1.12/model.sdf
+++ b/sdf/1.12/model.sdf
@@ -79,6 +79,10 @@
Override the name of the included model.
+
+ Override the namespace of the included entity.
+
+
Override the static value of the included model.
diff --git a/sdf/1.12/world.sdf b/sdf/1.12/world.sdf
index bc5005364..12cff010f 100644
--- a/sdf/1.12/world.sdf
+++ b/sdf/1.12/world.sdf
@@ -45,6 +45,10 @@
Override the name of the included entity.
+
+ Override the namespace of the included entity.
+
+
Override the static value of the included entity.
diff --git a/src/InterfaceElements.cc b/src/InterfaceElements.cc
index e5a861538..717ceb576 100644
--- a/src/InterfaceElements.cc
+++ b/src/InterfaceElements.cc
@@ -52,6 +52,13 @@ class sdf::NestedInclude::Implementation
/// Example: `my_new_model`
public: std::optional localModelName;
+ /// \brief Namespace relative to immediate parent as specified in
+ /// `//include/namespace`. This is nullopt if `//include/namespace` is not set. Then the
+ /// namespace of the model must be determined by the custom model parser from the
+ /// included model file.
+ /// Example: `my_new_model_namespace`
+ public: std::optional localModelNs;
+
/// \brief Whether the model is static as defined by `//include/static`. This
/// is nullopt if `//include/static` is not set.
public: std::optional isStatic;
@@ -129,6 +136,18 @@ void NestedInclude::SetLocalModelName(const std::string &_localModelName)
this->dataPtr->localModelName = _localModelName;
}
+/////////////////////////////////////////////////
+const std::optional &NestedInclude::LocalModelNamespace() const
+{
+ return this->dataPtr->localModelNs;
+}
+
+/////////////////////////////////////////////////
+void NestedInclude::SetLocalModelNamespace(const std::string &_localModelNs)
+{
+ this->dataPtr->localModelNs = _localModelNs;
+}
+
/////////////////////////////////////////////////
const std::optional &NestedInclude::IsStatic() const
{
diff --git a/src/Model.cc b/src/Model.cc
index 803e9e5f5..20583f23f 100644
--- a/src/Model.cc
+++ b/src/Model.cc
@@ -1141,7 +1141,14 @@ sdf::ElementPtr Model::ToElement(const OutputConfig &_config) const
sdf::ElementPtr includeElem = worldElem->AddElement("include");
includeElem->GetElement("uri")->Set(this->Uri());
includeElem->GetElement("name")->Set(this->Name());
- // TODO(C88)
+ if (this->dataPtr->nsFromName)
+ {
+ includeElem->GetElement("namespace")->Set("__name__");
+ }
+ else
+ {
+ includeElem->GetElement("namespace")->Set(this->Namespace());
+ }
includeElem->GetElement("pose")->Set(this->RawPose());
if (!this->dataPtr->poseRelativeTo.empty())
{
diff --git a/src/Utils.cc b/src/Utils.cc
index e43632356..d6477d827 100644
--- a/src/Utils.cc
+++ b/src/Utils.cc
@@ -249,6 +249,10 @@ sdf::Errors loadIncludedInterfaceModels(sdf::ElementPtr _sdf,
{
include.SetLocalModelName(includeElem->Get("name"));
}
+ if (includeElem->HasElement("namespace"))
+ {
+ include.SetLocalModelNamespace(includeElem->Get("namespace"));
+ }
if (includeElem->HasElement("static"))
{
include.SetIsStatic(includeElem->Get("static"));
diff --git a/src/parser.cc b/src/parser.cc
index 992893987..735601273 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -1831,6 +1831,13 @@ bool readXml(tinyxml2::XMLElement *_xml, ElementPtr _sdf,
"[@name=\"" + overrideName + "\"]");
}
+ if (elemXml->FirstChildElement("namespace"))
+ {
+ const std::string overrideNamespace =
+ elemXml->FirstChildElement("namespace")->GetText();
+ topLevelElem->GetAttribute("namespace")->SetFromString(overrideNamespace);
+ }
+
tinyxml2::XMLElement *poseElemXml =
elemXml->FirstChildElement("pose");
if (poseElemXml)
From 96aa04c24ef4c3996d370203c96d87e68adcd545 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Fri, 29 May 2026 12:16:31 +0800
Subject: [PATCH 04/12] Add test for ns_support of include
Signed-off-by: C88-YQ <1409947012@qq.com>
---
test/integration/element_tracing.cc | 4 ++--
test/integration/includes.cc | 5 +++++
test/integration/interface_api.cc | 3 +++
test/sdf/includes.sdf | 2 ++
4 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/test/integration/element_tracing.cc b/test/integration/element_tracing.cc
index 443bf8ad0..6d92f5d77 100644
--- a/test/integration/element_tracing.cc
+++ b/test/integration/element_tracing.cc
@@ -209,7 +209,7 @@ TEST(ElementTracing, includes)
ASSERT_NE(nullptr, overrideActorPluginElem);
EXPECT_EQ(actorFilePath, overrideActorPluginElem->FilePath());
ASSERT_TRUE(overrideActorPluginElem->LineNumber().has_value());
- EXPECT_EQ(40, overrideActorPluginElem->LineNumber().value());
+ EXPECT_EQ(42, overrideActorPluginElem->LineNumber().value());
EXPECT_EQ(overrideActorPluginXmlPath, overrideActorPluginElem->XmlPath());
// Lights
@@ -270,7 +270,7 @@ TEST(ElementTracing, includes)
ASSERT_NE(nullptr, overrideModelPluginElem);
EXPECT_EQ(modelFilePath, overrideModelPluginElem->FilePath());
ASSERT_TRUE(overrideModelPluginElem->LineNumber().has_value());
- EXPECT_EQ(14, overrideModelPluginElem->LineNumber().value());
+ EXPECT_EQ(15, overrideModelPluginElem->LineNumber().value());
EXPECT_EQ(overrideModelPluginXmlPath, overrideModelPluginElem->XmlPath());
#ifdef _WIN32
diff --git a/test/integration/includes.cc b/test/integration/includes.cc
index adc80248e..464a1f796 100644
--- a/test/integration/includes.cc
+++ b/test/integration/includes.cc
@@ -155,6 +155,7 @@ TEST(IncludesTest, Includes)
const sdf::Model *model = world->ModelByIndex(0);
ASSERT_NE(nullptr, model);
EXPECT_EQ("test_model", model->Name());
+ EXPECT_EQ("", model->Namespace());
EXPECT_FALSE(model->Static());
EXPECT_EQ(1u, model->LinkCount());
ASSERT_FALSE(nullptr == model->LinkByIndex(0));
@@ -203,6 +204,7 @@ TEST(IncludesTest, Includes)
const sdf::Model *model1 = world->ModelByIndex(1);
ASSERT_NE(nullptr, model1);
EXPECT_EQ("override_model_name", model1->Name());
+ EXPECT_EQ("override_model_namespace", model1->Namespace());
EXPECT_TRUE(model1->Static());
EXPECT_EQ(gz::math::Pose3d(1, 2, 3, 0, 0, 0), model1->RawPose());
EXPECT_EQ("", model1->PoseRelativeTo());
@@ -212,6 +214,7 @@ TEST(IncludesTest, Includes)
const sdf::Model *model2 = world->ModelByIndex(2);
ASSERT_NE(nullptr, model2);
EXPECT_EQ("test_model_with_file", model2->Name());
+ EXPECT_EQ("test_model_with_file", model2->Namespace());
EXPECT_FALSE(model2->Static());
EXPECT_EQ(1u, model2->LinkCount());
ASSERT_NE(nullptr, model2->LinkByIndex(0));
@@ -260,6 +263,7 @@ TEST(IncludesTest, Includes_15)
const sdf::Model *model = world->ModelByIndex(0);
ASSERT_NE(nullptr, model);
EXPECT_EQ("test_model", model->Name());
+ EXPECT_EQ("", model->Namespace());
EXPECT_EQ(1u, model->LinkCount());
ASSERT_FALSE(nullptr == model->LinkByName("link"));
@@ -310,6 +314,7 @@ TEST(IncludesTest, Includes_15_convert)
sdf::ElementPtr modelElem = worldElem->GetElement("model");
ASSERT_NE(nullptr, modelElem);
EXPECT_EQ(modelElem->Get("name"), "test_model");
+ EXPECT_EQ(modelElem->Get("namespace"), "");
sdf::ElementPtr linkElem = modelElem->GetElement("link");
ASSERT_NE(nullptr, linkElem);
diff --git a/test/integration/interface_api.cc b/test/integration/interface_api.cc
index d255a0927..fb64457fa 100644
--- a/test/integration/interface_api.cc
+++ b/test/integration/interface_api.cc
@@ -170,6 +170,7 @@ TEST_F(InterfaceAPI, NestedIncludeData)
file_wont_be_parsed.nonce_1
box
+ box_ns
1 0 0 0 0 0
value1
@@ -194,6 +195,7 @@ TEST_F(InterfaceAPI, NestedIncludeData)
EXPECT_EQ(sdf::filesystem::append(this->modelDir, fileName),
_include.ResolvedFileName());
EXPECT_EQ("box", *_include.LocalModelName());
+ EXPECT_EQ("box_ns", *_include.LocalModelNamespace());
EXPECT_TRUE(_include.IsStatic().has_value());
EXPECT_TRUE(_include.IsStatic().value());
@@ -227,6 +229,7 @@ TEST_F(InterfaceAPI, NestedIncludeData)
EXPECT_EQ(sdf::filesystem::append(modelDir, fileName),
_include.ResolvedFileName());
EXPECT_FALSE(_include.LocalModelName().has_value());
+ EXPECT_FALSE(_include.LocalModelNamespace().has_value());
EXPECT_FALSE(_include.IsStatic());
// Add error for test expectation later on.
diff --git a/test/sdf/includes.sdf b/test/sdf/includes.sdf
index fd75a8deb..06886d221 100644
--- a/test/sdf/includes.sdf
+++ b/test/sdf/includes.sdf
@@ -9,6 +9,7 @@
test_model
override_model_name
+ override_model_namespace
1 2 3 0 0 0
true
@@ -17,6 +18,7 @@
test_model/model.sdf
test_model_with_file
+ __name__
From bc8a9a62759035a7aa892ffc430c621927c7914b Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Fri, 29 May 2026 12:22:55 +0800
Subject: [PATCH 05/12] Pass codecheck
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/InterfaceElements.hh | 6 +++---
src/InterfaceElements.cc | 6 +++---
src/Utils.cc | 3 ++-
src/parser.cc | 3 ++-
4 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/include/sdf/InterfaceElements.hh b/include/sdf/InterfaceElements.hh
index 02e68a93b..38262bd6a 100644
--- a/include/sdf/InterfaceElements.hh
+++ b/include/sdf/InterfaceElements.hh
@@ -96,9 +96,9 @@ class SDFORMAT_VISIBLE NestedInclude
public: void SetLocalModelName(const std::string &_localModelName);
/// \brief Namespace relative to immediate parent as specified in
- /// `//include/namespace`. This is nullopt if `//include/namespace` is not set. Then the
- /// namespace of the model must be determined by the custom model parser from the
- /// included model file.
+ /// `//include/namespace`. This is nullopt if `//include/namespace` is not
+ /// set. Then the namespace of the model must be determined by the custom
+ /// model parser from the included model file.
/// Example: `my_new_model_namespace`
/// \return The local namespace. nullopt if `//include/namespace` is not set
public: const std::optional &LocalModelNamespace() const;
diff --git a/src/InterfaceElements.cc b/src/InterfaceElements.cc
index 717ceb576..7acaeb8e1 100644
--- a/src/InterfaceElements.cc
+++ b/src/InterfaceElements.cc
@@ -53,9 +53,9 @@ class sdf::NestedInclude::Implementation
public: std::optional localModelName;
/// \brief Namespace relative to immediate parent as specified in
- /// `//include/namespace`. This is nullopt if `//include/namespace` is not set. Then the
- /// namespace of the model must be determined by the custom model parser from the
- /// included model file.
+ /// `//include/namespace`. This is nullopt if `//include/namespace` is not
+ /// set. Then the namespace of the model must be determined by the custom
+ /// model parser from the included model file.
/// Example: `my_new_model_namespace`
public: std::optional localModelNs;
diff --git a/src/Utils.cc b/src/Utils.cc
index d6477d827..31084c041 100644
--- a/src/Utils.cc
+++ b/src/Utils.cc
@@ -251,7 +251,8 @@ sdf::Errors loadIncludedInterfaceModels(sdf::ElementPtr _sdf,
}
if (includeElem->HasElement("namespace"))
{
- include.SetLocalModelNamespace(includeElem->Get("namespace"));
+ include.SetLocalModelNamespace(
+ includeElem->Get("namespace"));
}
if (includeElem->HasElement("static"))
{
diff --git a/src/parser.cc b/src/parser.cc
index 735601273..dfdada3ff 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -1835,7 +1835,8 @@ bool readXml(tinyxml2::XMLElement *_xml, ElementPtr _sdf,
{
const std::string overrideNamespace =
elemXml->FirstChildElement("namespace")->GetText();
- topLevelElem->GetAttribute("namespace")->SetFromString(overrideNamespace);
+ topLevelElem->GetAttribute("namespace")->SetFromString(
+ overrideNamespace);
}
tinyxml2::XMLElement *poseElemXml =
From 4e3305c631bbb6e293513e7396288d1430f4e286 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Thu, 4 Jun 2026 22:07:25 +0800
Subject: [PATCH 06/12] Add test to Model
Signed-off-by: C88-YQ <1409947012@qq.com>
---
src/Model_TEST.cc | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/src/Model_TEST.cc b/src/Model_TEST.cc
index cb4325d76..a6f908543 100644
--- a/src/Model_TEST.cc
+++ b/src/Model_TEST.cc
@@ -31,10 +31,16 @@ TEST(DOMModel, Construction)
sdf::Model model;
EXPECT_EQ(nullptr, model.Element());
EXPECT_TRUE(model.Name().empty());
+ EXPECT_TRUE(model.Namespace().empty());
model.SetName("test_model");
EXPECT_EQ("test_model", model.Name());
+ model.SetNamespace("__name__");
+ EXPECT_EQ("test_model", model.Namespace());
+ model.SetNamespace("test_namespace");
+ EXPECT_EQ("test_namespace", model.Namespace());
+
EXPECT_FALSE(model.Static());
model.SetStatic(true);
EXPECT_TRUE(model.Static());
@@ -165,9 +171,11 @@ TEST(DOMModel, CopyConstructor)
{
sdf::Model model;
model.SetName("test_model");
+ model.SetNamespace("test_ns");
sdf::Model model2(model);
EXPECT_EQ("test_model", model2.Name());
+ EXPECT_EQ("test_ns", model2.Namespace());
}
/////////////////////////////////////////////////
@@ -175,10 +183,12 @@ TEST(DOMModel, CopyAssignmentOperator)
{
sdf::Model model;
model.SetName("test_model");
+ model.SetNamespace("test_ns");
sdf::Model model2;
model2 = model;
EXPECT_EQ("test_model", model2.Name());
+ EXPECT_EQ("test_ns", model2.Namespace());
}
/////////////////////////////////////////////////
@@ -186,9 +196,11 @@ TEST(DOMModel, MoveConstructor)
{
sdf::Model model;
model.SetName("test_model");
+ model.SetNamespace("test_ns");
sdf::Model model2(std::move(model));
EXPECT_EQ("test_model", model2.Name());
+ EXPECT_EQ("test_ns", model2.Namespace());
}
/////////////////////////////////////////////////
@@ -196,10 +208,12 @@ TEST(DOMModel, MoveAssignmentOperator)
{
sdf::Model model;
model.SetName("test_model");
+ model.SetNamespace("test_ns");
sdf::Model model2;
model2 = std::move(model);
EXPECT_EQ("test_model", model2.Name());
+ EXPECT_EQ("test_ns", model2.Namespace());
}
/////////////////////////////////////////////////
@@ -207,9 +221,11 @@ TEST(DOMModel, CopyAssignmentAfterMove)
{
sdf::Model model1;
model1.SetName("model1");
+ model1.SetNamespace("test_ns1");
sdf::Model model2;
model2.SetName("model2");
+ model2.SetNamespace("test_ns2");
// This is similar to what std::swap does except it uses std::move for each
// assignment
@@ -219,6 +235,8 @@ TEST(DOMModel, CopyAssignmentAfterMove)
EXPECT_EQ("model2", model1.Name());
EXPECT_EQ("model1", model2.Name());
+ EXPECT_EQ("test_ns2", model1.Namespace());
+ EXPECT_EQ("test_ns1", model2.Namespace());
}
/////////////////////////////////////////////////
@@ -333,6 +351,7 @@ TEST(DOMModel, ToElement)
sdf::Model model;
model.SetName("my-model");
+ model.SetNamespace("my-ns");
model.SetStatic(true);
model.SetSelfCollide(true);
model.SetAllowAutoDisable(true);
@@ -443,11 +462,13 @@ TEST(DOMModel, Uri)
{
sdf::Model model;
std::string name = "my-model";
+ std::string ns = "__name__";
gz::math::Pose3d pose(1, 2, 3, 0.1, 0.2, 0.3);
std::string uri =
"https://fuel.gazebosim.org/1.0/openrobotics/models/my-model";
model.SetName(name);
+ model.SetNamespace(ns);
model.SetRawPose(pose);
model.SetStatic(true);
model.SetPlacementFrameName("link0");
@@ -468,6 +489,10 @@ TEST(DOMModel, Uri)
ASSERT_NE(nullptr, nameElem);
EXPECT_EQ(name, nameElem->Get());
+ sdf::ElementPtr nsElem = elem->FindElement("namespace");
+ ASSERT_NE(nullptr, nsElem);
+ EXPECT_EQ(ns, nsElem->Get());
+
sdf::ElementPtr poseElem = elem->FindElement("pose");
ASSERT_NE(nullptr, poseElem);
EXPECT_EQ(pose, poseElem->Get());
@@ -499,6 +524,10 @@ TEST(DOMModel, Uri)
ASSERT_NE(nullptr, nameAttr);
EXPECT_EQ(name, nameAttr->GetAsString());
+ sdf::ParamPtr nsAttr = elem->GetAttribute("namespace");
+ ASSERT_NE(nullptr, nsAttr);
+ EXPECT_EQ(ns, nsAttr->GetAsString());
+
sdf::ParamPtr placementFrameAttr = elem->GetAttribute("placement_frame");
ASSERT_NE(nullptr, placementFrameAttr);
EXPECT_EQ("link0", placementFrameAttr->GetAsString());
From a4d9465f7ce74da0fa80b50cae0df462221216b8 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Fri, 5 Jun 2026 14:06:32 +0800
Subject: [PATCH 07/12] Remove ns attribute of world
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/World.hh | 9 --------
sdf/1.12/world.sdf | 9 --------
src/World.cc | 55 --------------------------------------------
3 files changed, 73 deletions(-)
diff --git a/include/sdf/World.hh b/include/sdf/World.hh
index 992a3c8f0..3e35222b7 100644
--- a/include/sdf/World.hh
+++ b/include/sdf/World.hh
@@ -91,15 +91,6 @@ namespace sdf
/// \param[in] _name Name of the world.
public: void SetName(const std::string &_name);
- /// \brief Get the namespace associated with the world.
- /// \return Namespace of the world.
- public: std::string Namespace() const;
-
- /// \brief Set the namespace associated with the world.
- /// \param[in] _ns Namespace of the world. If set to `"__name__"`, the
- /// world name will be used as the namespace.
- public: void SetNamespace(const std::string &_ns);
-
/// \brief Get the audio device name. The audio device can be used to
/// playback audio files. A value of "default" or an empty string
/// indicates that the system's default audio device should be used.
diff --git a/sdf/1.12/world.sdf b/sdf/1.12/world.sdf
index 12cff010f..4e578a6eb 100644
--- a/sdf/1.12/world.sdf
+++ b/sdf/1.12/world.sdf
@@ -5,15 +5,6 @@
Unique name of the world
-
-
- Optional namespace associated with the world. This namespace can be
- used by downstream systems and applications to organize related entities
- and communication interfaces.If set to "__name__", the model name will
- be used as the namespace.
-
-
-
Global audio properties.
diff --git a/src/World.cc b/src/World.cc
index e5f32dae1..e17005c9f 100644
--- a/src/World.cc
+++ b/src/World.cc
@@ -95,12 +95,6 @@ class sdf::World::Implementation
/// \brief Name of the world.
public: std::string name = "";
- /// \brief Namespace of the world.
- public: std::string ns = "";
-
- /// \brief True when namespace tracks the world name via "__name__".
- public: bool nsFromName = false;
-
/// \brief The physics profiles specified in this world.
public: std::vector physics;
@@ -169,22 +163,6 @@ Errors World::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
"] is reserved."});
}
- // Read the world's namespace
- if (_sdf->HasAttribute("namespace"))
- {
- auto ns = _sdf->Get("namespace", "").first;
- if (ns == "__name__")
- {
- this->dataPtr->nsFromName = true;
- ns = this->dataPtr->name;
- }
- else
- {
- this->dataPtr->nsFromName = false;
- }
- this->dataPtr->ns = ns;
- }
-
// Read the audio element
if (_sdf->HasElement("audio"))
{
@@ -450,31 +428,6 @@ std::string World::Name() const
void World::SetName(const std::string &_name)
{
this->dataPtr->name = _name;
- if (this->dataPtr->nsFromName)
- {
- this->dataPtr->ns = _name;
- }
-}
-
-/////////////////////////////////////////////////
-std::string World::Namespace() const
-{
- return this->dataPtr->ns;
-}
-
-/////////////////////////////////////////////////
-void World::SetNamespace(const std::string &_ns)
-{
- if (_ns == "__name__")
- {
- this->dataPtr->nsFromName = true;
- this->dataPtr->ns = this->dataPtr->name;
- }
- else
- {
- this->dataPtr->nsFromName = false;
- this->dataPtr->ns = _ns;
- }
}
/////////////////////////////////////////////////
@@ -1130,14 +1083,6 @@ sdf::ElementPtr World::ToElement(const OutputConfig &_config) const
sdf::initFile("world.sdf", elem);
elem->GetAttribute("name")->Set(this->Name());
- if (this->dataPtr->nsFromName)
- {
- elem->GetAttribute("namespace")->Set("__name__");
- }
- else
- {
- elem->GetAttribute("namespace")->Set(this->Namespace());
- }
elem->GetElement("gravity")->Set(this->Gravity());
elem->GetElement("magnetic_field")->Set(this->MagneticField());
From c0c0ca1084026790959759b506a5c3baef611c6a Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Mon, 8 Jun 2026 20:49:42 +0800
Subject: [PATCH 08/12] Support __name__ placeholder within namespace strings
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/Model.hh | 12 +++++---
sdf/1.12/model.sdf | 4 +--
src/Model.cc | 70 +++++++++++++++++++-------------------------
src/Model_TEST.cc | 14 +++++----
4 files changed, 48 insertions(+), 52 deletions(-)
diff --git a/include/sdf/Model.hh b/include/sdf/Model.hh
index 6833e2926..ecd59b0c8 100644
--- a/include/sdf/Model.hh
+++ b/include/sdf/Model.hh
@@ -91,13 +91,17 @@ namespace sdf
/// \param[in] _name Name of the model.
public: void SetName(const std::string &_name);
- /// \brief Get the namespace associated with the model.
- /// \return Namespace of the model.
+ /// \brief Get the resolved namespace associated with the model.
+ /// \return Resolved namespace of the model.
public: std::string Namespace() const;
+ /// \brief Get the raw namespace associated with the model.
+ /// \return Raw namespace of the model.
+ public: std::string RawNamespace() const;
+
/// \brief Set the namespace associated with the model.
- /// \param[in] _ns Namespace of the model. If set to `"__name__"`, the
- /// model name will be used as the namespace.
+ /// \param[in] _ns Namespace of the model. The `__name__` placeholder
+ /// will be replaced with the model name.
public: void SetNamespace(const std::string &_ns);
/// \brief Check if this model should be static.
diff --git a/sdf/1.12/model.sdf b/sdf/1.12/model.sdf
index 11da74e69..919537678 100644
--- a/sdf/1.12/model.sdf
+++ b/sdf/1.12/model.sdf
@@ -15,8 +15,8 @@
Optional namespace associated with the model. This namespace can be
used by downstream systems and applications to organize related entities
- and communication interfaces. If set to "__name__", the model name will
- be used as the namespace.
+ and communication interfaces. The `__name__` placeholder will be
+ replaced with the model name.
diff --git a/src/Model.cc b/src/Model.cc
index 20583f23f..92444af20 100644
--- a/src/Model.cc
+++ b/src/Model.cc
@@ -44,11 +44,11 @@ class sdf::Model::Implementation
/// \brief Name of the model.
public: std::string name = "";
- /// \brief Namespace of the model.
- public: std::string ns = "";
+ /// \brief TODO
+ public: std::string rawNamespace = "";
- /// \brief True when namespace tracks the model name via "__name__".
- public: bool nsFromName = false;
+ /// \brief TODO
+ public: std::string resolvedNamespace = "";
/// \brief True if this model is specified as static, false otherwise.
public: bool isStatic = false;
@@ -187,17 +187,14 @@ Errors Model::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
// Read the model's namespace
if (_sdf->HasAttribute("namespace"))
{
- auto ns = _sdf->Get("namespace", "").first;
- if (ns == "__name__")
+ this->dataPtr->rawNamespace = _sdf->Get("namespace", "").first;
+ this->dataPtr->resolvedNamespace = this->dataPtr->rawNamespace;
+ auto pos = this->dataPtr->resolvedNamespace.find("__name__");
+ if (pos != std::string::npos)
{
- this->dataPtr->nsFromName = true;
- ns = this->dataPtr->name;
+ this->dataPtr->resolvedNamespace.replace(pos, strlen("__name__"),
+ this->dataPtr->name);
}
- else
- {
- this->dataPtr->nsFromName = false;
- }
- this->dataPtr->ns = ns;
}
// Read the model's canonical_link attribute
@@ -556,30 +553,37 @@ std::string Model::Name() const
void Model::SetName(const std::string &_name)
{
this->dataPtr->name = _name;
- if (this->dataPtr->nsFromName)
+ auto pos = this->dataPtr->rawNamespace.find("__name__");
+ if (pos != std::string::npos)
{
- this->dataPtr->ns = _name;
+ this->dataPtr->resolvedNamespace = this->dataPtr->rawNamespace;
+ this->dataPtr->resolvedNamespace.replace(pos, strlen("__name__"),
+ this->dataPtr->name);
}
}
/////////////////////////////////////////////////
std::string Model::Namespace() const
{
- return this->dataPtr->ns;
+ return this->dataPtr->resolvedNamespace;
+}
+
+/////////////////////////////////////////////////
+std::string Model::RawNamespace() const
+{
+ return this->dataPtr->rawNamespace;
}
/////////////////////////////////////////////////
void Model::SetNamespace(const std::string &_ns)
{
- if (_ns == "__name__")
- {
- this->dataPtr->ns = this->dataPtr->name;
- this->dataPtr->nsFromName = true;
- }
- else
+ this->dataPtr->rawNamespace = _ns;
+ this->dataPtr->resolvedNamespace = this->dataPtr->rawNamespace;
+ auto pos = this->dataPtr->rawNamespace.find("__name__");
+ if (pos != std::string::npos)
{
- this->dataPtr->ns = _ns;
- this->dataPtr->nsFromName = false;
+ this->dataPtr->resolvedNamespace.replace(pos, strlen("__name__"),
+ this->dataPtr->name);
}
}
@@ -1141,14 +1145,7 @@ sdf::ElementPtr Model::ToElement(const OutputConfig &_config) const
sdf::ElementPtr includeElem = worldElem->AddElement("include");
includeElem->GetElement("uri")->Set(this->Uri());
includeElem->GetElement("name")->Set(this->Name());
- if (this->dataPtr->nsFromName)
- {
- includeElem->GetElement("namespace")->Set("__name__");
- }
- else
- {
- includeElem->GetElement("namespace")->Set(this->Namespace());
- }
+ includeElem->GetElement("namespace")->Set(this->RawNamespace());
includeElem->GetElement("pose")->Set(this->RawPose());
if (!this->dataPtr->poseRelativeTo.empty())
{
@@ -1172,14 +1169,7 @@ sdf::ElementPtr Model::ToElement(const OutputConfig &_config) const
sdf::ElementPtr elem(new sdf::Element);
sdf::initFile("model.sdf", elem);
elem->GetAttribute("name")->Set(this->Name());
- if (this->dataPtr->nsFromName)
- {
- elem->GetAttribute("namespace")->Set("__name__");
- }
- else
- {
- elem->GetAttribute("namespace")->Set(this->Namespace());
- }
+ elem->GetAttribute("namespace")->Set(this->RawNamespace());
if (!this->dataPtr->canonicalLink.empty())
{
diff --git a/src/Model_TEST.cc b/src/Model_TEST.cc
index a6f908543..3be11eff0 100644
--- a/src/Model_TEST.cc
+++ b/src/Model_TEST.cc
@@ -33,11 +33,12 @@ TEST(DOMModel, Construction)
EXPECT_TRUE(model.Name().empty());
EXPECT_TRUE(model.Namespace().empty());
+ model.SetName("test_name");
+ EXPECT_EQ("test_name", model.Name());
+ model.SetNamespace("test/__name__/test_ns");
+ EXPECT_EQ("test/test_name/test_ns", model.Namespace());
model.SetName("test_model");
- EXPECT_EQ("test_model", model.Name());
-
- model.SetNamespace("__name__");
- EXPECT_EQ("test_model", model.Namespace());
+ EXPECT_EQ("test/test_model/test_ns", model.Namespace());
model.SetNamespace("test_namespace");
EXPECT_EQ("test_namespace", model.Namespace());
@@ -351,7 +352,7 @@ TEST(DOMModel, ToElement)
sdf::Model model;
model.SetName("my-model");
- model.SetNamespace("my-ns");
+ model.SetNamespace("__name__/ns");
model.SetStatic(true);
model.SetSelfCollide(true);
model.SetAllowAutoDisable(true);
@@ -421,6 +422,7 @@ TEST(DOMModel, ToElement)
model2.Load(elem);
EXPECT_EQ(model.Name(), model2.Name());
+ EXPECT_EQ(model.Namespace(), model2.Namespace());
EXPECT_EQ(model.Static(), model2.Static());
EXPECT_EQ(model.SelfCollide(), model2.SelfCollide());
EXPECT_EQ(model.AllowAutoDisable(), model2.AllowAutoDisable());
@@ -462,7 +464,7 @@ TEST(DOMModel, Uri)
{
sdf::Model model;
std::string name = "my-model";
- std::string ns = "__name__";
+ std::string ns = "__name__/ns";
gz::math::Pose3d pose(1, 2, 3, 0.1, 0.2, 0.3);
std::string uri =
"https://fuel.gazebosim.org/1.0/openrobotics/models/my-model";
From e3fbb744ff01ce12b4496a0b25d51fa806b76e97 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Fri, 12 Jun 2026 08:52:54 +0800
Subject: [PATCH 09/12] Update
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/Model.hh | 7 +++++++
sdf/1.11/model.sdf | 13 +++++++++++++
sdf/1.12/model.sdf | 2 +-
src/Model.cc | 40 +++++++++++++++++++---------------------
src/Model_TEST.cc | 6 +++---
src/parser.cc | 7 +++++--
6 files changed, 48 insertions(+), 27 deletions(-)
diff --git a/include/sdf/Model.hh b/include/sdf/Model.hh
index ecd59b0c8..6ea56c64a 100644
--- a/include/sdf/Model.hh
+++ b/include/sdf/Model.hh
@@ -570,6 +570,13 @@ namespace sdf
private: sdf::Frame PrepareForMerge(sdf::Errors &_errors,
const std::string &_parentOfProxyFrame);
+ /// \brief Resolve namespace placeholders for this model.
+ /// \param[in] _rawNs The raw namespace string.
+ /// \param[in] _modelName The model name used to replace "__name__".
+ /// \return The namespace string with model placeholders resolved.
+ private: std::string ResolveNamespace (const std::string &_rawNs,
+ const std::string &_modelName);
+
/// \brief Allow Root::Load, World::SetPoseRelativeToGraph, or
/// World::SetFrameAttachedToGraph to call SetPoseRelativeToGraph and
/// SetFrameAttachedToGraph
diff --git a/sdf/1.11/model.sdf b/sdf/1.11/model.sdf
index f8e85cb48..1027db32d 100644
--- a/sdf/1.11/model.sdf
+++ b/sdf/1.11/model.sdf
@@ -11,6 +11,15 @@
+
+
+ Optional namespace associated with the model. This namespace can be
+ used by downstream systems and applications to organize related entities
+ and communication interfaces. The `__name__` placeholder will be
+ replaced with the model name.
+
+
+
The name of the model's canonical link, to which the model's implicit
@@ -69,6 +78,10 @@
Override the name of the included model.
+
+ Override the namespace of the included model.
+
+
Override the static value of the included model.
diff --git a/sdf/1.12/model.sdf b/sdf/1.12/model.sdf
index 919537678..2e8640a46 100644
--- a/sdf/1.12/model.sdf
+++ b/sdf/1.12/model.sdf
@@ -80,7 +80,7 @@
- Override the namespace of the included entity.
+ Override the namespace of the included model.
diff --git a/src/Model.cc b/src/Model.cc
index 92444af20..56af66b02 100644
--- a/src/Model.cc
+++ b/src/Model.cc
@@ -188,13 +188,8 @@ Errors Model::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
if (_sdf->HasAttribute("namespace"))
{
this->dataPtr->rawNamespace = _sdf->Get("namespace", "").first;
- this->dataPtr->resolvedNamespace = this->dataPtr->rawNamespace;
- auto pos = this->dataPtr->resolvedNamespace.find("__name__");
- if (pos != std::string::npos)
- {
- this->dataPtr->resolvedNamespace.replace(pos, strlen("__name__"),
- this->dataPtr->name);
- }
+ this->dataPtr->resolvedNamespace =
+ this->ResolveNamespace(this->dataPtr->rawNamespace, this->Name());
}
// Read the model's canonical_link attribute
@@ -553,13 +548,8 @@ std::string Model::Name() const
void Model::SetName(const std::string &_name)
{
this->dataPtr->name = _name;
- auto pos = this->dataPtr->rawNamespace.find("__name__");
- if (pos != std::string::npos)
- {
- this->dataPtr->resolvedNamespace = this->dataPtr->rawNamespace;
- this->dataPtr->resolvedNamespace.replace(pos, strlen("__name__"),
- this->dataPtr->name);
- }
+ this->dataPtr->resolvedNamespace =
+ this->ResolveNamespace(this->dataPtr->rawNamespace, _name);
}
/////////////////////////////////////////////////
@@ -578,13 +568,7 @@ std::string Model::RawNamespace() const
void Model::SetNamespace(const std::string &_ns)
{
this->dataPtr->rawNamespace = _ns;
- this->dataPtr->resolvedNamespace = this->dataPtr->rawNamespace;
- auto pos = this->dataPtr->rawNamespace.find("__name__");
- if (pos != std::string::npos)
- {
- this->dataPtr->resolvedNamespace.replace(pos, strlen("__name__"),
- this->dataPtr->name);
- }
+ this->dataPtr->resolvedNamespace = this->ResolveNamespace(_ns, this->Name());
}
/////////////////////////////////////////////////
@@ -1467,3 +1451,17 @@ sdf::Frame Model::PrepareForMerge(sdf::Errors &_errors,
return proxyFrame;
}
+
+std::string Model::ResolveNamespace(const std::string &_rawNs,
+ const std::string &_modelName)
+{
+ std::string resolved = _rawNs;
+ std::size_t pos = 0;
+
+ while ((pos = resolved.find("__name__", pos)) != std::string::npos)
+ {
+ resolved.replace(pos, strlen("__name__"), _modelName);
+ pos += _modelName.size();
+ }
+ return resolved;
+}
\ No newline at end of file
diff --git a/src/Model_TEST.cc b/src/Model_TEST.cc
index 3be11eff0..898249d26 100644
--- a/src/Model_TEST.cc
+++ b/src/Model_TEST.cc
@@ -35,10 +35,10 @@ TEST(DOMModel, Construction)
model.SetName("test_name");
EXPECT_EQ("test_name", model.Name());
- model.SetNamespace("test/__name__/test_ns");
- EXPECT_EQ("test/test_name/test_ns", model.Namespace());
+ model.SetNamespace("test/__name__1/__name__2/test_ns");
+ EXPECT_EQ("test/test_name1/test_name2/test_ns", model.Namespace());
model.SetName("test_model");
- EXPECT_EQ("test/test_model/test_ns", model.Namespace());
+ EXPECT_EQ("test/test_model1/test_model2/test_ns", model.Namespace());
model.SetNamespace("test_namespace");
EXPECT_EQ("test_namespace", model.Namespace());
diff --git a/src/parser.cc b/src/parser.cc
index dfdada3ff..65cd7b958 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -1835,8 +1835,11 @@ bool readXml(tinyxml2::XMLElement *_xml, ElementPtr _sdf,
{
const std::string overrideNamespace =
elemXml->FirstChildElement("namespace")->GetText();
- topLevelElem->GetAttribute("namespace")->SetFromString(
- overrideNamespace);
+ auto nsAttribute = topLevelElem->GetAttribute("namespace");
+ if (nsAttribute)
+ {
+ nsAttribute->SetFromString(overrideNamespace);
+ }
}
tinyxml2::XMLElement *poseElemXml =
From 9a4a462b6263d033347b9a012b4a4ecdf6b5ad94 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Fri, 12 Jun 2026 09:34:19 +0800
Subject: [PATCH 10/12] Use optional string for model namespaces
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/Model.hh | 13 ++++++----
src/Model.cc | 49 ++++++++++++++++++++++++------------
src/Model_TEST.cc | 33 ++++++++++++++++--------
test/integration/includes.cc | 10 +++++---
4 files changed, 70 insertions(+), 35 deletions(-)
diff --git a/include/sdf/Model.hh b/include/sdf/Model.hh
index 6ea56c64a..6bea56a64 100644
--- a/include/sdf/Model.hh
+++ b/include/sdf/Model.hh
@@ -92,12 +92,14 @@ namespace sdf
public: void SetName(const std::string &_name);
/// \brief Get the resolved namespace associated with the model.
- /// \return Resolved namespace of the model.
- public: std::string Namespace() const;
+ /// \return Resolved namespace of the model if it has been set,
+ /// otherwise std::nullopt.
+ public: std::optional Namespace() const;
/// \brief Get the raw namespace associated with the model.
- /// \return Raw namespace of the model.
- public: std::string RawNamespace() const;
+ /// \return Raw namespace of the model if it has been set,
+ /// otherwise std::nullopt.
+ public: std::optional RawNamespace() const;
/// \brief Set the namespace associated with the model.
/// \param[in] _ns Namespace of the model. The `__name__` placeholder
@@ -574,7 +576,8 @@ namespace sdf
/// \param[in] _rawNs The raw namespace string.
/// \param[in] _modelName The model name used to replace "__name__".
/// \return The namespace string with model placeholders resolved.
- private: std::string ResolveNamespace (const std::string &_rawNs,
+ private: std::optional ResolveNamespace (
+ const std::optional &_rawNs,
const std::string &_modelName);
/// \brief Allow Root::Load, World::SetPoseRelativeToGraph, or
diff --git a/src/Model.cc b/src/Model.cc
index 56af66b02..1656c6a7d 100644
--- a/src/Model.cc
+++ b/src/Model.cc
@@ -15,6 +15,7 @@
*
*/
#include
+#include
#include
#include
#include
@@ -44,11 +45,11 @@ class sdf::Model::Implementation
/// \brief Name of the model.
public: std::string name = "";
- /// \brief TODO
- public: std::string rawNamespace = "";
+ /// \brief The unresolved namespace specified by the user.
+ public: std::optional rawNamespace;
- /// \brief TODO
- public: std::string resolvedNamespace = "";
+ /// \brief The namespace after model name placeholders have been resolved.
+ public: std::optional resolvedNamespace;
/// \brief True if this model is specified as static, false otherwise.
public: bool isStatic = false;
@@ -185,7 +186,8 @@ Errors Model::Load(sdf::ElementPtr _sdf, const ParserConfig &_config)
}
// Read the model's namespace
- if (_sdf->HasAttribute("namespace"))
+ auto nsAttribute = _sdf->GetAttribute("namespace");
+ if (nsAttribute && nsAttribute->GetSet())
{
this->dataPtr->rawNamespace = _sdf->Get("namespace", "").first;
this->dataPtr->resolvedNamespace =
@@ -553,13 +555,13 @@ void Model::SetName(const std::string &_name)
}
/////////////////////////////////////////////////
-std::string Model::Namespace() const
+std::optional Model::Namespace() const
{
return this->dataPtr->resolvedNamespace;
}
/////////////////////////////////////////////////
-std::string Model::RawNamespace() const
+std::optional Model::RawNamespace() const
{
return this->dataPtr->rawNamespace;
}
@@ -568,7 +570,8 @@ std::string Model::RawNamespace() const
void Model::SetNamespace(const std::string &_ns)
{
this->dataPtr->rawNamespace = _ns;
- this->dataPtr->resolvedNamespace = this->ResolveNamespace(_ns, this->Name());
+ this->dataPtr->resolvedNamespace =
+ this->ResolveNamespace(this->dataPtr->rawNamespace, this->Name());
}
/////////////////////////////////////////////////
@@ -1129,7 +1132,11 @@ sdf::ElementPtr Model::ToElement(const OutputConfig &_config) const
sdf::ElementPtr includeElem = worldElem->AddElement("include");
includeElem->GetElement("uri")->Set(this->Uri());
includeElem->GetElement("name")->Set(this->Name());
- includeElem->GetElement("namespace")->Set(this->RawNamespace());
+ const auto rawNamespace = this->RawNamespace();
+ if (rawNamespace.has_value())
+ {
+ includeElem->GetElement("namespace")->Set(rawNamespace.value());
+ }
includeElem->GetElement("pose")->Set(this->RawPose());
if (!this->dataPtr->poseRelativeTo.empty())
{
@@ -1153,7 +1160,11 @@ sdf::ElementPtr Model::ToElement(const OutputConfig &_config) const
sdf::ElementPtr elem(new sdf::Element);
sdf::initFile("model.sdf", elem);
elem->GetAttribute("name")->Set(this->Name());
- elem->GetAttribute("namespace")->Set(this->RawNamespace());
+ const auto rawNamespace = this->RawNamespace();
+ if (rawNamespace.has_value())
+ {
+ elem->GetAttribute("namespace")->Set(rawNamespace.value());
+ }
if (!this->dataPtr->canonicalLink.empty())
{
@@ -1452,16 +1463,22 @@ sdf::Frame Model::PrepareForMerge(sdf::Errors &_errors,
return proxyFrame;
}
-std::string Model::ResolveNamespace(const std::string &_rawNs,
- const std::string &_modelName)
+std::optional Model::ResolveNamespace(
+ const std::optional &_rawNs,
+ const std::string &_modelName)
{
- std::string resolved = _rawNs;
+ if (_rawNs == std::nullopt)
+ {
+ return std::nullopt;
+ }
+
+ std::optional resolved = _rawNs;
std::size_t pos = 0;
- while ((pos = resolved.find("__name__", pos)) != std::string::npos)
+ while ((pos = resolved->find("__name__", pos)) != std::string::npos)
{
- resolved.replace(pos, strlen("__name__"), _modelName);
+ resolved->replace(pos, strlen("__name__"), _modelName);
pos += _modelName.size();
}
return resolved;
-}
\ No newline at end of file
+}
diff --git a/src/Model_TEST.cc b/src/Model_TEST.cc
index 898249d26..9e410b9f8 100644
--- a/src/Model_TEST.cc
+++ b/src/Model_TEST.cc
@@ -31,16 +31,23 @@ TEST(DOMModel, Construction)
sdf::Model model;
EXPECT_EQ(nullptr, model.Element());
EXPECT_TRUE(model.Name().empty());
- EXPECT_TRUE(model.Namespace().empty());
+ EXPECT_FALSE(model.Namespace().has_value());
+ EXPECT_FALSE(model.RawNamespace().has_value());
model.SetName("test_name");
EXPECT_EQ("test_name", model.Name());
+ model.SetNamespace("");
+ ASSERT_TRUE(model.Namespace().has_value());
+ EXPECT_EQ("", model.Namespace().value());
model.SetNamespace("test/__name__1/__name__2/test_ns");
- EXPECT_EQ("test/test_name1/test_name2/test_ns", model.Namespace());
+ ASSERT_TRUE(model.Namespace().has_value());
+ EXPECT_EQ("test/test_name1/test_name2/test_ns", model.Namespace().value());
model.SetName("test_model");
- EXPECT_EQ("test/test_model1/test_model2/test_ns", model.Namespace());
+ ASSERT_TRUE(model.Namespace().has_value());
+ EXPECT_EQ("test/test_model1/test_model2/test_ns", model.Namespace().value());
model.SetNamespace("test_namespace");
- EXPECT_EQ("test_namespace", model.Namespace());
+ ASSERT_TRUE(model.Namespace().has_value());
+ EXPECT_EQ("test_namespace", model.Namespace().value());
EXPECT_FALSE(model.Static());
model.SetStatic(true);
@@ -176,7 +183,8 @@ TEST(DOMModel, CopyConstructor)
sdf::Model model2(model);
EXPECT_EQ("test_model", model2.Name());
- EXPECT_EQ("test_ns", model2.Namespace());
+ ASSERT_TRUE(model2.Namespace().has_value());
+ EXPECT_EQ("test_ns", model2.Namespace().value());
}
/////////////////////////////////////////////////
@@ -189,7 +197,8 @@ TEST(DOMModel, CopyAssignmentOperator)
sdf::Model model2;
model2 = model;
EXPECT_EQ("test_model", model2.Name());
- EXPECT_EQ("test_ns", model2.Namespace());
+ ASSERT_TRUE(model2.Namespace().has_value());
+ EXPECT_EQ("test_ns", model2.Namespace().value());
}
/////////////////////////////////////////////////
@@ -201,7 +210,8 @@ TEST(DOMModel, MoveConstructor)
sdf::Model model2(std::move(model));
EXPECT_EQ("test_model", model2.Name());
- EXPECT_EQ("test_ns", model2.Namespace());
+ ASSERT_TRUE(model2.Namespace().has_value());
+ EXPECT_EQ("test_ns", model2.Namespace().value());
}
/////////////////////////////////////////////////
@@ -214,7 +224,8 @@ TEST(DOMModel, MoveAssignmentOperator)
sdf::Model model2;
model2 = std::move(model);
EXPECT_EQ("test_model", model2.Name());
- EXPECT_EQ("test_ns", model2.Namespace());
+ ASSERT_TRUE(model2.Namespace().has_value());
+ EXPECT_EQ("test_ns", model2.Namespace().value());
}
/////////////////////////////////////////////////
@@ -236,8 +247,10 @@ TEST(DOMModel, CopyAssignmentAfterMove)
EXPECT_EQ("model2", model1.Name());
EXPECT_EQ("model1", model2.Name());
- EXPECT_EQ("test_ns2", model1.Namespace());
- EXPECT_EQ("test_ns1", model2.Namespace());
+ ASSERT_TRUE(model1.Namespace().has_value());
+ EXPECT_EQ("test_ns2", model1.Namespace().value());
+ ASSERT_TRUE(model2.Namespace().has_value());
+ EXPECT_EQ("test_ns1", model2.Namespace().value());
}
/////////////////////////////////////////////////
diff --git a/test/integration/includes.cc b/test/integration/includes.cc
index 464a1f796..bbb0c6dd2 100644
--- a/test/integration/includes.cc
+++ b/test/integration/includes.cc
@@ -155,7 +155,7 @@ TEST(IncludesTest, Includes)
const sdf::Model *model = world->ModelByIndex(0);
ASSERT_NE(nullptr, model);
EXPECT_EQ("test_model", model->Name());
- EXPECT_EQ("", model->Namespace());
+ EXPECT_FALSE(model->Namespace().has_value());
EXPECT_FALSE(model->Static());
EXPECT_EQ(1u, model->LinkCount());
ASSERT_FALSE(nullptr == model->LinkByIndex(0));
@@ -204,7 +204,8 @@ TEST(IncludesTest, Includes)
const sdf::Model *model1 = world->ModelByIndex(1);
ASSERT_NE(nullptr, model1);
EXPECT_EQ("override_model_name", model1->Name());
- EXPECT_EQ("override_model_namespace", model1->Namespace());
+ ASSERT_TRUE(model1->Namespace().has_value());
+ EXPECT_EQ("override_model_namespace", *model1->Namespace());
EXPECT_TRUE(model1->Static());
EXPECT_EQ(gz::math::Pose3d(1, 2, 3, 0, 0, 0), model1->RawPose());
EXPECT_EQ("", model1->PoseRelativeTo());
@@ -214,7 +215,8 @@ TEST(IncludesTest, Includes)
const sdf::Model *model2 = world->ModelByIndex(2);
ASSERT_NE(nullptr, model2);
EXPECT_EQ("test_model_with_file", model2->Name());
- EXPECT_EQ("test_model_with_file", model2->Namespace());
+ ASSERT_TRUE(model2->Namespace().has_value());
+ EXPECT_EQ("test_model_with_file", *model2->Namespace());
EXPECT_FALSE(model2->Static());
EXPECT_EQ(1u, model2->LinkCount());
ASSERT_NE(nullptr, model2->LinkByIndex(0));
@@ -263,7 +265,7 @@ TEST(IncludesTest, Includes_15)
const sdf::Model *model = world->ModelByIndex(0);
ASSERT_NE(nullptr, model);
EXPECT_EQ("test_model", model->Name());
- EXPECT_EQ("", model->Namespace());
+ EXPECT_FALSE(model->Namespace().has_value());
EXPECT_EQ(1u, model->LinkCount());
ASSERT_FALSE(nullptr == model->LinkByName("link"));
From 7e75def17c89a1b86bc72747913e5d4f918bdffc Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Fri, 12 Jun 2026 09:43:26 +0800
Subject: [PATCH 11/12] Rename SetNamesapce to SetRawNamespace
Signed-off-by: C88-YQ <1409947012@qq.com>
---
include/sdf/Model.hh | 6 +++---
src/Model.cc | 2 +-
src/Model_TEST.cc | 22 +++++++++++-----------
3 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/include/sdf/Model.hh b/include/sdf/Model.hh
index 6bea56a64..538642cdb 100644
--- a/include/sdf/Model.hh
+++ b/include/sdf/Model.hh
@@ -101,10 +101,10 @@ namespace sdf
/// otherwise std::nullopt.
public: std::optional RawNamespace() const;
- /// \brief Set the namespace associated with the model.
- /// \param[in] _ns Namespace of the model. The `__name__` placeholder
+ /// \brief Set the raw namespace associated with the model.
+ /// \param[in] _ns Raw namespace of the model. The `__name__` placeholder
/// will be replaced with the model name.
- public: void SetNamespace(const std::string &_ns);
+ public: void SetRawNamespace(const std::string &_ns);
/// \brief Check if this model should be static.
/// A static model is one that is not subject to physical forces (in other
diff --git a/src/Model.cc b/src/Model.cc
index 1656c6a7d..616fb8d60 100644
--- a/src/Model.cc
+++ b/src/Model.cc
@@ -567,7 +567,7 @@ std::optional Model::RawNamespace() const
}
/////////////////////////////////////////////////
-void Model::SetNamespace(const std::string &_ns)
+void Model::SetRawNamespace(const std::string &_ns)
{
this->dataPtr->rawNamespace = _ns;
this->dataPtr->resolvedNamespace =
diff --git a/src/Model_TEST.cc b/src/Model_TEST.cc
index 9e410b9f8..d9fdcfb32 100644
--- a/src/Model_TEST.cc
+++ b/src/Model_TEST.cc
@@ -36,16 +36,16 @@ TEST(DOMModel, Construction)
model.SetName("test_name");
EXPECT_EQ("test_name", model.Name());
- model.SetNamespace("");
+ model.SetRawNamespace("");
ASSERT_TRUE(model.Namespace().has_value());
EXPECT_EQ("", model.Namespace().value());
- model.SetNamespace("test/__name__1/__name__2/test_ns");
+ model.SetRawNamespace("test/__name__1/__name__2/test_ns");
ASSERT_TRUE(model.Namespace().has_value());
EXPECT_EQ("test/test_name1/test_name2/test_ns", model.Namespace().value());
model.SetName("test_model");
ASSERT_TRUE(model.Namespace().has_value());
EXPECT_EQ("test/test_model1/test_model2/test_ns", model.Namespace().value());
- model.SetNamespace("test_namespace");
+ model.SetRawNamespace("test_namespace");
ASSERT_TRUE(model.Namespace().has_value());
EXPECT_EQ("test_namespace", model.Namespace().value());
@@ -179,7 +179,7 @@ TEST(DOMModel, CopyConstructor)
{
sdf::Model model;
model.SetName("test_model");
- model.SetNamespace("test_ns");
+ model.SetRawNamespace("test_ns");
sdf::Model model2(model);
EXPECT_EQ("test_model", model2.Name());
@@ -192,7 +192,7 @@ TEST(DOMModel, CopyAssignmentOperator)
{
sdf::Model model;
model.SetName("test_model");
- model.SetNamespace("test_ns");
+ model.SetRawNamespace("test_ns");
sdf::Model model2;
model2 = model;
@@ -206,7 +206,7 @@ TEST(DOMModel, MoveConstructor)
{
sdf::Model model;
model.SetName("test_model");
- model.SetNamespace("test_ns");
+ model.SetRawNamespace("test_ns");
sdf::Model model2(std::move(model));
EXPECT_EQ("test_model", model2.Name());
@@ -219,7 +219,7 @@ TEST(DOMModel, MoveAssignmentOperator)
{
sdf::Model model;
model.SetName("test_model");
- model.SetNamespace("test_ns");
+ model.SetRawNamespace("test_ns");
sdf::Model model2;
model2 = std::move(model);
@@ -233,11 +233,11 @@ TEST(DOMModel, CopyAssignmentAfterMove)
{
sdf::Model model1;
model1.SetName("model1");
- model1.SetNamespace("test_ns1");
+ model1.SetRawNamespace("test_ns1");
sdf::Model model2;
model2.SetName("model2");
- model2.SetNamespace("test_ns2");
+ model2.SetRawNamespace("test_ns2");
// This is similar to what std::swap does except it uses std::move for each
// assignment
@@ -365,7 +365,7 @@ TEST(DOMModel, ToElement)
sdf::Model model;
model.SetName("my-model");
- model.SetNamespace("__name__/ns");
+ model.SetRawNamespace("__name__/ns");
model.SetStatic(true);
model.SetSelfCollide(true);
model.SetAllowAutoDisable(true);
@@ -483,7 +483,7 @@ TEST(DOMModel, Uri)
"https://fuel.gazebosim.org/1.0/openrobotics/models/my-model";
model.SetName(name);
- model.SetNamespace(ns);
+ model.SetRawNamespace(ns);
model.SetRawPose(pose);
model.SetStatic(true);
model.SetPlacementFrameName("link0");
From a4003070a4983fb3aa4bad4b09e63b9a56fc2107 Mon Sep 17 00:00:00 2001
From: C88-YQ <1409947012@qq.com>
Date: Wed, 17 Jun 2026 20:38:10 +0800
Subject: [PATCH 12/12] Add ns element into sdf/1.11/world.sdf
Signed-off-by: C88-YQ <1409947012@qq.com>
---
sdf/1.11/world.sdf | 4 ++++
sdf/1.12/world.sdf | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/sdf/1.11/world.sdf b/sdf/1.11/world.sdf
index 6235e193b..62df59dde 100644
--- a/sdf/1.11/world.sdf
+++ b/sdf/1.11/world.sdf
@@ -40,6 +40,10 @@
Override the static value of the included entity.
+
+ Override the namespace of the included model.
+
+
diff --git a/sdf/1.12/world.sdf b/sdf/1.12/world.sdf
index 4e578a6eb..a62f23628 100644
--- a/sdf/1.12/world.sdf
+++ b/sdf/1.12/world.sdf
@@ -37,7 +37,7 @@
- Override the namespace of the included entity.
+ Override the namespace of the included model.