723b8b1ea6
This change introduces MjVFS, an explicit object to mirror the C mjVFS. This is meant to replace the MjSpec.assets dict. This latter while convenient guides users towards harmful authoring patterns with respect to data duplication and spec attachment workflows. Making VFS management explicit should encourage better memory usage and allow us to make better compile time optimizations. From this change, `spec.assets` is deprecated. However we will temporarily support backwards compatibility due to the wide spread usage. An error will be thrown if calling code tries to use a spec that uses both the assets dict and the new MjVfs. PiperOrigin-RevId: 908772050 Change-Id: I77c6d0369307fc300c954768fed17401261e18da
1719 lines
63 KiB
C++
1719 lines
63 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 <stdexcept>
|
|
#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 "vfs.h"
|
|
#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");
|
|
|
|
// ============================= MJVFS =====================================
|
|
py::class_<MjVfs>(m, "MjVfs")
|
|
.def(py::init<>())
|
|
.def("close", &MjVfs::Close)
|
|
.def("__enter__", [](MjVfs& self) -> MjVfs& { return self; })
|
|
.def("__exit__",
|
|
[](MjVfs& self, py::object, py::object, py::object) {
|
|
self.Close();
|
|
})
|
|
.def("__setitem__",
|
|
[](MjVfs& self, const std::string& name, py::bytes data) {
|
|
if (!self.is_open()) {
|
|
throw std::runtime_error("VFS is closed");
|
|
}
|
|
std::string_view buffer = data;
|
|
const int err = mj_addBufferVFS(
|
|
self.get(), name.c_str(), buffer.data(), buffer.size());
|
|
if (err == 2) {
|
|
throw py::value_error(
|
|
"Repeated file name in VFS: " + name);
|
|
} else if (err) {
|
|
throw py::value_error(
|
|
"Failed to add buffer to VFS: " + name);
|
|
}
|
|
})
|
|
.def("__delitem__",
|
|
[](MjVfs& self, const std::string& name) {
|
|
if (!self.is_open()) {
|
|
throw std::runtime_error("VFS is closed");
|
|
}
|
|
if (mj_deleteFileVFS(self.get(), name.c_str())) {
|
|
throw py::key_error(name);
|
|
}
|
|
})
|
|
.def("__contains__",
|
|
[](MjVfs& self, const std::string& name) {
|
|
if (!self.is_open()) {
|
|
throw std::runtime_error("VFS is closed");
|
|
}
|
|
return mj_containsBufferVFS(self.get(), name.c_str()) == 1;
|
|
});
|
|
|
|
|
|
// ============================= 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,
|
|
MjVfs* vfs) -> MjSpec {
|
|
if (vfs && (include.has_value() || assets.has_value())) {
|
|
throw py::value_error(
|
|
"Cannot specify both 'vfs' and 'include'/'assets'.");
|
|
}
|
|
if (vfs) {
|
|
raw::MjSpec* spec;
|
|
{
|
|
py::gil_scoped_release no_gil;
|
|
char error[1024];
|
|
spec = InterceptMjErrors(mj_parse)(
|
|
filename.c_str(), nullptr, vfs->get(),
|
|
error, sizeof(error));
|
|
if (!spec) {
|
|
throw py::value_error(error);
|
|
}
|
|
}
|
|
return MjSpec(spec);
|
|
}
|
|
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_parse)(
|
|
filename, nullptr, 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(), py::arg("vfs") = 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.
|
|
vfs : MjVfs, optional
|
|
A VFS to use for resolving includes and assets. Cannot be used with
|
|
include or assets.
|
|
)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,
|
|
MjVfs* vfs) -> MjSpec {
|
|
if (vfs && (include.has_value() || assets.has_value())) {
|
|
throw py::value_error(
|
|
"Cannot specify both 'vfs' and 'include'/'assets'.");
|
|
}
|
|
if (vfs) {
|
|
raw::MjSpec* spec;
|
|
{
|
|
py::gil_scoped_release no_gil;
|
|
char error[1024];
|
|
spec = InterceptMjErrors(mj_parseXMLString)(
|
|
xml.c_str(), vfs->get(), error, sizeof(error));
|
|
if (!spec) {
|
|
throw py::value_error(error);
|
|
}
|
|
}
|
|
return MjSpec(spec);
|
|
}
|
|
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(), py::arg("vfs") = 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.
|
|
vfs : MjVfs, optional
|
|
A VFS to use for resolving includes and assets. Cannot be used with
|
|
include or assets.
|
|
)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,
|
|
std::optional<MjVfs*> vfs) -> py::object {
|
|
mjVFS* vfs_ptr = vfs.has_value() ? (*vfs)->get() : nullptr;
|
|
return mjmodel_from_raw_ptr(
|
|
reinterpret_cast<uintptr_t>(self.Compile(vfs_ptr)));
|
|
},
|
|
py::arg("vfs") = py::none());
|
|
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(
|
|
"encode",
|
|
[](MjSpec& self, std::string filename,
|
|
std::optional<py::object> model,
|
|
std::optional<std::string> content_type) -> int {
|
|
raw::MjModel* m = nullptr;
|
|
if (model.has_value() && !model->is_none()) {
|
|
auto& wrapper =
|
|
py::cast<_impl::MjModelWrapper&>(*model);
|
|
m = wrapper.get();
|
|
}
|
|
|
|
mjVFS vfs;
|
|
mjVFS* vfs_ptr = nullptr;
|
|
if (!self.assets.empty()) {
|
|
mj_defaultVFS(&vfs);
|
|
vfs_ptr = &vfs;
|
|
for (const auto& asset : self.assets) {
|
|
std::string buffer_name =
|
|
py::cast<std::string>(asset.first);
|
|
std::string buffer =
|
|
py::cast<std::string>(asset.second);
|
|
mj_addBufferVFS(vfs_ptr, buffer_name.c_str(),
|
|
buffer.c_str(), buffer.size());
|
|
}
|
|
}
|
|
|
|
std::array<char, 1024> err;
|
|
err[0] = '\0';
|
|
const char* ct =
|
|
content_type.has_value() ? content_type->c_str() : nullptr;
|
|
int nbytes = mj_encode(self.ptr, m, filename.c_str(), ct,
|
|
vfs_ptr, err.data(), err.size());
|
|
|
|
if (vfs_ptr) {
|
|
mj_deleteVFS(vfs_ptr);
|
|
}
|
|
|
|
if (nbytes < 0) {
|
|
throw FatalError(std::string(err.data()));
|
|
}
|
|
return nbytes;
|
|
},
|
|
py::arg("filename"), py::arg("model") = py::none(),
|
|
py::arg("content_type") = py::none());
|
|
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) {
|
|
if (mjs_delete(self.ptr, body.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
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"));
|
|
mjsActuator.def(
|
|
"set_to_dcmotor",
|
|
[](raw::MjsActuator* self, std::array<double, 2> motorconst,
|
|
double resistance,
|
|
std::array<double, 3> nominal, std::array<double, 3> saturation,
|
|
std::array<double, 2> inductance, std::array<double, 3> cogging,
|
|
std::array<double, 6> controller, std::array<double, 6> thermal,
|
|
std::array<double, 5> lugre, int input_mode) {
|
|
std::string err = mjs_setToDCMotor(
|
|
self, motorconst.data(), resistance, nominal.data(),
|
|
saturation.data(), inductance.data(), cogging.data(),
|
|
controller.data(), thermal.data(), lugre.data(), input_mode);
|
|
if (!err.empty()) {
|
|
throw pybind11::value_error(err);
|
|
}
|
|
},
|
|
py::arg("motorconst"), py::arg("resistance"),
|
|
py::arg("nominal") = std::array<double, 3>{0, 0, 0},
|
|
py::arg("saturation") = std::array<double, 3>{0, 0, 0},
|
|
py::arg("inductance") = std::array<double, 2>{0, 0},
|
|
py::arg("cogging") = std::array<double, 3>{0, 0, 0},
|
|
py::arg("controller") = std::array<double, 6>{0, 0, 0, 0, 0, 0},
|
|
py::arg("thermal") = std::array<double, 6>{0, 0, 0, 0, 0, 0},
|
|
py::arg("lugre") = std::array<double, 5>{0, 0, 0, 0, 0},
|
|
py::arg("input_mode") = 0);
|
|
|
|
// ============================= 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) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSHFIELD ===================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsHField& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSSKIN =====================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsSkin& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSTEXTURE ==================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsTexture& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSKEY ======================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsKey& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSTEXT =====================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsText& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSNUMERIC ==================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsNumeric& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSEXCLUDE ==================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsExclude& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= MJSTUPLE ====================================
|
|
mjSpec.def("delete", [](MjSpec& self, raw::MjsTuple& obj) {
|
|
if (mjs_delete(self.ptr, obj.element) != 0) {
|
|
throw pybind11::value_error(mjs_getError(self.ptr));
|
|
}
|
|
});
|
|
|
|
// ============================= 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
|