Merge branch 'usd-integration' of github.com:awesome-aj0123/mujoco into usd-integration

This commit is contained in:
Abhishek Joshi
2023-10-19 12:25:14 -05:00
244 changed files with 26188 additions and 3990 deletions
+1 -2
View File
@@ -1,4 +1,3 @@
include LICENSE *.md
recursive-include mujoco *.h *.cc *.mm CMakeLists.txt Simulate*.cmake
recursive-include cmake *.cmake
recursive-include mujoco *.h *.cc *.mm CMakeLists.txt *.cmake
recursive-include mujoco/mjpython mjpython.* Info.plist
+64 -37
View File
@@ -1,56 +1,83 @@
auditwheel==5.4.0 \
absl-py==2.0.0 \
--hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3
auditwheel==5.4.0; platform_system == 'Linux' \
--hash=sha256:d8410a17523427ba3f7b60c9701d23de28b8f94fa5dab732aa6c30d160df8127
etils[epath]==1.3.0; python_version == '3.8' \
--hash=sha256:809a92ff72f12149441492cf4d9a26b56a4741dffb4dfb9c4c7b7afe055c2d28
etils[epath]==1.5.1; python_version >= '3.9' \
--hash=sha256:2c1bfa2817eb4881cb509097f1e65ac6160126ba74ec47b3bb47ee678628d8c8
glfw==2.6.2 \
--hash=sha256:c2dcf2395d99ff2506428213bee305bb9ba024043d1f574216e61e6f5df808e9 \
--hash=sha256:c385c9976133aed57ff4f0ff5210276844cefb4d8a7bf61bbcc1caf10385744b \
--hash=sha256:fb56cd24f9e173cdddc8f6ebaac4f52304a1dfcae1dadd583038355b73c1ae06 \
--hash=sha256:faa5596aad5490cdd8657931a66636508c1015a8b7b47018318bb72fcd2b9014 \
--hash=sha256:d8e4f087eba45f7f4815e3e912867ed5ca16d1047b0958c52047f5b53be67059
numpy==1.24.4; python_version == '3.8' \
--hash=sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706 \
--hash=sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc \
--hash=sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e \
--hash=sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f \
--hash=sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61
numpy==1.25.1; python_version >= '3.9' \
--hash=sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171 \
--hash=sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6 \
--hash=sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09 \
--hash=sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800 \
--hash=sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e \
--hash=sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57 \
--hash=sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588 \
--hash=sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf \
--hash=sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b \
--hash=sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa \
--hash=sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631 \
--hash=sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd \
--hash=sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625 \
--hash=sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f \
--hash=sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105 \
pip==23.2 \
--hash=sha256:78e5353a9dda374b462f2054f83a7b63f3f065c98236a68361845c1b0ee7e35f
numpy==1.26.0; python_version >= '3.9' \
--hash=sha256:166b36197e9debc4e384e9c652ba60c0bacc216d0fc89e78f973a9760b503388 \
--hash=sha256:f042f66d0b4ae6d48e70e28d487376204d3cbf43b84c03bac57e28dac6151581 \
--hash=sha256:e5e18e5b14a7560d8acf1c596688f4dfd19b4f2945b245a71e5af4ddb7422feb \
--hash=sha256:7f6bad22a791226d0a5c7c27a80a20e11cfe09ad5ef9084d4d3fc4a299cca505 \
--hash=sha256:ee84ca3c58fe48b8ddafdeb1db87388dce2c3c3f701bf447b05e4cfcc3679112 \
--hash=sha256:637c58b468a69869258b8ae26f4a4c6ff8abffd4a8334c830ffb63e0feefe99a \
--hash=sha256:306545e234503a24fe9ae95ebf84d25cba1fdc27db971aa2d9f1ab6bba19a9dd \
--hash=sha256:8c6adc33561bd1d46f81131d5352348350fc23df4d742bb246cdfca606ea1208 \
--hash=sha256:e062aa24638bb5018b7841977c360d2f5917268d125c833a686b7cbabbec496c \
--hash=sha256:eae430ecf5794cb7ae7fa3808740b015aa80747e5266153128ef055975a72b99 \
--hash=sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd \
--hash=sha256:0621f7daf973d34d18b4e4bafb210bbaf1ef5e0100b5fa750bd9cde84c7ac292 \
--hash=sha256:51be5f8c349fdd1a5568e72713a21f518e7d6707bcf8503b528b88d33b57dc68 \
--hash=sha256:767254ad364991ccfc4d81b8152912e53e103ec192d1bb4ea6b1f5a7117040be \
--hash=sha256:09aaee96c2cbdea95de76ecb8a586cb687d281c881f5f17bfc0fb7f5890f6b91 \
--hash=sha256:4a873a8180479bc829313e8d9798d5234dfacfc2e8a7ac188418189bb8eafbd2 \
--hash=sha256:914b28d3215e0c721dc75db3ad6d62f51f630cb0c277e6b3bcb39519bed10bd8 \
--hash=sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f \
--hash=sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c \
--hash=sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2
pip==23.2.1 \
--hash=sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be
PyOpenGL==3.1.7 \
--hash=sha256:a6ab19cf290df6101aaf7470843a9c46207789855746399d0af92521a0a92b7a
pytest==7.4.0 \
--hash=sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32
setuptools==68.0.0 \
--hash=sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f
wheel==0.40.0 \
--hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247
pytest==7.4.2 \
--hash=sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002
setuptools==68.2.2 \
--hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a
wheel==0.41.2 \
--hash=sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8
# Transitive dependencies of auditwheel
pyelftools==0.29 \
--hash=sha256:519f38cf412f073b2d7393aa4682b0190fa901f7c3fa0bff2b82d537690c7fc1
pyelftools==0.30; platform_system == 'Linux' \
--hash=sha256:544c3440eddb9a0dce70b6611de0b28163d71def759d2ed57a0d00118fc5da86
# Transitive dependencies of etils[epath]
fsspec==2023.9.2 \
--hash=sha256:603dbc52c75b84da501b9b2ec8c11e1f61c25984c4a0dda1f129ef391fbfc9b4
importlib-resources==6.1.0 \
--hash=sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83
typing_extensions==4.8.0 \
--hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0
zipp==3.17.0 \
--hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31
# Transitive dependencies of pytest
attrs==23.1.0 \
attrs==23.1.0; platform_system == 'Windows' \
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04
colorama==0.4.6 \
colorama==0.4.6; platform_system == 'Windows' \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
exceptiongroup==1.1.2; python_version < '3.11' \
--hash=sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f
exceptiongroup==1.1.3; python_version < '3.11' \
--hash=sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3
iniconfig==2.0.0 \
--hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
packaging==23.1 \
--hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61
pluggy==1.2.0 \
--hash=sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849
pyparsing==3.1.0 \
--hash=sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1
packaging==23.2 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
pluggy==1.3.0 \
--hash=sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7
pyparsing==3.1.1 \
--hash=sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb
tomli==2.0.1; python_version < '3.11' \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc
+3 -3
View File
@@ -50,13 +50,13 @@ export PYTHONPATH="${old_pythonpath}"
cp "${package_dir}"/../LICENSE .
# Copy over CMake scripts.
mkdir cmake
cp "${package_dir}"/../cmake/*.cmake cmake
mkdir mujoco/cmake
cp "${package_dir}"/../cmake/*.cmake mujoco/cmake
# Copy over Simulate source code.
cp -r "${package_dir}"/../simulate mujoco
python setup.py sdist --formats=gztar
python -m build . --sdist
tar -tf dist/mujoco-*.tar.gz
popd
+22 -6
View File
@@ -1,6 +1,22 @@
absl-py==1.4.0 \
--hash=sha256:0d3fe606adfa4f7db64792dd4c7aee4ee0c38ab75dfd353b7a83ed3e957fcb47
pip==23.2 \
--hash=sha256:78e5353a9dda374b462f2054f83a7b63f3f065c98236a68361845c1b0ee7e35f
setuptools==68.0.0 \
--hash=sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f
absl-py==2.0.0 \
--hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3
build==1.0.3 \
--hash=sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f
pip==23.2.1 \
--hash=sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be
setuptools==68.2.2 \
--hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a
# Transitive dependencies of build
colorama==0.4.6; platform_system == 'Windows' \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
importlib-metadata==6.8.0; python_version < '3.10' \
--hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb
packaging==23.2 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
pyproject_hooks==1.0.0 \
--hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8
tomli==2.0.1; python_version < '3.11' \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc
zipp==3.17.0; python_version < '3.10' \
--hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31
+7 -2
View File
@@ -140,7 +140,7 @@ findorfetch(
GIT_REPO
https://github.com/abseil/abseil-cpp
GIT_TAG
c2435f8342c2d0ed8101cb43adfd605fdc52dca2 # LTS 20230125.3
fb3621f4f897824c0dbe0615fa94543df6192f30 # LTS 20230802.1
TARGETS
${MUJOCO_PYTHON_ABSL_TARGETS}
EXCLUDE_FROM_ALL
@@ -173,7 +173,7 @@ findorfetch(
GIT_REPO
https://gitlab.com/libeigen/eigen
GIT_TAG
211c5dfc6741a5570ad007983c113ef4d144f9f3
e8515f78ac098329ab9f8cab21c87caede090a3f
TARGETS
Eigen3::Eigen
EXCLUDE_FROM_ALL
@@ -311,6 +311,11 @@ macro(mujoco_pybind11_module name)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# Only allow fallthrough annotation via __attribute__.
target_compile_options(${name} PRIVATE -Wimplicit-fallthrough=5)
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109561
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 11)
target_compile_options(${name} PRIVATE -Wno-maybe-uninitialized)
endif()
endif()
endif()
set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+1
View File
@@ -829,6 +829,7 @@ Euler integrator, semi-implicit in velocity.
self.assertEqual(mujoco.mjtGeom.mjGEOM_ARROW, 100)
self.assertEqual(mujoco.mjtGeom.mjGEOM_ARROW1, 101)
self.assertEqual(mujoco.mjtGeom.mjGEOM_ARROW2, 102)
self.assertEqual(mujoco.mjtGeom.mjGEOM_TRIANGLE, 107)
self.assertEqual(mujoco.mjtGeom.mjGEOM_NONE, 1001)
def test_enum_from_int(self):
+1 -1
View File
@@ -17,7 +17,7 @@
import ctypes
import enum
_CGL = ctypes.CDLL('/System/Library/OpenGL.framework/OpenGL')
_CGL = ctypes.CDLL('/System/Library/Frameworks/OpenGL.framework/OpenGL')
CGLContextObj = ctypes.c_void_p
CGLPixelFormatObj = ctypes.c_void_p
+11 -8
View File
@@ -126,7 +126,7 @@ PYBIND11_MODULE(_functions, pymodule) {
Def<traits::mj_resetData>(pymodule);
Def<traits::mj_resetDataDebug>(pymodule);
Def<traits::mj_resetDataKeyframe>(pymodule);
// Skipped: mj_stackAlloc (doesn't make sense in Python)
// Skipped: mj_stackAllocByte (doesn't make sense in Python)
// Skipped: mj_deleteData (have MjData.__del__)
Def<traits::mj_resetCallbacks>(pymodule);
Def<traits::mj_setConst>(pymodule);
@@ -199,6 +199,7 @@ PYBIND11_MODULE(_functions, pymodule) {
Def<traits::mj_kinematics>(pymodule);
Def<traits::mj_comPos>(pymodule);
Def<traits::mj_camlight>(pymodule);
Def<traits::mj_flex>(pymodule);
Def<traits::mj_tendon>(pymodule);
Def<traits::mj_transmission>(pymodule);
Def<traits::mj_crb>(pymodule);
@@ -581,6 +582,7 @@ PYBIND11_MODULE(_functions, pymodule) {
Def<traits::mj_rayHfield>(pymodule);
Def<traits::mj_rayMesh>(pymodule);
Def<traits::mju_rayGeom>(pymodule);
Def<traits::mju_rayFlex>(pymodule);
Def<traits::mju_raySkin>(pymodule);
// Interaction
@@ -1343,6 +1345,7 @@ PYBIND11_MODULE(_functions, pymodule) {
static_cast<char*>(data->arena),
data->narena - data->pstack);
#endif
data->parena = 0;
data->ncon = 0;
data->nefc = 0;
data->contact = static_cast<raw::MjContact*>(data->arena);
@@ -1356,7 +1359,7 @@ PYBIND11_MODULE(_functions, pymodule) {
data->ncon = ncon;
data->nefc = nefc;
data->contact =
static_cast<raw::MjContact*>(InterceptMjErrors(::mj_arenaAlloc)(
static_cast<raw::MjContact*>(InterceptMjErrors(::mj_arenaAllocByte)(
data, ncon * sizeof(raw::MjContact), alignof(raw::MjContact)));
if (!data->contact) {
cleanup(data);
@@ -1367,12 +1370,12 @@ PYBIND11_MODULE(_functions, pymodule) {
#define MJ_M(x) d.metadata().x
#undef MJ_D
#define MJ_D(x) data->x
#define X(type, name, nr, nc) \
data->name = static_cast<type*>(InterceptMjErrors(::mj_arenaAlloc)( \
data, sizeof(type) * (nr) * (nc), alignof(type))); \
if (!data->name) { \
cleanup(data); \
throw FatalError("insufficient arena memory available"); \
#define X(type, name, nr, nc) \
data->name = static_cast<type*>(InterceptMjErrors(::mj_arenaAllocByte)( \
data, sizeof(type) * (nr) * (nc), alignof(type))); \
if (!data->name) { \
cleanup(data); \
throw FatalError("insufficient arena memory available"); \
}
MJDATA_ARENA_POINTERS_PRIMAL
+8 -8
View File
@@ -78,14 +78,14 @@
X( mjtNum, cam_, ipd, ncam, 1 ) \
X( mjtNum, cam_, user, ncam, MJ_M(nuser_cam) )
#define MJMODEL_EQUALITY \
X( int, eq_, type, neq, 1 ) \
X( int, eq_, obj1id, neq, 1 ) \
X( int, eq_, obj2id, neq, 1 ) \
X( mjtByte, eq_, active, neq, 1 ) \
X( mjtNum, eq_, solref, neq, mjNREF ) \
X( mjtNum, eq_, solimp, neq, mjNIMP ) \
X( mjtNum, eq_, data, neq, mjNEQDATA )
#define MJMODEL_EQUALITY \
X( int, eq_, type, neq, 1 ) \
X( int, eq_, obj1id, neq, 1 ) \
X( int, eq_, obj2id, neq, 1 ) \
X( mjtByte, eq_, active0, neq, 1 ) \
X( mjtNum, eq_, solref, neq, mjNREF ) \
X( mjtNum, eq_, solimp, neq, mjNIMP ) \
X( mjtNum, eq_, data, neq, mjNEQDATA )
#define MJMODEL_EXCLUDE \
X( int, exclude_, signature, nexclude, 1 )
+26 -5
View File
@@ -115,12 +115,27 @@ class _MjPythonImpl(mujoco.viewer._MjPythonBase):
self._termination = self.__class__.NOT_TERMINATED
self._busy = False
def launch_on_ui_thread(self, model, data, handle_return, key_callback):
def launch_on_ui_thread(
self,
model,
data,
handle_return,
key_callback,
show_left_ui,
show_right_ui,
):
with self._cond:
if self._busy or self._task is not None:
raise RuntimeError('another MuJoCo viewer is already open')
else:
self._task = (model, data, handle_return, key_callback)
self._task = (
model,
data,
handle_return,
key_callback,
show_left_ui,
show_right_ui,
)
self._cond.notify()
def terminate(self):
@@ -294,11 +309,17 @@ while True:
break
# Otherwise, launch the viewer.
model, data, handle_return, key_callback = task
model, data, handle_return, key_callback, show_left_ui, show_right_ui = task
ctypes.CDLL(None).mjpython_show_dock_icon()
mujoco.viewer._launch_internal(
model, data, run_physics_thread=False, handle_return=handle_return,
key_callback=key_callback)
model,
data,
run_physics_thread=False,
handle_return=handle_return,
key_callback=key_callback,
show_left_ui=show_left_ui,
show_right_ui=show_right_ui,
)
ctypes.CDLL(None).mjpython_hide_dock_icon()
finally:
+111
View File
@@ -0,0 +1,111 @@
# Copyright 2023 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.
# ==============================================================================
"""CLI for converting legacy MSH files to Wavefront OBJ files.
Usage:
python -m mujoco.msh2obj -i <msh_file> -o <obj_file>
"""
import argparse
import dataclasses
import io
import pathlib
import numpy as np
@dataclasses.dataclass(frozen=True)
class Msh:
"""MuJoCo legacy binary msh file."""
vertex_positions: np.ndarray
vertex_normals: np.ndarray
vertex_texcoords: np.ndarray
face_vertex_indices: np.ndarray
@staticmethod
def create(file: pathlib.Path) -> "Msh":
"""Create a Msh object from a .msh file."""
if not file.exists():
raise FileNotFoundError(f"{file} does not exist.")
with open(file, "rb") as f:
nvertex = np.fromfile(f, dtype=np.int32, count=1)[0]
nnormal = np.fromfile(f, dtype=np.int32, count=1)[0]
ntexcoord = np.fromfile(f, dtype=np.int32, count=1)[0]
nface = np.fromfile(f, dtype=np.int32, count=1)[0]
vertex_positions = np.fromfile(f, dtype=np.float32, count=3 * nvertex)
vertex_normals = np.fromfile(f, dtype=np.float32, count=3 * nnormal)
vertex_texcoords = np.fromfile(f, dtype=np.float32, count=2 * ntexcoord)
face_vertex_indices = np.fromfile(f, dtype=np.int32, count=3 * nface)
if vertex_positions.size != 3 * nvertex:
raise ValueError(
f"Invalid number of vertices: {vertex_positions.size} != 3*{nvertex}."
)
if vertex_normals.size != 3 * nnormal:
raise ValueError(
f"Invalid number of normals: {vertex_normals.size} != 3*{nnormal}."
)
if vertex_texcoords.size != 2 * ntexcoord:
raise ValueError(
f"Invalid number of texcoords: {vertex_texcoords.size} != "
"2*{ntexcoord}."
)
if face_vertex_indices.size != 3 * nface:
raise ValueError(
f"Invalid number of faces: {face_vertex_indices.size} != 3*{nface}."
)
vertex_positions = vertex_positions.reshape(-1, 3)
vertex_normals = vertex_normals.reshape(-1, 3)
face_vertex_indices = face_vertex_indices.reshape(-1, 3)
# Undo vertical flip done by MuJoCo's OBJ loader.
vertex_texcoords = vertex_texcoords.reshape(-1, 2)
vertex_texcoords[:, 1] = 1 - vertex_texcoords[:, 1]
return Msh(
vertex_positions=vertex_positions,
vertex_normals=vertex_normals,
vertex_texcoords=vertex_texcoords,
face_vertex_indices=face_vertex_indices,
)
def msh_to_obj(msh_file: pathlib.Path) -> str:
"""Convert a legacy .msh file to the .obj format."""
msh = Msh.create(msh_file)
out = io.StringIO()
for x, y, z in msh.vertex_positions:
out.write(f"v {x} {y} {z}\n")
for x, y, z in msh.vertex_normals:
out.write(f"vn {x} {y} {z}\n")
for u, v in msh.vertex_texcoords:
out.write(f"vt {u} {v}\n")
for i, j, k in msh.face_vertex_indices:
out.write(f"f {i+1}/{i+1}/{i+1} {j+1}/{j+1}/{j+1} {k+1}/{k+1}/{k+1}\n")
return out.getvalue()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("-i", "--input", type=str, help="Path to the msh file.")
parser.add_argument("-o", "--output", type=str, help="Path to the obj file.")
args = parser.parse_args()
with open(pathlib.Path(args.output), "w") as f:
f.write(msh_to_obj(pathlib.Path(args.input)))
+77
View File
@@ -0,0 +1,77 @@
# Copyright 2023 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 msh2obj.py."""
from absl.testing import absltest
from etils import epath
import mujoco
from mujoco import msh2obj
import numpy as np
_MESH_FIELDS = (
"mesh_vertadr",
"mesh_vertnum",
"mesh_faceadr",
"mesh_facenum",
"mesh_bvhadr",
"mesh_bvhnum",
"mesh_normaladr",
"mesh_normalnum",
"mesh_texcoordadr",
"mesh_texcoordnum",
"mesh_graphadr",
"mesh_vert",
"mesh_normal",
"mesh_face",
"mesh_facenormal",
"mesh_facetexcoord",
"mesh_graph",
"mesh_texcoord",
)
_XML = """
<mujoco>
<asset>
<mesh name="abdomen_1_body" file="abdomen_1_body.obj"/>
</asset>
</mujoco>
"""
class MshTest(absltest.TestCase):
def test_obj_model_matches_msh_model(self) -> None:
test_path = epath.resource_path("mujoco") / "testdata"
msh_xml = test_path / "msh.xml"
msh_model = mujoco.MjModel.from_xml_path(msh_xml.as_posix())
msh_path = test_path / "abdomen_1_body.msh"
obj = msh2obj.msh_to_obj(msh_path)
obj_model = mujoco.MjModel.from_xml_string(
_XML, {"abdomen_1_body.obj": obj.encode()})
for field in _MESH_FIELDS:
np.testing.assert_allclose(
getattr(msh_model, field),
getattr(obj_model, field),
atol=1e-6,
err_msg=f"Field {field} does not match between msh and obj models.",
)
if __name__ == "__main__":
absltest.main()
+1 -1
View File
@@ -22,7 +22,7 @@
// They should be regarded as part of MuJoCo's internal implementation detail.
extern "C" {
MJAPI void _mjPRIVATE__set_tls_error_fn(void (*h)(const char*));
MJAPI void* mj_arenaAlloc(mjData* d, int bytes, int alignment);
MJAPI void* mj_arenaAllocByte(mjData* d, int bytes, int alignment);
}
#endif // MUJOCO_PYTHON_PRIVATE_H_
+1
View File
@@ -215,6 +215,7 @@ PYBIND11_MODULE(_render, pymodule) {
X(windowDoublebuffer);
X(currentBuffer);
X(readPixelFormat);
X(readDepthMap);
#undef X
#define X(var) \
+31 -5
View File
@@ -86,6 +86,7 @@ the clause:
_render.mjr_setBuffer(
_enums.mjtFramebuffer.mjFB_OFFSCREEN.value, self._mjr_context
)
self._mjr_context.readDepthMap = _enums.mjtDepthMap.mjDEPTH_ZEROFAR
# Default render flags.
self._depth_rendering = False
@@ -138,7 +139,9 @@ the clause:
"""
original_flags = self._scene.flags.copy()
if self._segmentation_rendering:
# Using segmented rendering for depth makes the calculated depth more
# accurate at far distances.
if self._depth_rendering or self._segmentation_rendering:
self._scene.flags[_enums.mjtRndFlag.mjRND_SEGMENT] = True
self._scene.flags[_enums.mjtRndFlag.mjRND_IDCOLOR] = True
@@ -173,11 +176,34 @@ the clause:
near = self._model.vis.map.znear * extent
far = self._model.vis.map.zfar * extent
# Convert from [0 1] to depth in units of length, see links below:
# http://stackoverflow.com/a/6657284/1461210
# https://www.khronos.org/opengl/wiki/Depth_Buffer_Precision
out = near / (1 - out * (1 - near / far))
# Calculate OpenGL perspective matrix values in float32 precision
# so they are close to what glFrustum returns
# https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
zfar = np.float32(far)
znear = np.float32(near)
c_coef = -(zfar + znear) / (zfar - znear)
d_coef = -(np.float32(2) * zfar * znear) / (zfar - znear)
# In reverse Z mode the perspective matrix is transformed by the following
c_coef = np.float32(-0.5) * c_coef - np.float32(0.5)
d_coef = np.float32(-0.5) * d_coef
# We need 64 bits to convert Z from ndc to metric depth without noticeable
# losses in precision
out_64 = out.astype(np.float64)
# Undo OpenGL projection
# Note: We do not need to take action to convert from window coordinates
# to normalized device coordinates because in reversed Z mode the mapping
# is identity
out_64 = d_coef / (out_64 + c_coef)
# Cast result back to float32 for backwards compatibility
# This has a small accuracy cost
out[:] = out_64.astype(np.float32)
# Reset scene flags.
np.copyto(self._scene.flags, original_flags)
elif self._segmentation_rendering:
_render.mjr_readPixels(out, None, self._rect, self._mjr_context)
+36 -17
View File
@@ -69,19 +69,23 @@ class UIAdapterWithPyCallback : public Adapter {
class SimulateWrapper {
public:
SimulateWrapper(std::unique_ptr<PlatformUIAdapter> platform_ui_adapter,
py::object scn, py::object cam, py::object opt,
py::object pert, bool fully_managed)
py::object cam, py::object opt,
py::object pert, py::object user_scn, bool is_passive)
: simulate_(new mujoco::Simulate(
std::move(platform_ui_adapter), scn.cast<MjvSceneWrapper&>().get(),
std::move(platform_ui_adapter),
cam.cast<MjvCameraWrapper&>().get(),
opt.cast<MjvOptionWrapper&>().get(),
pert.cast<MjvPerturbWrapper&>().get(), fully_managed)),
pert.cast<MjvPerturbWrapper&>().get(), is_passive)),
m_(py::none()),
d_(py::none()),
scn_(scn),
cam_(cam),
opt_(opt),
pert_(pert) {}
pert_(pert),
user_scn_(user_scn) {
if (!user_scn.is_none()) {
simulate_->user_scn = user_scn_.cast<MjvSceneWrapper&>().get();
}
}
~SimulateWrapper() { Destroy(); }
@@ -93,9 +97,9 @@ class SimulateWrapper {
}
}
void WaitUntilDestroyed() {
void WaitUntilExit() {
// TODO: replace with atomic wait when we migrate to C++20
while (!destroyed_.load()) {
while (simulate_ && simulate_->exitrequest.load() != 2) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
@@ -127,10 +131,10 @@ class SimulateWrapper {
// simulate object.
py::object m_;
py::object d_;
py::object scn_;
py::object cam_;
py::object opt_;
py::object pert_;
py::object user_scn_;
mjModel* m_raw_ = nullptr;
mjData* d_raw_ = nullptr;
@@ -194,19 +198,25 @@ PYBIND11_MODULE(_simulate, pymodule) {
py::class_<SimulateWrapper>(pymodule, "Simulate")
.def_readonly_static("MAX_GEOM", &mujoco::Simulate::kMaxGeom)
.def(py::init([](py::object scn, py::object cam, py::object opt,
py::object pert, bool fully_managed,
py::object pert, bool run_physics_thread,
py::object key_callback) {
bool is_passive = !run_physics_thread;
return std::make_unique<SimulateWrapper>(
std::make_unique<UIAdapterWithPyCallback<UIAdapter>>(
key_callback),
scn, cam, opt, pert, fully_managed);
scn, cam, opt, pert, is_passive);
}))
.def("destroy", &SimulateWrapper::Destroy,
.def("destroy", &SimulateWrapper::Destroy)
.def("load_message", CallIfNotNull(&mujoco::Simulate::LoadMessage),
py::call_guard<py::gil_scoped_release>())
.def("load", &SimulateWrapper::Load)
.def("load_message_clear",
CallIfNotNull(&mujoco::Simulate::LoadMessageClear),
py::call_guard<py::gil_scoped_release>())
.def("sync", CallIfNotNull(&mujoco::Simulate::Sync),
py::call_guard<py::gil_scoped_release>())
.def("add_to_history", CallIfNotNull(&mujoco::Simulate::AddToHistory),
py::call_guard<py::gil_scoped_release>())
.def("render_loop", CallIfNotNull(&mujoco::Simulate::RenderLoop),
py::call_guard<py::gil_scoped_release>())
.def("lock", GetIfNotNull(&mujoco::Simulate::mtx),
@@ -255,9 +265,8 @@ PYBIND11_MODULE(_simulate, pymodule) {
int value = 0;
sim->exitrequest.compare_exchange_strong(value, 1);
wrapper.WaitUntilDestroyed();
},
py::call_guard<py::gil_scoped_release>())
wrapper.WaitUntilExit();
})
.def_property_readonly("uiloadrequest",
CallIfNotNull(+[](mujoco::Simulate& sim) {
@@ -303,7 +312,17 @@ PYBIND11_MODULE(_simulate, pymodule) {
const auto max_length = sizeof_arr(sim.load_error);
std::strncpy(sim.load_error, error.c_str(), max_length - 1);
sim.load_error[max_length - 1] = '\0';
}));
}))
.def_property("ui0_enable", GetIfNotNull(&mujoco::Simulate::ui0_enable),
CallIfNotNull(+[](mujoco::Simulate& sim, int enabled) {
sim.ui0_enable = enabled;
}),
py::call_guard<py::gil_scoped_release>())
.def_property("ui1_enable", GetIfNotNull(&mujoco::Simulate::ui1_enable),
CallIfNotNull(+[](mujoco::Simulate& sim, int enabled) {
sim.ui1_enable = enabled;
}),
py::call_guard<py::gil_scoped_release>());
pymodule.def("set_glfw_dlhandle", [](std::uintptr_t dlhandle) {
mujoco::Glfw(reinterpret_cast<void*>(dlhandle));
+130 -12
View File
@@ -234,6 +234,7 @@ MjModelWrapper::MjWrapper(raw::MjModel* ptr)
MJMODEL_POINTERS,
text_data_bytes(ptr->text_data, ptr->ntextdata),
names_bytes(ptr->names, ptr->nnames),
paths_bytes(ptr->paths, ptr->npaths),
indexer_(ptr, owner_) {
bool is_newly_inserted = false;
{
@@ -255,6 +256,7 @@ MjModelWrapper::MjWrapper(MjModelWrapper&& other)
MJMODEL_POINTERS,
text_data_bytes(ptr_->text_data, ptr_->ntextdata),
names_bytes(ptr_->names, ptr_->nnames),
paths_bytes(ptr_->paths, ptr_->npaths),
indexer_(ptr_, owner_) {
bool is_newly_inserted = false;
{
@@ -488,7 +490,11 @@ MjContactWrapper::MjWrapper()
X(solref),
X(solreffriction),
X(solimp),
X(H) {}
X(H),
X(geom),
X(flex),
X(elem),
X(vert) {}
MjContactWrapper::MjWrapper(raw::MjContact* ptr, py::handle owner)
: WrapperBase(ptr, owner),
@@ -498,7 +504,11 @@ MjContactWrapper::MjWrapper(raw::MjContact* ptr, py::handle owner)
X(solref),
X(solreffriction),
X(solimp),
X(H) {}
X(H),
X(geom),
X(flex),
X(elem),
X(vert) {}
#undef X
MjContactWrapper::MjWrapper(const MjContactWrapper& other)
@@ -820,11 +830,11 @@ MjDataWrapper MjDataWrapper::Deserialize(std::istream& input) {
#define MJ_M(x) m.x
#undef MJ_D
#define MJ_D(x) d->x
#define X(type, name, nr, nc) \
if ((nr) * (nc)) { \
d->name = static_cast<decltype(d->name)>( \
mj_arenaAlloc(d, sizeof(type) * (nr) * (nc), alignof(type))); \
ReadBytes(input, d->name, sizeof(type) * (nr) * (nc)); \
#define X(type, name, nr, nc) \
if ((nr) * (nc)) { \
d->name = static_cast<decltype(d->name)>( \
mj_arenaAllocByte(d, sizeof(type) * (nr) * (nc), alignof(type))); \
ReadBytes(input, d->name, sizeof(type) * (nr) * (nc)); \
}
MJDATA_ARENA_POINTERS_CONTACT
@@ -1048,7 +1058,9 @@ MjvGeomWrapper::MjWrapper()
static_assert(sizeof(ptr_->mat) == sizeof(ptr_->mat[0])*9);
return InitPyArray(std::array{3, 3}, ptr_->mat, owner_);
}()),
X(rgba) {}
X(rgba) {
mjv_initGeom(ptr_, mjGEOM_NONE, nullptr, nullptr, nullptr, nullptr);
}
MjvGeomWrapper::MjWrapper(raw::MjvGeom* ptr, py::handle owner)
: WrapperBase(ptr, owner),
@@ -1106,6 +1118,7 @@ MjvOptionWrapper::MjWrapper()
X(jointgroup),
X(tendongroup),
X(actuatorgroup),
X(flexgroup),
X(skingroup),
X(flags) {}
#undef X
@@ -1137,6 +1150,18 @@ MjvSceneWrapper::MjWrapper()
nskinvert(0),
XN(geoms, 0),
XN(geomorder, 0),
XN(flexedgeadr, 0),
XN(flexedgenum, 0),
XN(flexvertadr, 0),
XN(flexvertnum, 0),
XN(flexfaceadr, 0),
XN(flexfacenum, 0),
XN(flexfaceused, 0),
XN(flexedge, 0),
XN(flexvert, 0),
XN(flexface, 0),
XN(flexnormal, 0),
XN(flextexcoord, 0),
XN(skinfacenum, 0),
XN(skinvertadr, 0),
XN(skinvertnum, 0),
@@ -1166,8 +1191,54 @@ MjvSceneWrapper::MjWrapper(const MjModelWrapper& model, int maxgeom)
}
return nskinvert;
}(model.get())),
nflexface([](const raw::MjModel* m) {
int nflexface = 0;
int flexfacenum = 0;
for (int f=0; f < m->nflex; f++) {
if (m->flex_dim[f] == 0) {
// 1D : 0
flexfacenum = 0;
} else if (m->flex_dim[f] == 2) {
// 2D: 2*fragments + 2*elements
flexfacenum = 2*m->flex_shellnum[f] + 2*m->flex_elemnum[f];
} else {
// 3D: max(fragments, 4*maxlayer)
// find number of elements in biggest layer
int maxlayer = 0, layer = 0, nlayer = 1;
while (nlayer) {
nlayer = 0;
for (int e=0; e < m->flex_elemnum[f]; e++) {
if (m->flex_elemlayer[m->flex_elemadr[f]+e] == layer) {
nlayer++;
}
}
maxlayer = mjMAX(maxlayer, nlayer);
layer++;
}
flexfacenum = mjMAX(m->flex_shellnum[f], 4*maxlayer);
}
// accumulate over flexes
nflexface += flexfacenum;
}
return nflexface;
}(model.get())),
nflexedge(model.get()->nflexedge),
nflexvert(model.get()->nflexvert),
XN(geoms, ptr_->maxgeom),
XN(geomorder, ptr_->maxgeom),
XN(flexedgeadr, ptr_->nflex),
XN(flexedgenum, ptr_->nflex),
XN(flexvertadr, ptr_->nflex),
XN(flexvertnum, ptr_->nflex),
XN(flexfaceadr, ptr_->nflex),
XN(flexfacenum, ptr_->nflex),
XN(flexfaceused, ptr_->nflex),
XN(flexedge, 2*nflexedge),
XN(flexvert, 3*nflexvert),
XN(flexface, 9*nflexface),
XN(flexnormal, 9*nflexface),
XN(flextexcoord, 6*nflexface),
XN(skinfacenum, ptr_->nskin),
XN(skinvertadr, ptr_->nskin),
XN(skinvertnum, ptr_->nskin),
@@ -1204,6 +1275,18 @@ MjvSceneWrapper::MjWrapper(const MjvSceneWrapper& other)
XN(geoms, ptr_->ngeom);
XN(geomorder, ptr_->ngeom);
XN(flexedgeadr, ptr_->nflex);
XN(flexedgenum, ptr_->nflex);
XN(flexvertadr, ptr_->nflex);
XN(flexvertnum, ptr_->nflex);
XN(flexfaceadr, ptr_->nflex);
XN(flexfacenum, ptr_->nflex);
XN(flexfaceused, ptr_->nflex);
XN(flexedge, 2*nflexedge);
XN(flexvert, 3*nflexvert);
XN(flexface, 9*nflexface);
XN(flexnormal, 9*nflexface);
XN(flextexcoord, 6*nflexface);
XN(skinfacenum, ptr_->nskin);
XN(skinvertadr, ptr_->nskin);
XN(skinvertnum, ptr_->nskin);
@@ -1509,10 +1592,11 @@ This is useful for example when the MJB is not available as a file on disk.)"));
MJMODEL_INTS
#undef X
#define X(dtype, var, dim0, dim1) \
if constexpr (std::string_view(#var) != "text_data" && \
std::string_view(#var) != "names") { \
DefinePyArray(mjModel, #var, &MjModelWrapper::var); \
#define X(dtype, var, dim0, dim1) \
if constexpr (std::string_view(#var) != "text_data" && \
std::string_view(#var) != "names" && \
std::string_view(#var) != "paths") { \
DefinePyArray(mjModel, #var, &MjModelWrapper::var); \
}
MJMODEL_POINTERS
#undef X
@@ -1527,6 +1611,11 @@ This is useful for example when the MJB is not available as a file on disk.)"));
// Return the full bytes array of concatenated names
return m.names_bytes;
});
mjModel.def_property_readonly(
"paths", [](const MjModelWrapper& m) -> const auto& {
// Return the full bytes array of concatenated paths
return m.paths_bytes;
});
#define XGROUP(MjModelGroupedViews, field, nfield, FIELD_XMACROS) \
mjModel.def( \
@@ -1746,6 +1835,10 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(solreffriction);
X(solimp);
X(H);
X(geom);
X(flex);
X(elem);
X(vert);
#undef X
py::class_<MjContactList> mjContactList(m, "_MjContactList");
@@ -1784,6 +1877,10 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(int, geom2);
X(int, exclude);
X(int, efc_address);
XN(int, geom);
XN(int, flex);
XN(int, elem);
XN(int, vert);
#undef X
#undef XN
@@ -1975,6 +2072,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
c.get()->var = rhs; \
})
X(select);
X(flexselect);
X(skinselect);
X(active);
X(active2);
@@ -2036,6 +2134,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
c.get()->var = rhs; \
})
X(frustum_center);
X(frustum_width);
X(frustum_bottom);
X(frustum_top);
X(frustum_near);
@@ -2143,6 +2242,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(label);
X(frame);
X(bvh_depth);
X(flex_layer);
#undef X
#define X(var) DefinePyArray(mjvOption, #var, &MjvOptionWrapper::var)
@@ -2151,6 +2251,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(jointgroup);
X(tendongroup);
X(actuatorgroup);
X(flexgroup);
X(skingroup);
X(flags);
#undef X
@@ -2174,8 +2275,13 @@ This is useful for example when the MJB is not available as a file on disk.)"));
})
X(maxgeom);
X(ngeom);
X(nflex);
X(nskin);
X(nlight);
X(flexvertopt);
X(flexedgeopt);
X(flexfaceopt);
X(flexskinopt);
X(enabletransform);
X(scale);
X(stereo);
@@ -2185,6 +2291,18 @@ This is useful for example when the MJB is not available as a file on disk.)"));
#define X(var) DefinePyArray(mjvScene, #var, &MjvSceneWrapper::var)
X(geoms);
X(geomorder);
X(flexedgeadr);
X(flexedgenum);
X(flexvertadr);
X(flexvertnum);
X(flexfaceadr);
X(flexfacenum);
X(flexfaceused);
X(flexedge);
X(flexvert);
X(flexface);
X(flexnormal);
X(flextexcoord);
X(skinfacenum);
X(skinvertadr);
X(skinvertnum);
+21
View File
@@ -16,6 +16,7 @@
#define MUJOCO_PYTHON_STRUCTS_H_
#include <array>
#include <cstddef>
#include <istream>
#include <memory>
#include <optional>
@@ -476,6 +477,7 @@ class MjWrapper<raw::MjModel> : public WrapperBase<raw::MjModel> {
// TODO(nimrod): Exclude text_data and names from the MJMODEL_POINTERS macro.
pybind11::bytes text_data_bytes;
pybind11::bytes names_bytes;
pybind11::bytes paths_bytes;
protected:
explicit MjWrapper(raw::MjModel* ptr);
@@ -506,6 +508,10 @@ class MjWrapper<raw::MjContact> : public WrapperBase<raw::MjContact> {
X(solreffriction);
X(solimp);
X(H);
X(geom);
X(flex);
X(elem);
X(vert);
#undef X
};
@@ -579,6 +585,7 @@ class MjWrapper<raw::MjData>: public WrapperBase<raw::MjData> {
py_array_or_tuple_t<mjContact> contact;
py_array_or_tuple_t<size_t> maxuse_threadstack;
py_array_or_tuple_t<raw::MjWarningStat> warning;
py_array_or_tuple_t<raw::MjTimerStat> timer;
py_array_or_tuple_t<raw::MjSolverStat> solver;
@@ -730,6 +737,7 @@ class MjWrapper<raw::MjvOption> : public WrapperBase<raw::MjvOption> {
X(jointgroup);
X(tendongroup);
X(actuatorgroup);
X(flexgroup);
X(skingroup);
X(flags);
#undef X
@@ -751,10 +759,23 @@ class MjWrapper<raw::MjvScene> : public WrapperBase<raw::MjvScene> {
~MjWrapper() = default;
int nskinvert;
int nflexface, nflexedge, nflexvert;
#define X(dtype, var) py_array_or_tuple_t<dtype> var
X(mjvGeom, geoms);
X(int, geomorder);
X(int, flexedgeadr);
X(int, flexedgenum);
X(int, flexvertadr);
X(int, flexvertnum);
X(int, flexfaceadr);
X(int, flexfacenum);
X(int, flexfaceused);
X(int, flexedge);
X(float, flexvert);
X(float, flexface);
X(float, flexnormal);
X(float, flextexcoord);
X(int, skinfacenum);
X(int, skinvertadr);
X(int, skinvertnum);
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
<mujoco>
<asset>
<mesh name="abdomen_1_body" file="abdomen_1_body.msh"/>
</asset>
</mujoco>
+75 -27
View File
@@ -67,20 +67,16 @@ class Handle:
def __init__(
self,
sim: _Simulate,
scn: mujoco.MjvScene,
cam: mujoco.MjvCamera,
opt: mujoco.MjvOption,
pert: mujoco.MjvPerturb,
user_scn: Optional[mujoco.MjvScene],
):
self._sim = weakref.ref(sim)
self._scn = scn
self._cam = cam
self._opt = opt
self._pert = pert
@property
def scn(self):
return self._scn
self._user_scn = user_scn
@property
def cam(self):
@@ -94,40 +90,52 @@ class Handle:
def perturb(self):
return self._pert
@property
def user_scn(self):
return self._user_scn
def close(self):
sim = self._sim()
if sim is not None:
sim.exit()
def is_running(self) -> bool:
def _get_sim(self) -> Optional[_Simulate]:
sim = self._sim()
if sim is not None:
return sim.exitrequest < 2
return False
try:
return sim if sim.exitrequest == 0 else None
except mujoco.UnexpectedError:
# UnexpectedError is raised when accessing `exitrequest` after the
# underlying simulate instance has been deleted in C++.
return None
return None
def is_running(self) -> bool:
return self._get_sim() is not None
def lock(self):
sim = self._sim()
sim = self._get_sim()
if sim is not None:
return sim.lock()
return contextlib.nullcontext()
def sync(self):
sim = self._sim()
sim = self._get_sim()
if sim is not None:
sim.sync() # locks internally
def update_hfield(self, hfieldid: int):
sim = self._sim()
sim = self._get_sim()
if sim is not None:
sim.update_hfield(hfieldid) # locks internally and blocks until done
def update_mesh(self, meshid: int):
sim = self._sim()
sim = self._get_sim()
if sim is not None:
sim.update_mesh(meshid) # locks internally and blocks until done
def update_texture(self, texid: int):
sim = self._sim()
sim = self._get_sim()
if sim is not None:
sim.update_texture(texid) # locks internally and blocks until done
@@ -174,9 +182,11 @@ def _reload(
) -> Optional[Tuple[mujoco.MjModel, mujoco.MjData]]:
"""Internal function for reloading a model in the viewer."""
try:
simulate.load_message('') # path is unknown at this point
load_tuple = loader()
except Exception as e: # pylint: disable=broad-except
simulate.load_error = str(e)
simulate.load_message_clear()
else:
m, d = load_tuple[:2]
@@ -235,6 +245,7 @@ def _physics_loop(simulate: _Simulate, loader: Optional[_InternalLoaderType]):
if m is not None:
assert d is not None
if simulate.run:
stepped = False
# Record CPU time at start of iteration.
startcpu = time.time()
@@ -273,6 +284,7 @@ def _physics_loop(simulate: _Simulate, loader: Optional[_InternalLoaderType]):
# Run single step, let next iteration deal with timing.
mujoco.mj_step(m, d)
stepped = True
# In-sync: step until ahead of cpu.
else:
@@ -290,14 +302,21 @@ def _physics_loop(simulate: _Simulate, loader: Optional[_InternalLoaderType]):
# Call mj_step.
mujoco.mj_step(m, d)
stepped = True
# Break if reset.
if d.time < prevsim:
break
# save current state to history buffer
if (stepped):
simulate.add_to_history()
else: # simulate.run is False: GUI is paused.
# Run mj_forward, to update rendering and joint sliders.
mujoco.mj_forward(m, d)
simulate.speed_changed = True
def _launch_internal(
@@ -308,6 +327,8 @@ def _launch_internal(
loader: Optional[_InternalLoaderType] = None,
handle_return: Optional['queue.Queue[Handle]'] = None,
key_callback: Optional[KeyCallbackType] = None,
show_left_ui: bool = True,
show_right_ui: bool = True,
) -> None:
"""Internal API, so that the public API has more readable type annotations."""
if model is None and data is not None:
@@ -329,14 +350,19 @@ def _launch_internal(
loader = _loader
if model and not run_physics_thread:
scn = mujoco.MjvScene(model, _Simulate.MAX_GEOM)
else:
scn = mujoco.MjvScene()
cam = mujoco.MjvCamera()
opt = mujoco.MjvOption()
pert = mujoco.MjvPerturb()
simulate = _Simulate(scn, cam, opt, pert, run_physics_thread, key_callback)
if model and not run_physics_thread:
user_scn = mujoco.MjvScene(model, _Simulate.MAX_GEOM)
else:
user_scn = None
simulate = _Simulate(
cam, opt, pert, user_scn, run_physics_thread, key_callback
)
simulate.ui0_enable = show_left_ui
simulate.ui1_enable = show_right_ui
# Initialize GLFW if not using mjpython.
if _MJPYTHON is None:
@@ -346,8 +372,9 @@ def _launch_internal(
notify_loaded = None
if handle_return:
notify_loaded = (
lambda: handle_return.put_nowait(Handle(simulate, scn, cam, opt, pert)))
notify_loaded = lambda: handle_return.put_nowait(
Handle(simulate, cam, opt, pert, user_scn)
)
if run_physics_thread:
side_thread = threading.Thread(
@@ -371,13 +398,23 @@ def _launch_internal(
simulate.destroy()
def launch(model: Optional[mujoco.MjModel] = None,
data: Optional[mujoco.MjData] = None,
*,
loader: Optional[LoaderType] = None) -> None:
def launch(
model: Optional[mujoco.MjModel] = None,
data: Optional[mujoco.MjData] = None,
*,
loader: Optional[LoaderType] = None,
show_left_ui: bool = True,
show_right_ui: bool = True,
) -> None:
"""Launches the Simulate GUI."""
_launch_internal(
model, data, run_physics_thread=True, loader=loader)
model,
data,
run_physics_thread=True,
loader=loader,
show_left_ui=show_left_ui,
show_right_ui=show_right_ui,
)
def launch_from_path(path: str) -> None:
@@ -390,6 +427,8 @@ def launch_passive(
data: mujoco.MjData,
*,
key_callback: Optional[KeyCallbackType] = None,
show_left_ui: bool = True,
show_right_ui: bool = True,
) -> Handle:
"""Launches a passive Simulate GUI without blocking the running thread."""
if not isinstance(model, mujoco.MjModel):
@@ -411,6 +450,8 @@ def launch_passive(
run_physics_thread=False,
handle_return=handle_return,
key_callback=key_callback,
show_left_ui=show_left_ui,
show_right_ui=show_right_ui,
),
)
thread.daemon = True
@@ -420,7 +461,14 @@ def launch_passive(
raise RuntimeError(
'`launch_passive` requires that the Python script be run under '
'`mjpython` on macOS')
_MJPYTHON.launch_on_ui_thread(model, data, handle_return, key_callback)
_MJPYTHON.launch_on_ui_thread(
model,
data,
handle_return,
key_callback,
show_left_ui,
show_right_ui,
)
return handle_return.get()
+61
View File
@@ -0,0 +1,61 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "mujoco"
version = "2.3.8"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
description = "MuJoCo Physics Simulator"
requires-python = ">=3.8"
license = {text = "Apache License 2.0"}
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
]
dependencies = [
"absl-py",
"etils[epath]",
"glfw",
"numpy",
"pyopengl",
]
dynamic = ["readme", "scripts"]
[project.urls]
Homepage = "https://github.com/google-deepmind/mujoco"
Documentation = "https://mujoco.readthedocs.io/en/2.3.8"
Repository = "https://github.com/google-deepmind/mujoco"
Changelog = "https://mujoco.readthedocs.io/en/2.3.8/changelog.html"
[tool.setuptools]
include-package-data = false
[tool.setuptools.packages.find]
include = ["mujoco*"]
exclude = [
"mujoco.codegen",
"mujoco.mjpython",
]
[tool.setuptools.package-data]
mujoco = [
"libmujoco.*.dylib",
"libmujoco*.so.*",
"mujoco.dll",
"include/mujoco/*.h",
"testdata/*.xml",
"testdata/*.msh",
]
+6 -69
View File
@@ -27,13 +27,9 @@ import sys
import sysconfig
import setuptools
from setuptools import find_packages
from setuptools import setup
from setuptools.command import build_ext
from setuptools.command import install_scripts
__version__ = '2.3.8'
MUJOCO_CMAKE = 'MUJOCO_CMAKE'
MUJOCO_CMAKE_ARGS = 'MUJOCO_CMAKE_ARGS'
MUJOCO_PATH = 'MUJOCO_PATH'
@@ -242,7 +238,8 @@ class BuildCMakeExtension(build_ext.build_ext):
"""Check for CMake."""
cmake = os.environ.get(MUJOCO_CMAKE, 'cmake')
build_cfg = 'Debug' if self.debug else 'Release'
cmake_module_path = os.path.join(os.path.dirname(__file__), 'cmake')
cmake_module_path = os.path.join(
os.path.dirname(__file__), 'mujoco', 'cmake')
cmake_args = [
f'-DPython3_ROOT_DIR:PATH={sys.prefix}',
f'-DPython3_EXECUTABLE:STRING={sys.executable}',
@@ -334,41 +331,9 @@ class InstallScripts(install_scripts.install_scripts):
else:
self.outfiles.append(oldfile)
def find_data_files(package_dir, patterns):
"""Recursively finds files whose names match the given shell patterns."""
paths = set()
for directory, _, filenames in os.walk(package_dir):
for pattern in patterns:
for filename in fnmatch.filter(filenames, pattern):
# NB: paths must be relative to the package directory.
relative_dirpath = os.path.relpath(directory, package_dir)
paths.add(os.path.join(relative_dirpath, filename))
return list(paths)
SETUP_KWARGS = dict(
name='mujoco',
version=__version__,
author='Google DeepMind',
author_email='mujoco@deepmind.com',
description='MuJoCo Physics Simulator',
setuptools.setup(
long_description=get_long_description(),
long_description_content_type='text/markdown',
url='https://github.com/google-deepmind/mujoco',
license='Apache License 2.0',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: Apache Software License',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Topic :: Scientific/Engineering',
],
cmdclass=dict(
build_ext=BuildCMakeExtension,
install_scripts=InstallScripts,
@@ -384,35 +349,7 @@ SETUP_KWARGS = dict(
CMakeExtension('mujoco._simulate'),
CMakeExtension('mujoco._structs'),
],
python_requires='>=3.8',
install_requires=[
'absl-py',
'glfw',
'numpy',
'pyopengl',
],
tests_require=[
'absl-py',
'glfw',
'numpy',
'pyopengl',
],
test_suite='mujoco',
packages=find_packages(),
package_data={
'mujoco':
find_data_files(
package_dir='mujoco',
patterns=[
'libmujoco.*.dylib',
'libmujoco*.so.*',
'mujoco.dll',
'include/mujoco/*.h',
]),
},
scripts=[
'mujoco/mjpython/mjpython.py'
] if platform.system() == 'Darwin' else [],
)
if platform.system() == 'Darwin':
SETUP_KWARGS['scripts'] = ['mujoco/mjpython/mjpython.py']
setup(**SETUP_KWARGS)