Add plugins list to mjSpec Python API.

Fixes #2061.

PiperOrigin-RevId: 677762985
Change-Id: I8e407373e7b505d9b0f1c023e97c5ea92169bbf6
This commit is contained in:
Alessio Quaglino
2024-09-23 06:12:42 -07:00
committed by Copybara-Service
parent 91cedfd65c
commit 0ba11b9623
18 changed files with 127 additions and 44 deletions
+9
View File
@@ -4432,6 +4432,15 @@ mjs_asMaterial
Safely cast an element as mjsMaterial, or return NULL if the element is not an mjsMaterial.
.. _mjs_asPlugin:
mjs_asPlugin
~~~~~~~~~~~~
.. mujoco-include:: mjs_asPlugin
Safely cast an element as mjsPlugin, or return NULL if the element is not an mjsPlugin.
.. _AttributeSetters:
Attribute setters
+2 -1
View File
@@ -1738,7 +1738,7 @@ typedef struct mjsOrientation_ { // alternative orientation specifiers
double euler[3]; // Euler angles
} mjsOrientation;
typedef struct mjsPlugin_ { // plugin specification
mjsElement* instance; // element type
mjsElement* element; // element type
mjString* name; // name
mjString* instance_name; // instance name
int plugin_slot; // global registered slot number of the plugin
@@ -3622,6 +3622,7 @@ mjsHField* mjs_asHField(mjsElement* element);
mjsSkin* mjs_asSkin(mjsElement* element);
mjsTexture* mjs_asTexture(mjsElement* element);
mjsMaterial* mjs_asMaterial(mjsElement* element);
mjsPlugin* mjs_asPlugin(mjsElement* element);
void mjs_setBuffer(mjByteVec* dest, const void* array, int size);
void mjs_setString(mjString* dest, const char* text);
void mjs_setStringVec(mjStringVec* dest, const char* text);
+1 -1
View File
@@ -181,7 +181,7 @@ typedef struct mjsOrientation_ { // alternative orientation specifiers
typedef struct mjsPlugin_ { // plugin specification
mjsElement* instance; // element type
mjsElement* element; // element type
mjString* name; // name
mjString* instance_name; // instance name
int plugin_slot; // global registered slot number of the plugin
+3
View File
@@ -1628,6 +1628,9 @@ MJAPI mjsTexture* mjs_asTexture(mjsElement* element);
// Safely cast an element as mjsMaterial, or return NULL if the element is not an mjsMaterial.
MJAPI mjsMaterial* mjs_asMaterial(mjsElement* element);
// Safely cast an element as mjsPlugin, or return NULL if the element is not an mjsPlugin.
MJAPI mjsPlugin* mjs_asPlugin(mjsElement* element);
//---------------------------------- Attribute setters ---------------------------------------------
+16
View File
@@ -10270,6 +10270,22 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Safely cast an element as mjsMaterial, or return NULL if the element is not an mjsMaterial.', # pylint: disable=line-too-long
)),
('mjs_asPlugin',
FunctionDecl(
name='mjs_asPlugin',
return_type=PointerType(
inner_type=ValueType(name='mjsPlugin'),
),
parameters=(
FunctionParameterDecl(
name='element',
type=PointerType(
inner_type=ValueType(name='mjsElement'),
),
),
),
doc='Safely cast an element as mjsPlugin, or return NULL if the element is not an mjsPlugin.', # pylint: disable=line-too-long
)),
('mjs_setBuffer',
FunctionDecl(
name='mjs_setBuffer',
+1 -1
View File
@@ -8565,7 +8565,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
declname='struct mjsPlugin_',
fields=(
StructFieldDecl(
name='instance',
name='element',
type=PointerType(
inner_type=ValueType(name='mjsElement'),
),
+15 -3
View File
@@ -274,6 +274,18 @@ PYBIND11_MODULE(_specs, m) {
mjSpec.def("detach_body", [](MjSpec& self, raw::MjsBody& body) {
mjs_detachBody(self.ptr, &body);
});
mjSpec.def_property_readonly(
"plugins",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_PLUGIN);
while (el) {
list.append(mjs_asPlugin(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"actuators",
[](MjSpec& self) -> py::list {
@@ -900,12 +912,12 @@ PYBIND11_MODULE(_specs, m) {
// ============================= MJSPLUGIN ===================================
mjsPlugin.def_property(
"id",
[](raw::MjsPlugin& self) -> int { return mjs_getId(self.instance); },
[](raw::MjsPlugin& self) -> int { return mjs_getId(self.element); },
[](raw::MjsPlugin& self, raw::MjsPlugin* other) {
self.instance = other->instance;
self.element = other->element;
});
mjsPlugin.def("delete",
[](raw::MjsPlugin& self) { mjs_delete(self.instance); });
[](raw::MjsPlugin& self) { mjs_delete(self.element); });
#include "specs.cc.inc"
} // PYBIND11_MODULE // NOLINT
+27
View File
@@ -690,5 +690,32 @@ class SpecsTest(absltest.TestCase):
):
spec.recompile(model, data)
def test_delete_unused_plugin(self):
spec = mujoco.MjSpec()
spec.from_string(textwrap.dedent("""
<mujoco model="MuJoCo Model">
<extension>
<plugin plugin="mujoco.pid">
<instance name="pid1">
<config key="kp" value="4.0"/>
</instance>
</plugin>
</extension>
<worldbody>
<body>
<geom size="1"/>
</body>
</worldbody>
</mujoco>
"""))
plugin = spec.plugins[0]
self.assertIsNotNone(plugin)
plugin.delete()
model = spec.compile()
self.assertIsNotNone(model)
self.assertEqual(model.nplugin, 0)
if __name__ == '__main__':
absltest.main()
+12 -2
View File
@@ -471,7 +471,7 @@ mjsKey* mjs_addKey(mjSpec* s) {
mjsPlugin* mjs_addPlugin(mjSpec* s) {
mjCModel* modelC = static_cast<mjCModel*>(s->element);
mjCPlugin* plugin = modelC->AddPlugin();
plugin->spec.instance = static_cast<mjsElement*>(plugin);
plugin->spec.element = static_cast<mjsElement*>(plugin);
return &plugin->spec;
}
@@ -887,6 +887,16 @@ mjsMaterial* mjs_asMaterial(mjsElement* element) {
// return plugin given mjsElement
mjsPlugin* mjs_asPlugin(mjsElement* element) {
if (element && element->elemtype == mjOBJ_PLUGIN) {
return &(static_cast<mjCPlugin*>(element)->spec);
}
return nullptr;
}
// copy buffer to destination buffer
void mjs_setBuffer(mjByteVec* dest, const void* array, int size) {
const std::byte* buffer = static_cast<const std::byte*>(array);
@@ -995,7 +1005,7 @@ const double* mjs_getDouble(const mjDoubleVec* source, int* size) {
// set plugin attributes
void mjs_setPluginAttributes(mjsPlugin* plugin, void* attributes) {
mjCPlugin* pluginC = static_cast<mjCPlugin*>(plugin->instance);
mjCPlugin* pluginC = static_cast<mjCPlugin*>(plugin->element);
std::map<std::string, std::string, std::less<>>* config_attribs =
reinterpret_cast<std::map<std::string, std::string, std::less<>>*>(attributes);
pluginC->config_attribs = std::move(*config_attribs);
+3
View File
@@ -296,6 +296,9 @@ MJAPI mjsTexture* mjs_asTexture(mjsElement* element);
// Safely cast an element as mjsMaterial, or return NULL if the element is not an mjsMaterial.
MJAPI mjsMaterial* mjs_asMaterial(mjsElement* element);
// Safely cast an element as mjsPlugin, or return NULL if the element is not an mjsPlugin.
MJAPI mjsPlugin* mjs_asPlugin(mjsElement* element);
//---------------------------------- Attribute setters ---------------------------------------------
+2 -2
View File
@@ -315,7 +315,7 @@ bool mjCComposite::Make(mjSpec* spec, mjsBody* body, char* error, int error_sz)
// overwrite plugin name
if (plugin_instance_name.empty() && plugin.active) {
plugin_instance_name = "composite" + prefix;
(static_cast<mjCPlugin*>(plugin.instance))->name = plugin_instance_name;
(static_cast<mjCPlugin*>(plugin.element))->name = plugin_instance_name;
}
// dispatch
@@ -714,7 +714,7 @@ mjsBody* mjCComposite::AddCableBody(mjCModel* model, mjsBody* body, int ix,
if (plugin.active) {
mjsPlugin* pplugin = &body->plugin;
pplugin->active = true;
pplugin->instance = plugin.instance;
pplugin->element = plugin.element;
mjs_setString(pplugin->name, mjs_getString(plugin.name));
mjs_setString(pplugin->instance_name, plugin_instance_name.c_str());
}
+3 -3
View File
@@ -422,7 +422,7 @@ bool mjCFlexcomp::Make(mjSpec* spec, mjsBody* body, char* error, int error_sz) {
// overwrite plugin name
if (plugin.active && plugin_instance_name.empty()) {
plugin_instance_name = "flexcomp_" + name;
static_cast<mjCPlugin*>(plugin.instance)->name = plugin_instance_name;
static_cast<mjCPlugin*>(plugin.element)->name = plugin_instance_name;
}
// create bodies, construct flex vert and vertbody
@@ -440,7 +440,7 @@ bool mjCFlexcomp::Make(mjSpec* spec, mjsBody* body, char* error, int error_sz) {
if (plugin.active) {
mjsPlugin* pplugin = &body->plugin;
pplugin->active = true;
pplugin->instance = static_cast<mjsElement*>(plugin.instance);
pplugin->element = static_cast<mjsElement*>(plugin.element);
mjs_setString(pplugin->name, mjs_getString(plugin.name));
mjs_setString(pplugin->instance_name, plugin_instance_name.c_str());
}
@@ -504,7 +504,7 @@ bool mjCFlexcomp::Make(mjSpec* spec, mjsBody* body, char* error, int error_sz) {
if (plugin.active) {
mjsPlugin* pplugin = &pb->plugin;
pplugin->active = true;
pplugin->instance = static_cast<mjsElement*>(plugin.instance);
pplugin->element = static_cast<mjsElement*>(plugin.element);
mjs_setString(pplugin->name, mjs_getString(plugin.name));
mjs_setString(pplugin->instance_name, plugin_instance_name.c_str());
}
+6 -6
View File
@@ -246,7 +246,7 @@ void mjCMesh::CopyFromSpec() {
facetexcoord_ = spec_facetexcoord_;
maxhullvert_ = spec.maxhullvert;
plugin.active = spec.plugin.active;
plugin.instance = spec.plugin.instance;
plugin.element = spec.plugin.element;
plugin.name = spec.plugin.name;
plugin.instance_name = spec.plugin.instance_name;
@@ -270,7 +270,7 @@ mjCMesh::~mjCMesh() {
if (center_) mju_free(center_);
if (graph_) mju_free(graph_);
if (spec.plugin.active && spec.plugin.instance_name->empty()) {
model->DeleteElement(spec.plugin.instance);
model->DeleteElement(spec.plugin.element);
}
}
@@ -289,9 +289,9 @@ void mjCMesh::LoadSDF() {
name.c_str(), id);
}
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.instance);
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.element);
model->ResolvePlugin(this, plugin_name, plugin_instance_name, &plugin_instance);
plugin.instance = plugin_instance;
plugin.element = plugin_instance;
const mjpPlugin* pplugin = mjp_getPluginAtSlot(plugin_instance->spec.plugin_slot);
if (!(pplugin->capabilityflags & mjPLUGIN_SDF)) {
throw mjCError(this, "plugin '%s' does not support signed distance fields", pplugin->name);
@@ -2919,9 +2919,9 @@ void mjCFlex::Compile(const mjVFS* vfs) {
useredge = VectorToString(edgeidx_);
for (const auto& vbodyid : vertbodyid) {
if (model->Bodies()[vbodyid]->plugin.instance) {
if (model->Bodies()[vbodyid]->plugin.element) {
mjCPlugin* plugin_instance =
static_cast<mjCPlugin*>(model->Bodies()[vbodyid]->plugin.instance);
static_cast<mjCPlugin*>(model->Bodies()[vbodyid]->plugin.element);
if (damping > 0) {
plugin_instance->config_attribs["damping"] = std::to_string(damping);
}
+6 -4
View File
@@ -938,6 +938,8 @@ mjsElement* mjCModel::NextObject(mjsElement* object, mjtObj type) {
return GetNext(textures_, object);
case mjOBJ_MATERIAL:
return GetNext(materials_, object);
case mjOBJ_PLUGIN:
return GetNext(plugins_, object);
default:
return nullptr;
}
@@ -3932,7 +3934,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
std::vector<std::vector<int>> plugin_to_actuators(nplugin);
for (int i = 0; i < nu; ++i) {
if (actuators_[i]->plugin.active) {
int actuator_plugin = static_cast<mjCPlugin*>(actuators_[i]->plugin.instance)->id;
int actuator_plugin = static_cast<mjCPlugin*>(actuators_[i]->plugin.element)->id;
m->actuator_plugin[i] = actuator_plugin;
plugin_to_actuators[actuator_plugin].push_back(i);
} else {
@@ -3942,7 +3944,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
for (int i = 0; i < nbody; ++i) {
if (bodies_[i]->plugin.active) {
m->body_plugin[i] = static_cast<mjCPlugin*>(bodies_[i]->plugin.instance)->id;
m->body_plugin[i] = static_cast<mjCPlugin*>(bodies_[i]->plugin.element)->id;
} else {
m->body_plugin[i] = -1;
}
@@ -3950,7 +3952,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
for (int i = 0; i < ngeom; ++i) {
if (geoms_[i]->plugin.active) {
m->geom_plugin[i] = static_cast<mjCPlugin*>(geoms_[i]->plugin.instance)->id;
m->geom_plugin[i] = static_cast<mjCPlugin*>(geoms_[i]->plugin.element)->id;
} else {
m->geom_plugin[i] = -1;
}
@@ -3959,7 +3961,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
std::vector<std::vector<int>> plugin_to_sensors(nplugin);
for (int i = 0; i < nsensor; ++i) {
if (sensors_[i]->type == mjSENS_PLUGIN) {
int sensor_plugin = static_cast<mjCPlugin*>(sensors_[i]->plugin.instance)->id;
int sensor_plugin = static_cast<mjCPlugin*>(sensors_[i]->plugin.element)->id;
m->sensor_plugin[i] = sensor_plugin;
plugin_to_sensors[sensor_plugin].push_back(i);
} else {
+16 -16
View File
@@ -959,7 +959,7 @@ void mjCBody::CopyFromSpec() {
*static_cast<mjsBody*>(this) = spec;
userdata_ = spec_userdata_;
plugin.active = spec.plugin.active;
plugin.instance = spec.plugin.instance;
plugin.element = spec.plugin.element;
plugin.name = spec.plugin.name;
plugin.instance_name = spec.plugin.instance_name;
}
@@ -986,7 +986,7 @@ mjCBody::~mjCBody() {
lights.clear();
if (spec.plugin.active && spec.plugin.instance_name->empty()) {
model->DeleteElement(spec.plugin.instance);
model->DeleteElement(spec.plugin.element);
}
}
@@ -1669,9 +1669,9 @@ void mjCBody::Compile(void) {
name.c_str(), id);
}
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.instance);
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.element);
model->ResolvePlugin(this, plugin_name, plugin_instance_name, &plugin_instance);
plugin.instance = plugin_instance;
plugin.element = plugin_instance;
const mjpPlugin* pplugin = mjp_getPluginAtSlot(plugin_instance->spec.plugin_slot);
if (!(pplugin->capabilityflags & mjPLUGIN_PASSIVE)) {
throw mjCError(this, "plugin '%s' does not support passive forces", pplugin->name);
@@ -2132,7 +2132,7 @@ mjCGeom::mjCGeom(const mjCGeom& other) {
mjCGeom::~mjCGeom() {
if (spec.plugin.active && spec.plugin.instance_name->empty()) {
model->DeleteElement(spec.plugin.instance);
model->DeleteElement(spec.plugin.element);
}
}
@@ -2176,7 +2176,7 @@ void mjCGeom::CopyFromSpec() {
meshname_ = spec_meshname_;
material_ = spec_material_;
plugin.active = spec.plugin.active;
plugin.instance = spec.plugin.instance;
plugin.element = spec.plugin.element;
plugin.name = spec.plugin.name;
plugin.instance_name = spec.plugin.instance_name;
}
@@ -2913,9 +2913,9 @@ void mjCGeom::Compile(void) {
this, "neither 'plugin' nor 'instance' is specified for geom");
}
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.instance);
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.element);
model->ResolvePlugin(this, plugin_name, plugin_instance_name, &plugin_instance);
plugin.instance = plugin_instance;
plugin.element = plugin_instance;
const mjpPlugin* pplugin = mjp_getPluginAtSlot(plugin_instance->spec.plugin_slot);
if (!(pplugin->capabilityflags & mjPLUGIN_SDF)) {
throw mjCError(this, "plugin '%s' does not support sign distance fields", pplugin->name);
@@ -5554,7 +5554,7 @@ mjCActuator::mjCActuator(const mjCActuator& other) {
mjCActuator::~mjCActuator() {
if (spec.plugin.active && spec.plugin.instance_name->empty()) {
model->DeleteElement(spec.plugin.instance);
model->DeleteElement(spec.plugin.element);
}
}
@@ -5640,7 +5640,7 @@ void mjCActuator::CopyFromSpec() {
refsite_ = spec_refsite_;
slidersite_ = spec_slidersite_;
plugin.active = spec.plugin.active;
plugin.instance = spec.plugin.instance;
plugin.element = spec.plugin.element;
plugin.name = spec.plugin.name;
plugin.instance_name = spec.plugin.instance_name;
}
@@ -5869,9 +5869,9 @@ void mjCActuator::Compile(void) {
name.c_str(), id);
}
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.instance);
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.element);
model->ResolvePlugin(this, plugin_name, plugin_instance_name, &plugin_instance);
plugin.instance = plugin_instance;
plugin.element = plugin_instance;
const mjpPlugin* pplugin = mjp_getPluginAtSlot(plugin_instance->spec.plugin_slot);
if (!(pplugin->capabilityflags & mjPLUGIN_ACTUATOR)) {
throw mjCError(this, "plugin '%s' does not support actuators", pplugin->name);
@@ -5916,7 +5916,7 @@ mjCSensor::mjCSensor(const mjCSensor& other) {
mjCSensor::~mjCSensor() {
if (spec.plugin.active && spec.plugin.instance_name->empty()) {
model->DeleteElement(spec.plugin.instance);
model->DeleteElement(spec.plugin.element);
}
}
@@ -5968,7 +5968,7 @@ void mjCSensor::CopyFromSpec() {
objname_ = spec_objname_;
refname_ = spec_refname_;
plugin.active = spec.plugin.active;
plugin.instance = spec.plugin.instance;
plugin.element = spec.plugin.element;
plugin.name = spec.plugin.name;
plugin.instance_name = spec.plugin.instance_name;
}
@@ -6373,9 +6373,9 @@ void mjCSensor::Compile(void) {
// resolve plugin instance, or create one if using the "plugin" attribute shortcut
{
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.instance);
mjCPlugin* plugin_instance = static_cast<mjCPlugin*>(plugin.element);
model->ResolvePlugin(this, plugin_name, plugin_instance_name, &plugin_instance);
plugin.instance = plugin_instance;
plugin.element = plugin_instance;
const mjpPlugin* pplugin = mjp_getPluginAtSlot(plugin_instance->spec.plugin_slot);
if (!(pplugin->capabilityflags & mjPLUGIN_SENSOR)) {
throw mjCError(this, "plugin '%s' does not support sensors", pplugin->name);
+1 -1
View File
@@ -2767,7 +2767,7 @@ void mjXReader::OnePlugin(XMLElement* elem, mjsPlugin* plugin) {
mjs_setString(plugin->name, name.c_str());
mjs_setString(plugin->instance_name, instance_name.c_str());
if (instance_name.empty()) {
plugin->instance = mjs_addPlugin(spec)->instance;
plugin->element = mjs_addPlugin(spec)->element;
ReadPluginConfigs(elem, plugin);
} else {
spec->hasImplicitPluginElem = true;
+2 -2
View File
@@ -827,8 +827,8 @@ void mjXWriter::OnePlugin(XMLElement* elem, const mjsPlugin* plugin) {
} else {
WriteAttrTxt(elem, "plugin", plugin_name);
const mjpPlugin* pplugin = mjp_getPluginAtSlot(
static_cast<mjCPlugin*>(plugin->instance)->spec.plugin_slot);
const char* c = &(static_cast<mjCPlugin*>(plugin->instance)->flattened_attributes[0]);
static_cast<mjCPlugin*>(plugin->element)->spec.plugin_slot);
const char* c = &(static_cast<mjCPlugin*>(plugin->element)->flattened_attributes[0]);
for (int i = 0; i < pplugin->nattribute; ++i) {
string value(c);
if (!value.empty()) {
+2 -2
View File
@@ -147,7 +147,7 @@ TEST_F(PluginTest, ActivatePlugin) {
// associate plugin to body
mjsBody* body = mjs_addBody(mjs_findBody(spec, "world"), 0);
mjs_setString(body->plugin.name, plugin_name.c_str());
body->plugin.instance = mjs_addPlugin(spec)->instance;
body->plugin.element = mjs_addPlugin(spec)->element;
body->plugin.active = true;
mjsGeom* geom = mjs_addGeom(body, 0);
geom->type = mjGEOM_BOX;
@@ -191,7 +191,7 @@ TEST_F(PluginTest, DeletePlugin) {
mjsActuator* actuator = mjs_addActuator(spec, 0);
mjs_setString(actuator->target, "j1");
mjs_setString(actuator->plugin.name, plugin_name.c_str());
actuator->plugin.instance = mjs_addPlugin(spec)->instance;
actuator->plugin.element = mjs_addPlugin(spec)->element;
actuator->plugin.active = true;
actuator->trntype = mjTRN_JOINT;