Check for duplicated names when setting element names.

PiperOrigin-RevId: 778827590
Change-Id: I77a208c1571cf735cae80840ea830aa347681f71
This commit is contained in:
Alessio Quaglino
2025-07-03 03:19:51 -07:00
committed by Copybara-Service
parent 594e17074a
commit 564c51ddb9
12 changed files with 96 additions and 74 deletions
+1 -1
View File
@@ -4235,7 +4235,7 @@ Attribute setters
.. mujoco-include:: mjs_setName .. mujoco-include:: mjs_setName
Set element's name. Set element's name, return 0 on success.
.. _mjs_setBuffer: .. _mjs_setBuffer:
+3
View File
@@ -17,6 +17,9 @@ General
:ref:`visual/global/cameraid<visual-global-cameraid>`. :ref:`visual/global/cameraid<visual-global-cameraid>`.
- Added support to only sync the state in the Python :ref:`passive viewer<PyViewerPassive>`'s ``Sync`` method, this is - Added support to only sync the state in the Python :ref:`passive viewer<PyViewerPassive>`'s ``Sync`` method, this is
useful to improve performance. The default behavior is unchanged and copies the entire model and data. useful to improve performance. The default behavior is unchanged and copies the entire model and data.
- In the mjSpec C API, directly setting an element's name using :ref:`mjs_setString` has been replaced with a new
function :ref:`mjs_setName` which allows checking for naming collisions at set-time rather than compile-time, for
earlier catching of errors.
Bug fixes Bug fixes
^^^^^^^^^ ^^^^^^^^^
+1 -1
View File
@@ -3456,7 +3456,7 @@ mjsElement* mjs_firstChild(mjsBody* body, mjtObj type, int recurse);
mjsElement* mjs_nextChild(mjsBody* body, mjsElement* child, int recurse); mjsElement* mjs_nextChild(mjsBody* body, mjsElement* child, int recurse);
mjsElement* mjs_firstElement(mjSpec* s, mjtObj type); mjsElement* mjs_firstElement(mjSpec* s, mjtObj type);
mjsElement* mjs_nextElement(mjSpec* s, mjsElement* element); mjsElement* mjs_nextElement(mjSpec* s, mjsElement* element);
void mjs_setName(mjsElement* element, const char* name); int mjs_setName(mjsElement* element, const char* name);
void mjs_setBuffer(mjByteVec* dest, const void* array, int size); void mjs_setBuffer(mjByteVec* dest, const void* array, int size);
void mjs_setString(mjString* dest, const char* text); void mjs_setString(mjString* dest, const char* text);
void mjs_setStringVec(mjStringVec* dest, const char* text); void mjs_setStringVec(mjStringVec* dest, const char* text);
+2 -2
View File
@@ -1600,8 +1600,8 @@ MJAPI mjsElement* mjs_nextElement(mjSpec* s, mjsElement* element);
//---------------------------------- Attribute setters --------------------------------------------- //---------------------------------- Attribute setters ---------------------------------------------
// Set element's name. // Set element's name, return 0 on success.
MJAPI void mjs_setName(mjsElement* element, const char* name); MJAPI int mjs_setName(mjsElement* element, const char* name);
// Copy buffer. // Copy buffer.
MJAPI void mjs_setBuffer(mjByteVec* dest, const void* array, int size); MJAPI void mjs_setBuffer(mjByteVec* dest, const void* array, int size);
@@ -677,7 +677,9 @@ def generate_name() -> None:
return mjs_getName(self.element); return mjs_getName(self.element);
}}, }},
[](raw::{titlecase}& self, std::string& name) -> void {{ [](raw::{titlecase}& self, std::string& name) -> void {{
mjs_setName(self.element, name.c_str()); if (mjs_setName(self.element, name.c_str())) {{
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}}
}}, py::return_value_policy::reference_internal); }}, py::return_value_policy::reference_internal);
""" """
print(code) print(code)
+2 -2
View File
@@ -10194,7 +10194,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
('mjs_setName', ('mjs_setName',
FunctionDecl( FunctionDecl(
name='mjs_setName', name='mjs_setName',
return_type=ValueType(name='void'), return_type=ValueType(name='int'),
parameters=( parameters=(
FunctionParameterDecl( FunctionParameterDecl(
name='element', name='element',
@@ -10209,7 +10209,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
), ),
), ),
), ),
doc="Set element's name.", doc="Set element's name, return 0 on success.",
)), )),
('mjs_setBuffer', ('mjs_setBuffer',
FunctionDecl( FunctionDecl(
+2 -7
View File
@@ -887,7 +887,7 @@ class SpecsTest(absltest.TestCase):
): ):
s.compile() s.compile()
def test_recompile_error(self): def test_duplicate_name_error(self):
main_xml = """ main_xml = """
<mujoco> <mujoco>
<worldbody> <worldbody>
@@ -899,16 +899,11 @@ class SpecsTest(absltest.TestCase):
""" """
spec = mujoco.MjSpec.from_string(main_xml) spec = mujoco.MjSpec.from_string(main_xml)
model = spec.compile()
data = mujoco.MjData(model)
spec.add_material().name = 'yellow' spec.add_material().name = 'yellow'
spec.add_material().name = 'yellow'
with self.assertRaisesRegex( with self.assertRaisesRegex(
ValueError, "Error: repeated name 'yellow' in material" ValueError, "Error: repeated name 'yellow' in material"
): ):
spec.recompile(model, data) spec.add_material().name = 'yellow'
def test_delete_unused_plugin(self): def test_delete_unused_plugin(self):
spec = mujoco.MjSpec.from_string(""" spec = mujoco.MjSpec.from_string("""
+9 -2
View File
@@ -1342,14 +1342,21 @@ mjsPlugin* mjs_asPlugin(mjsElement* element) {
// set element name // set element name
void mjs_setName(mjsElement* element, const char* name) { int mjs_setName(mjsElement* element, const char* name) {
if (element->elemtype == mjOBJ_DEFAULT) { if (element->elemtype == mjOBJ_DEFAULT) {
mjCDef* def = static_cast<mjCDef*>(element); mjCDef* def = static_cast<mjCDef*>(element);
def->name = std::string(name); def->name = std::string(name);
return; return 0;
} }
mjCBase* baseC = static_cast<mjCBase*>(element); mjCBase* baseC = static_cast<mjCBase*>(element);
baseC->name = std::string(name); baseC->name = std::string(name);
try {
baseC->model->CheckRepeat(element->elemtype);
} catch (mjCError& e) {
baseC->model->SetError(e);
return -1;
}
return 0;
} }
+2 -2
View File
@@ -339,8 +339,8 @@ MJAPI mjsPlugin* mjs_asPlugin(mjsElement* element);
//---------------------------------- Attribute setters --------------------------------------------- //---------------------------------- Attribute setters ---------------------------------------------
// Set element's name. // Set element's name, return 0 on success.
MJAPI void mjs_setName(mjsElement* element, const char* name); MJAPI int mjs_setName(mjsElement* element, const char* name);
// Copy buffer. // Copy buffer.
MJAPI void mjs_setBuffer(mjByteVec* dest, const void* array, int size); MJAPI void mjs_setBuffer(mjByteVec* dest, const void* array, int size);
+64 -50
View File
@@ -116,50 +116,6 @@ bool IsNullPose(const T pos[3], const T quat[4]) {
return IsSamePose(pos, zero, quat, qunit); return IsSamePose(pos, zero, quat, qunit);
} }
// set ids, check for repeated names
template <class T>
static void processlist(mjListKeyMap& ids, vector<T*>& list,
mjtObj type, bool checkrepeat = true) {
// assign ids for regular elements
if (type < mjNOBJECT) {
for (size_t i=0; i < list.size(); i++) {
// check for incompatible id setting; SHOULD NOT OCCUR
if (list[i]->id != -1 && list[i]->id != i) {
throw mjCError(list[i], "incompatible id in %s array, position %d", mju_type2Str(type), i);
}
// id equals position in array
list[i]->id = i;
// add to ids map
ids[type][list[i]->name] = i;
}
}
// check for repeated names
if (checkrepeat) {
// created vectors with all names
vector<string> allnames;
for (size_t i=0; i < list.size(); i++) {
if (!list[i]->name.empty()) {
allnames.push_back(list[i]->name);
}
}
// sort and check for duplicates
if (allnames.size() > 1) {
std::sort(allnames.begin(), allnames.end());
auto adjacent = std::adjacent_find(allnames.begin(), allnames.end());
if (adjacent != allnames.end()) {
string msg = "repeated name '" + *adjacent + "' in " + mju_type2Str(type);
throw mjCError(nullptr, "%s", msg.c_str());
}
}
}
}
} // namespace } // namespace
//---------------------------------- CONSTRUCTOR AND DESTRUCTOR ------------------------------------ //---------------------------------- CONSTRUCTOR AND DESTRUCTOR ------------------------------------
@@ -308,7 +264,7 @@ void mjCModel::CopyList(std::vector<T*>& dest,
dest.back()->CopyPlugin(); dest.back()->CopyPlugin();
} }
if (!dest.empty()) { if (!dest.empty()) {
processlist(ids, dest, dest[0]->elemtype); ProcessList_(ids, dest, dest[0]->elemtype);
} }
} }
@@ -548,7 +504,7 @@ void mjCModel::RemoveFromList(std::vector<T*>& list, const mjCModel& other) {
} }
if (removed > 0 && !list.empty()) { if (removed > 0 && !list.empty()) {
// if any elements were removed, update ids using processlist // if any elements were removed, update ids using processlist
processlist(ids, list, list[0]->elemtype, /*checkrepeat=*/false); ProcessList_(ids, list, list[0]->elemtype, /*checkrepeat=*/false);
} }
} }
@@ -604,7 +560,7 @@ void mjCModel::RemovePlugins() {
// if any elements were removed, update ids using processlist // if any elements were removed, update ids using processlist
if (removed > 0 && !plugins_.empty()) { if (removed > 0 && !plugins_.empty()) {
processlist(ids, plugins_, plugins_[0]->elemtype, /*checkrepeat=*/false); ProcessList_(ids, plugins_, plugins_[0]->elemtype, /*checkrepeat=*/false);
} }
} }
@@ -4052,7 +4008,7 @@ void mjCModel::FuseStatic(void) {
} }
// remove empty names // remove empty names
processlist(ids, bodies_, mjOBJ_BODY, true); ProcessList_(ids, bodies_, mjOBJ_BODY, /*checkrepeat=*/true);
} }
@@ -4083,12 +4039,70 @@ void mjCModel::ProcessLists(bool checkrepeat) {
for (int i = 0; i < mjNOBJECT; i++) { for (int i = 0; i < mjNOBJECT; i++) {
if (i != mjOBJ_XBODY && object_lists_[i]) { if (i != mjOBJ_XBODY && object_lists_[i]) {
ids[i].clear(); ids[i].clear();
processlist(ids, *object_lists_[i], (mjtObj) i, checkrepeat); ProcessList_(ids, *object_lists_[i], (mjtObj) i, checkrepeat);
} }
} }
// check repeated names in meta elements // check repeated names in meta elements
processlist(ids, frames_, mjOBJ_FRAME, checkrepeat); ProcessList_(ids, frames_, mjOBJ_FRAME, checkrepeat);
}
// set ids, check for repeated names
template <class T>
void mjCModel::ProcessList_(mjListKeyMap& ids, vector<T*>& list,
mjtObj type, bool checkrepeat) {
// assign ids for regular elements
if (type < mjNOBJECT) {
for (size_t i=0; i < list.size(); i++) {
// check for incompatible id setting; SHOULD NOT OCCUR
if (list[i]->id != -1 && list[i]->id != i) {
throw mjCError(list[i], "incompatible id in %s array, position %d", mju_type2Str(type), i);
}
// id equals position in array
list[i]->id = i;
// add to ids map
ids[type][list[i]->name] = i;
}
}
// check for repeated names
if (checkrepeat) {
CheckRepeat(type);
}
}
// check for repeated names in list
void mjCModel::CheckRepeat(mjtObj type) {
std::vector<mjCBase*>* list = nullptr;
if (type < mjNOBJECT) {
list = object_lists_[type];
} else if (type == mjOBJ_FRAME) {
list = (std::vector<mjCBase*>*) &frames_;
}
// created vectors with all names
vector<string> allnames;
for (size_t i=0; i < list->size(); i++) {
if (!(*list)[i]->name.empty()) {
allnames.push_back((*list)[i]->name);
}
}
// sort and check for duplicates
if (allnames.size() > 1) {
std::sort(allnames.begin(), allnames.end());
auto adjacent = std::adjacent_find(allnames.begin(), allnames.end());
if (adjacent != allnames.end()) {
string msg = "repeated name '" + *adjacent + "' in " + mju_type2Str(type);
throw mjCError(nullptr, "%s", msg.c_str());
}
}
} }
+7
View File
@@ -323,6 +323,9 @@ class mjCModel : public mjCModel_, private mjSpec {
// set attached flag // set attached flag
void SetAttached(bool deepcopy) { attached_ |= !deepcopy; } void SetAttached(bool deepcopy) { attached_ |= !deepcopy; }
// check for repeated names in list
void CheckRepeat(mjtObj type);
private: private:
// settings for each defaults class // settings for each defaults class
std::vector<mjCDef*> defaults_; std::vector<mjCDef*> defaults_;
@@ -411,6 +414,10 @@ class mjCModel : public mjCModel_, private mjSpec {
// populate objects ids // populate objects ids
void ProcessLists(bool checkrepeat = true); void ProcessLists(bool checkrepeat = true);
// process list of objects
template <class T> void ProcessList_(mjListKeyMap& ids, std::vector<T*>& list,
mjtObj type, bool checkrepeat = true);
// reset lists of kinematic tree // reset lists of kinematic tree
void ResetTreeLists(); void ResetTreeLists();
-6
View File
@@ -1403,7 +1403,6 @@ mjCJoint* mjCBody::AddFreeJoint() {
model->ResetTreeLists(); model->ResetTreeLists();
model->MakeTreeLists(); model->MakeTreeLists();
// update signature // update signature
model->spec.element->signature = model->Signature(); model->spec.element->signature = model->Signature();
return obj; return obj;
@@ -1425,7 +1424,6 @@ mjCJoint* mjCBody::AddJoint(mjCDef* _def) {
model->ResetTreeLists(); model->ResetTreeLists();
model->MakeTreeLists(); model->MakeTreeLists();
// update signature // update signature
model->spec.element->signature = model->Signature(); model->spec.element->signature = model->Signature();
return obj; return obj;
@@ -1447,7 +1445,6 @@ mjCGeom* mjCBody::AddGeom(mjCDef* _def) {
model->ResetTreeLists(); model->ResetTreeLists();
model->MakeTreeLists(); model->MakeTreeLists();
// update signature // update signature
model->spec.element->signature = model->Signature(); model->spec.element->signature = model->Signature();
return obj; return obj;
@@ -1469,7 +1466,6 @@ mjCSite* mjCBody::AddSite(mjCDef* _def) {
model->ResetTreeLists(); model->ResetTreeLists();
model->MakeTreeLists(); model->MakeTreeLists();
// update signature // update signature
model->spec.element->signature = model->Signature(); model->spec.element->signature = model->Signature();
return obj; return obj;
@@ -1491,7 +1487,6 @@ mjCCamera* mjCBody::AddCamera(mjCDef* _def) {
model->ResetTreeLists(); model->ResetTreeLists();
model->MakeTreeLists(); model->MakeTreeLists();
// update signature // update signature
model->spec.element->signature = model->Signature(); model->spec.element->signature = model->Signature();
return obj; return obj;
@@ -1513,7 +1508,6 @@ mjCLight* mjCBody::AddLight(mjCDef* _def) {
model->ResetTreeLists(); model->ResetTreeLists();
model->MakeTreeLists(); model->MakeTreeLists();
// update signature // update signature
model->spec.element->signature = model->Signature(); model->spec.element->signature = model->Signature();
return obj; return obj;