Add mjs_getOriginSpec to retrieve the original spec of an element.

The new function mjs_getOriginSpec returns the mjSpec that was used to define a given mjsElement. Unlike mjs_getSpec, this value remains constant even after the element has been attached to a different model.

PiperOrigin-RevId: 911930951
Change-Id: Ia9cd79d9dfabc513d6121eadcffc5d41075224c9
This commit is contained in:
Sam Haves
2026-05-07 06:32:54 -07:00
committed by Copybara-Service
parent 8b06fe4c16
commit ec50260e26
9 changed files with 90 additions and 0 deletions
+10
View File
@@ -4783,6 +4783,16 @@ Find and get utilities
Get spec from body.
.. _mjs_getOriginSpec:
`mjs_getOriginSpec <#mjs_getOriginSpec>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjs_getOriginSpec
get spec that originally defined an element
contrary to mjs_getSpec, this does not change after attachment
.. _mjs_getCompiler:
`mjs_getCompiler <#mjs_getCompiler>`__
+3
View File
@@ -19,6 +19,9 @@ General
energy gain in the presence of contacts and in fluid media.
- Added :ref:`mju_sym2dense`, converting a lower-triangular, implicitly symmetric CSR matrix to a dense
symmetric matrix. The inertia matrix ``mjData.M`` is an example of such a matrix.
- Added :ref:`mjs_getOriginSpec`, returning the spec that originally defined an element, prior to attachment. This is in
contrast to :ref:`mjs_getSpec` which returns the spec currently owning the element. If the element is not the result
of an attach operation, the functions are identical.
.. admonition:: Future breaking API changes
:class: warning
+1
View File
@@ -3687,6 +3687,7 @@ mjsTexture* mjs_addTexture(mjSpec* s);
mjsMaterial* mjs_addMaterial(mjSpec* s, const mjsDefault* def);
int mjs_makeMesh(mjsMesh* mesh, mjtMeshBuiltin builtin, double* params, int nparams);
mjSpec* mjs_getSpec(const mjsElement* element);
mjSpec* mjs_getOriginSpec(const mjsElement* element);
mjsCompiler* mjs_getCompiler(const mjsElement* element);
mjSpec* mjs_findSpec(const mjSpec* spec, const char* name);
mjsBody* mjs_findBody(const mjSpec* s, const char* name);
+4
View File
@@ -1776,6 +1776,10 @@ MJAPI int mjs_makeMesh(mjsMesh* mesh, mjtMeshBuiltin builtin, double* params, in
// Get spec from body.
MJAPI mjSpec* mjs_getSpec(const mjsElement* element);
// get spec that originally defined an element
// contrary to mjs_getSpec, this does not change after attachment
MJAPI mjSpec* mjs_getOriginSpec(const mjsElement* element);
// Get compiler associated with element's origin spec.
MJAPI mjsCompiler* mjs_getCompiler(const mjsElement* element);
+16
View File
@@ -11126,6 +11126,22 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Get spec from body.',
)),
('mjs_getOriginSpec',
FunctionDecl(
name='mjs_getOriginSpec',
return_type=PointerType(
inner_type=ValueType(name='mjSpec'),
),
parameters=(
FunctionParameterDecl(
name='element',
type=PointerType(
inner_type=ValueType(name='mjsElement', is_const=True),
),
),
),
doc='get spec that originally defined an element contrary to mjs_getSpec, this does not change after attachment', # pylint: disable=line-too-long
)),
('mjs_getCompiler',
FunctionDecl(
name='mjs_getCompiler',
+10
View File
@@ -1308,6 +1308,16 @@ mjSpec* mjs_getSpec(const mjsElement* element) {
// get spec that originally defined an element
// contrary to mjs_getSpec, this does not change after attachment
mjSpec* mjs_getOriginSpec(const mjsElement* element) {
const mjCModel* model = static_cast<const mjCBase*>(element)->model;
const mjsCompiler* compiler = static_cast<const mjCBase*>(element)->compiler;
return model->FindSpec(compiler);
}
mjsCompiler* mjs_getCompiler(const mjsElement* element) {
return static_cast<const mjCBase*>(element)->compiler;
}
+4
View File
@@ -224,6 +224,10 @@ MJAPI int mjs_makeMesh(mjsMesh* mesh, mjtMeshBuiltin builtin, double* params, in
// Get spec from body.
MJAPI mjSpec* mjs_getSpec(const mjsElement* element);
// get spec that originally defined an element
// contrary to mjs_getSpec, this does not change after attachment
MJAPI mjSpec* mjs_getOriginSpec(const mjsElement* element);
// Find spec (model asset) by name.
MJAPI mjSpec* mjs_findSpec(const mjSpec* spec, const char* name);
+33
View File
@@ -205,6 +205,39 @@ TEST_F(MujocoTest, AttachAndChildDeletion) {
mj_deleteSpec(parent_spec);
}
TEST_F(MujocoTest, OriginSpecInvariantToAttachment) {
mjSpec* child_spec = mj_makeSpec();
mjsBody* child_world = mjs_findBody(child_spec, "world");
mjsBody* child_body = mjs_addBody(child_world, 0);
mjsJoint* freejoint = mjs_addJoint(child_body, 0);
freejoint->type = mjJNT_FREE;
mjs_setName(freejoint->element, "child_freejoint");
mjSpec* parent_spec = mj_makeSpec();
mjsBody* parent_world = mjs_findBody(parent_spec, "world");
mjsBody* parent_body = mjs_addBody(parent_world, 0);
mjs_setName(parent_body->element, "parent_body");
// Attach child spec to parent_body
mjsElement* attached =
mjs_attach(parent_body->element, child_spec->element, "pre_", "");
ASSERT_THAT(attached, NotNull());
// The freejoint should still be in parent_spec because deletion failed
mjsElement* child_spec_joint =
mjs_findElement(parent_spec, mjOBJ_JOINT, "pre_child_freejoint");
EXPECT_EQ(mjs_getSpec(child_spec_joint), parent_spec);
EXPECT_EQ(mjs_getOriginSpec(child_spec_joint), child_spec);
mjsElement* parent_spec_body =
mjs_findElement(parent_spec, mjOBJ_BODY, "parent_body");
EXPECT_EQ(mjs_getOriginSpec(parent_spec_body), parent_spec);
mj_deleteSpec(child_spec);
mj_deleteSpec(parent_spec);
}
int open_mock(mjResource* resource) {
static const char parent_xml[] = R"(
<mujoco>
+9
View File
@@ -9793,6 +9793,14 @@ std::string mjs_getName_wrapper(MjsElement& element) {
return *mjs_getName(element.get());
}
std::optional<MjSpec> mjs_getOriginSpec_wrapper(const MjsElement& element) {
mjSpec* result = mjs_getOriginSpec(element.get());
if (result == nullptr) {
return std::nullopt;
}
return MjSpec(result);
}
std::optional<MjsBody> mjs_getParent_wrapper(const MjsElement& element) {
mjsBody* result = mjs_getParent(element.get());
if (result == nullptr) {
@@ -13343,6 +13351,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
function("mjs_getFrame", &mjs_getFrame_wrapper);
function("mjs_getId", &mjs_getId_wrapper);
function("mjs_getName", &mjs_getName_wrapper);
function("mjs_getOriginSpec", &mjs_getOriginSpec_wrapper);
function("mjs_getParent", &mjs_getParent_wrapper);
function("mjs_getSpec", &mjs_getSpec_wrapper);
function("mjs_getSpecDefault", &mjs_getSpecDefault_wrapper);