Files
Mujoco_WASM/python/mujoco/specs.cc
T
Alessio Quaglino 2bdea1a0aa Use "/" as default prefix during attach if not specified.
The user can recover the old behavior by passing prefix="".

This enables to attach children with default and save a valid result to XML.

PiperOrigin-RevId: 825011185
Change-Id: Id20c2589da928bf92b77bdb01cfba73dfc1d2f52
2025-10-28 06:40:34 -07:00

1518 lines
56 KiB
C++

// Copyright 2024 DeepMind Technologies Limited
//
// 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.
#include <array>
#include <cstddef> // IWYU pragma: keep
#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include <string_view> // IWYU pragma: keep
#include <unordered_map>
#include <utility>
#include <vector> // IWYU pragma: keep
#include <Eigen/Core>
#include <Eigen/Eigen>
#include <mujoco/mjspec.h> // IWYU pragma: keep
#include <mujoco/mujoco.h>
#include "errors.h"
#include "indexers.h" // IWYU pragma: keep
#include "specs_wrapper.h" // IWYU pragma: keep
#include "raw.h"
#include "structs.h" // IWYU pragma: keep
#include <pybind11/cast.h>
#include <pybind11/eigen.h>
#include <pybind11/eigen/matrix.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <pybind11/stl.h>
namespace py = ::pybind11;
namespace mujoco::python {
using MjInt2 = Eigen::Map<Eigen::Vector2i>;
using MjInt3 = Eigen::Map<Eigen::Vector3i>;
using MjFloat2 = Eigen::Map<Eigen::Vector2f>;
using MjFloat3 = Eigen::Map<Eigen::Vector3f>;
using MjFloat4 = Eigen::Map<Eigen::Vector4f>;
using MjDouble2 = Eigen::Map<Eigen::Vector2d>;
using MjDouble3 = Eigen::Map<Eigen::Vector3d>;
using MjDouble4 = Eigen::Map<Eigen::Vector4d>;
using MjDouble5 = Eigen::Map<Eigen::Matrix<double, 5, 1>>;
using MjDouble6 = Eigen::Map<Eigen::Matrix<double, 6, 1>>;
using MjDouble10 = Eigen::Map<Eigen::Matrix<double, 10, 1>>;
using MjDouble11 = Eigen::Map<Eigen::Matrix<double, 11, 1>>;
using MjDoubleVec = Eigen::Map<Eigen::VectorXd>;
using MjIntRef2 = Eigen::Ref<const Eigen::Vector2i>;
using MjIntRef3 = Eigen::Ref<const Eigen::Vector3i>;
using MjFloatRef2 = Eigen::Ref<const Eigen::Vector2f>;
using MjFloatRef3 = Eigen::Ref<const Eigen::Vector3f>;
using MjFloatRef4 = Eigen::Ref<const Eigen::Vector4f>;
using MjDoubleRef2 = Eigen::Ref<const Eigen::Vector2d>;
using MjDoubleRef3 = Eigen::Ref<const Eigen::Vector3d>;
using MjDoubleRef4 = Eigen::Ref<const Eigen::Vector4d>;
using MjDoubleRef5 = Eigen::Ref<const Eigen::Matrix<double, 5, 1>>;
using MjDoubleRef6 = Eigen::Ref<const Eigen::Matrix<double, 6, 1>>;
using MjDoubleRef10 = Eigen::Ref<const Eigen::Matrix<double, 10, 1>>;
using MjDoubleRef11 = Eigen::Ref<const Eigen::Matrix<double, 11, 1>>;
using MjDoubleRefVec = Eigen::Ref<const Eigen::VectorXd>;
template <typename LoadFunc>
static raw::MjSpec* LoadSpecFileImpl(
const std::string& filename,
const std::vector<_impl::VfsAsset>& assets,
LoadFunc&& loadfunc) {
mjVFS vfs;
mjVFS* vfs_ptr = nullptr;
if (!assets.empty()) {
mj_defaultVFS(&vfs);
vfs_ptr = &vfs;
for (const auto& asset : assets) {
std::string buffer_name = _impl::StripPath(asset.name);
const int vfs_error = InterceptMjErrors(mj_addBufferVFS)(
vfs_ptr, buffer_name.c_str(), asset.content, asset.content_size);
if (vfs_error) {
mj_deleteVFS(vfs_ptr);
if (vfs_error == 2) {
throw py::value_error("Repeated file name in assets dict: " +
buffer_name);
} else {
throw py::value_error("Asset failed to load: " + buffer_name);
}
}
}
}
raw::MjSpec* spec = loadfunc(filename.c_str(), vfs_ptr);
mj_deleteVFS(vfs_ptr);
return spec;
}
template <typename T>
struct MjTypeVec {
MjTypeVec(T* data, int size) : ptr(data), size(size) {}
T* ptr;
int size;
};
template <typename T>
void DefineArray(py::module& m, const std::string& typestr) {
using Class = MjTypeVec<T>;
py::class_<Class>(m, typestr.c_str())
.def(py::init([](T* data, int size) { return Class(data, size); }))
.def("__getitem__",
[](Class& v, int i) {
if (i < 0 || i >= v.size) {
throw py::index_error("Index out of range.");
}
return v.ptr[i];
})
.def("__setitem__",
[](Class& v, int i, T c) {
if (i < 0 || i >= v.size) {
throw py::index_error("Index out of range.");
}
v.ptr[i] = std::move(c);
})
.def("__len__", [](Class& v) { return v.size; })
.def("__iter__", [](Class& v) {
return py::make_iterator(v.ptr, v.ptr + v.size);
}, py::keep_alive<0, 1>(), py::return_value_policy::reference_internal);
};
py::list FindAllImpl(raw::MjsBody& body, mjtObj objtype, bool recursive) {
py::list list;
raw::MjsElement* el = mjs_firstChild(&body, objtype, recursive);
std::string error = mjs_getError(mjs_getSpec(body.element));
if (!el && !error.empty()) {
throw pybind11::value_error(error);
}
while (el) {
switch (objtype) {
case mjOBJ_BODY:
list.append(mjs_asBody(el));
break;
case mjOBJ_CAMERA:
list.append(mjs_asCamera(el));
break;
case mjOBJ_FRAME:
list.append(mjs_asFrame(el));
break;
case mjOBJ_GEOM:
list.append(mjs_asGeom(el));
break;
case mjOBJ_JOINT:
list.append(mjs_asJoint(el));
break;
case mjOBJ_LIGHT:
list.append(mjs_asLight(el));
break;
case mjOBJ_SITE:
list.append(mjs_asSite(el));
break;
default:
// this should never happen
throw pybind11::value_error(
"body.find_all supports the types: body, frame, geom, site, "
"joint, light, camera.");
break;
}
el = mjs_nextChild(&body, el, recursive);
}
return list; // list of pointers, so they can be copied
}
static std::string addSuffixBeforeExtension(const std::string& original_path,
const std::string& suffix_to_add) {
// Find the position of the last dot
size_t dot_pos = original_path.rfind('.');
// Check if a dot was found
if (dot_pos != std::string::npos) {
std::string new_path = original_path;
new_path.insert(dot_pos, suffix_to_add);
return new_path;
} else {
// No extension found, just append
return original_path + suffix_to_add;
}
}
static std::string addPrefixToFileName(const std::string& original_path,
const std::string& prefix_to_add) {
// Find the position of the last slash
size_t slash_pos = original_path.find_last_of("/\\");
// Check if a slash was found
if (slash_pos != std::string::npos) {
std::string new_path = original_path;
new_path.insert(slash_pos + 1, prefix_to_add);
return new_path;
} else {
// No slash found, just prepend
return prefix_to_add + original_path;
}
}
static std::string addPrefixAndSuffix(const std::string& original_path,
const std::string& prefix_to_add,
const std::string& suffix_to_add) {
std::string prefixed_path = addPrefixToFileName(original_path, prefix_to_add);
return addSuffixBeforeExtension(prefixed_path, suffix_to_add);
}
PYBIND11_MODULE(_specs, m) {
auto structs_m = py::module::import("mujoco._structs");
py::function mjmodel_from_raw_ptr =
structs_m.attr("MjModel").attr("_from_model_ptr");
py::function mjmodel_mjdata_from_spec_ptr =
structs_m.attr("_recompile_spec_addr");
py::class_<MjSpec> mjSpec(m, "MjSpec");
py::class_<raw::MjsElement> mjsElement(m, "MjsElement");
py::class_<raw::MjsDefault> mjsDefault(m, "MjsDefault");
py::class_<raw::MjsBody> mjsBody(m, "MjsBody");
py::class_<raw::MjsFrame> mjsFrame(m, "MjsFrame");
py::class_<raw::MjsGeom> mjsGeom(m, "MjsGeom");
py::class_<raw::MjsJoint> mjsJoint(m, "MjsJoint");
py::class_<raw::MjsLight> mjsLight(m, "MjsLight");
py::class_<raw::MjsMaterial> mjsMaterial(m, "MjsMaterial");
py::class_<raw::MjsSite> mjsSite(m, "MjsSite");
py::class_<raw::MjsMesh> mjsMesh(m, "MjsMesh");
py::class_<raw::MjsSkin> mjsSkin(m, "MjsSkin");
py::class_<raw::MjsTexture> mjsTexture(m, "MjsTexture");
py::class_<raw::MjsText> mjsText(m, "MjsText");
py::class_<raw::MjsTuple> mjsTuple(m, "MjsTuple");
py::class_<raw::MjsCamera> mjsCamera(m, "MjsCamera");
py::class_<raw::MjsFlex> mjsFlex(m, "MjsFlex");
py::class_<raw::MjsHField> mjsHField(m, "MjsHField");
py::class_<raw::MjsKey> mjsKey(m, "MjsKey");
py::class_<raw::MjsNumeric> mjsNumeric(m, "MjsNumeric");
py::class_<raw::MjsPair> mjsPair(m, "MjsPair");
py::class_<raw::MjsExclude> mjsExclude(m, "MjsExclude");
py::class_<raw::MjsEquality> mjsEquality(m, "MjsEquality");
py::class_<raw::MjsTendon> mjsTendon(m, "MjsTendon");
py::class_<raw::MjsSensor> mjsSensor(m, "MjsSensor");
py::class_<raw::MjsActuator> mjsActuator(m, "MjsActuator");
py::class_<raw::MjsPlugin> mjsPlugin(m, "MjsPlugin");
py::class_<raw::MjsOrientation> mjsOrientation(m, "MjsOrientation");
py::class_<raw::MjsWrap> mjsWrap(m, "MjsWrap");
py::class_<raw::MjOption> mjOption(m, "MjOption");
py::class_<raw::MjStatistic> mjStatistic(m, "MjStatistic");
py::class_<raw::MjVisual> mjVisual(m, "MjVisual");
py::class_<raw::MjVisualHeadlight> mjVisualHeadlight(m, "MjVisualHeadlight");
py::class_<raw::MjVisualRgba> mjVisualRgba(m, "MjVisualRgba");
py::class_<raw::MjsCompiler> mjsCompiler(m, "MjsCompiler");
DefineArray<char>(m, "MjCharVec");
DefineArray<std::string>(m, "MjStringVec");
DefineArray<std::byte>(m, "MjByteVec");
DefineArray<double>(m, "MjDoubleVec");
DefineArray<float>(m, "MjFloatVec");
DefineArray<int>(m, "MjIntVec");
// ============================= MJSPEC =====================================
mjSpec.def(py::init<>());
mjSpec.def_property_readonly(
"parent", [](MjSpec& self) -> MjSpec* { return self.parent; });
mjSpec.def_static(
"from_file",
[](std::string& filename,
std::optional<std::unordered_map<std::string, py::bytes>>& include,
std::optional<py::dict>& assets) -> MjSpec {
const auto files = _impl::ConvertAssetsDict(include);
raw::MjSpec* spec;
{
py::gil_scoped_release no_gil;
char error[1024];
spec = LoadSpecFileImpl(
filename, files,
[&error](const char* filename, const mjVFS* vfs) {
return InterceptMjErrors(mj_parseXML)(
filename, vfs, error, sizeof(error));
});
if (!spec) {
throw py::value_error(error);
}
}
if (assets.has_value()) {
return MjSpec(spec, assets.value());
}
return MjSpec(spec);
},
py::arg("filename"), py::arg("include") = py::none(),
py::arg("assets") = py::none(), R"mydelimiter(
Creates a spec from an XML file.
Parameters
----------
filename : str
Path to the XML file.
include : dict, optional
A dictionary of xml files included by the model. The keys are file names
and the values are file contents.
assets : dict, optional
A dictionary of assets to be used by the spec. The keys are asset names
and the values are asset contents.
)mydelimiter",
py::return_value_policy::move);
mjSpec.def_static(
"from_string",
[](std::string& xml,
std::optional<std::unordered_map<std::string, py::bytes>>& include,
std::optional<py::dict>& assets) -> MjSpec {
auto files = _impl::ConvertAssetsDict(include);
raw::MjSpec* spec;
{
py::gil_scoped_release no_gil;
std::string model_filename = "model_.xml";
if (include.has_value()) {
while (include->find(model_filename) !=
include->end()) {
model_filename =
model_filename.substr(0, model_filename.size() - 4) + "_.xml";
}
}
files.emplace_back(
model_filename.c_str(), xml.c_str(), xml.length());
char error[1024];
spec = LoadSpecFileImpl(
model_filename, files,
[&error](const char* filename, const mjVFS* vfs) {
return InterceptMjErrors(mj_parseXML)(
filename, vfs, error, sizeof(error));
});
if (!spec) {
throw py::value_error(error);
}
}
if (assets.has_value()) {
return MjSpec(spec, assets.value());
}
return MjSpec(spec);
},
py::arg("xml"), py::arg("include") = py::none(),
py::arg("assets") = py::none(), R"mydelimiter(
Creates a spec from an XML string.
Parameters
----------
xml : str
XML string.
include : dict, optional
A dictionary of xml files included by the model. The keys are file names
and the values are file contents.
assets : dict, optional
A dictionary of assets to be used by the spec. The keys are asset names
and the values are asset contents.
)mydelimiter",
py::return_value_policy::move);
mjSpec.def("recompile", [mjmodel_mjdata_from_spec_ptr](
const MjSpec& self, py::object m, py::object d) {
return mjmodel_mjdata_from_spec_ptr(reinterpret_cast<uintptr_t>(self.ptr),
m, d);
});
mjSpec.def("copy", [](const MjSpec& self) -> MjSpec {
return MjSpec(self);
});
mjSpec.def_property_readonly("_address", [](const MjSpec& self) {
return reinterpret_cast<uintptr_t>(self.ptr);
});
mjSpec.def_property(
"copy_during_attach",
[](MjSpec& self) {
throw pybind11::value_error("copy_during_attach can only be set.");
},
[](MjSpec& self, bool deepcopy) {
return mjs_setDeepCopy(self.ptr, deepcopy);
});
mjSpec.def_property_readonly(
"worldbody",
[](MjSpec& self) -> raw::MjsBody* {
return mjs_findBody(self.ptr, "world");
},
py::return_value_policy::reference_internal);
mjSpec.def(
"find_default",
[](MjSpec& self, std::string& classname) -> raw::MjsDefault* {
return mjs_findDefault(self.ptr, classname.c_str());
},
py::return_value_policy::reference_internal);
mjSpec.def("compile", [mjmodel_from_raw_ptr](MjSpec& self) -> py::object {
return mjmodel_from_raw_ptr(reinterpret_cast<uintptr_t>(self.Compile()));
});
mjSpec.def_property(
"assets",
[](MjSpec& self) -> py::dict {
return self.assets;
},
[](MjSpec& self, py::dict& assets) {
for (auto item : assets) {
self.assets[item.first] = item.second;
};
}, py::return_value_policy::reference_internal);
mjSpec.def_property(
"override_assets",
[](MjSpec& self) -> bool {
return self.override_assets;
},
[](MjSpec& self, bool override_assets) {
self.override_assets = override_assets;
});
mjSpec.def("to_xml", [](MjSpec& self) -> std::string {
mj_deleteModel(self.Compile());
std::array<char, 1024> err;
int size = mj_saveXMLString(self.ptr, nullptr, 0, nullptr, 0);
std::unique_ptr<char[]> buf(new char[size + 1]);
buf[0] = '\0';
err[0] = '\0';
mj_saveXMLString(self.ptr, buf.get(), size + 1, err.data(), err.size());
if (err[0] != '\0') {
throw FatalError(std::string(err.data()));
}
return std::string(buf.get());
});
mjSpec.def("to_file", [](MjSpec& self, std::string& file) {
std::array<char, 1024> err;
if (mj_saveXML(self.ptr, file.c_str(), err.data(), err.size()) < 0) {
throw FatalError(std::string(err.data()));
}
});
mjSpec.def(
"add_default",
[](MjSpec* spec, std::string& classname,
raw::MjsDefault* parent) -> raw::MjsDefault* {
return mjs_addDefault(spec->ptr, classname.c_str(), parent);
},
py::return_value_policy::reference_internal);
mjSpec.def("delete", [](MjSpec& self, raw::MjsDefault& def) {
if (mjs_delete(self.ptr, def.element) != 0) {
throw pybind11::value_error(mjs_getError(self.ptr));
}
});
mjSpec.def_property_readonly(
"default",
[](MjSpec& self) -> raw::MjsDefault* {
return mjs_getSpecDefault(self.ptr);
},
py::return_value_policy::reference_internal);
mjSpec.def("delete", [](MjSpec& self, raw::MjsBody& body) {
mjs_delete(self.ptr, body.element);
});
mjSpec.def(
"attach",
[](MjSpec& self, MjSpec& child, std::optional<std::string>& prefix,
std::optional<std::string>& suffix, std::optional<py::object>& site,
std::optional<py::object>& frame) -> raw::MjsFrame* {
if (!frame.has_value() && !site.has_value()) {
throw pybind11::value_error(
"One of frame or site must be specified.");
}
if (frame.has_value() && site.has_value()) {
throw pybind11::value_error(
"Only one of frame or site can be specified.");
}
const char* p = prefix.has_value() ? prefix.value().c_str() : "/";
const char* s = suffix.has_value() ? suffix.value().c_str() : "";
raw::MjsElement* attached_frame = nullptr;
if (frame.has_value()) {
raw::MjsFrame* frame_ptr = nullptr;
try {
frame_ptr = frame->cast<raw::MjsFrame*>();
} catch (const py::cast_error& e) {
frame_ptr =
mjs_findFrame(self.ptr, frame->cast<std::string>().c_str());
}
if (!frame_ptr) {
throw pybind11::value_error("Frame not found.");
}
if (mjs_getSpec(frame_ptr->element) != self.ptr) {
throw pybind11::value_error(
"Frame spec does not match parent spec.");
}
attached_frame =
mjs_attach(frame_ptr->element, child.ptr->element, p, s);
if (!attached_frame) {
throw pybind11::value_error(mjs_getError(self.ptr));
}
}
if (site.has_value()) {
raw::MjsSite* site_ptr = nullptr;
try {
site_ptr = site->cast<raw::MjsSite*>();
} catch (const py::cast_error& e) {
site_ptr = mjs_asSite(mjs_findElement(
self.ptr, mjOBJ_SITE, site->cast<std::string>().c_str()));
}
if (!site_ptr) {
throw pybind11::value_error("Site not found.");
}
if (mjs_getSpec(site_ptr->element) != self.ptr) {
throw pybind11::value_error(
"Site spec does not match parent spec.");
}
attached_frame =
mjs_attach(site_ptr->element, child.ptr->element, p, s);
if (!attached_frame) {
throw pybind11::value_error(mjs_getError(self.ptr));
}
}
// add prefix and suffix to the assets keys
std::string pre(p);
std::string suf(s);
for (const auto& asset : child.assets) {
std::string asset_name =
addPrefixAndSuffix(asset.first.cast<std::string>(), pre, suf);
if (self.assets.contains(asset_name) && !self.override_assets) {
throw pybind11::value_error("Asset " +
asset.first.cast<std::string>() +
" already exists in parent spec.");
}
self.assets[py::str(asset_name)] = asset.second;
}
raw::MjsElement* mesh = mjs_firstElement(child.ptr, mjOBJ_MESH);
raw::MjsElement* tex = mjs_firstElement(child.ptr, mjOBJ_TEXTURE);
raw::MjsElement* parent_mesh = mjs_firstElement(self.ptr, mjOBJ_MESH);
raw::MjsElement* parent_tex = mjs_firstElement(self.ptr, mjOBJ_TEXTURE);
bool child_has_assets = mesh || tex;
bool parent_has_assets = parent_mesh || parent_tex;
bool child_use_asset_dict = !child.assets.empty();
bool parent_use_asset_dict = !self.assets.empty();
if (!child_use_asset_dict && child_has_assets &&
parent_use_asset_dict) {
PyErr_WarnEx(
PyExc_Warning,
"Attaching a child without asset dict to a parent with an "
"asset dict might result in missing assets when attaching again.",
1);
}
if (!parent_use_asset_dict && parent_has_assets &&
child_use_asset_dict) {
PyErr_WarnEx(
PyExc_Warning,
"Attaching a child with asset dict to a parent without an "
"asset dict might result in missing assets when attaching again.",
1);
}
if (child_use_asset_dict) {
while (mesh) {
std::string file = mjs_getString(mjs_asMesh(mesh)->file);
if (!file.empty()) {
std::string mesh_file = addPrefixAndSuffix(file, pre, suf);
mjs_setString(mjs_asMesh(mesh)->file, mesh_file.c_str());
}
mesh = mjs_nextElement(child.ptr, mesh);
}
while (tex) {
std::string file = mjs_getString(mjs_asTexture(tex)->file);
if (!file.empty()) {
std::string tex_file = addPrefixAndSuffix(file, pre, suf);
mjs_setString(mjs_asTexture(tex)->file, tex_file.c_str());
}
tex = mjs_nextElement(child.ptr, tex);
}
}
child.parent = &self;
return mjs_asFrame(attached_frame);
},
py::arg("child"), py::arg("prefix") = py::none(),
py::arg("suffix") = py::none(), py::arg("site") = py::none(),
py::arg("frame") = py::none(),
py::return_value_policy::reference_internal);
mjSpec.def(
"activate_plugin",
[](MjSpec& self, std::string& name) {
mjs_activatePlugin(self.ptr, name.c_str());
},
py::arg("name"),
py::return_value_policy::reference_internal);
mjSpec.def_static(
"resolve_orientation",
[](bool degree, const MjTypeVec<char>& sequence,
const raw::MjsOrientation* orientation) -> std::array<double, 4> {
std::array<double, 4> quat = {0, 0, 0, 0};
const char* err = mjs_resolveOrientation(quat.data(), degree,
sequence.ptr, orientation);
if (err) {
throw pybind11::value_error(err);
}
return quat;
},
py::arg("degree"), py::arg("sequence") = py::none(),
py::arg("orientation"), py::return_value_policy::copy);
mjSpec.def_property(
"meshdir",
[](MjSpec& self) -> std::string_view {
return *self.ptr->compiler.meshdir;
},
[](MjSpec& self, std::string_view meshdir) {
*(self.ptr->compiler.meshdir) = meshdir;
});
mjSpec.def_property(
"texturedir",
[](MjSpec& self) -> std::string_view {
return *self.ptr->compiler.texturedir;
},
[](MjSpec& self, std::string_view texturedir) {
*(self.ptr->compiler.texturedir) = texturedir;
});
// ============================= MJSBODY =====================================
mjsBody.def(
"add_freejoint",
[](raw::MjsBody& self, py::kwargs kwargs) -> raw::MjsJoint* {
auto out = mjs_addFreeJoint(&self);
py::dict kwarg_dict = kwargs;
for (auto item : kwarg_dict) {
std::string key = py::str(item.first);
if (key == "align") {
try {
out->align = kwargs["align"].cast<int>();
} catch (const py::cast_error& e) {
throw pybind11::value_error("align is the wrong type.");
}
} else if (key == "name") {
try {
mjs_setName(out->element,
kwargs["name"].cast<std::string>().c_str());
} catch (const py::cast_error& e) {
throw pybind11::value_error("name is the wrong type.");
}
} else if (key == "group") {
try {
out->group = kwargs["group"].cast<int>();
} catch (const py::cast_error& e) {
throw pybind11::value_error("group is the wrong type.");
}
} else {
throw pybind11::type_error(
"Invalid " + key +
" keyword argument. Valid options are: align, group, name.");
}
}
return out;
},
py::return_value_policy::reference_internal);
mjsBody.def("set_frame", [](raw::MjsBody& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
});
mjsBody.def_property(
"classname",
[](raw::MjsBody& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsBody& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"find_all",
[](raw::MjsBody& self, mjtObj objtype) -> py::list {
return FindAllImpl(self, objtype, true);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"find_all",
[](raw::MjsBody& self, std::string& name) -> py::list {
mjtObj objtype = mjOBJ_UNKNOWN;
if (name == "body") {
objtype = mjOBJ_BODY;
} else if (name == "frame") {
objtype = mjOBJ_FRAME;
} else if (name == "geom") {
objtype = mjOBJ_GEOM;
} else if (name == "site") {
objtype = mjOBJ_SITE;
} else if (name == "joint") {
objtype = mjOBJ_JOINT;
} else if (name == "light") {
objtype = mjOBJ_LIGHT;
} else if (name == "camera") {
objtype = mjOBJ_CAMERA;
} else {
throw pybind11::value_error(
"body.find_all supports the types: body, frame, geom, site, "
"joint, light, camera.");
}
return FindAllImpl(self, objtype, true);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"find_child",
[](raw::MjsBody& self, std::string& name) -> raw::MjsBody* {
return mjs_findChild(&self, name.c_str());
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_body",
[](raw::MjsBody& self) -> raw::MjsBody* {
return mjs_asBody(mjs_firstChild(&self, mjOBJ_BODY, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_body",
[](raw::MjsBody& self, raw::MjsBody& child) -> raw::MjsBody* {
return mjs_asBody(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"bodies",
[](raw::MjsBody& self) -> py::list {
return FindAllImpl(self, mjOBJ_BODY, false);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_camera",
[](raw::MjsBody& self) -> raw::MjsCamera* {
return mjs_asCamera(mjs_firstChild(&self, mjOBJ_CAMERA, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_camera",
[](raw::MjsBody& self, raw::MjsCamera& child) -> raw::MjsCamera* {
return mjs_asCamera(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"cameras",
[](raw::MjsBody& self) -> py::list {
return FindAllImpl(self, mjOBJ_CAMERA, false);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_light",
[](raw::MjsBody& self) -> raw::MjsLight* {
return mjs_asLight(mjs_firstChild(&self, mjOBJ_LIGHT, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_light",
[](raw::MjsBody& self, raw::MjsLight& child) -> raw::MjsLight* {
return mjs_asLight(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"lights",
[](raw::MjsBody& self) -> py::list {
return FindAllImpl(self, mjOBJ_LIGHT, false);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_joint",
[](raw::MjsBody& self) -> raw::MjsJoint* {
return mjs_asJoint(mjs_firstChild(&self, mjOBJ_JOINT, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_joint",
[](raw::MjsBody& self, raw::MjsJoint& child) -> raw::MjsJoint* {
return mjs_asJoint(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"joints",
[](raw::MjsBody& self) -> py::list {
return FindAllImpl(self, mjOBJ_JOINT, false);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_geom",
[](raw::MjsBody& self) -> raw::MjsGeom* {
return mjs_asGeom(mjs_firstChild(&self, mjOBJ_GEOM, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_geom",
[](raw::MjsBody& self, raw::MjsGeom& child) -> raw::MjsGeom* {
return mjs_asGeom(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"geoms",
[](raw::MjsBody& self) -> py::list {
return FindAllImpl(self, mjOBJ_GEOM, false);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_site",
[](raw::MjsBody& self) -> raw::MjsSite* {
return mjs_asSite(mjs_firstChild(&self, mjOBJ_SITE, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_site",
[](raw::MjsBody& self, raw::MjsSite& child) -> raw::MjsSite* {
return mjs_asSite(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"sites",
[](raw::MjsBody& self) -> py::list {
return FindAllImpl(self, mjOBJ_SITE, false);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_frame",
[](raw::MjsBody& self) -> raw::MjsFrame* {
return mjs_asFrame(mjs_firstChild(&self, mjOBJ_FRAME, false));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"next_frame",
[](raw::MjsBody& self, raw::MjsFrame& child) -> raw::MjsFrame* {
return mjs_asFrame(mjs_nextChild(&self, child.element, false));
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"frames",
[](raw::MjsBody& self) -> py::list {
return FindAllImpl(self, mjOBJ_FRAME, false);
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"parent",
[](raw::MjsBody& self) -> raw::MjsBody* {
return mjs_getParent(self.element);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"attach_frame",
[](raw::MjsBody& self, raw::MjsFrame& frame,
std::optional<std::string>& prefix,
std::optional<std::string>& suffix) -> raw::MjsFrame* {
const char* p = prefix.has_value() ? prefix.value().c_str() : "";
const char* s = suffix.has_value() ? suffix.value().c_str() : "";
auto new_frame = mjs_attach(self.element, frame.element, p, s);
if (!new_frame) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
return mjs_asFrame(new_frame);
},
py::arg("frame"), py::arg("prefix") = py::none(),
py::arg("suffix") = py::none(),
py::return_value_policy::reference_internal);
mjsBody.def(
"to_frame",
[](raw::MjsBody* self) -> raw::MjsFrame* {
raw::MjsFrame* frame = mjs_bodyToFrame(&self);
if (!frame) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
// TODO: set the body to py::none
return frame;
},
py::return_value_policy::reference_internal);
mjsBody.def_property_readonly(
"frame",
[](raw::MjsBody* self) -> raw::MjsFrame* {
return mjs_getFrame(self->element);
},
py::return_value_policy::reference_internal);
// ============================= MJSFRAME ====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsFrame& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsFrame.def("set_frame", [](raw::MjsFrame& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
});
mjsFrame.def_property_readonly(
"parent",
[](raw::MjsFrame& self) -> raw::MjsBody* {
return mjs_getParent(self.element);
},
py::return_value_policy::reference_internal);
mjsFrame.def(
"attach_body",
[](raw::MjsFrame& self, raw::MjsBody& body,
std::optional<std::string>& prefix,
std::optional<std::string>& suffix) -> raw::MjsBody* {
const char* p = prefix.has_value() ? prefix.value().c_str() : "";
const char* s = suffix.has_value() ? suffix.value().c_str() : "";
auto new_body = mjs_attach(self.element, body.element, p, s);
if (!new_body) {
throw pybind11::value_error(
mjs_getError(mjs_getSpec(self.element)));
}
return mjs_asBody(new_body);
},
py::arg("body"), py::arg("prefix") = py::none(),
py::arg("suffix") = py::none(),
py::return_value_policy::reference_internal);
mjsFrame.def_property_readonly(
"frame",
[](raw::MjsFrame& self) -> raw::MjsFrame* {
return mjs_getFrame(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSGEOM =====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsGeom& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsGeom.def("set_frame", [](raw::MjsGeom& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
});
mjsGeom.def_property_readonly(
"parent",
[](raw::MjsGeom& self) -> raw::MjsBody* {
return mjs_getParent(self.element);
},
py::return_value_policy::reference_internal);
mjsGeom.def_property(
"classname",
[](raw::MjsGeom& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsGeom& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsGeom.def_property_readonly(
"frame",
[](raw::MjsGeom& self) -> raw::MjsFrame* {
return mjs_getFrame(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSJOINT ====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsJoint& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsJoint.def("set_frame", [](raw::MjsJoint& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
});
mjsJoint.def_property_readonly(
"parent",
[](raw::MjsJoint& self) -> raw::MjsBody* {
return mjs_getParent(self.element);
},
py::return_value_policy::reference_internal);
mjsJoint.def_property(
"classname",
[](raw::MjsJoint& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsJoint& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsJoint.def_property_readonly(
"frame",
[](raw::MjsJoint& self) -> raw::MjsFrame* {
return mjs_getFrame(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSSITE =====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsSite& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsSite.def("set_frame", [](raw::MjsSite& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
});
mjsSite.def_property_readonly(
"parent",
[](raw::MjsSite& self) -> raw::MjsBody* {
return mjs_getParent(self.element);
},
py::return_value_policy::reference_internal);
mjsSite.def_property(
"classname",
[](raw::MjsSite& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsSite& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsSite.def(
"attach_body",
[](raw::MjsSite& self, raw::MjsBody& body,
std::optional<std::string>& prefix,
std::optional<std::string>& suffix) -> raw::MjsBody* {
const char* p = prefix.has_value() ? prefix.value().c_str() : "";
const char* s = suffix.has_value() ? suffix.value().c_str() : "";
auto new_body = mjs_attach(self.element, body.element, p, s);
if (!new_body) {
throw pybind11::value_error(
mjs_getError(mjs_getSpec(self.element)));
}
return mjs_asBody(new_body);
},
py::arg("body"), py::arg("prefix") = py::none(),
py::arg("suffix") = py::none(),
py::return_value_policy::reference_internal);
mjsSite.def_property_readonly(
"frame",
[](raw::MjsSite& self) -> raw::MjsFrame* {
return mjs_getFrame(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSCAMERA ===================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsCamera& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsCamera.def("set_frame", [](raw::MjsCamera& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
});
mjsCamera.def_property_readonly(
"parent",
[](raw::MjsCamera& self) -> raw::MjsBody* {
return mjs_getParent(self.element);
},
py::return_value_policy::reference_internal);
mjsCamera.def_property(
"classname",
[](raw::MjsCamera& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsCamera& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsCamera.def_property_readonly(
"frame",
[](raw::MjsCamera& self) -> raw::MjsFrame* {
return mjs_getFrame(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSLIGHT ====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsLight& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsLight.def("set_frame", [](raw::MjsLight& self, raw::MjsFrame& frame) {
if (mjs_setFrame(self.element, &frame) != 0) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self.element)));
}
});
mjsLight.def_property_readonly(
"parent",
[](raw::MjsLight& self) -> raw::MjsBody* {
return mjs_getParent(self.element);
},
py::return_value_policy::reference_internal);
mjsLight.def_property(
"classname",
[](raw::MjsLight& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsLight& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsLight.def_property_readonly(
"frame",
[](raw::MjsLight& self) -> raw::MjsFrame* {
return mjs_getFrame(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSMATERIAL =================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsMaterial& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsMaterial.def_property(
"classname",
[](raw::MjsMaterial& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsMaterial& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
// ============================= MJSMESH =====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsMesh& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsMesh.def_property(
"classname",
[](raw::MjsMesh& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsMesh& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsMesh.def(
"make_wedge",
[](raw::MjsMesh* self, std::array<int, 2>& resolution,
std::array<double, 2>& fov, double gamma) {
double params[5] = {static_cast<double>(resolution[0]),
static_cast<double>(resolution[1]), fov[0], fov[1],
gamma};
if (mjs_makeMesh(self, mjMESH_BUILTIN_WEDGE, params, 5)) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
},
py::arg("resolution") = std::array<int, 2>{0, 0},
py::arg("fov") = std::array<double, 2>{0, 0}, py::arg("gamma") = 0);
mjsMesh.def(
"make_sphere",
[](raw::MjsMesh* self, int subdivision) {
double params[1] = {static_cast<double>(subdivision)};
if (mjs_makeMesh(self, mjMESH_BUILTIN_SPHERE, params, 1)) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
},
py::arg("subdivision"));
mjsMesh.def(
"make_hemisphere",
[](raw::MjsMesh* self, int resolution) {
double params[1] = {static_cast<double>(resolution)};
if (mjs_makeMesh(self, mjMESH_BUILTIN_HEMISPHERE, params, 1)) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
},
py::arg("resolution"));
mjsMesh.def(
"make_cone",
[](raw::MjsMesh* self, int nedge, double radius) {
double params[2] = {static_cast<double>(nedge), radius};
if (mjs_makeMesh(self, mjMESH_BUILTIN_CONE, params, 2)) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
},
py::arg("nedge"), py::arg("radius"));
mjsMesh.def(
"make_supersphere",
[](raw::MjsMesh* self, int resolution, double e, double n) {
double params[3] = {static_cast<double>(resolution), e, n};
if (mjs_makeMesh(self, mjMESH_BUILTIN_SUPERSPHERE, params, 3)) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
},
py::arg("resolution"), py::arg("e"), py::arg("n"));
mjsMesh.def(
"make_supertorus",
[](raw::MjsMesh* self, int resolution, double radius, double s,
double t) {
double params[4] = {static_cast<double>(resolution), radius, s, t};
if (mjs_makeMesh(self, mjMESH_BUILTIN_SUPERTORUS, params, 4)) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
},
py::arg("resolution"), py::arg("radius"), py::arg("s"), py::arg("t"));
mjsMesh.def(
"make_plate",
[](raw::MjsMesh* self, std::array<int, 2>& resolution) {
double params[2] = {static_cast<double>(resolution[0]),
static_cast<double>(resolution[1])};
if (mjs_makeMesh(self, mjMESH_BUILTIN_PLATE, params, 2)) {
throw pybind11::value_error(mjs_getError(mjs_getSpec(self->element)));
}
},
py::arg("resolution") = std::array<int, 2>{0, 0});
// ============================= MJSPAIR =====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsPair& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsPair.def_property(
"classname",
[](raw::MjsPair& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsPair& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
// ============================= MJSEQUAL ====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsEquality& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsEquality.def_property(
"classname",
[](raw::MjsEquality& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsEquality& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
// ============================= MJSACTUATOR =================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsActuator& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsActuator.def_property(
"classname",
[](raw::MjsActuator& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
[](raw::MjsActuator& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
},
py::return_value_policy::reference_internal);
mjsActuator.def("set_to_motor", [](raw::MjsActuator* self) {
std::string err = mjs_setToMotor(self);
if (!err.empty()) {
throw pybind11::value_error(err);
}
});
mjsActuator.def(
"set_to_position",
[](raw::MjsActuator* self, double kp, double kv, double dampratio,
double timeconst, bool inheritrange) {
std::string err = mjs_setToPosition(
self, kp, kv == -1 ? nullptr : &kv,
dampratio == -1 ? nullptr : &dampratio,
timeconst == -1 ? nullptr : &timeconst, inheritrange);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kp"), py::arg("kv") = -1, py::arg("dampratio") = -1,
py::arg("timeconst") = -1, py::arg("inheritrange") = false);
mjsActuator.def(
"set_to_intvelocity",
[](raw::MjsActuator* self, double kp, double kv, double dampratio,
double timeconst, bool inheritrange) {
std::string err = mjs_setToIntVelocity(
self, kp, kv == -1 ? nullptr : &kv,
dampratio == -1 ? nullptr : &dampratio,
timeconst == -1 ? nullptr : &timeconst, inheritrange);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kp"), py::arg("kv") = -1, py::arg("dampratio") = -1,
py::arg("timeconst") = -1, py::arg("inheritrange") = false);
mjsActuator.def(
"set_to_velocity",
[](raw::MjsActuator* self, double kv) {
std::string err = mjs_setToVelocity(self, kv);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kv"));
mjsActuator.def(
"set_to_damper",
[](raw::MjsActuator* self, double kv) {
std::string err = mjs_setToDamper(self, kv);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("kv"));
mjsActuator.def(
"set_to_cylinder",
[](raw::MjsActuator* self, double timeconst, double bias, double area,
double diameter) {
std::string err =
mjs_setToCylinder(self, timeconst, bias, area, diameter);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("timeconst"), py::arg("bias"), py::arg("area"),
py::arg("diameter") = -1);
mjsActuator.def(
"set_to_muscle",
[](raw::MjsActuator* self, double timeconst[2], double tausmooth,
double range[2], double force, double scale, double lmin, double lmax,
double vmax, double fpmax, double fvmax) {
std::string err =
mjs_setToMuscle(self, timeconst, tausmooth, range, force, scale,
lmin, lmax, vmax, fpmax, fvmax);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("timeconst") = -1, py::arg("tausmooth"),
py::arg("range") = std::array<double, 2>{-1, -1}, py::arg("force") = -1,
py::arg("scale") = -1, py::arg("lmin") = -1, py::arg("lmax") = -1,
py::arg("vmax") = -1, py::arg("fpmax") = -1, py::arg("fvmax") = -1);
mjsActuator.def(
"set_to_adhesion",
[](raw::MjsActuator* self, double gain) {
std::string err = mjs_setToAdhesion(self, gain);
if (!err.empty()) {
throw pybind11::value_error(err);
}
},
py::arg("gain"));
// ============================= MJSTENDONPATH ===============================
// helper struct for tendon path indexing
struct MjsTendonPath {
raw::MjsTendon* tendon;
};
py::class_<MjsTendonPath>(m, "MjsTendonPath")
.def("__getitem__",
[](MjsTendonPath& self, int i) -> const raw::MjsWrap* {
int num_wrap = mjs_getWrapNum(self.tendon);
if (i < 0 || i >= num_wrap) {
throw py::index_error("Index out of range.");
}
return mjs_getWrap(self.tendon, i);
},
py::return_value_policy::reference_internal)
.def("__len__", [](MjsTendonPath& self) {
return mjs_getWrapNum(self.tendon);
});
// ============================= MJSWRAP =====================================
mjsWrap.def_property_readonly(
"target",
[](raw::MjsWrap& self) -> py::object {
raw::MjsElement* target = mjs_getWrapTarget(&self);
if (!target) {
return py::none();
}
switch (target->elemtype) {
case mjOBJ_SITE:
return py::cast(mjs_asSite(target));
case mjOBJ_GEOM:
return py::cast(mjs_asGeom(target));
case mjOBJ_JOINT:
return py::cast(mjs_asJoint(target));
default:
throw pybind11::value_error("Unsupported wrap target type: " +
std::to_string(target->elemtype));
}
return py::none();
},
py::return_value_policy::reference_internal);
mjsWrap.def_property_readonly(
"sidesite",
[](raw::MjsWrap& self) -> raw::MjsSite* {
return mjs_getWrapSideSite(&self);
},
py::return_value_policy::reference_internal);
mjsWrap.def_property_readonly(
"divisor",
[](raw::MjsWrap& self) -> py::object {
if (self.type != mjWRAP_PULLEY) {
return py::none();
}
return py::cast(mjs_getWrapDivisor(&self));
},
py::return_value_policy::reference_internal);
mjsWrap.def_property_readonly(
"coef",
[](raw::MjsWrap& self) -> py::object {
if (self.type != mjWRAP_JOINT) {
return py::none();
}
return py::cast(mjs_getWrapCoef(&self));
},
py::return_value_policy::reference_internal);
mjSpec.def("delete", [](MjSpec& self, raw::MjsTendon& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTENDON ===================================
mjsTendon.def(
"default",
[](raw::MjsTendon& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
mjsTendon.def_property_readonly(
"path",
[](raw::MjsTendon& self) {
return MjsTendonPath{&self};
},
py::return_value_policy::reference_internal);
mjsTendon.def(
"wrap_site",
[](raw::MjsTendon& self, std::string& name) {
return mjs_wrapSite(&self, name.c_str());
},
py::return_value_policy::reference_internal);
mjsTendon.def(
"wrap_geom",
[](raw::MjsTendon& self, std::string& name, std::string& sidesite) {
return mjs_wrapGeom(&self, name.c_str(), sidesite.c_str());
},
py::return_value_policy::reference_internal);
mjsTendon.def(
"wrap_joint",
[](raw::MjsTendon& self, std::string& name, double coef) {
return mjs_wrapJoint(&self, name.c_str(), coef);
},
py::return_value_policy::reference_internal);
mjsTendon.def(
"wrap_pulley",
[](raw::MjsTendon& self, double divisor) {
return mjs_wrapPulley(&self, divisor);
},
py::return_value_policy::reference_internal);
// ============================= MJSSENSOR ===================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsSensor& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsSensor.def("get_data_size", [](raw::MjsSensor& self) -> int {
return mjs_sensorDim(&self);
});
// ============================= MJSFLEX =====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsFlex& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSHFIELD ===================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsHField& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSSKIN =====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsSkin& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTEXTURE ==================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsTexture& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSKEY ======================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsKey& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTEXT =====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsText& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSNUMERIC ==================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsNumeric& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSEXCLUDE ==================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsExclude& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSTUPLE ====================================
mjSpec.def("delete", [](MjSpec& self, raw::MjsTuple& obj) {
mjs_delete(self.ptr, obj.element);
});
// ============================= MJSPLUGIN ===================================
mjsPlugin.def_property(
"id",
[](raw::MjsPlugin& self) -> int { return mjs_getId(self.element); },
[](raw::MjsPlugin& self, raw::MjsPlugin* other) {
self.element = other->element;
});
mjSpec.def("delete", [](MjSpec& self, raw::MjsPlugin& obj) {
mjs_delete(self.ptr, obj.element);
});
mjsPlugin.def_property(
"config",
[](raw::MjsPlugin& self) -> py::dict {
const std::map<std::string, std::string, std::less<>>* config_attribs =
static_cast<const std::map<std::string, std::string, std::less<>>*>(
mjs_getPluginAttributes(&self));
py::dict config;
for (const auto& [key, value] : *config_attribs) {
config[py::str(key)] = value;
}
return config;
},
[](raw::MjsPlugin& self, py::dict& config) {
std::map<std::string, std::string, std::less<>> config_attribs;
for (const auto& [key, value] : config) {
std::string key_str = key.cast<std::string>();
if (config_attribs.find(key_str) != config_attribs.end()) {
throw pybind11::value_error("Duplicate config key: " + key_str);
}
config_attribs[key_str] = value.cast<std::string>();
}
mjs_setPluginAttributes(&self, &config_attribs);
},
py::return_value_policy::reference_internal);
// ============================= MJVISUAL ====================================
mjVisual.def_property(
"global_",
[](raw::MjVisual& self) -> raw::MjVisualGlobal& { return self.global; },
[](raw::MjVisual& self, raw::MjVisualGlobal& value) {
self.global = value;
});
#include "specs.cc.inc"
} // PYBIND11_MODULE // NOLINT
} // namespace mujoco::python