Merge branch 'usd-integration' of github.com:awesome-aj0123/mujoco into usd-integration
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)))
|
||||
@@ -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()
|
||||
@@ -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_
|
||||
|
||||
@@ -215,6 +215,7 @@ PYBIND11_MODULE(_render, pymodule) {
|
||||
X(windowDoublebuffer);
|
||||
X(currentBuffer);
|
||||
X(readPixelFormat);
|
||||
X(readDepthMap);
|
||||
#undef X
|
||||
|
||||
#define X(var) \
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
BIN
Binary file not shown.
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="abdomen_1_body" file="abdomen_1_body.msh"/>
|
||||
</asset>
|
||||
</mujoco>
|
||||
+75
-27
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user