Update spec.from_string() and spec.from_file() to match the respective mjModel functions.
PiperOrigin-RevId: 678230403 Change-Id: I7f3d8c5fd44d30b5695eee1f8513cc27f4158d94
This commit is contained in:
committed by
Copybara-Service
parent
910cd89cc7
commit
5bca7876c4
+75
-38
@@ -19,6 +19,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view> // IWYU pragma: keep
|
#include <string_view> // IWYU pragma: keep
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector> // IWYU pragma: keep
|
#include <vector> // IWYU pragma: keep
|
||||||
|
|
||||||
#include <Eigen/Core>
|
#include <Eigen/Core>
|
||||||
@@ -73,6 +74,37 @@ struct MjSpec {
|
|||||||
raw::MjSpec* ptr;
|
raw::MjSpec* ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
PYBIND11_MODULE(_specs, m) {
|
PYBIND11_MODULE(_specs, m) {
|
||||||
auto structs_m = py::module::import("mujoco._structs");
|
auto structs_m = py::module::import("mujoco._structs");
|
||||||
py::function mjmodel_from_spec_ptr =
|
py::function mjmodel_from_spec_ptr =
|
||||||
@@ -196,27 +228,25 @@ PYBIND11_MODULE(_specs, m) {
|
|||||||
mjSpec.def(
|
mjSpec.def(
|
||||||
"from_file",
|
"from_file",
|
||||||
[](MjSpec& self, std::string& filename,
|
[](MjSpec& self, std::string& filename,
|
||||||
std::optional<py::dict>& assets) -> void {
|
std::optional<std::unordered_map<std::string, py::bytes>>& assets)
|
||||||
mjVFS vfs;
|
-> void {
|
||||||
mj_defaultVFS(&vfs);
|
const auto converted_assets = _impl::ConvertAssetsDict(assets);
|
||||||
if (assets.has_value()) {
|
{
|
||||||
for (auto item : assets.value()) {
|
py::gil_scoped_release no_gil;
|
||||||
std::string buffer = py::cast<std::string>(item.second);
|
char error[1024];
|
||||||
mj_addBufferVFS(&vfs, py::cast<std::string>(item.first).c_str(),
|
mj_deleteSpec(self.ptr);
|
||||||
buffer.c_str(), buffer.size());
|
self.ptr = LoadSpecFileImpl(
|
||||||
};
|
filename, converted_assets,
|
||||||
}
|
[&error](const char* filename, const mjVFS* vfs) {
|
||||||
std::array<char, 1024> err;
|
return InterceptMjErrors(mj_parseXML)(
|
||||||
err[0] = '\0';
|
filename, vfs, error, sizeof(error));
|
||||||
mj_deleteSpec(self.ptr);
|
});
|
||||||
self.ptr = mj_parseXML(filename.c_str(), &vfs, err.data(), err.size());
|
if (!self.ptr) {
|
||||||
mj_deleteVFS(&vfs);
|
throw py::value_error(error);
|
||||||
if (!self.ptr) {
|
}
|
||||||
throw FatalError(std::string(err.data()));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
py::arg("filename"),
|
py::arg("filename"), py::arg("assets") = py::none(), R"mydelimiter(
|
||||||
py::arg("assets") = py::none(), R"mydelimiter(
|
|
||||||
Creates a spec from an XML file.
|
Creates a spec from an XML file.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -230,27 +260,34 @@ PYBIND11_MODULE(_specs, m) {
|
|||||||
mjSpec.def(
|
mjSpec.def(
|
||||||
"from_string",
|
"from_string",
|
||||||
[](MjSpec& self, std::string& xml,
|
[](MjSpec& self, std::string& xml,
|
||||||
std::optional<py::dict>& assets) -> void {
|
std::optional<std::unordered_map<std::string, py::bytes>>& assets)
|
||||||
mjVFS vfs;
|
-> void {
|
||||||
mj_defaultVFS(&vfs);
|
auto converted_assets = _impl::ConvertAssetsDict(assets);
|
||||||
if (assets.has_value()) {
|
{
|
||||||
for (auto item : assets.value()) {
|
py::gil_scoped_release no_gil;
|
||||||
std::string buffer = py::cast<std::string>(item.second);
|
std::string model_filename = "model_.xml";
|
||||||
mj_addBufferVFS(&vfs, py::cast<std::string>(item.first).c_str(),
|
if (assets.has_value()) {
|
||||||
buffer.c_str(), buffer.size());
|
while (assets->find(model_filename) != assets->end()) {
|
||||||
};
|
model_filename =
|
||||||
}
|
model_filename.substr(0, model_filename.size() - 4) + "_.xml";
|
||||||
std::array<char, 1024> err;
|
}
|
||||||
err[0] = '\0';
|
}
|
||||||
mj_deleteSpec(self.ptr);
|
converted_assets.emplace_back(
|
||||||
self.ptr = mj_parseXMLString(xml.c_str(), &vfs, err.data(), err.size());
|
model_filename.c_str(), xml.c_str(), xml.length());
|
||||||
mj_deleteVFS(&vfs);
|
char error[1024];
|
||||||
if (!self.ptr) {
|
mj_deleteSpec(self.ptr);
|
||||||
throw FatalError(std::string(err.data()));
|
self.ptr = LoadSpecFileImpl(
|
||||||
|
model_filename, converted_assets,
|
||||||
|
[&error](const char* filename, const mjVFS* vfs) {
|
||||||
|
return InterceptMjErrors(mj_parseXML)(
|
||||||
|
filename, vfs, error, sizeof(error));
|
||||||
|
});
|
||||||
|
if (!self.ptr) {
|
||||||
|
throw py::value_error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
py::arg("xml"),
|
py::arg("xml"), py::arg("assets") = py::none(), R"mydelimiter(
|
||||||
py::arg("assets") = py::none(), R"mydelimiter(
|
|
||||||
Creates a spec from an XML string.
|
Creates a spec from an XML string.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|||||||
@@ -611,6 +611,25 @@ class SpecsTest(absltest.TestCase):
|
|||||||
model = spec.compile({'cube.obj': cube})
|
model = spec.compile({'cube.obj': cube})
|
||||||
self.assertEqual(model.nmeshvert, 8)
|
self.assertEqual(model.nmeshvert, 8)
|
||||||
|
|
||||||
|
def test_include(self):
|
||||||
|
included_xml = """
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body>
|
||||||
|
<geom type="box" size="1 1 1"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
"""
|
||||||
|
spec = mujoco.MjSpec()
|
||||||
|
spec.from_string(textwrap.dedent("""
|
||||||
|
<mujoco model="MuJoCo Model">
|
||||||
|
<include file="included.xml"/>
|
||||||
|
</mujoco>
|
||||||
|
"""), {'included.xml': included_xml.encode('utf-8')})
|
||||||
|
self.assertEqual(spec.worldbody.first_body().first_geom().type,
|
||||||
|
mujoco.mjtGeom.mjGEOM_BOX)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
filename = '../../test/testdata/model.xml'
|
filename = '../../test/testdata/model.xml'
|
||||||
|
|
||||||
|
|||||||
@@ -87,21 +87,6 @@ inline std::size_t NConMax(const mjData* d) {
|
|||||||
return d->narena / sizeof(mjContact);
|
return d->narena / sizeof(mjContact);
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip path prefix from filename and make lowercase
|
|
||||||
std::string StripPath(const char* name) {
|
|
||||||
std::string filename(name);
|
|
||||||
size_t start = filename.find_last_of("/\\");
|
|
||||||
|
|
||||||
// get name without path
|
|
||||||
if (start != std::string::npos) {
|
|
||||||
filename = filename.substr(start + 1, filename.size() - start - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// make lowercase
|
|
||||||
std::transform(filename.begin(), filename.end(), filename.begin(),
|
|
||||||
[](unsigned char c) { return std::tolower(c); });
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// ==================== MJOPTION ===============================================
|
// ==================== MJOPTION ===============================================
|
||||||
@@ -318,16 +303,6 @@ MjModelWrapper::~MjWrapper() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
struct VfsAsset {
|
|
||||||
VfsAsset(const char* name, const void* content, std::size_t content_size)
|
|
||||||
: name(name), content(content), content_size(content_size) {}
|
|
||||||
const char* name;
|
|
||||||
const void* content;
|
|
||||||
std::size_t content_size;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function for both LoadXMLFile and LoadBinaryFile.
|
// Helper function for both LoadXMLFile and LoadBinaryFile.
|
||||||
// Creates a temporary MJB from the assets dictionary if one is supplied.
|
// Creates a temporary MJB from the assets dictionary if one is supplied.
|
||||||
template <typename LoadFunc>
|
template <typename LoadFunc>
|
||||||
@@ -365,22 +340,6 @@ static raw::MjModel* LoadModelFileImpl(
|
|||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a dict with py::bytes value to a vector of standard C++ types.
|
|
||||||
// This allows us to release the GIL early. Note that the vector consists only
|
|
||||||
// of pointers to existing data so no substantial data copies are being made.
|
|
||||||
static std::vector<VfsAsset>
|
|
||||||
ConvertAssetsDict(
|
|
||||||
const std::optional<std::unordered_map<std::string, py::bytes>>& assets) {
|
|
||||||
std::vector<VfsAsset> out;
|
|
||||||
if (assets.has_value()) {
|
|
||||||
for (const auto& [name, content] : *assets) {
|
|
||||||
out.emplace_back(name.c_str(), PYBIND11_BYTES_AS_STRING(content.ptr()),
|
|
||||||
py::len(content));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
MjModelWrapper MjModelWrapper::LoadXMLFile(
|
MjModelWrapper MjModelWrapper::LoadXMLFile(
|
||||||
const std::string& filename,
|
const std::string& filename,
|
||||||
const std::optional<std::unordered_map<std::string, py::bytes>>& assets) {
|
const std::optional<std::unordered_map<std::string, py::bytes>>& assets) {
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
#ifndef MUJOCO_PYTHON_STRUCTS_H_
|
#ifndef MUJOCO_PYTHON_STRUCTS_H_
|
||||||
#define MUJOCO_PYTHON_STRUCTS_H_
|
#define MUJOCO_PYTHON_STRUCTS_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cctype>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
@@ -39,9 +41,51 @@
|
|||||||
#include <pybind11/pybind11.h>
|
#include <pybind11/pybind11.h>
|
||||||
#include <pybind11/pytypes.h>
|
#include <pybind11/pytypes.h>
|
||||||
|
|
||||||
|
namespace py = ::pybind11;
|
||||||
|
|
||||||
namespace mujoco::python {
|
namespace mujoco::python {
|
||||||
namespace _impl {
|
namespace _impl {
|
||||||
|
|
||||||
|
struct VfsAsset {
|
||||||
|
VfsAsset(const char* name, const void* content, std::size_t content_size)
|
||||||
|
: name(name), content(content), content_size(content_size) {}
|
||||||
|
const char* name;
|
||||||
|
const void* content;
|
||||||
|
std::size_t content_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// strip path prefix from filename and make lowercase
|
||||||
|
inline std::string StripPath(const char* name) {
|
||||||
|
std::string filename(name);
|
||||||
|
size_t start = filename.find_last_of("/\\");
|
||||||
|
|
||||||
|
// get name without path
|
||||||
|
if (start != std::string::npos) {
|
||||||
|
filename = filename.substr(start + 1, filename.size() - start - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make lowercase
|
||||||
|
std::transform(filename.begin(), filename.end(), filename.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Converts a dict with py::bytes value to a vector of standard C++ types.
|
||||||
|
// This allows us to release the GIL early. Note that the vector consists only
|
||||||
|
// of pointers to existing data so no substantial data copies are being made.
|
||||||
|
inline std::vector<VfsAsset> ConvertAssetsDict(
|
||||||
|
const std::optional<std::unordered_map<std::string, py::bytes>>& assets) {
|
||||||
|
std::vector<VfsAsset> out;
|
||||||
|
if (assets.has_value()) {
|
||||||
|
for (const auto& [name, content] : *assets) {
|
||||||
|
out.emplace_back(name.c_str(), PYBIND11_BYTES_AS_STRING(content.ptr()),
|
||||||
|
py::len(content));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class WrapperBase {
|
class WrapperBase {
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user