Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4d2d969
Add ns component
C88-YQ May 29, 2026
fb964ab
Create ns component when creating entity
C88-YQ May 29, 2026
0908f9e
Add ns helper function
C88-YQ May 29, 2026
c826ea7
Add test for hasNamespace & scopedNamespace
C88-YQ Jun 5, 2026
e881c21
Pass codecheck
C88-YQ Jun 5, 2026
3808622
Remove the namespace attribute of world & sensor & particleEmitter
C88-YQ Jun 5, 2026
f1eea9c
Add test for SdfEntityCreator
C88-YQ Jun 5, 2026
6126f27
Add ns support to diff_drive
C88-YQ Jun 5, 2026
703cb12
Add test for ns support in diff_drive
C88-YQ Jun 5, 2026
67539db
Only prepend namespace to relative topic names
C88-YQ Jun 8, 2026
571a3bc
Merge branch 'main' into ns_support
C88-YQ Jun 8, 2026
8a8fcf8
Add namespace support to entity creation services
C88-YQ Jun 11, 2026
87553b5
Add tests for namespace support in entity creation services
C88-YQ Jun 11, 2026
7f93c87
Adapt namespace support for gz-msgs
C88-YQ Jun 12, 2026
99214ab
Avoid creating Namespace component without explicit namespace
C88-YQ Jun 17, 2026
68b174b
Update UserCommands test for explicit namespace component creation
C88-YQ Jun 17, 2026
85d298f
Update hasNamespace
C88-YQ Jun 18, 2026
7f0667e
Always create Namespace components for model entities
C88-YQ Jun 23, 2026
987c698
Stop scoped namespace lookup at absolute namespace
C88-YQ Jun 26, 2026
fdcfadc
Add ns support to systems/imu
C88-YQ Jun 26, 2026
728c33c
Add ns support to systems/AckermannSteering
C88-YQ Jun 26, 2026
5981615
Add ns support to systems/multicopter_control/MulticopterVelocityControl
C88-YQ Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions include/gz/sim/EntityComponentManager.hh
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ namespace gz
/// \param[in] _name The name that should be given to the cloned entity.
/// Set this to an empty string if the cloned entity name should be
/// auto-generated to something unique.
/// \param[in] _ns The namespace that should be given to the cloned entity.
/// \param[in] _allowRename True if _name can be modified to be a unique
/// name if it isn't already a unique name. False if _name cannot be
/// modified to be a unique name. If _allowRename is set to False, and
Expand All @@ -117,7 +118,8 @@ namespace gz
/// cloned.
/// \sa Clone
public: Entity Clone(Entity _entity, Entity _parent,
const std::string &_name, bool _allowRename);
const std::string &_name, const std::string &_ns,
bool _allowRename);

/// \brief Get the number of entities on the server.
/// \return Entity count.
Expand Down Expand Up @@ -371,13 +373,15 @@ namespace gz
/// \param[in] _entity The entity to clone.
/// \param[in] _parent The parent of the cloned entity.
/// \param[in] _name The name that should be given to the cloned entity.
/// \param[in] _ns The namespace that should be given to the cloned entity.
/// \param[in] _allowRename True if _name can be modified to be a unique
/// name if it isn't already a unique name. False if _name cannot be
/// modified to be a unique name.
/// \return The cloned entity. kNullEntity is returned if cloning failed.
/// \sa Clone
private: Entity CloneImpl(Entity _entity, Entity _parent,
const std::string &_name, bool _allowRename);
const std::string &_name, const std::string &_ns,
bool _allowRename);

/// \brief A version of Each() that doesn't use a cache. The cached
/// version, Each(), is preferred.
Expand Down
17 changes: 17 additions & 0 deletions include/gz/sim/Util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ namespace gz
const EntityComponentManager &_ecm, const std::string &_delim = "/",
bool _includePrefix = true);

/// \brief Helper function to check whether any entity has a non-empty
/// namespace component.
/// \param[in] _ecm Immutable reference to ECM.
/// \return True if any entity has a non-empty namespace.
bool GZ_SIM_VISIBLE hasNamespace(
const EntityComponentManager &_ecm);

/// \brief Helper function to generate the full scoped namespace of an entity,
/// including namespaces inherited from all parent entities.
/// \param[in] _ecm Immutable reference to ECM.
/// \param[in] _entity Entity to get the scoped namespace for.
/// \param[in] _delim Delimiter to put between namespaces, defaults to "/".
/// \return Scoped namespace, or empty string if no namespace is found.
std::string GZ_SIM_VISIBLE scopedNamespace(
const EntityComponentManager &_ecm, const Entity &_entity,
const std::string &_delim = "/");

/// \brief Helper function to get an entity given its scoped name.
/// The scope may start at any level by default. For example, in this
/// hierarchy:
Expand Down
43 changes: 43 additions & 0 deletions include/gz/sim/components/Namespace.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2026 Jiayi Cai
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef GZ_SIM_COMPONENTS_NAMESPACE_HH_
#define GZ_SIM_COMPONENTS_NAMESPACE_HH_

#include <string>
#include <gz/sim/components/Factory.hh>
#include <gz/sim/components/Component.hh>
#include <gz/sim/components/Serialization.hh>
#include <gz/sim/config.hh>

namespace gz
{
namespace sim
{
// Inline bracket to help doxygen filtering.
inline namespace GZ_SIM_VERSION_NAMESPACE {
namespace components
{
/// \brief This component holds an entity's namespace.
using Namespace = Component<std::string, class NamespaceTag,
serializers::StringSerializer>;
GZ_SIM_REGISTER_COMPONENT("gz_sim_components.Namespace", Namespace)
}
}
}
}

#endif
34 changes: 28 additions & 6 deletions src/EntityComponentManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
#include "gz/sim/components/Factory.hh"
#include "gz/sim/components/Joint.hh"
#include "gz/sim/components/Link.hh"
#include "gz/sim/components/Model.hh"
#include "gz/sim/components/Name.hh"
#include "gz/sim/components/Namespace.hh"
#include "gz/sim/components/ParentEntity.hh"
#include "gz/sim/components/ParentLinkName.hh"
#include "gz/sim/components/Recreate.hh"
Expand Down Expand Up @@ -394,15 +396,16 @@ Entity EntityComponentManagerPrivate::CreateEntityImplementation(Entity _entity)

/////////////////////////////////////////////////
Entity EntityComponentManager::Clone(Entity _entity, Entity _parent,
const std::string &_name, bool _allowRename)
const std::string &_name, const std::string &_ns,
bool _allowRename)
{
// Clear maps so they're populated for the entity being cloned
this->dataPtr->oldToClonedCanonicalLink.clear();
this->dataPtr->oldModelCanonicalLink.clear();
this->dataPtr->originalToClonedLink.clear();
this->dataPtr->clonedToOriginalJointLinks.clear();

auto clonedEntity = this->CloneImpl(_entity, _parent, _name, _allowRename);
auto clonedEntity = this->CloneImpl(_entity, _parent, _name, _ns, _allowRename);

if (kNullEntity != clonedEntity)
{
Expand Down Expand Up @@ -455,7 +458,8 @@ Entity EntityComponentManager::Clone(Entity _entity, Entity _parent,

/////////////////////////////////////////////////
Entity EntityComponentManager::CloneImpl(Entity _entity, Entity _parent,
const std::string &_name, bool _allowRename)
const std::string &_name, const std::string &_ns,
bool _allowRename)
{
auto uniqueNameGenerated = false;

Expand Down Expand Up @@ -529,13 +533,30 @@ Entity EntityComponentManager::CloneImpl(Entity _entity, Entity _parent,
}
this->CreateComponent(clonedEntity, components::Name(clonedName));

auto originalNsComp = this->Component<components::Namespace>(_entity);
if (nullptr != originalNsComp)
{
std::string ns;
if (!_ns.empty())
{
ns = _ns;
}
else
{
// If the namespace is not provided, use the original entity's namespace.
ns = originalNsComp->Data();
}
this->CreateComponent(clonedEntity, components::Namespace(ns));
}

// copy all components from _entity to clonedEntity
for (const auto &type : this->ComponentTypes(_entity))
{
// skip the Name and ParentEntity components since those were already
// skip the Name, Namespace and ParentEntity components since those were already
// handled above
if ((type == components::Name::typeId) ||
(type == components::ParentEntity::typeId))
(type == components::ParentEntity::typeId) ||
(type == components::Namespace::typeId))
continue;

auto originalComp = this->ComponentImplementation(_entity, type);
Expand Down Expand Up @@ -641,8 +662,9 @@ Entity EntityComponentManager::CloneImpl(Entity _entity, Entity _parent,
name = nameComp->Data();
}
}

auto clonedChild = this->CloneImpl(childEntity, clonedEntity, name,
_allowRename);
"", _allowRename);
if (kNullEntity == clonedChild)
{
gzerr << "Cloning child entity [" << childEntity << "] failed.\n";
Expand Down
40 changes: 27 additions & 13 deletions src/EntityComponentManager_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "gz/sim/components/Joint.hh"
#include "gz/sim/components/Link.hh"
#include "gz/sim/components/Name.hh"
#include "gz/sim/components/Namespace.hh"
#include "gz/sim/components/ParentEntity.hh"
#include "gz/sim/components/ParentLinkName.hh"
#include "gz/sim/components/Pose.hh"
Expand Down Expand Up @@ -2814,11 +2815,13 @@ TEST_P(EntityComponentManagerFixture,

Entity topLevelEntity = manager.CreateEntity();
manager.CreateComponent(topLevelEntity, components::Name("topLevelEntity"));
manager.CreateComponent(topLevelEntity, components::Namespace("topLevelNs"));
manager.CreateComponent(topLevelEntity, IntComponent(123));
manager.CreateComponent(topLevelEntity, StringComponent("string0"));

Entity childEntity1 = manager.CreateEntity();
manager.CreateComponent(childEntity1, components::Name("childEntity1"));
manager.CreateComponent(childEntity1, components::Namespace("childNs1"));
manager.CreateComponent(childEntity1,
components::ParentEntity(topLevelEntity));
manager.CreateComponent(childEntity1, IntComponent(456));
Expand All @@ -2827,11 +2830,14 @@ TEST_P(EntityComponentManagerFixture,
Entity grandChildEntity1 = manager.CreateEntity();
manager.CreateComponent(grandChildEntity1,
components::Name("grandChildEntity1"));
manager.CreateComponent(grandChildEntity1,
components::Namespace("grandChildNs1"));
manager.CreateComponent(grandChildEntity1,
components::ParentEntity(childEntity1));

Entity childEntity2 = manager.CreateEntity();
manager.CreateComponent(childEntity2, components::Name("childEntity2"));
manager.CreateComponent(childEntity2, components::Namespace("childNs2"));
manager.CreateComponent(childEntity2,
components::ParentEntity(topLevelEntity));
manager.CreateComponent(childEntity2, IntComponent(789));
Expand Down Expand Up @@ -2859,6 +2865,8 @@ TEST_P(EntityComponentManagerFixture,
components::ParentEntity::typeId));
CompareEntityComponents<components::Name>(manager, topLevelEntity,
_clonedEntity, false);
CompareEntityComponents<components::Namespace>(manager, topLevelEntity,
_clonedEntity, true);
CompareEntityComponents<IntComponent>(manager, topLevelEntity,
_clonedEntity, true);
CompareEntityComponents<StringComponent>(manager, topLevelEntity,
Expand All @@ -2869,7 +2877,7 @@ TEST_P(EntityComponentManagerFixture,

// clone the topLevelEntity
auto clonedTopLevelEntity =
manager.Clone(topLevelEntity, kNullEntity, "", allowRename);
manager.Clone(topLevelEntity, kNullEntity, "", "", allowRename);
EXPECT_EQ(8u, manager.EntityCount());
clonedEntities.insert(clonedTopLevelEntity);
validateTopLevelClone(clonedTopLevelEntity);
Expand All @@ -2887,20 +2895,24 @@ TEST_P(EntityComponentManagerFixture,
EXPECT_EQ(clonedTopLevelEntity, parentComp->Data());
CompareEntityComponents<components::Name>(manager, _clonedChild,
_originalChild, false);
CompareEntityComponents<components::Namespace>(manager, _clonedChild,
_originalChild, true);
CompareEntityComponents<IntComponent>(manager, _clonedChild,
_originalChild, true);
CompareEntityComponents<StringComponent>(manager, _clonedChild,
_originalChild, true);
};

auto validateGrandChildClone =
[&](const Entity _clonedEntity, bool _sameParent)
[&](const Entity _clonedEntity, bool _sameNs, bool _sameParent)
{
EXPECT_NE(kNullEntity, _clonedEntity);
EXPECT_EQ(manager.ComponentTypes(_clonedEntity),
manager.ComponentTypes(grandChildEntity1));
CompareEntityComponents<components::Name>(manager, _clonedEntity,
grandChildEntity1, false);
CompareEntityComponents<components::Namespace>(manager, _clonedEntity,
grandChildEntity1, _sameNs);
CompareEntityComponents<components::ParentEntity>(manager,
_clonedEntity, grandChildEntity1, _sameParent);
EXPECT_TRUE(manager.EntitiesByComponents(
Expand Down Expand Up @@ -2930,7 +2942,7 @@ TEST_P(EntityComponentManagerFixture,

ASSERT_EQ(1u, clonedGrandChildren.size());
clonedEntities.insert(clonedGrandChildren[0]);
validateGrandChildClone(clonedGrandChildren[0], false);
validateGrandChildClone(clonedGrandChildren[0], true, false);
auto parentComp =
manager.Component<components::ParentEntity>(clonedGrandChildren[0]);
ASSERT_NE(nullptr, parentComp);
Expand All @@ -2950,31 +2962,33 @@ TEST_P(EntityComponentManagerFixture,
EXPECT_TRUE(comparedToOriginalChild);
}

// clone a child entity
// clone a child entity with a namespace provided
auto grandChildParentComp =
manager.Component<components::ParentEntity>(grandChildEntity1);
ASSERT_NE(nullptr, grandChildParentComp);
auto clonedGrandChildEntity = manager.Clone(grandChildEntity1,
grandChildParentComp->Data(), "", allowRename);
grandChildParentComp->Data(), "", "clonedGrandChildNs", allowRename);
EXPECT_EQ(9u, manager.EntityCount());
clonedEntities.insert(clonedGrandChildEntity);
validateGrandChildClone(clonedGrandChildEntity, true);
validateGrandChildClone(clonedGrandChildEntity, false, true);

// Try cloning an entity with a name that already exists, but allow renaming.
// This should succeed and generate a cloned entity with a unique name.
// Try cloning an entity with a name that already exists, but allow renaming
// and without a namespace provided.
// This should succeed and generate a cloned entity with a unique name
// and a same namespace.
const auto existingName = "grandChildEntity1";
EXPECT_NE(kNullEntity,
manager.EntityByComponents(components::Name(existingName)));
auto renamedClonedEntity = manager.Clone(grandChildEntity1,
grandChildParentComp->Data(), existingName, allowRename);
grandChildParentComp->Data(), existingName, "", allowRename);
EXPECT_EQ(10u, manager.EntityCount());
clonedEntities.insert(clonedGrandChildEntity);
validateGrandChildClone(renamedClonedEntity, true);
validateGrandChildClone(renamedClonedEntity, true, true);

// Try cloning an entity with a name that already exists, without allowing
// renaming. This should fail since entities should have unique names.
auto failedClonedEntity = manager.Clone(grandChildEntity1,
grandChildParentComp->Data(), existingName, noAllowRename);
grandChildParentComp->Data(), existingName, "", noAllowRename);
EXPECT_EQ(10u, manager.EntityCount());
EXPECT_EQ(kNullEntity, failedClonedEntity);

Expand Down Expand Up @@ -3010,7 +3024,7 @@ TEST_P(EntityComponentManagerFixture,

// clone a joint that has a parent and child link.
auto clonedParentModelEntity = manager.Clone(parentModelEntity, kNullEntity,
"", true);
"", "", true);
ASSERT_NE(kNullEntity, clonedParentModelEntity);
// We just cloned a model with two links and a joint, a total of 4 new
// entities.
Expand Down Expand Up @@ -3062,7 +3076,7 @@ TEST_P(EntityComponentManagerFixture,

// try to clone an entity that does not exist
EXPECT_EQ(kNullEntity, manager.Clone(kNullEntity, topLevelEntity, "",
allowRename));
"", allowRename));
EXPECT_EQ(18u, manager.EntityCount());
}

Expand Down
11 changes: 11 additions & 0 deletions src/SdfEntityCreator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
#include "gz/sim/components/Material.hh"
#include "gz/sim/components/Model.hh"
#include "gz/sim/components/Name.hh"
#include "gz/sim/components/Namespace.hh"
#include "gz/sim/components/NavSat.hh"
#include "gz/sim/components/ParentEntity.hh"
#include "gz/sim/components/ParentLinkName.hh"
Expand Down Expand Up @@ -526,6 +527,16 @@ Entity SdfEntityCreator::CreateEntities(const sdf::Model *_model,
components::Pose(ResolveSdfPose(_model->SemanticPose())));
this->dataPtr->ecm->CreateComponent(modelEntity,
components::Name(_model->Name()));
if (_model->Namespace().has_value())
{
this->dataPtr->ecm->CreateComponent(modelEntity,
components::Namespace(_model->Namespace().value()));
}
else
{
this->dataPtr->ecm->CreateComponent(modelEntity,
components::Namespace(""));
}
bool isStatic = _model->Static() || _staticParent;
this->dataPtr->ecm->CreateComponent(modelEntity,
components::Static(isStatic));
Expand Down
Loading
Loading