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.