Add assets to MjSpec wrapper.

This enables associating assets with a spec object.

Also, remove `spec.compile(assets)` and replace it with the `spec.assets` attribute, which must be be set before compile if assets are present.

PiperOrigin-RevId: 714981385
Change-Id: Ic3a33c3b75d3a7e14868622aadb57b2a8461a649
This commit is contained in:
Alessio Quaglino
2025-01-13 08:40:56 -08:00
committed by Copybara-Service
parent 64d0f57c50
commit ba183adb67
2 changed files with 56 additions and 30 deletions
+53 -29
View File
@@ -71,25 +71,43 @@ using MjDoubleRefVec = Eigen::Ref<const Eigen::VectorXd>;
struct MjSpec { struct MjSpec {
MjSpec() : ptr(mj_makeSpec()) {} MjSpec() : ptr(mj_makeSpec()) {}
MjSpec(raw::MjSpec* ptr) : ptr(ptr) {} MjSpec(raw::MjSpec* ptr,
const std::unordered_map<std::string, py::bytes>& assets_ = {})
: ptr(ptr) {
for (const auto& asset : assets_) {
assets[asset.first.c_str()] = asset.second;
}
}
// copy constructor and assignment // copy constructor and assignment
MjSpec(const MjSpec& other) : ptr(mj_copySpec(other.ptr)) {} MjSpec(const MjSpec& other) : ptr(mj_copySpec(other.ptr)) {
assets = other.assets;
}
MjSpec& operator=(const MjSpec& other) { MjSpec& operator=(const MjSpec& other) {
ptr = mj_copySpec(other.ptr); ptr = mj_copySpec(other.ptr);
assets = other.assets;
return *this; return *this;
} }
// move constructor and move assignment // move constructor and move assignment
MjSpec(MjSpec&& other) : ptr(other.ptr) { other.ptr = nullptr; } MjSpec(MjSpec&& other) : ptr(other.ptr) {
other.ptr = nullptr;
assets = other.assets;
other.assets.clear();
}
MjSpec& operator=(MjSpec&& other) { MjSpec& operator=(MjSpec&& other) {
ptr = other.ptr; ptr = other.ptr;
other.ptr = nullptr; other.ptr = nullptr;
assets = other.assets;
other.assets.clear();
return *this; return *this;
} }
~MjSpec() { mj_deleteSpec(ptr); } ~MjSpec() {
mj_deleteSpec(ptr);
}
raw::MjSpec* ptr; raw::MjSpec* ptr;
py::dict assets;
}; };
template <typename LoadFunc> template <typename LoadFunc>
@@ -263,6 +281,9 @@ PYBIND11_MODULE(_specs, m) {
throw py::value_error(error); throw py::value_error(error);
} }
} }
if (assets.has_value()) {
return MjSpec(spec, assets.value());
}
return MjSpec(spec); return MjSpec(spec);
}, },
py::arg("filename"), py::arg("assets") = py::none(), R"mydelimiter( py::arg("filename"), py::arg("assets") = py::none(), R"mydelimiter(
@@ -305,6 +326,9 @@ PYBIND11_MODULE(_specs, m) {
throw py::value_error(error); throw py::value_error(error);
} }
} }
if (assets.has_value()) {
return MjSpec(spec, assets.value());
}
return MjSpec(spec); return MjSpec(spec);
}, },
py::arg("xml"), py::arg("assets") = py::none(), R"mydelimiter( py::arg("xml"), py::arg("assets") = py::none(), R"mydelimiter(
@@ -324,7 +348,7 @@ PYBIND11_MODULE(_specs, m) {
m, d); m, d);
}); });
mjSpec.def("copy", [](const MjSpec& self) -> MjSpec { mjSpec.def("copy", [](const MjSpec& self) -> MjSpec {
return MjSpec(mj_copySpec(self.ptr)); return MjSpec(self);
}); });
mjSpec.def_property_readonly( mjSpec.def_property_readonly(
"worldbody", "worldbody",
@@ -370,33 +394,33 @@ PYBIND11_MODULE(_specs, m) {
return mjs_findDefault(self.ptr, classname.c_str()); return mjs_findDefault(self.ptr, classname.c_str());
}, },
py::return_value_policy::reference_internal); py::return_value_policy::reference_internal);
mjSpec.def("compile", [mjmodel_from_spec_ptr](MjSpec& self) { mjSpec.def("compile", [mjmodel_from_spec_ptr](MjSpec& self) -> py::object {
return mjmodel_from_spec_ptr(reinterpret_cast<uintptr_t>(self.ptr)); if (self.assets.empty()) {
return mjmodel_from_spec_ptr(reinterpret_cast<uintptr_t>(self.ptr));
}
mjVFS vfs;
mj_defaultVFS(&vfs);
for (auto item : self.assets) {
std::string buffer = py::cast<std::string>(item.second);
mj_addBufferVFS(&vfs, py::cast<std::string>(item.first).c_str(),
buffer.c_str(), buffer.size());
};
auto model =
mjmodel_from_spec_ptr(reinterpret_cast<uintptr_t>(self.ptr),
reinterpret_cast<uintptr_t>(&vfs));
mj_deleteVFS(&vfs);
return model;
}); });
mjSpec.def( mjSpec.def_property(
"compile", "assets",
[mjmodel_from_spec_ptr](MjSpec& self, py::dict& assets) -> py::object { [](MjSpec& self) -> py::dict {
mjVFS vfs; return self.assets;
mj_defaultVFS(&vfs); },
[](MjSpec& self, py::dict& assets) {
for (auto item : assets) { for (auto item : assets) {
std::string buffer = py::cast<std::string>(item.second); self.assets[item.first] = item.second;
mj_addBufferVFS(&vfs, py::cast<std::string>(item.first).c_str(),
buffer.c_str(), buffer.size());
}; };
auto model = }, py::return_value_policy::reference_internal);
mjmodel_from_spec_ptr(reinterpret_cast<uintptr_t>(self.ptr),
reinterpret_cast<uintptr_t>(&vfs));
mj_deleteVFS(&vfs);
return model;
}, R"mydelimiter(
Compiles the spec and returns the compiled model.
Parameters
----------
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");
mjSpec.def("to_xml", [](MjSpec& self) -> std::string { mjSpec.def("to_xml", [](MjSpec& self) -> std::string {
int size = mj_saveXMLString(self.ptr, nullptr, 0, nullptr, 0); int size = mj_saveXMLString(self.ptr, nullptr, 0, nullptr, 0);
std::unique_ptr<char[]> buf(new char[size + 1]); std::unique_ptr<char[]> buf(new char[size + 1]);
+3 -1
View File
@@ -714,8 +714,10 @@ class SpecsTest(absltest.TestCase):
geom = spec.worldbody.add_geom() geom = spec.worldbody.add_geom()
geom.type = mujoco.mjtGeom.mjGEOM_MESH geom.type = mujoco.mjtGeom.mjGEOM_MESH
geom.meshname = 'cube' geom.meshname = 'cube'
model = spec.compile({'cube.obj': cube}) spec.assets = {'cube.obj': cube}
model = spec.compile()
self.assertEqual(model.nmeshvert, 8) self.assertEqual(model.nmeshvert, 8)
self.assertEqual(spec.assets['cube.obj'], cube)
def test_include(self): def test_include(self):
included_xml = """ included_xml = """