Merge remote-tracking branch 'upstream/main' into tendons

This commit is contained in:
Abhishek Joshi
2024-07-04 15:10:29 -05:00
143 changed files with 7752 additions and 3408 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
include LICENSE *.md
recursive-include mujoco *.h *.cc *.mm CMakeLists.txt *.cmake
recursive-include mujoco *.h *.cc *.cc.inc *.mm CMakeLists.txt *.cmake
recursive-include mujoco/mjpython mjpython.* Info.plist
+2
View File
@@ -44,6 +44,8 @@ python "${package_dir}"/mujoco/codegen/generate_enum_traits.py > \
mujoco/enum_traits.h
python "${package_dir}"/mujoco/codegen/generate_function_traits.py > \
mujoco/function_traits.h
python "${package_dir}"/mujoco/codegen/generate_spec_bindings.py > \
mujoco/specs.cc.inc
export PYTHONPATH="${old_pythonpath}"
# Copy over the LICENSE file.
+25
View File
@@ -405,6 +405,29 @@ target_link_libraries(
structs_header
)
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/specs.cc.inc)
add_custom_command(
OUTPUT specs.cc.inc
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_spec_bindings.py > specs.cc.inc
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_spec_bindings.py
)
endif()
mujoco_pybind11_module(
_specs
specs.cc
specs.cc.inc
)
target_link_libraries(
_specs
PRIVATE mujoco
Eigen3::Eigen
errors_header
raw
structs_header
)
mujoco_pybind11_module(_simulate simulate.cc)
target_link_libraries(
_simulate
@@ -424,6 +447,7 @@ set(LIBRARIES_FOR_WHEEL
"$<TARGET_FILE:_render>"
"$<TARGET_FILE:_rollout>"
"$<TARGET_FILE:_simulate>"
"$<TARGET_FILE:_specs>"
"$<TARGET_FILE:_structs>"
"$<TARGET_FILE:mujoco>"
)
@@ -459,6 +483,7 @@ if(MUJOCO_PYTHON_MAKE_WHEEL)
_render
_rollout
_simulate
_specs
_structs
mujoco
)
+1
View File
@@ -43,6 +43,7 @@ from mujoco._enums import *
from mujoco._errors import *
from mujoco._functions import *
from mujoco._render import *
from mujoco._specs import *
from mujoco._structs import *
from mujoco.gl_context import *
from mujoco.renderer import Renderer
-16
View File
@@ -409,19 +409,6 @@ class MuJoCoBindingsTest(parameterized.TestCase):
np.testing.assert_array_equal(self.model.geom_size[1], [0.5, 0.5, 0.5])
np.testing.assert_array_equal(model_copy.geom_size[1], [0.1, 0.1, 0.1])
def test_assets_array_filename_too_long(self):
# Longest allowed filename (excluding null byte)
limit = mujoco.mjMAXVFSNAME - 1
contents = b'<mujoco/>'
valid_filename = 'a' * limit
mujoco.MjModel.from_xml_path(valid_filename, {valid_filename: contents})
invalid_filename = 'a' * (limit + 1)
expected_message = (
f'Filename length 1000 exceeds 999 character limit: {invalid_filename}')
with self.assertRaisesWithLiteralMatch(ValueError, expected_message):
mujoco.MjModel.from_xml_path(invalid_filename,
{invalid_filename: contents})
def test_mjdata_can_copy(self):
self.data.qpos = [0, 0, 0.1*np.sqrt(2) - 0.001,
np.cos(np.pi/8), np.sin(np.pi/8), 0, 0, 0,
@@ -856,9 +843,6 @@ Return the current version of MuJoCo as a null-terminated string.
Euler integrator, semi-implicit in velocity.
""")
def test_int_constant(self):
self.assertEqual(mujoco.mjMAXVFSNAME, 1000)
def test_float_constant(self):
self.assertEqual(mujoco.mjMAXVAL, 1e10)
self.assertEqual(mujoco.mjMINVAL, 1e-15)
@@ -0,0 +1,221 @@
# 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.
# ==============================================================================
"""Generates the bindings for the MuJoCo specs."""
from collections.abc import Sequence
from absl import app
from introspect import ast_nodes
from introspect import structs
SCALAR_TYPES = {'int', 'double', 'float', 'mjtByte', 'mjtNum'}
def _value_binding_code(
field: ast_nodes.ValueType, classname: str = '', varname: str = ''
) -> str:
"""Creates a string that defines Python bindings for a value type."""
fulltype = field.name
if field.name not in SCALAR_TYPES:
fulltype += '&'
fullvarname = varname
rawclassname = classname.replace('mjs', 'raw::Mjs')
if classname == 'mjSpec': # raw mjSpec has a wrapper
rawclassname = classname.replace('mjS', 'MjS')
fullvarname = 'ptr->' + varname
if field.name.startswith('mjs'): # all other mjs are raw structs
fulltype = field.name.replace('mjs', 'raw::Mjs')
if field.name != 'mjsPlugin' and field.name != 'mjsOrientation':
fulltype = fulltype + '*' # plugin and orientation are pointers
def_property_args = (
f'"{varname}"',
f"""[]({rawclassname}& self) -> {fulltype} {{
return self.{fullvarname};
}}""",
f"""[]({rawclassname}& self, {fulltype} {varname}) {{
self.{fullvarname} = {varname};
}}""",
)
if field.name not in SCALAR_TYPES:
def_property_args += ('py::return_value_policy::reference_internal',)
return f'{classname}.def_property({",".join(def_property_args)});'
def _array_binding_code(
field: ast_nodes.ArrayType, classname: str = '', varname: str = ''
) -> str:
"""Creates a string that declares Python bindings for an array type."""
if len(field.extents) > 1:
raise NotImplementedError()
innertype = field.inner_type.decl()
rawclassname = classname.replace('mjs', 'raw::Mjs')
fullvarname = varname
if classname == 'mjSpec': # raw mjSpec has a wrapper
rawclassname = classname.replace('mjS', 'MjS')
fullvarname = 'ptr->' + varname
if innertype == 'double' or innertype == 'mjtNum':
innertype = 'MjDouble' # custom Eigen type
elif innertype == 'float':
innertype = 'MjFloat' # custom Eigen type
elif innertype == 'int':
innertype = 'MjInt' # custom Eigen type
elif innertype == 'char':
# char array special case
return f"""\
{classname}.def_property(
"{varname}",
[]({rawclassname}& self) -> py::array_t<char> {{
return py::array_t<char>({field.extents[0]}, self.{fullvarname});
}},
[]({rawclassname}& self, py::object rhs) {{
int i = 0;
for (auto val : rhs) {{
self.{fullvarname}[i++] = py::cast<char>(val);
}}
}}, py::return_value_policy::reference_internal);"""
# all other array types
return f"""\
{classname}.def_property(
"{varname}",
[]({rawclassname}& self) -> {innertype}{field.extents[0]} {{
return {innertype}{field.extents[0]}(self.{fullvarname});
}},
[]({rawclassname}& self, {innertype}Ref{field.extents[0]} {varname}) {{
{innertype}{field.extents[0]}(self.{fullvarname}) = {varname};
}}, py::return_value_policy::reference_internal);"""
def _ptr_binding_code(
field: ast_nodes.PointerType, classname: str = '', varname: str = ''
) -> str:
"""Creates a string that declares Python bindings for a pointer type."""
vartype = field.inner_type.decl()
rawclassname = classname.replace('mjs', 'raw::Mjs')
fullvarname = varname
if classname == 'mjSpec': # raw mjSpec has a wrapper
rawclassname = classname.replace('mjS', 'MjS')
fullvarname = 'ptr->' + varname
if vartype == 'mjsElement': # this is ignored by the caller
return 'mjsElement'
if vartype.startswith('mjs'): # for structs, use the value case
return _value_binding_code(field.inner_type, classname, varname)
elif vartype == 'mjString': # C++ string -> Python string
return f"""\
{classname}.def_property(
"{varname}",
[]({rawclassname}& self) -> std::string_view {{
return *self.{fullvarname};
}},
[]({rawclassname}& self, std::string_view {varname}) {{
*(self.{fullvarname}) = {varname};
}});"""
elif ( # C++ vectors of values -> Python array
vartype == 'mjDoubleVec'
or vartype == 'mjFloatVec'
or vartype == 'mjIntVec'
):
vartype = vartype.replace('mj', '').replace('Vec', '').lower()
return f"""\
{classname}.def_property(
"{varname}",
[]({rawclassname}& self) -> py::array_t<{vartype}> {{
return py::array_t<{vartype}>(self.{fullvarname}->size(),
self.{fullvarname}->data());
}},
[]({rawclassname}& self, py::object rhs) {{
self.{fullvarname}->clear();
self.{fullvarname}->reserve(py::len(rhs));
for (auto val : rhs) {{
self.{fullvarname}->push_back(py::cast<{vartype}>(val));
}}
}}, py::return_value_policy::reference_internal);"""
elif vartype == 'mjStringVec': # C++ vector of strings -> Python list
return f"""\
{classname}.def_property(
"{varname}",
[]({rawclassname}& self) -> py::list {{
py::list list;
for (auto val : *self.{fullvarname}) {{
list.append(val);
}}
return list;
}},
[]({rawclassname}& self, py::object rhs) {{
self.{fullvarname}->clear();
self.{fullvarname}->reserve(py::len(rhs));
for (auto val : rhs) {{
self.{fullvarname}->push_back(py::cast<std::string>(val));
}}
}}, py::return_value_policy::reference_internal);"""
elif 'VecVec' in vartype: # C++ vector of vectors -> Python list of lists
vartype = vartype.replace('mj', '').replace('VecVec', '').lower()
return f"""\
{classname}.def_property(
"{varname}",
[]({rawclassname}& self) -> py::list {{
py::list list;
for (auto inner_vec : *self.{fullvarname}) {{
py::list inner_list;
for (auto val : inner_vec) {{
inner_list.append(val);
}}
list.append(inner_list);
}}
return list;
}},
[]({rawclassname}& self, py::object rhs) {{
self.{fullvarname}->clear();
self.{fullvarname}->reserve(py::len(rhs));
for (auto inner_list : rhs) {{
auto inner_vec = py::cast<std::vector<{vartype}>>(inner_list);
self.{fullvarname}->push_back(inner_vec);
}}
}}, py::return_value_policy::reference_internal);"""
raise NotImplementedError()
def _binding_code(field: ast_nodes.StructFieldDecl, key: str) -> str:
if isinstance(field.type, ast_nodes.ValueType):
return _value_binding_code(field.type, key, field.name)
elif isinstance(field.type, ast_nodes.PointerType):
return _ptr_binding_code(field.type, key, field.name)
elif isinstance(field.type, ast_nodes.ArrayType):
return _array_binding_code(field.type, key, field.name)
return ''
def generate() -> None:
for key in structs.STRUCTS.keys():
if (key.startswith('mjs') or key == 'mjSpec') and key != 'mjsElement':
print('\n // ' + key)
for field in structs.STRUCTS[key].fields:
code = _binding_code(field, key)
if code != 'mjsElement':
print(code)
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
generate()
if __name__ == '__main__':
app.run(main)
-2
View File
@@ -57,8 +57,6 @@ PYBIND11_MODULE(_constants, pymodule) {
X(mjMINIMP);
X(mjMAXIMP);
X(mjMAXCONPAIR);
X(mjMAXVFS);
X(mjMAXVFSNAME);
X(mjNEQDATA);
X(mjNDYN);
X(mjNGAIN);
+8 -8
View File
@@ -17,7 +17,6 @@
#include <array>
#include <cstdint>
#include <memory>
#include <sstream>
#include <string>
#include <optional>
@@ -29,6 +28,7 @@
#include "functions.h"
#include "private.h"
#include "raw.h"
#include "structs.h"
#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
@@ -108,9 +108,10 @@ PYBIND11_MODULE(_functions, pymodule) {
// Skipped: mj_copyModel (have MjModel.__copy__, memory managed by MjModel)
pymodule.def(
"mj_saveModel",
[](const MjModelWrapper& m, const std::optional<std::string>& filename = std::nullopt,
std::optional<
Eigen::Ref<Eigen::Vector<std::uint8_t, Eigen::Dynamic>>> buffer = std::nullopt) {
[](const MjModelWrapper& m,
const std::optional<std::string>& filename = std::nullopt,
std::optional<Eigen::Ref<Eigen::Vector<std::uint8_t, Eigen::Dynamic>>>
buffer = std::nullopt) {
void* buffer_ptr = nullptr;
int buffer_sz = 0;
if (buffer.has_value()) {
@@ -122,8 +123,7 @@ PYBIND11_MODULE(_functions, pymodule) {
buffer_ptr, buffer_sz);
},
py::arg("m"), py::arg_v("filename", std::nullopt),
py::arg_v("buffer", std::nullopt),
py::doc(traits::mj_saveModel::doc),
py::arg_v("buffer", std::nullopt), py::doc(traits::mj_saveModel::doc),
py::call_guard<py::gil_scoped_release>());
// Skipped: mj_loadModel (have MjModel.from_binary_path)
// Skipped: mj_deleteModel (have MjModel.__del__)
@@ -700,8 +700,8 @@ PYBIND11_MODULE(_functions, pymodule) {
Def<traits::mju_dist3>(pymodule);
Def<traits::mju_mulMatVec3>(pymodule);
Def<traits::mju_mulMatTVec3>(pymodule);
Def<traits::mju_rotVecMat>(pymodule);
Def<traits::mju_rotVecMatT>(pymodule);
// skipped: mju_rotVecMat
// skipped: mju_rotVecMatT
Def<traits::mju_cross>(pymodule);
Def<traits::mju_zero4>(pymodule);
Def<traits::mju_unit4>(pymodule);
+1 -1
View File
@@ -168,7 +168,7 @@
X( float, light_, specular, nlight, 3 )
#define MJMODEL_MATERIAL \
X( int, mat_, texid, nmat, 1 ) \
X( int, mat_, texid, nmat, mjNTEXMAT ) \
X( mjtByte, mat_, texuniform, nmat, 1 ) \
X( float, mat_, texrepeat, nmat, 2 ) \
X( float, mat_, emission, nmat, 1 ) \
+69
View File
@@ -0,0 +1,69 @@
# 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.
# ==============================================================================
"""Test that copying mujoco.MjData multiple times doesn't leak memory."""
import copy
import textwrap
from absl.testing import absltest
import mujoco
class MemoryLeakTest(absltest.TestCase):
# Regression test for https://github.com/google-deepmind/mujoco/issues/1572
def test_deepcopy_mjdata_leak(self):
# MuJoCo model with textures that take up significant memory.
model_xml = textwrap.dedent("""
<mujoco>
<asset>
<material name="body"/>
<texture name="grid" type="2d" builtin="checker"
width="1024" height="1024" rgb1="0 0 0" rgb2="1 1 1"/>
<material name="grid" texture="grid" texrepeat="1 1"
texuniform="true" reflectance=".2"/>
</asset>
<worldbody>
<geom size="0 0 .05" type="plane" material="grid"/>
</worldbody>
</mujoco>
""")
limit = self._memory_limit(4 * 2**30)
try:
model = mujoco.MjModel.from_xml_string(model_xml)
data = mujoco.MjData(model)
total = 0
for _ in range(1000):
mujoco.mj_step(model, data)
last_data = copy.deepcopy(data)
total += last_data.time
finally:
self._memory_limit(limit)
def _memory_limit(self, limit_in_bytes: int) -> int:
"""Limits max memory usage, and returns previous limit."""
soft = -1
try:
import resource # pylint: disable=g-import-not-at-top
soft, hard = resource.getrlimit(resource.RLIMIT_AS)
resource.setrlimit(resource.RLIMIT_AS, (limit_in_bytes, hard))
except (ImportError, ValueError):
# On Windows or systems where setting resource limits fails, do nothing.
pass
return soft
if __name__ == '__main__':
absltest.main()
+30
View File
@@ -18,6 +18,7 @@
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjspec.h>
#include <mujoco/mjvisualize.h>
// Type aliases for MuJoCo C structs to allow us refer to consistently refer
@@ -28,6 +29,35 @@ using MjContact = ::mjContact;
using MjData = ::mjData;
using MjLROpt = ::mjLROpt;
using MjModel = ::mjModel;
using MjSpec = ::mjSpec;
using MjsElement = ::mjsElement;
using MjsOrientation = ::mjsOrientation;
using MjsPlugin = ::mjsPlugin;
using MjsBody = ::mjsBody;
using MjsFrame = ::mjsFrame;
using MjsJoint = ::mjsJoint;
using MjsGeom = ::mjsGeom;
using MjsSite = ::mjsSite;
using MjsCamera = ::mjsCamera;
using MjsLight = ::mjsLight;
using MjsFlex = ::mjsFlex;
using MjsMesh = ::mjsMesh;
using MjsHField = ::mjsHField;
using MjsSkin = ::mjsSkin;
using MjsTexture = ::mjsTexture;
using MjsMaterial = ::mjsMaterial;
using MjsPair = ::mjsPair;
using MjsExclude = ::mjsExclude;
using MjsEquality = ::mjsEquality;
using MjsTendon = ::mjsTendon;
using MjsWrap = ::mjsWrap;
using MjsActuator = ::mjsActuator;
using MjsSensor = ::mjsSensor;
using MjsNumeric = ::mjsNumeric;
using MjsText = ::mjsText;
using MjsTuple = ::mjsTuple;
using MjsKey = ::mjsKey;
using MjsDefault = ::mjsDefault;
using MjOption = ::mjOption;
using MjSolverStat = ::mjSolverStat;
using MjStatistic = ::mjStatistic;
+12
View File
@@ -54,6 +54,9 @@ class MjWrapper<raw::MjrContext> : public WrapperBase<raw::MjrContext> {
X(auxFBO_r);
X(auxColor);
X(auxColor_r);
X(mat_texid);
X(mat_texuniform);
X(mat_texrepeat);
X(textureType);
X(texture);
X(skinvertVBO);
@@ -95,6 +98,9 @@ MjrContextWrapper::MjWrapper()
X(auxFBO_r),
X(auxColor),
X(auxColor_r),
X(mat_texid),
X(mat_texuniform),
X(mat_texrepeat),
X(textureType),
X(texture),
X_SKIN(skinvertVBO),
@@ -119,6 +125,9 @@ MjrContextWrapper::MjWrapper(const MjModelWrapper& model, int fontscale)
X(auxFBO_r),
X(auxColor),
X(auxColor_r),
X(mat_texid),
X(mat_texuniform),
X(mat_texrepeat),
X(textureType),
X(texture),
X_SKIN(skinvertVBO),
@@ -238,6 +247,9 @@ PYBIND11_MODULE(_render, pymodule) {
X(auxFBO_r);
X(auxColor);
X(auxColor_r);
X(mat_texid);
X(mat_texuniform);
X(mat_texrepeat);
X(textureType);
X(texture);
X(skinvertVBO);
+5
View File
@@ -123,6 +123,9 @@ class SimulateWrapper {
mujoco::Simulate* simulate() { return simulate_; }
py::object GetModel() const { return m_; }
py::object GetData() const { return d_; }
private:
mujoco::Simulate* simulate_;
std::atomic_int destroyed_ = 0;
@@ -222,6 +225,8 @@ PYBIND11_MODULE(_simulate, pymodule) {
.def("lock", GetIfNotNull(&mujoco::Simulate::mtx),
py::call_guard<py::gil_scoped_release>(),
py::return_value_policy::reference_internal)
.def_property_readonly("m", &SimulateWrapper::GetModel)
.def_property_readonly("d", &SimulateWrapper::GetData)
.def_property_readonly("ctrl_noise_std",
GetIfNotNull(&mujoco::Simulate::ctrl_noise_std),
py::call_guard<py::gil_scoped_release>())
+954
View File
@@ -0,0 +1,954 @@
// 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 <memory>
#include <string>
#include <string_view> // IWYU pragma: keep
#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 "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>;
struct MjSpec {
MjSpec() : ptr(mj_makeSpec()) {}
~MjSpec() { mj_deleteSpec(ptr); }
raw::MjSpec* ptr;
};
PYBIND11_MODULE(_specs, m) {
auto structs_m = py::module::import("mujoco._structs");
py::function mjmodel_from_spec_ptr =
structs_m.attr("MjModel").attr("_from_spec_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");
// ============================= MJSPEC =====================================
mjSpec.def(py::init<>());
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) -> raw::MjSpec* { return mj_copySpec(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"worldbody",
[](MjSpec& self) -> raw::MjsBody* {
return mjs_findBody(self.ptr, "world");
},
py::return_value_policy::reference_internal);
mjSpec.def(
"find_body",
[](MjSpec& self, std::string& name) -> raw::MjsBody* {
return mjs_findBody(self.ptr, name.c_str());
},
py::return_value_policy::reference_internal);
mjSpec.def(
"find_mesh",
[](MjSpec& self, std::string& name) -> raw::MjsMesh* {
return mjs_findMesh(self.ptr, name.c_str());
},
py::return_value_policy::reference_internal);
mjSpec.def(
"find_frame",
[](MjSpec& self, std::string& name) -> raw::MjsFrame* {
return mjs_findFrame(self.ptr, name.c_str());
},
py::return_value_policy::reference_internal);
mjSpec.def(
"find_keyframe",
[](MjSpec& self, std::string& name) -> raw::MjsKey* {
return mjs_findKeyframe(self.ptr, name.c_str());
},
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_spec_ptr](MjSpec& self) {
return mjmodel_from_spec_ptr(reinterpret_cast<uintptr_t>(self.ptr));
});
mjSpec.def(
"copy_back",
[](MjSpec& self, raw::MjModel& model) {
return mj_copyBack(self.ptr, &model);
},
py::return_value_policy::reference_internal);
mjSpec.def("to_xml", [](MjSpec& self) -> std::string {
int size = mj_saveXMLString(self.ptr, nullptr, 0, nullptr, 0);
std::unique_ptr<char[]> buf(new char[size + 1]);
std::array<char, 1024> err;
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("from_file", [](MjSpec& self, std::string& filename) -> void {
std::array<char, 1024> err;
err[0] = '\0';
mj_deleteSpec(self.ptr);
self.ptr = mj_parseXML(filename.c_str(), 0, err.data(), err.size());
if (!self.ptr) {
throw FatalError(std::string(err.data()));
}
});
mjSpec.def("from_string", [](MjSpec& self, std::string& xml) -> void {
std::array<char, 1024> err;
err[0] = '\0';
mj_deleteSpec(self.ptr);
self.ptr = mj_parseXMLString(xml.c_str(), 0, err.data(), err.size());
if (!self.ptr) {
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(
"default",
[](MjSpec& self) -> raw::MjsDefault* {
return mjs_getSpecDefault(self.ptr);
},
py::return_value_policy::reference_internal);
mjSpec.def(
"add_material",
[](MjSpec& self, raw::MjsDefault* default_) -> raw::MjsMaterial* {
return mjs_addMaterial(self.ptr, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjSpec.def(
"add_mesh",
[](MjSpec& self, raw::MjsDefault* default_) -> raw::MjsMesh* {
return mjs_addMesh(self.ptr, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjSpec.def(
"add_skin",
[](MjSpec& self) -> raw::MjsSkin* { return mjs_addSkin(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_texture",
[](MjSpec& self) -> raw::MjsTexture* { return mjs_addTexture(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_text",
[](MjSpec& self) -> raw::MjsText* { return mjs_addText(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_tuple",
[](MjSpec& self) -> raw::MjsTuple* { return mjs_addTuple(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_flex",
[](MjSpec& self) -> raw::MjsFlex* { return mjs_addFlex(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_hfield",
[](MjSpec& self) -> raw::MjsHField* { return mjs_addHField(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_key",
[](MjSpec& self) -> raw::MjsKey* { return mjs_addKey(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_numeric",
[](MjSpec& self) -> raw::MjsNumeric* { return mjs_addNumeric(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_pair",
[](MjSpec& self, raw::MjsDefault* default_) -> raw::MjsPair* {
return mjs_addPair(self.ptr, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjSpec.def(
"add_exclude",
[](MjSpec& self) -> raw::MjsExclude* { return mjs_addExclude(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_equality",
[](MjSpec& self, raw::MjsDefault* default_) -> raw::MjsEquality* {
return mjs_addEquality(self.ptr, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjSpec.def(
"add_tendon",
[](MjSpec& self, raw::MjsDefault* default_) -> raw::MjsTendon* {
return mjs_addTendon(self.ptr, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjSpec.def(
"add_sensor",
[](MjSpec& self) -> raw::MjsSensor* { return mjs_addSensor(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def(
"add_actuator",
[](MjSpec& self, raw::MjsDefault* default_) -> raw::MjsActuator* {
return mjs_addActuator(self.ptr, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjSpec.def(
"add_plugin",
[](MjSpec& self) -> raw::MjsPlugin* { return mjs_addPlugin(self.ptr); },
py::return_value_policy::reference_internal);
mjSpec.def("detach_body", [](MjSpec& self, raw::MjsBody& body) {
mjs_detachBody(self.ptr, &body);
});
mjSpec.def_property_readonly(
"actuators",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_ACTUATOR);
while (el) {
list.append(mjs_asActuator(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"sensors",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_SENSOR);
while (el) {
list.append(mjs_asSensor(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"flexes",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_FLEX);
while (el) {
list.append(mjs_asFlex(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"pairs",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_PAIR);
while (el) {
list.append(mjs_asPair(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"equality",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_EQUALITY);
while (el) {
list.append(mjs_asEquality(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"excludes",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_EXCLUDE);
while (el) {
list.append(mjs_asExclude(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"tendons",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_TENDON);
while (el) {
list.append(mjs_asTendon(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"numeric",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_NUMERIC);
while (el) {
list.append(mjs_asNumeric(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"text",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_TEXT);
while (el) {
list.append(mjs_asText(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"tuple",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_TUPLE);
while (el) {
list.append(mjs_asTuple(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"key",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_KEY);
while (el) {
list.append(mjs_asKey(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"mesh",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_MESH);
while (el) {
list.append(mjs_asMesh(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"hfield",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_HFIELD);
while (el) {
list.append(mjs_asHField(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"skin",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_SKIN);
while (el) {
list.append(mjs_asSkin(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"texture",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_TEXTURE);
while (el) {
list.append(mjs_asTexture(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
mjSpec.def_property_readonly(
"material",
[](MjSpec& self) -> py::list {
py::list list;
raw::MjsElement* el = mjs_firstElement(self.ptr, mjOBJ_MATERIAL);
while (el) {
list.append(mjs_asMaterial(el));
el = mjs_nextElement(self.ptr, el);
}
return list;
},
py::return_value_policy::reference_internal);
// ============================= MJSBODY =====================================
mjsBody.def_property_readonly(
"id", [](raw::MjsBody& self) -> int { return mjs_getId(self.element); });
mjsBody.def("delete", [](raw::MjsBody& self) { mjs_delete(self.element); });
mjsBody.def(
"add_body",
[](raw::MjsBody& self, raw::MjsDefault* default_) -> raw::MjsBody* {
return mjs_addBody(&self, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjsBody.def(
"add_frame",
[](raw::MjsBody& self, raw::MjsFrame* parentframe_) -> raw::MjsFrame* {
return mjs_addFrame(&self, parentframe_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjsBody.def(
"add_geom",
[](raw::MjsBody& self, raw::MjsDefault* default_) -> raw::MjsGeom* {
return mjs_addGeom(&self, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjsBody.def(
"add_joint",
[](raw::MjsBody& self, raw::MjsDefault* default_) -> raw::MjsJoint* {
return mjs_addJoint(&self, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjsBody.def(
"add_freejoint",
[](raw::MjsBody& self) -> raw::MjsJoint* {
return mjs_addFreeJoint(&self);
},
py::return_value_policy::reference_internal);
mjsBody.def(
"add_light",
[](raw::MjsBody& self, raw::MjsDefault* default_) -> raw::MjsLight* {
return mjs_addLight(&self, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjsBody.def(
"add_site",
[](raw::MjsBody& self, raw::MjsDefault* default_) -> raw::MjsSite* {
return mjs_addSite(&self, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjsBody.def(
"add_camera",
[](raw::MjsBody& self, raw::MjsDefault* default_) -> raw::MjsCamera* {
return mjs_addCamera(&self, default_);
},
py::arg_v("default", nullptr),
py::return_value_policy::reference_internal);
mjsBody.def("set_frame",
[](raw::MjsBody& self, raw::MjsFrame& frame) -> void {
mjs_setFrame(self.element, &frame);
});
mjsBody.def("set_default",
[](raw::MjsBody& self, raw::MjsDefault& default_) -> void {
mjs_setDefault(self.element, &default_);
});
mjsBody.def(
"default",
[](raw::MjsBody& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
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));
},
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));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_camera",
[](raw::MjsBody& self) -> raw::MjsCamera* {
return mjs_asCamera(mjs_firstChild(&self, mjOBJ_CAMERA));
},
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));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_light",
[](raw::MjsBody& self) -> raw::MjsLight* {
return mjs_asLight(mjs_firstChild(&self, mjOBJ_LIGHT));
},
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));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_joint",
[](raw::MjsBody& self) -> raw::MjsJoint* {
return mjs_asJoint(mjs_firstChild(&self, mjOBJ_JOINT));
},
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));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_geom",
[](raw::MjsBody& self) -> raw::MjsGeom* {
return mjs_asGeom(mjs_firstChild(&self, mjOBJ_GEOM));
},
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));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_site",
[](raw::MjsBody& self) -> raw::MjsSite* {
return mjs_asSite(mjs_firstChild(&self, mjOBJ_SITE));
},
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));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"first_frame",
[](raw::MjsBody& self) -> raw::MjsFrame* {
return mjs_asFrame(mjs_firstChild(&self, mjOBJ_FRAME));
},
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));
},
py::return_value_policy::reference_internal);
mjsBody.def(
"spec",
[](raw::MjsBody& self) -> raw::MjSpec* { return mjs_getSpec(&self); },
py::return_value_policy::reference_internal);
mjsBody.def("attach_frame",
[](raw::MjsBody& self, raw::MjsFrame& frame, std::string& prefix,
std::string& suffix) -> void {
mjs_attachFrame(&self, &frame, prefix.c_str(), suffix.c_str());
});
// ============================= MJSFRAME ====================================
mjsFrame.def_property_readonly(
"id", [](raw::MjsFrame& self) -> int { return mjs_getId(self.element); });
mjsFrame.def("delete", [](raw::MjsFrame& self) { mjs_delete(self.element); });
mjsFrame.def("set_frame", [](raw::MjsFrame& self, raw::MjsFrame& frame) {
mjs_setFrame(self.element, &frame);
});
mjsFrame.def("attach_body", [](raw::MjsFrame& self, raw::MjsBody& body,
std::string& prefix, std::string& suffix) {
mjs_attachBody(&self, &body, prefix.c_str(), suffix.c_str());
});
// ============================= MJSGEOM =====================================
mjsGeom.def_property_readonly(
"id", [](raw::MjsGeom& self) -> int { return mjs_getId(self.element); });
mjsGeom.def("delete", [](raw::MjsGeom& self) { mjs_delete(self.element); });
mjsGeom.def("set_frame", [](raw::MjsGeom& self, raw::MjsFrame& frame) {
mjs_setFrame(self.element, &frame);
});
mjsGeom.def("set_default", [](raw::MjsGeom& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsGeom.def(
"default",
[](raw::MjsGeom& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSJOINT ====================================
mjsJoint.def_property_readonly(
"id", [](raw::MjsJoint& self) -> int { return mjs_getId(self.element); });
mjsJoint.def("delete", [](raw::MjsJoint& self) { mjs_delete(self.element); });
mjsJoint.def("set_frame", [](raw::MjsJoint& self, raw::MjsFrame& frame) {
mjs_setFrame(self.element, &frame);
});
mjsJoint.def("set_default", [](raw::MjsJoint& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsJoint.def(
"default",
[](raw::MjsJoint& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSSITE =====================================
mjsSite.def_property_readonly(
"id", [](raw::MjsSite& self) -> int { return mjs_getId(self.element); });
mjsSite.def("delete", [](raw::MjsSite& self) { mjs_delete(self.element); });
mjsSite.def("set_frame", [](raw::MjsSite& self, raw::MjsFrame& frame) {
mjs_setFrame(self.element, &frame);
});
mjsSite.def("set_default", [](raw::MjsSite& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsSite.def(
"default",
[](raw::MjsSite& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSCAMERA ===================================
mjsCamera.def_property_readonly("id", [](raw::MjsCamera& self) -> int {
return mjs_getId(self.element);
});
mjsCamera.def("delete",
[](raw::MjsCamera& self) { mjs_delete(self.element); });
mjsCamera.def("set_frame", [](raw::MjsCamera& self, raw::MjsFrame& frame) {
mjs_setFrame(self.element, &frame);
});
mjsCamera.def("set_default", [](raw::MjsCamera& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsCamera.def(
"default",
[](raw::MjsCamera& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSLIGHT ====================================
mjsLight.def_property_readonly(
"id", [](raw::MjsLight& self) -> int { return mjs_getId(self.element); });
mjsLight.def("delete", [](raw::MjsLight& self) { mjs_delete(self.element); });
mjsLight.def("set_frame", [](raw::MjsLight& self, raw::MjsFrame& frame) {
mjs_setFrame(self.element, &frame);
});
mjsLight.def("set_default", [](raw::MjsLight& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsLight.def(
"default",
[](raw::MjsLight& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSMATERIAL =================================
mjsMaterial.def_property_readonly("id", [](raw::MjsMaterial& self) -> int {
return mjs_getId(self.element);
});
mjsMaterial.def("delete",
[](raw::MjsMaterial& self) { mjs_delete(self.element); });
mjsMaterial.def("set_default",
[](raw::MjsMaterial& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsMaterial.def(
"default",
[](raw::MjsMaterial& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSMESH =====================================
mjsMesh.def_property_readonly(
"id", [](raw::MjsMesh& self) -> int { return mjs_getId(self.element); });
mjsMesh.def("delete", [](raw::MjsMesh& self) { mjs_delete(self.element); });
mjsMesh.def("set_default", [](raw::MjsMesh& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsMesh.def(
"default",
[](raw::MjsMesh& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSPAIR =====================================
mjsPair.def_property_readonly(
"id", [](raw::MjsPair& self) -> int { return mjs_getId(self.element); });
mjsPair.def("delete", [](raw::MjsPair& self) { mjs_delete(self.element); });
mjsPair.def("set_default", [](raw::MjsPair& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsPair.def(
"default",
[](raw::MjsPair& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSEQUAL ====================================
mjsEquality.def_property_readonly("id", [](raw::MjsEquality& self) -> int {
return mjs_getId(self.element);
});
mjsEquality.def("delete",
[](raw::MjsEquality& self) { mjs_delete(self.element); });
mjsEquality.def("set_default",
[](raw::MjsEquality& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsEquality.def(
"default",
[](raw::MjsEquality& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSACTUATOR =================================
mjsActuator.def_property_readonly("id", [](raw::MjsActuator& self) -> int {
return mjs_getId(self.element);
});
mjsActuator.def("delete",
[](raw::MjsActuator& self) { mjs_delete(self.element); });
mjsActuator.def("set_default",
[](raw::MjsActuator& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsActuator.def(
"default",
[](raw::MjsActuator& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
py::return_value_policy::reference_internal);
// ============================= MJSTENDON ===================================
mjsTendon.def_property_readonly("id", [](raw::MjsTendon& self) -> int {
return mjs_getId(self.element);
});
mjsTendon.def("delete",
[](raw::MjsTendon& self) { mjs_delete(self.element); });
mjsTendon.def("set_default", [](raw::MjsTendon& self, raw::MjsDefault& def) {
mjs_setDefault(self.element, &def);
});
mjsTendon.def(
"default",
[](raw::MjsTendon& self) -> raw::MjsDefault* {
return mjs_getDefault(self.element);
},
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 ===================================
mjsSensor.def_property_readonly("id", [](raw::MjsSensor& self) -> int {
return mjs_getId(self.element);
});
mjsSensor.def("delete",
[](raw::MjsSensor& self) { mjs_delete(self.element); });
// ============================= MJSFLEX =====================================
mjsFlex.def_property_readonly(
"id", [](raw::MjsFlex& self) -> int { return mjs_getId(self.element); });
mjsFlex.def("delete", [](raw::MjsFlex& self) { mjs_delete(self.element); });
// ============================= MJSHFIELD ===================================
mjsHField.def_property_readonly("id", [](raw::MjsHField& self) -> int {
return mjs_getId(self.element);
});
mjsHField.def("delete",
[](raw::MjsHField& self) { mjs_delete(self.element); });
// ============================= MJSSKIN =====================================
mjsSkin.def_property_readonly(
"id", [](raw::MjsSkin& self) -> int { return mjs_getId(self.element); },
py::return_value_policy::reference_internal);
mjsSkin.def("delete", [](raw::MjsSkin& self) { mjs_delete(self.element); });
// ============================= MJSTEXTURE ==================================
mjsTexture.def_property_readonly("id", [](raw::MjsTexture& self) -> int {
return mjs_getId(self.element);
});
mjsTexture.def("delete",
[](raw::MjsTexture& self) { mjs_delete(self.element); });
// ============================= MJSKEY ======================================
mjsKey.def_property_readonly(
"id", [](raw::MjsKey& self) -> int { return mjs_getId(self.element); });
mjsKey.def("delete", [](raw::MjsKey& self) { mjs_delete(self.element); });
// ============================= MJSTEXT =====================================
mjsText.def_property_readonly(
"id", [](raw::MjsText& self) -> int { return mjs_getId(self.element); });
mjsText.def("delete", [](raw::MjsText& self) { mjs_delete(self.element); });
// ============================= MJSNUMERIC ==================================
mjsNumeric.def_property_readonly("id", [](raw::MjsNumeric& self) -> int {
return mjs_getId(self.element);
});
mjsNumeric.def("delete",
[](raw::MjsNumeric& self) { mjs_delete(self.element); });
// ============================= MJSEXCLUDE ==================================
mjsExclude.def_property_readonly("id", [](raw::MjsExclude& self) -> int {
return mjs_getId(self.element);
});
mjsExclude.def("delete",
[](raw::MjsExclude& self) { mjs_delete(self.element); });
// ============================= MJSTUPLE ====================================
mjsTuple.def_property_readonly(
"id", [](raw::MjsTuple& self) -> int { return mjs_getId(self.element); });
mjsTuple.def("delete", [](raw::MjsTuple& self) { mjs_delete(self.element); });
// ============================= MJSPLUGIN ===================================
mjsPlugin.def_property_readonly("id", [](raw::MjsPlugin& self) -> int {
return mjs_getId(self.instance);
});
mjsPlugin.def("delete",
[](raw::MjsPlugin& self) { mjs_delete(self.instance); });
#include "specs.cc.inc"
} // PYBIND11_MODULE // NOLINT
} // namespace mujoco::python
+237
View File
@@ -0,0 +1,237 @@
# 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.
# ==============================================================================
"""Tests for mjSpec bindings."""
import inspect
import textwrap
from absl.testing import absltest
import mujoco
import numpy as np
def get_linenumber():
cf = inspect.currentframe()
return cf.f_back.f_lineno
class SpecsTest(absltest.TestCase):
def test_basic(self):
# Create a spec.
spec = mujoco.MjSpec()
# Check that euler sequence order is set correctly.
self.assertEqual(spec.eulerseq[0], ord('x'))
spec.eulerseq = ['z', 'y', 'x']
self.assertEqual(spec.eulerseq[0], ord('z'))
# Add a body, check that it has default orientation.
body = spec.worldbody.add_body()
self.assertEqual(body.name, '')
np.testing.assert_array_equal(body.quat, [1, 0, 0, 0])
# Change the name of the body and read it back twice.
body.name = 'foobar'
self.assertEqual(body.name, 'foobar')
body.name = 'baz'
self.assertEqual(body.name, 'baz')
# Change the position of the body and read it back.
body.pos = [1, 2, 3]
np.testing.assert_array_equal(body.pos, [1, 2, 3])
self.assertEqual(body.pos.shape, (3,))
# Change the orientation of the body and read it back.
body.quat = [0, 1, 0, 0]
np.testing.assert_array_equal(body.quat, [0, 1, 0, 0])
self.assertEqual(body.quat.shape, (4,))
# Add a site to the body with user data and read it back.
site = body.add_site()
site.name = 'sitename'
site.userdata = [1, 2, 3, 4, 5, 6]
self.assertEqual(site.name, 'sitename')
np.testing.assert_array_equal(site.userdata, [1, 2, 3, 4, 5, 6])
# Check that the site and body have no id before compilation.
self.assertEqual(body.id, -1)
self.assertEqual(site.id, -1)
# Compile the spec and check for expected values in the model.
model = spec.compile()
self.assertEqual(spec.worldbody.id, 0)
self.assertEqual(body.id, 1)
self.assertEqual(site.id, 0)
self.assertEqual(model.nbody, 2) # 2 bodies, including the world body
np.testing.assert_array_equal(model.body_pos[1], [1, 2, 3])
np.testing.assert_array_equal(model.body_quat[1], [0, 1, 0, 0])
self.assertEqual(model.nsite, 1)
self.assertEqual(model.nuser_site, 6)
np.testing.assert_array_equal(model.site_user[0], [1, 2, 3, 4, 5, 6])
self.assertEqual(spec.to_xml(), textwrap.dedent("""\
<mujoco model="MuJoCo Model">
<compiler angle="radian"/>
<size nuser_site="6"/>
<worldbody>
<body name="baz" pos="1 2 3" quat="0 1 0 0">
<site name="sitename" pos="0 0 0" user="1 2 3 4 5 6"/>
</body>
</worldbody>
</mujoco>
"""),)
def test_compile_errors_with_line_info(self):
spec = mujoco.MjSpec()
added_on_line = get_linenumber() + 1
geom = spec.worldbody.add_geom()
geom.name = 'MyGeom'
geom.info = f'geom added on line {added_on_line}'
# Try to compile, get error.
expected_error = (
'Error: size 0 must be positive in geom\n'
+ f'Element name \'MyGeom\', id 0, geom added on line {added_on_line}'
)
with self.assertRaisesRegex(ValueError, expected_error):
spec.compile()
def test_recompile(self):
# Create a spec.
spec = mujoco.MjSpec()
# Add movable body1.
body1 = spec.worldbody.add_body()
geom = body1.add_geom()
geom.size[0] = 1
geom.pos = [1, 1, 0]
joint = body1.add_joint()
joint.type = mujoco.mjtJoint.mjJNT_BALL
# Compile model, make data.
model = spec.compile()
data = mujoco.MjData(model)
# Simulate for 1 second.
while data.time < 1:
mujoco.mj_step(model, data)
# Add movable body2.
body2 = spec.worldbody.add_body()
body2.pos[1] = 3
geom = body2.add_geom()
geom.size[0] = 1
geom.pos = [0, 1, 0]
joint = body2.add_joint()
joint.type = mujoco.mjtJoint.mjJNT_BALL
# Recompile model and data while maintaining the state.
model_new, data_new = spec.recompile(model, data)
# Check that the state is preserved.
np.testing.assert_array_equal(model_new.body_pos[1], model.body_pos[1])
np.testing.assert_array_equal(data_new.qpos[:4], data.qpos)
np.testing.assert_array_equal(data_new.qvel[:3], data.qvel)
def test_uncompiled_spec_cannot_be_written(self):
spec = mujoco.MjSpec()
# Cannot write XML of an uncompiled spec.
expected_error = 'XML Write error: Only compiled model can be written'
with self.assertRaisesWithLiteralMatch(mujoco.FatalError, expected_error):
spec.to_xml()
def test_modelname_default_class(self):
spec = mujoco.MjSpec()
spec.modelname = 'test'
main = spec.default()
main.geom.size[0] = 2
def1 = spec.add_default('def1', main)
def1.geom.size[0] = 3
spec.worldbody.add_geom(def1)
spec.worldbody.add_geom(main)
spec.compile()
self.assertEqual(spec.to_xml(), textwrap.dedent("""\
<mujoco model="test">
<compiler angle="radian"/>
<default>
<geom size="2 0 0"/>
<default class="def1">
<geom size="3 0 0"/>
</default>
</default>
<worldbody>
<geom class="def1"/>
<geom/>
</worldbody>
</mujoco>
"""))
spec = mujoco.MjSpec()
spec.modelname = 'test'
main = spec.default()
main.geom.size[0] = 2
def1 = spec.add_default('def1', main)
def1.geom.size[0] = 3
spec.worldbody.add_geom(def1)
spec.worldbody.add_geom(main)
spec.compile()
self.assertEqual(spec.to_xml(), textwrap.dedent("""\
<mujoco model="test">
<compiler angle="radian"/>
<default>
<geom size="2 0 0"/>
<default class="def1">
<geom size="3 0 0"/>
</default>
</default>
<worldbody>
<geom class="def1"/>
<geom/>
</worldbody>
</mujoco>
"""))
def test_element_list(self):
spec = mujoco.MjSpec()
sensor1 = spec.add_sensor()
sensor2 = spec.add_sensor()
sensor3 = spec.add_sensor()
sensor1.name = 'sensor1'
sensor2.name = 'sensor2'
sensor3.name = 'sensor3'
self.assertLen(spec.sensors, 3)
self.assertEqual(spec.sensors[0].name, 'sensor1')
self.assertEqual(spec.sensors[1].name, 'sensor2')
self.assertEqual(spec.sensors[2].name, 'sensor3')
if __name__ == '__main__':
absltest.main()
+47 -23
View File
@@ -28,6 +28,7 @@
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
@@ -316,24 +317,22 @@ static raw::MjModel* LoadModelFileImpl(
const std::string& filename,
const std::vector<VfsAsset>& assets,
LoadFunc&& loadfunc) {
std::unique_ptr<mjVFS, void(*)(mjVFS*)> vfs(nullptr, [](mjVFS*){});
mjVFS vfs;
mjVFS* vfs_ptr = nullptr;
if (!assets.empty()) {
// mjVFS should be allocated on the heap, because it's ~2MB
vfs = decltype(vfs)(new mjVFS, [](mjVFS* vfs) {
mj_deleteVFS(vfs);
delete vfs;
});
mj_defaultVFS(vfs.get());
mj_defaultVFS(&vfs);
vfs_ptr = &vfs;
for (const auto& asset : assets) {
const int vfs_error = InterceptMjErrors(mj_addBufferVFS)(
vfs.get(), asset.name, asset.content, asset.content_size);
vfs_ptr, asset.name, asset.content, asset.content_size);
if (vfs_error) {
throw py::value_error("assets dict is too big");
}
}
}
raw::MjModel* model = loadfunc(filename.c_str(), vfs.get());
raw::MjModel* model = loadfunc(filename.c_str(), vfs_ptr);
mj_deleteVFS(vfs_ptr);
if (model && !model->buffer) {
mj_deleteModel(model);
model = nullptr;
@@ -350,12 +349,6 @@ ConvertAssetsDict(
std::vector<VfsAsset> out;
if (assets.has_value()) {
for (const auto& [name, content] : *assets) {
if (name.length() >= mjMAXVFSNAME) {
std::ostringstream error;
error << "Filename length " << name.length() << " exceeds "
<< mjMAXVFSNAME - 1 << " character limit: " << name;
throw py::value_error(error.str());
}
out.emplace_back(name.c_str(), PYBIND11_BYTES_AS_STRING(content.ptr()),
py::len(content));
}
@@ -430,6 +423,27 @@ MjModelWrapper MjModelWrapper::LoadXML(
return MjModelWrapper(model);
}
MjModelWrapper MjModelWrapper::CompileSpec(raw::MjSpec* spec) {
auto m = mj_compile(spec, nullptr);
if (!m || mjs_isWarning(spec)) {
throw py::value_error(mjs_getError(spec));
}
return MjModelWrapper(m);
}
py::tuple RecompileSpec(raw::MjSpec* spec, const MjModelWrapper& old_m,
const MjDataWrapper& old_d) {
raw::MjModel* m = static_cast<raw::MjModel*>(mju_malloc(sizeof(mjModel)));
m->buffer = nullptr;
raw::MjData* d = mj_copyData(nullptr, old_m.get(), old_d.get());
mj_recompile(spec, nullptr, m, d);
py::object m_pyobj = py::cast((MjModelWrapper(m)));
py::object d_pyobj =
py::cast((MjDataWrapper(py::cast<MjModelWrapper*>(m_pyobj), d)));
return py::make_tuple(m_pyobj, d_pyobj);
}
namespace {
// A byte at the start of serialized mjModel structs, which can be incremented
// when we change the serialization logic to reject pickles from an unsupported
@@ -1056,7 +1070,6 @@ MjvGLCameraWrapper::MjWrapper(const MjvGLCameraWrapper& other)
#define X(var) var(InitPyArray(ptr_->var, owner_))
MjvGeomWrapper::MjWrapper()
: WrapperBase(new raw::MjvGeom{}),
X(texrepeat),
X(size),
X(pos),
mat([this]() {
@@ -1069,7 +1082,6 @@ MjvGeomWrapper::MjWrapper()
MjvGeomWrapper::MjWrapper(raw::MjvGeom* ptr, py::handle owner)
: WrapperBase(ptr, owner),
X(texrepeat),
X(size),
X(pos),
mat([this]() {
@@ -1557,6 +1569,11 @@ PYBIND11_MODULE(_structs, m) {
py::arg("xml"), py::arg_v("assets", py::none()),
py::doc(
R"(Loads an MjModel from an XML string and an optional assets dictionary.)"));
mjModel.def_static(
"_from_spec_ptr", [](uintptr_t addr) {
return MjModelWrapper::CompileSpec(
reinterpret_cast<raw::MjSpec*>(addr));
});
mjModel.def_static(
"from_xml_path", &MjModelWrapper::LoadXMLFile,
py::arg("filename"), py::arg_v("assets", py::none()),
@@ -1907,9 +1924,11 @@ This is useful for example when the MJB is not available as a file on disk.)"));
mjData.def("__copy__", [](const MjDataWrapper& other) {
return MjDataWrapper(other);
});
mjData.def("__deepcopy__", [](const MjDataWrapper& other, py::dict) {
MjModelWrapper* model_copy = new MjModelWrapper(other.model());
return MjDataWrapper(other, model_copy);
mjData.def("__deepcopy__", [](const MjDataWrapper& other, py::dict memo) {
// Use copy.deepcopy(model) to make a model that Python is aware of.
py::object new_model_py =
py::cast(other.model()).attr("__deepcopy__")(memo);
return MjDataWrapper(other, new_model_py.cast<MjModelWrapper*>());
});
mjData.def(py::pickle(
[](const MjDataWrapper& d) { // __getstate__
@@ -2185,8 +2204,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(objtype);
X(objid);
X(category);
X(texid);
X(texuniform);
X(matid);
X(texcoord);
X(segid);
X(emission);
@@ -2199,7 +2217,6 @@ This is useful for example when the MJB is not available as a file on disk.)"));
#undef X
#define X(var) DefinePyArray(mjvGeom, #var, &MjvGeomWrapper::var)
X(texrepeat);
X(size);
X(pos);
X(mat);
@@ -2404,5 +2421,12 @@ This is useful for example when the MJB is not available as a file on disk.)"));
},
py::arg("cam1"), py::arg("cam2"),
py::doc(python_traits::mjv_averageCamera::doc));
m.def(
"_recompile_spec_addr",
[](uintptr_t spec_addr, const MjModelWrapper& m, const MjDataWrapper& d) {
return RecompileSpec(reinterpret_cast<raw::MjSpec*>(spec_addr), m, d);
}
);
} // PYBIND11_MODULE NOLINT(readability/fn_size)
} // namespace mujoco::python::_impl
+13 -6
View File
@@ -462,6 +462,10 @@ class MjWrapper<raw::MjModel> : public WrapperBase<raw::MjModel> {
public:
MjWrapper(const MjWrapper&);
MjWrapper(MjWrapper&&);
// Takes ownership of the raw mjModel pointer.
explicit MjWrapper(raw::MjModel* ptr);
~MjWrapper();
MjModelIndexer& indexer() { return indexer_; }
@@ -485,6 +489,8 @@ class MjWrapper<raw::MjModel> : public WrapperBase<raw::MjModel> {
const std::optional<
std::unordered_map<std::string, pybind11::bytes>>& assets);
static MjWrapper CompileSpec(raw::MjSpec* spec);
static constexpr char kFromRawPointer[] =
"__MUJOCO_STRUCTS_MJMODELWRAPPER_LOOKUP";
static MjWrapper* FromRawPointer(raw::MjModel* m) noexcept;
@@ -502,8 +508,6 @@ class MjWrapper<raw::MjModel> : public WrapperBase<raw::MjModel> {
pybind11::bytes paths_bytes;
protected:
explicit MjWrapper(raw::MjModel* ptr);
MjModelIndexer indexer_;
};
@@ -591,8 +595,14 @@ class MjWrapper<raw::MjData>: public WrapperBase<raw::MjData> {
explicit MjWrapper(MjModelWrapper* model);
MjWrapper(const MjWrapper& other);
MjWrapper(MjWrapper&&);
// Used for deepcopy
MjWrapper(const MjWrapper& other, MjModelWrapper* model);
// Internal constructor which takes ownership of given mjData pointer.
// Used for deserialization and recompile.
explicit MjWrapper(MjModelWrapper* model, raw::MjData* d);
~MjWrapper();
const MjModelWrapper& model() const { return *model_; }
@@ -622,9 +632,6 @@ class MjWrapper<raw::MjData>: public WrapperBase<raw::MjData> {
py_array_or_tuple_t<mjtNum> energy;
protected:
// Internal constructor which takes ownership of given mjData pointer.
// Used for deserialization.
explicit MjWrapper(MjModelWrapper* model, raw::MjData* d);
raw::MjData* Copy() const;
// A reference to the model that was used to create this mjData.
@@ -727,7 +734,7 @@ class MjWrapper<raw::MjvGeom> : public WrapperBase<raw::MjvGeom> {
py_array_or_tuple_t< \
std::remove_all_extents_t<decltype(raw::MjvGeom::var)>> \
var
X(texrepeat);
X(matid);
X(size);
X(pos);
X(mat);
+5 -1
View File
@@ -247,7 +247,11 @@ class USDExporter:
assert geom_name not in self.geom_names
texture_file = self.texture_files[geom.texid] if geom.texid != -1 else None
texture_file = (
self.texture_files[self.model.mat_texid[mujoco.mjNTEXMAT*geom.matid]]
if geom.matid != -1
else None
)
# handling meshes in our scene
if geom.type == mujoco.mjtGeom.mjGEOM_MESH:
+14
View File
@@ -94,6 +94,20 @@ class Handle:
def user_scn(self):
return self._user_scn
@property
def m(self):
sim = self._sim()
if sim is not None:
return sim.m
return None
@property
def d(self):
sim = self._sim()
if sim is not None:
return sim.d
return None
def close(self):
sim = self._sim()
if sim is not None:
+1
View File
@@ -347,6 +347,7 @@ setuptools.setup(
CMakeExtension('mujoco._render'),
CMakeExtension('mujoco._rollout'),
CMakeExtension('mujoco._simulate'),
CMakeExtension('mujoco._specs'),
CMakeExtension('mujoco._structs'),
],
scripts=[
+197 -176
View File
@@ -420,8 +420,8 @@
"data = mujoco.MjData(model)\n",
"\n",
"# Make renderer, render and show the pixels\n",
"renderer = mujoco.Renderer(model)\n",
"media.show_image(renderer.render())"
"with mujoco.Renderer(model) as renderer:\n",
" media.show_image(renderer.render())"
]
},
{
@@ -445,10 +445,11 @@
},
"outputs": [],
"source": [
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data)\n",
"with mujoco.Renderer(model) as renderer:\n",
" mujoco.mj_forward(model, data)\n",
" renderer.update_scene(data)\n",
"\n",
"media.show_image(renderer.render())"
" media.show_image(renderer.render())"
]
},
{
@@ -480,11 +481,11 @@
"model = mujoco.MjModel.from_xml_string(xml)\n",
"data = mujoco.MjData(model)\n",
"\n",
"renderer = mujoco.Renderer(model)\n",
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data)\n",
"with mujoco.Renderer(model) as renderer:\n",
" mujoco.mj_forward(model, data)\n",
" renderer.update_scene(data)\n",
"\n",
"media.show_image(renderer.render())"
" media.show_image(renderer.render())"
]
},
{
@@ -508,9 +509,10 @@
"source": [
"# Run this cell multiple times for different colors\n",
"model.geom('red_box').rgba[:3] = np.random.rand(3)\n",
"renderer.update_scene(data)\n",
"with mujoco.Renderer(model) as renderer:\n",
" renderer.update_scene(data)\n",
"\n",
"media.show_image(renderer.render())"
" media.show_image(renderer.render())"
]
},
{
@@ -540,12 +542,13 @@
"# Simulate and display video.\n",
"frames = []\n",
"mujoco.mj_resetData(model, data) # Reset state and time.\n",
"while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"with mujoco.Renderer(model) as renderer:\n",
" while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"\n",
"media.show_video(frames, fps=framerate)"
]
@@ -583,7 +586,6 @@
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(xml)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model)\n",
"\n",
"# enable joint visualization option:\n",
"scene_option = mujoco.MjvOption()\n",
@@ -595,12 +597,13 @@
"# Simulate and display video.\n",
"frames = []\n",
"mujoco.mj_resetData(model, data)\n",
"while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, scene_option=scene_option)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"with mujoco.Renderer(model) as renderer:\n",
" while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, scene_option=scene_option)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"\n",
"media.show_video(frames, fps=framerate)"
]
@@ -651,12 +654,13 @@
"# Simulate and display video.\n",
"frames = []\n",
"mujoco.mj_resetData(model, data)\n",
"while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, scene_option=scene_option)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"with mujoco.Renderer(model) as renderer:\n",
" while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, scene_option=scene_option)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"\n",
"media.show_video(frames, fps=60)"
]
@@ -760,12 +764,12 @@
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(tippe_top)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model)\n",
"\n",
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data, camera=\"closeup\")\n",
"with mujoco.Renderer(model) as renderer:\n",
" renderer.update_scene(data, camera=\"closeup\")\n",
"\n",
"media.show_image(renderer.render())"
" media.show_image(renderer.render())"
]
},
{
@@ -821,12 +825,13 @@
"# Simulate and display video.\n",
"frames = []\n",
"mujoco.mj_resetDataKeyframe(model, data, 0) # Reset the state to keyframe 0\n",
"while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, \"closeup\")\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"with mujoco.Renderer(model) as renderer:\n",
" while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, \"closeup\")\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"\n",
"media.show_video(frames, fps=framerate)"
]
@@ -939,12 +944,14 @@
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(chaotic_pendulum)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model, 480, 640)\n",
"height = 480\n",
"width = 640\n",
"\n",
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data, camera=\"fixed\")\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" mujoco.mj_forward(model, data)\n",
" renderer.update_scene(data, camera=\"fixed\")\n",
"\n",
"media.show_image(renderer.render())"
" media.show_image(renderer.render())"
]
},
{
@@ -970,7 +977,8 @@
"framerate = 30 # Hz\n",
"n_frames = int(n_seconds * framerate)\n",
"frames = []\n",
"renderer = mujoco.Renderer(model, 240, 320)\n",
"height = 240\n",
"width = 320\n",
"\n",
"# set initial state\n",
"mujoco.mj_resetData(model, data)\n",
@@ -981,17 +989,18 @@
"sim_time = 0\n",
"render_time = 0\n",
"n_steps = 0\n",
"for i in range(n_frames):\n",
" while data.time * framerate < i:\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" for i in range(n_frames):\n",
" while data.time * framerate < i:\n",
" tic = time.time()\n",
" mujoco.mj_step(model, data)\n",
" sim_time += time.time() - tic\n",
" n_steps += 1\n",
" tic = time.time()\n",
" mujoco.mj_step(model, data)\n",
" sim_time += time.time() - tic\n",
" n_steps += 1\n",
" tic = time.time()\n",
" renderer.update_scene(data, \"fixed\")\n",
" frame = renderer.render()\n",
" render_time += time.time() - tic\n",
" frames.append(frame)\n",
" renderer.update_scene(data, \"fixed\")\n",
" frame = renderer.render()\n",
" render_time += time.time() - tic\n",
" frames.append(frame)\n",
"\n",
"# print timing and play video\n",
"step_time = 1e6*sim_time/n_steps\n",
@@ -1233,12 +1242,14 @@
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(free_body_MJCF)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model, 400, 600)\n",
"height = 400\n",
"width = 600\n",
"\n",
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data, \"fixed\")\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" mujoco.mj_forward(model, data)\n",
" renderer.update_scene(data, \"fixed\")\n",
"\n",
"media.show_image(renderer.render())"
" media.show_image(renderer.render())"
]
},
{
@@ -1262,7 +1273,6 @@
"height = 240\n",
"width = 320\n",
"frames = []\n",
"renderer = mujoco.Renderer(model, height, width)\n",
"\n",
"# visualize contact frames and forces, make body transparent\n",
"options = mujoco.MjvOption()\n",
@@ -1282,12 +1292,13 @@
"data.qvel[3:6] = 5*np.random.randn(3)\n",
"\n",
"# Simulate and display video.\n",
"for i in range(n_frames):\n",
" while data.time < i/120.0: #1/4x real time\n",
" mujoco.mj_step(model, data)\n",
" renderer.update_scene(data, \"track\", options)\n",
" frame = renderer.render()\n",
" frames.append(frame)\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" for i in range(n_frames):\n",
" while data.time < i/120.0: #1/4x real time\n",
" mujoco.mj_step(model, data)\n",
" renderer.update_scene(data, \"track\", options)\n",
" frame = renderer.render()\n",
" frames.append(frame)\n",
"\n",
"media.show_video(frames, fps=30)"
]
@@ -1440,16 +1451,16 @@
"# load\n",
"model = mujoco.MjModel.from_xml_string(MJCF)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model, height, width)\n",
"\n",
"# Simulate and display video.\n",
"mujoco.mj_resetData(model, data)\n",
"for i in range(n_frames):\n",
" while data.time < i/30.0:\n",
" mujoco.mj_step(model, data)\n",
" renderer.update_scene(data, \"y\")\n",
" frame = renderer.render()\n",
" frames.append(frame)\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" mujoco.mj_resetData(model, data)\n",
" for i in range(n_frames):\n",
" while data.time < i/30.0:\n",
" mujoco.mj_step(model, data)\n",
" renderer.update_scene(data, \"y\")\n",
" frame = renderer.render()\n",
" frames.append(frame)\n",
"\n",
"media.show_video(frames, fps=30)"
]
@@ -1520,12 +1531,14 @@
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(MJCF)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model, 480, 480)\n",
"height = 480\n",
"width = 480\n",
"\n",
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data, \"fixed\")\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" mujoco.mj_forward(model, data)\n",
" renderer.update_scene(data, \"fixed\")\n",
"\n",
"media.show_image(renderer.render())\n"
" media.show_image(renderer.render())"
]
},
{
@@ -1553,21 +1566,20 @@
"times = []\n",
"sensordata = []\n",
"\n",
"renderer = mujoco.Renderer(model, height, width)\n",
"\n",
"# constant actuator signal\n",
"mujoco.mj_resetData(model, data)\n",
"data.ctrl = 20\n",
"\n",
"# Simulate and display video.\n",
"for i in range(n_frames):\n",
" while data.time < i/fps:\n",
" mujoco.mj_step(model, data)\n",
" times.append(data.time)\n",
" sensordata.append(data.sensor('accelerometer').data.copy())\n",
" renderer.update_scene(data, \"fixed\")\n",
" frame = renderer.render()\n",
" frames.append(frame)\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" for i in range(n_frames):\n",
" while data.time < i/fps:\n",
" mujoco.mj_step(model, data)\n",
" times.append(data.time)\n",
" sensordata.append(data.sensor('accelerometer').data.copy())\n",
" renderer.update_scene(data, \"fixed\")\n",
" frame = renderer.render()\n",
" frames.append(frame)\n",
"\n",
"media.show_video(frames, fps=fps)"
]
@@ -1644,12 +1656,12 @@
"</mujoco>\n",
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(xml)\n",
"renderer = mujoco.Renderer(model)\n",
"data = mujoco.MjData(model)\n",
"\n",
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data)\n",
"media.show_image(renderer.render())"
"with mujoco.Renderer(model) as renderer:\n",
" mujoco.mj_forward(model, data)\n",
" renderer.update_scene(data)\n",
" media.show_image(renderer.render())"
]
},
{
@@ -1664,9 +1676,10 @@
"\n",
"scene_option.frame = mujoco.mjtFrame.mjFRAME_GEOM\n",
"scene_option.flags[mujoco.mjtVisFlag.mjVIS_TRANSPARENT] = True\n",
"renderer.update_scene(data, scene_option=scene_option)\n",
"frame = renderer.render()\n",
"media.show_image(frame)"
"with mujoco.Renderer(model) as renderer:\n",
" renderer.update_scene(data, scene_option=scene_option)\n",
" frame = renderer.render()\n",
" media.show_image(frame)"
]
},
{
@@ -1679,25 +1692,24 @@
"source": [
"#@title Depth rendering {vertical-output: true}\n",
"\n",
"# update renderer to render depth\n",
"renderer.enable_depth_rendering()\n",
"with mujoco.Renderer(model) as renderer:\n",
" # update renderer to render depth\n",
" renderer.enable_depth_rendering()\n",
"\n",
"# reset the scene\n",
"renderer.update_scene(data)\n",
" # reset the scene\n",
" renderer.update_scene(data)\n",
"\n",
"# depth is a float array, in meters.\n",
"depth = renderer.render()\n",
" # depth is a float array, in meters.\n",
" depth = renderer.render()\n",
"\n",
"# Shift nearest values to the origin.\n",
"depth -= depth.min()\n",
"# Scale by 2 mean distances of near rays.\n",
"depth /= 2*depth[depth <= 1].mean()\n",
"# Scale to [0, 255]\n",
"pixels = 255*np.clip(depth, 0, 1)\n",
" # Shift nearest values to the origin.\n",
" depth -= depth.min()\n",
" # Scale by 2 mean distances of near rays.\n",
" depth /= 2*depth[depth <= 1].mean()\n",
" # Scale to [0, 255]\n",
" pixels = 255*np.clip(depth, 0, 1)\n",
"\n",
"media.show_image(pixels.astype(np.uint8))\n",
"\n",
"renderer.disable_depth_rendering()"
" media.show_image(pixels.astype(np.uint8))"
]
},
{
@@ -1710,25 +1722,26 @@
"source": [
"#@title Segmentation rendering {vertical-output: true}\n",
"\n",
"# update renderer to render segmentation\n",
"renderer.enable_segmentation_rendering()\n",
"with mujoco.Renderer(model) as renderer:\n",
" renderer.disable_depth_rendering()\n",
"\n",
"# reset the scene\n",
"renderer.update_scene(data)\n",
" # update renderer to render segmentation\n",
" renderer.enable_segmentation_rendering()\n",
"\n",
"seg = renderer.render()\n",
" # reset the scene\n",
" renderer.update_scene(data)\n",
"\n",
"# Display the contents of the first channel, which contains object\n",
"# IDs. The second channel, seg[:, :, 1], contains object types.\n",
"geom_ids = seg[:, :, 0]\n",
"# Infinity is mapped to -1\n",
"geom_ids = geom_ids.astype(np.float64) + 1\n",
"# Scale to [0, 1]\n",
"geom_ids = geom_ids / geom_ids.max()\n",
"pixels = 255*geom_ids\n",
"media.show_image(pixels.astype(np.uint8))\n",
" seg = renderer.render()\n",
"\n",
"renderer.disable_segmentation_rendering()"
" # Display the contents of the first channel, which contains object\n",
" # IDs. The second channel, seg[:, :, 1], contains object types.\n",
" geom_ids = seg[:, :, 0]\n",
" # Infinity is mapped to -1\n",
" geom_ids = geom_ids.astype(np.float64) + 1\n",
" # Scale to [0, 1]\n",
" geom_ids = geom_ids / geom_ids.max()\n",
" pixels = 255*geom_ids\n",
" media.show_image(pixels.astype(np.uint8))"
]
},
{
@@ -1792,37 +1805,39 @@
"source": [
"#@title Project from world to camera coordinates {vertical-output: true}\n",
"\n",
"# reset the scene\n",
"renderer.update_scene(data)\n",
"with mujoco.Renderer(model) as renderer:\n",
" renderer.disable_segmentation_rendering()\n",
" # reset the scene\n",
" renderer.update_scene(data)\n",
"\n",
"# Get the world coordinates of the box corners\n",
"box_pos = data.geom_xpos[model.geom('red_box').id]\n",
"box_mat = data.geom_xmat[model.geom('red_box').id].reshape(3, 3)\n",
"box_size = model.geom_size[model.geom('red_box').id]\n",
"offsets = np.array([-1, 1]) * box_size[:, None]\n",
"xyz_local = np.stack(list(itertools.product(*offsets))).T\n",
"xyz_global = box_pos[:, None] + box_mat @ xyz_local\n",
" # Get the world coordinates of the box corners\n",
" box_pos = data.geom_xpos[model.geom('red_box').id]\n",
" box_mat = data.geom_xmat[model.geom('red_box').id].reshape(3, 3)\n",
" box_size = model.geom_size[model.geom('red_box').id]\n",
" offsets = np.array([-1, 1]) * box_size[:, None]\n",
" xyz_local = np.stack(list(itertools.product(*offsets))).T\n",
" xyz_global = box_pos[:, None] + box_mat @ xyz_local\n",
"\n",
"# Camera matrices multiply homogenous [x, y, z, 1] vectors.\n",
"corners_homogeneous = np.ones((4, xyz_global.shape[1]), dtype=float)\n",
"corners_homogeneous[:3, :] = xyz_global\n",
" # Camera matrices multiply homogenous [x, y, z, 1] vectors.\n",
" corners_homogeneous = np.ones((4, xyz_global.shape[1]), dtype=float)\n",
" corners_homogeneous[:3, :] = xyz_global\n",
"\n",
"# Get the camera matrix.\n",
"m = compute_camera_matrix(renderer, data)\n",
" # Get the camera matrix.\n",
" m = compute_camera_matrix(renderer, data)\n",
"\n",
"# Project world coordinates into pixel space. See:\n",
"# https://en.wikipedia.org/wiki/3D_projection#Mathematical_formula\n",
"xs, ys, s = m @ corners_homogeneous\n",
"# x and y are in the pixel coordinate system.\n",
"x = xs / s\n",
"y = ys / s\n",
" # Project world coordinates into pixel space. See:\n",
" # https://en.wikipedia.org/wiki/3D_projection#Mathematical_formula\n",
" xs, ys, s = m @ corners_homogeneous\n",
" # x and y are in the pixel coordinate system.\n",
" x = xs / s\n",
" y = ys / s\n",
"\n",
"# Render the camera view and overlay the projected corner coordinates.\n",
"pixels = renderer.render()\n",
"fig, ax = plt.subplots(1, 1)\n",
"ax.imshow(pixels)\n",
"ax.plot(x, y, '+', c='w')\n",
"ax.set_axis_off()"
" # Render the camera view and overlay the projected corner coordinates.\n",
" pixels = renderer.render()\n",
" fig, ax = plt.subplots(1, 1)\n",
" ax.imshow(pixels)\n",
" ax.plot(x, y, '+', c='w')\n",
" ax.set_axis_off()"
]
},
{
@@ -1894,17 +1909,18 @@
"mujoco.mj_resetData(model, data)\n",
"mujoco.mj_forward(model, data)\n",
"\n",
"while data.time < duration:\n",
" # append data to the traces\n",
" positions.append(data.geom_xpos[data.geom(\"green_sphere\").id].copy())\n",
" times.append(data.time)\n",
" speeds.append(get_geom_speed(model, data, \"green_sphere\"))\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data)\n",
" modify_scene(renderer.scene)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"with mujoco.Renderer(model) as renderer:\n",
" while data.time < duration:\n",
" # append data to the traces\n",
" positions.append(data.geom_xpos[data.geom(\"green_sphere\").id].copy())\n",
" times.append(data.time)\n",
" speeds.append(get_geom_speed(model, data, \"green_sphere\"))\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data)\n",
" modify_scene(renderer.scene)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"\n",
"media.show_video(frames, fps=framerate)"
]
@@ -2018,8 +2034,7 @@
"</mujoco>\n",
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(dominos_xml)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model, height=1024, width=1440)"
"data = mujoco.MjData(model)\n"
]
},
{
@@ -2035,16 +2050,19 @@
"\n",
"duration = 2.5 # (seconds)\n",
"framerate = 60 # (Hz)\n",
"height = 1024\n",
"width = 1440\n",
"\n",
"# Simulate and display video.\n",
"frames = []\n",
"mujoco.mj_resetData(model, data) # Reset state and time.\n",
"while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, camera='top')\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate:\n",
" renderer.update_scene(data, camera='top')\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"\n",
"media.show_video(frames, fps=framerate)"
]
@@ -2061,6 +2079,8 @@
"#@title Render from moving camera\n",
"\n",
"duration = 3 # (seconds)\n",
"height = 1024\n",
"width = 1440\n",
"\n",
"# find time when box is thrown (speed > 2cm/s)\n",
"throw_time = 0.0\n",
@@ -2115,13 +2135,14 @@
"slowdown = 4 # 4x slow-down\n",
"mujoco.mj_resetData(model, data)\n",
"frames = []\n",
"while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate * slowdown:\n",
" cam.distance, cam.azimuth, cam.elevation, cam.lookat = cam_motion()\n",
" renderer.update_scene(data, cam)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"with mujoco.Renderer(model, height, width) as renderer:\n",
" while data.time < duration:\n",
" mujoco.mj_step(model, data)\n",
" if len(frames) < data.time * framerate * slowdown:\n",
" cam.distance, cam.azimuth, cam.elevation, cam.lookat = cam_motion()\n",
" renderer.update_scene(data, cam)\n",
" pixels = renderer.render()\n",
" frames.append(pixels)\n",
"\n",
"media.show_video(frames, fps=framerate)"
]