mujoco-studio 4.0.0__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mujoco/_render_filament.cp315-win_amd64.pyd +0 -0
- mujoco/studio/__init__.py +45 -0
- mujoco/studio/_studio.cp315-win_amd64.pyd +0 -0
- mujoco/studio/app/__init__.py +33 -0
- mujoco/studio/app/inputs.py +553 -0
- mujoco/studio/app/plugins.py +778 -0
- mujoco/studio/assets/AtkinsonHyperlegibleMono-Regular.ttf +0 -0
- mujoco/studio/assets/AtkinsonHyperlegibleNext[wght].ttf +0 -0
- mujoco/studio/assets/decor.filamat +0 -0
- mujoco/studio/assets/decor_fade.filamat +0 -0
- mujoco/studio/assets/fontawesome-webfont.ttf +0 -0
- mujoco/studio/assets/haze.filamat +0 -0
- mujoco/studio/assets/ibl.ktx +0 -0
- mujoco/studio/assets/outline_composite.filamat +0 -0
- mujoco/studio/assets/outline_flatten.filamat +0 -0
- mujoco/studio/assets/outline_jumpflood.filamat +0 -0
- mujoco/studio/assets/pbr.filamat +0 -0
- mujoco/studio/assets/pbr_packed.filamat +0 -0
- mujoco/studio/assets/pbr_packed_reflect.filamat +0 -0
- mujoco/studio/assets/pbr_packed_transparent.filamat +0 -0
- mujoco/studio/assets/pbr_reflect.filamat +0 -0
- mujoco/studio/assets/pbr_transparent.filamat +0 -0
- mujoco/studio/assets/phong_2d.filamat +0 -0
- mujoco/studio/assets/phong_2d_fade.filamat +0 -0
- mujoco/studio/assets/phong_2d_uv.filamat +0 -0
- mujoco/studio/assets/phong_2d_uv_fade.filamat +0 -0
- mujoco/studio/assets/phong_2d_uv_reflect.filamat +0 -0
- mujoco/studio/assets/phong_color.filamat +0 -0
- mujoco/studio/assets/phong_color_fade.filamat +0 -0
- mujoco/studio/assets/phong_color_reflect.filamat +0 -0
- mujoco/studio/assets/phong_cube.filamat +0 -0
- mujoco/studio/assets/phong_cube_fade.filamat +0 -0
- mujoco/studio/assets/phong_cube_reflect.filamat +0 -0
- mujoco/studio/assets/unlit_depth.filamat +0 -0
- mujoco/studio/assets/unlit_segmentation.filamat +0 -0
- mujoco/studio/assets/unlit_ui.filamat +0 -0
- mujoco/studio/dear_imgui/dear_imgui.cp315-win_amd64.pyd +0 -0
- mujoco/studio/endpoints.py +162 -0
- mujoco/studio/hot_reload.py +573 -0
- mujoco/studio/hot_reload_utils.py +220 -0
- mujoco/studio/implot/implot.cp315-win_amd64.pyd +0 -0
- mujoco/studio/launch_native.py +97 -0
- mujoco/studio/launch_passive.py +85 -0
- mujoco/studio/launch_thread.py +132 -0
- mujoco/studio/launch_web.py +111 -0
- mujoco/studio/messages.py +697 -0
- mujoco/studio/native_viewer.py +262 -0
- mujoco/studio/parser.py +57 -0
- mujoco/studio/plugin.py +225 -0
- mujoco/studio/plugin_registry.py +103 -0
- mujoco/studio/sim_history.py +217 -0
- mujoco/studio/step_control.py +114 -0
- mujoco/studio/viewer.py +115 -0
- mujoco/studio/viewer_handle.py +538 -0
- mujoco/studio/viewer_protocol.py +337 -0
- mujoco/studio/viewer_utils.py +80 -0
- mujoco/studio/web/__init__.py +32 -0
- mujoco/studio/web/client_camera.py +116 -0
- mujoco/studio/web/index.html +612 -0
- mujoco/studio/web/web_server.py +1477 -0
- mujoco/studio/web_viewer.py +471 -0
- mujoco_studio-4.0.0.dist-info/METADATA +4742 -0
- mujoco_studio-4.0.0.dist-info/RECORD +68 -0
- mujoco_studio-4.0.0.dist-info/WHEEL +5 -0
- mujoco_studio-4.0.0.dist-info/entry_points.txt +2 -0
- mujoco_studio-4.0.0.dist-info/licenses/LICENSE +202 -0
- mujoco_studio-4.0.0.dist-info/licenses/LICENSES_THIRD_PARTY.md +4675 -0
- mujoco_studio-4.0.0.dist-info/top_level.txt +1 -0
|
Binary file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright 2026 DeepMind Technologies Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""MuJoCo Studio package."""
|
|
15
|
+
|
|
16
|
+
import importlib as _importlib
|
|
17
|
+
import importlib.machinery as _machinery
|
|
18
|
+
import sys as _sys
|
|
19
|
+
|
|
20
|
+
from mujoco._version import __version__ as _core_version
|
|
21
|
+
|
|
22
|
+
__version__ = '4.0.0'
|
|
23
|
+
if _core_version != __version__:
|
|
24
|
+
raise ImportError(
|
|
25
|
+
f"Version mismatch between 'mujoco-studio' ({__version__}) and "
|
|
26
|
+
f"'mujoco-core' ({_core_version}). Please upgrade both packages "
|
|
27
|
+
'together:\n'
|
|
28
|
+
f" pip install --upgrade 'mujoco-studio=={__version__}' "
|
|
29
|
+
f"'mujoco-core=={__version__}'"
|
|
30
|
+
)
|
|
31
|
+
del _core_version
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
_studio_ext = _importlib.import_module('mujoco.studio._studio')
|
|
35
|
+
except ImportError:
|
|
36
|
+
pass
|
|
37
|
+
else:
|
|
38
|
+
for _name in ('native_viewer_cc', 'sim', 'ux', 'window'):
|
|
39
|
+
_submod = getattr(_studio_ext, _name, None)
|
|
40
|
+
if _submod is not None:
|
|
41
|
+
_submod.__spec__ = _machinery.ModuleSpec(f'{__name__}.{_name}', None)
|
|
42
|
+
globals()[_name] = _submod
|
|
43
|
+
_sys.modules[f'{__name__}.{_name}'] = _submod
|
|
44
|
+
del _name, _submod, _studio_ext
|
|
45
|
+
del _importlib, _machinery, _sys
|
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright 2026 DeepMind Technologies Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Built-in MuJoCo Studio application."""
|
|
15
|
+
|
|
16
|
+
# pylint: disable=g-importing-member
|
|
17
|
+
from mujoco.studio.app import inputs
|
|
18
|
+
from mujoco.studio.app import plugins
|
|
19
|
+
from mujoco.studio.app.plugins import App
|
|
20
|
+
from mujoco.studio.app.plugins import Config
|
|
21
|
+
from mujoco.studio.app.plugins import InitEvent
|
|
22
|
+
from mujoco.studio.app.plugins import install_console_hooks
|
|
23
|
+
from mujoco.studio.app.plugins import SIM_PLUGINS
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'App',
|
|
27
|
+
'Config',
|
|
28
|
+
'InitEvent',
|
|
29
|
+
'SIM_PLUGINS',
|
|
30
|
+
'inputs',
|
|
31
|
+
'install_console_hooks',
|
|
32
|
+
'plugins',
|
|
33
|
+
]
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
# Copyright 2026 DeepMind Technologies Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Temporary input (e.g., keyboard, mouse) handling functions for Studio app."""
|
|
15
|
+
|
|
16
|
+
# TODO(matijak): These free functions implement the keyboard and mouse event
|
|
17
|
+
# handling for Studio. They are separated from the main App class to
|
|
18
|
+
# clarify the long-term API and avoid cluttering it with a large amount of
|
|
19
|
+
# temporary code. When studio/platform has a proper API for registering key
|
|
20
|
+
# bindings and mouse behaviour, the event handling functions will delegate to
|
|
21
|
+
# code shared with the C++ studio application.
|
|
22
|
+
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
import mujoco
|
|
26
|
+
from mujoco.studio import sim
|
|
27
|
+
from mujoco.studio import ux
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
from mujoco.studio.dear_imgui import dear_imgui as imgui
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def handle_vis_options_keyboard_events(
|
|
34
|
+
vis_options: mujoco.MjvOption,
|
|
35
|
+
is_freecam_wasd: bool,
|
|
36
|
+
) -> bool:
|
|
37
|
+
"""Toggles visualization flags based on keyboard shortcuts.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
vis_options: The visualization options to modify.
|
|
41
|
+
is_freecam_wasd: If True, keys Q/E/A/D are reserved for camera movement and
|
|
42
|
+
will not toggle visualization flags.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
True if a key was handled, False otherwise.
|
|
46
|
+
"""
|
|
47
|
+
if imgui.GetIO().WantCaptureKeyboard:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
pressed = imgui.IsKeyChordPressed
|
|
51
|
+
|
|
52
|
+
# Frame and label cycling.
|
|
53
|
+
if pressed(imgui.Key.F6):
|
|
54
|
+
vis_options.frame = (vis_options.frame + 1) % mujoco.mjtFrame.mjNFRAME.value
|
|
55
|
+
elif pressed(imgui.Key.F7):
|
|
56
|
+
vis_options.label = (vis_options.label + 1) % mujoco.mjtLabel.mjNLABEL.value
|
|
57
|
+
|
|
58
|
+
# Visualization flag toggles (single-key shortcuts).
|
|
59
|
+
elif pressed(imgui.Key.H):
|
|
60
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_CONVEXHULL] ^= 1
|
|
61
|
+
elif pressed(imgui.Key.X):
|
|
62
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_TEXTURE] ^= 1
|
|
63
|
+
elif pressed(imgui.Key.J):
|
|
64
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_JOINT] ^= 1
|
|
65
|
+
elif not is_freecam_wasd and pressed(imgui.Key.Q):
|
|
66
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_CAMERA] ^= 1
|
|
67
|
+
elif pressed(imgui.Key.U):
|
|
68
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_ACTUATOR] ^= 1
|
|
69
|
+
elif pressed(imgui.Key.Comma):
|
|
70
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_ACTIVATION] ^= 1
|
|
71
|
+
elif pressed(imgui.Key.Z):
|
|
72
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_LIGHT] ^= 1
|
|
73
|
+
elif pressed(imgui.Key.V):
|
|
74
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_TENDON] ^= 1
|
|
75
|
+
elif pressed(imgui.Key.Y):
|
|
76
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_RANGEFINDER] ^= 1
|
|
77
|
+
elif not is_freecam_wasd and pressed(imgui.Key.E):
|
|
78
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_CONSTRAINT] ^= 1
|
|
79
|
+
elif pressed(imgui.Key.I):
|
|
80
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_INERTIA] ^= 1
|
|
81
|
+
elif pressed(imgui.Key.Apostrophe):
|
|
82
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_SCLINERTIA] ^= 1
|
|
83
|
+
elif pressed(imgui.Key.B):
|
|
84
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_PERTFORCE] ^= 1
|
|
85
|
+
elif pressed(imgui.Key.O):
|
|
86
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_PERTOBJ] ^= 1
|
|
87
|
+
elif pressed(imgui.Key.C):
|
|
88
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_CONTACTPOINT] ^= 1
|
|
89
|
+
elif pressed(imgui.Key.N):
|
|
90
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_ISLAND] ^= 1
|
|
91
|
+
elif pressed(imgui.Key.F):
|
|
92
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_CONTACTFORCE] ^= 1
|
|
93
|
+
elif pressed(imgui.Key.P):
|
|
94
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_CONTACTSPLIT] ^= 1
|
|
95
|
+
elif pressed(imgui.Key.T):
|
|
96
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_TRANSPARENT] ^= 1
|
|
97
|
+
elif not is_freecam_wasd and pressed(imgui.Key.A):
|
|
98
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_AUTOCONNECT] ^= 1
|
|
99
|
+
elif pressed(imgui.Key.M):
|
|
100
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_COM] ^= 1
|
|
101
|
+
elif not is_freecam_wasd and pressed(imgui.Key.D):
|
|
102
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_STATIC] ^= 1
|
|
103
|
+
elif pressed(imgui.Key.Semicolon):
|
|
104
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_SKIN] ^= 1
|
|
105
|
+
elif pressed(imgui.Key.GraveAccent):
|
|
106
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_BODYBVH] ^= 1
|
|
107
|
+
elif pressed(imgui.Key.Backslash):
|
|
108
|
+
vis_options.flags[mujoco.mjtVisFlag.mjVIS_MESHBVH] ^= 1
|
|
109
|
+
|
|
110
|
+
# Site group toggles (Shift + 0-5).
|
|
111
|
+
elif pressed(int(imgui.Key.N0) | int(imgui.Key.Shift)):
|
|
112
|
+
vis_options.sitegroup[0] ^= 1
|
|
113
|
+
elif pressed(int(imgui.Key.N1) | int(imgui.Key.Shift)):
|
|
114
|
+
vis_options.sitegroup[1] ^= 1
|
|
115
|
+
elif pressed(int(imgui.Key.N2) | int(imgui.Key.Shift)):
|
|
116
|
+
vis_options.sitegroup[2] ^= 1
|
|
117
|
+
elif pressed(int(imgui.Key.N3) | int(imgui.Key.Shift)):
|
|
118
|
+
vis_options.sitegroup[3] ^= 1
|
|
119
|
+
elif pressed(int(imgui.Key.N4) | int(imgui.Key.Shift)):
|
|
120
|
+
vis_options.sitegroup[4] ^= 1
|
|
121
|
+
elif pressed(int(imgui.Key.N5) | int(imgui.Key.Shift)):
|
|
122
|
+
vis_options.sitegroup[5] ^= 1
|
|
123
|
+
|
|
124
|
+
# Geom group toggles (0-5).
|
|
125
|
+
elif pressed(imgui.Key.N0):
|
|
126
|
+
vis_options.geomgroup[0] ^= 1
|
|
127
|
+
elif pressed(imgui.Key.N1):
|
|
128
|
+
vis_options.geomgroup[1] ^= 1
|
|
129
|
+
elif pressed(imgui.Key.N2):
|
|
130
|
+
vis_options.geomgroup[2] ^= 1
|
|
131
|
+
elif pressed(imgui.Key.N3):
|
|
132
|
+
vis_options.geomgroup[3] ^= 1
|
|
133
|
+
elif pressed(imgui.Key.N4):
|
|
134
|
+
vis_options.geomgroup[4] ^= 1
|
|
135
|
+
elif pressed(imgui.Key.N5):
|
|
136
|
+
vis_options.geomgroup[5] ^= 1
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def handle_step_control_keyboard_events(
|
|
145
|
+
step_control: sim.StepControl,
|
|
146
|
+
ux_state: ux.UxState,
|
|
147
|
+
) -> bool:
|
|
148
|
+
"""Handles keyboard shortcuts for simulation stepping control.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
step_control: The simulation step control object.
|
|
152
|
+
ux_state: The UX state object.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
True if a key was handled, False otherwise.
|
|
156
|
+
"""
|
|
157
|
+
if imgui.GetIO().WantCaptureKeyboard:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
pressed = imgui.IsKeyChordPressed
|
|
161
|
+
|
|
162
|
+
if pressed(imgui.Key.Space):
|
|
163
|
+
if step_control.get_pause_state() == sim.PauseState.UNPAUSED:
|
|
164
|
+
step_control.set_pause_state(sim.PauseState.NORMAL_PAUSED)
|
|
165
|
+
else:
|
|
166
|
+
step_control.set_pause_state(sim.PauseState.UNPAUSED)
|
|
167
|
+
return True
|
|
168
|
+
elif pressed(imgui.Key.Minus):
|
|
169
|
+
ux_state.speed_index = ux.set_speed_index(
|
|
170
|
+
step_control, ux_state.speed_index, ux_state.speed_index + 1
|
|
171
|
+
)
|
|
172
|
+
return True
|
|
173
|
+
elif pressed(imgui.Key.Equal):
|
|
174
|
+
ux_state.speed_index = ux.set_speed_index(
|
|
175
|
+
step_control, ux_state.speed_index, ux_state.speed_index - 1
|
|
176
|
+
)
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def handle_sim_history_keyboard_events(
|
|
183
|
+
step_control: sim.StepControl,
|
|
184
|
+
ux_state: ux.UxState,
|
|
185
|
+
) -> bool:
|
|
186
|
+
"""Handles keyboard shortcuts for scrubbing the simulation history.
|
|
187
|
+
|
|
188
|
+
Left/Right load the previous/next recorded frame while paused; Right at the
|
|
189
|
+
head of the history requests a single step instead. Matches the C++ Studio
|
|
190
|
+
app.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
step_control: The simulation step control object.
|
|
194
|
+
ux_state: The UX state holding the timeline history size and index.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
True if a key was handled, False otherwise.
|
|
198
|
+
"""
|
|
199
|
+
if imgui.GetIO().WantCaptureKeyboard or ux_state.history_size <= 0:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
# Scrubbing only makes sense while paused: a running sim would immediately
|
|
203
|
+
# step away from the loaded frame.
|
|
204
|
+
if step_control.get_pause_state() != sim.PauseState.NORMAL_PAUSED:
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
pressed = imgui.IsKeyChordPressed
|
|
208
|
+
|
|
209
|
+
if pressed(imgui.Key.LeftArrow):
|
|
210
|
+
ux_state.history_index -= 1
|
|
211
|
+
return True
|
|
212
|
+
elif pressed(imgui.Key.RightArrow):
|
|
213
|
+
if ux_state.history_index == 0:
|
|
214
|
+
step_control.request_single_step()
|
|
215
|
+
else:
|
|
216
|
+
ux_state.history_index += 1
|
|
217
|
+
return True
|
|
218
|
+
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def handle_reset_keyboard_events(
|
|
223
|
+
model: mujoco.MjModel, data: mujoco.MjData
|
|
224
|
+
) -> bool:
|
|
225
|
+
"""Handles keyboard shortcuts for simulation reset."""
|
|
226
|
+
if imgui.GetIO().WantCaptureKeyboard:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
if imgui.IsKeyChordPressed(imgui.Key.Backspace):
|
|
230
|
+
if model is not None and data is not None:
|
|
231
|
+
mujoco.mj_resetData(model, data)
|
|
232
|
+
mujoco.mj_forward(model, data)
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def handle_camera_select_keyboard_events(
|
|
239
|
+
model: mujoco.MjModel,
|
|
240
|
+
camera: mujoco.MjvCamera,
|
|
241
|
+
ux_state: ux.UxState,
|
|
242
|
+
) -> bool:
|
|
243
|
+
"""Handles keyboard shortcuts for camera selection.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
model: The MuJoCo model.
|
|
247
|
+
camera: The MuJoCo camera object.
|
|
248
|
+
ux_state: The UX state object.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
True if a key was handled, False otherwise.
|
|
252
|
+
"""
|
|
253
|
+
if imgui.GetIO().WantCaptureKeyboard:
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
pressed = imgui.IsKeyChordPressed
|
|
257
|
+
|
|
258
|
+
if pressed(imgui.Key.Escape):
|
|
259
|
+
ux_state.camera_index = ux.set_camera(model, camera, ux.TUMBLE_CAMERA_IDX)
|
|
260
|
+
return True
|
|
261
|
+
elif pressed(imgui.Key.LeftBracket):
|
|
262
|
+
ux_state.camera_index = ux.set_camera(
|
|
263
|
+
model, camera, ux_state.camera_index - 1
|
|
264
|
+
)
|
|
265
|
+
return True
|
|
266
|
+
elif pressed(imgui.Key.RightBracket):
|
|
267
|
+
ux_state.camera_index = ux.set_camera(
|
|
268
|
+
model, camera, ux_state.camera_index + 1
|
|
269
|
+
)
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def handle_miscellaneous_keyboard_events(app: Any) -> bool:
|
|
276
|
+
"""Handles miscellaneous keyboard shortcuts for toggling panels and windows.
|
|
277
|
+
|
|
278
|
+
Tab/Shift+Tab toggle Options and Inspector panels.
|
|
279
|
+
F2/F3 the Info and Profiler windows.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
app: The viewer application instance, whose config holds the UI state flags.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
True if a key was handled, False otherwise.
|
|
286
|
+
"""
|
|
287
|
+
if imgui.GetIO().WantCaptureKeyboard:
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
# Shift+Tab has to be tested before Tab, else the plain chord consumes it.
|
|
291
|
+
if imgui.IsKeyChordPressed(int(imgui.Key.Tab) | int(imgui.Key.Shift)):
|
|
292
|
+
app.config.show_inspector = not app.config.show_inspector
|
|
293
|
+
return True
|
|
294
|
+
elif imgui.IsKeyChordPressed(imgui.Key.Tab):
|
|
295
|
+
app.config.show_options = not app.config.show_options
|
|
296
|
+
return True
|
|
297
|
+
elif imgui.IsKeyChordPressed(imgui.Key.F2):
|
|
298
|
+
app.config.show_info = not app.config.show_info
|
|
299
|
+
return True
|
|
300
|
+
elif imgui.IsKeyChordPressed(imgui.Key.F3):
|
|
301
|
+
app.config.show_profiler = not app.config.show_profiler
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def handle_freecam_wasd_keyboard_events(
|
|
308
|
+
model: mujoco.MjModel,
|
|
309
|
+
data: mujoco.MjData,
|
|
310
|
+
camera: mujoco.MjvCamera,
|
|
311
|
+
cam_speed: float,
|
|
312
|
+
) -> tuple[bool, float]:
|
|
313
|
+
"""Handles keyboard shortcuts for free camera movement.
|
|
314
|
+
|
|
315
|
+
Flies the camera with W/S, A/D and Q/E using the camera controls shared with
|
|
316
|
+
the C++ Studio app and the web viewer (ux.move_camera_with_keys).
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
model: The MuJoCo model.
|
|
320
|
+
data: The MuJoCo data.
|
|
321
|
+
camera: The MuJoCo camera object.
|
|
322
|
+
cam_speed: The current camera speed.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
A tuple of (handled, updated_cam_speed).
|
|
326
|
+
"""
|
|
327
|
+
if imgui.GetIO().WantCaptureKeyboard:
|
|
328
|
+
return False, cam_speed
|
|
329
|
+
|
|
330
|
+
del data # Unused.
|
|
331
|
+
return ux.move_camera_with_keys(model, camera, cam_speed)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def handle_keyboard_events(
|
|
335
|
+
model: mujoco.MjModel,
|
|
336
|
+
data: mujoco.MjData,
|
|
337
|
+
camera: mujoco.MjvCamera,
|
|
338
|
+
vis_options: mujoco.MjvOption,
|
|
339
|
+
step_control: sim.StepControl,
|
|
340
|
+
ux_state: ux.UxState,
|
|
341
|
+
cam_speed: float,
|
|
342
|
+
) -> tuple[bool, float]:
|
|
343
|
+
"""Handle keyboard events according to Studio's bindings.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
model: The MuJoCo model.
|
|
347
|
+
data: The MuJoCo data.
|
|
348
|
+
camera: The MuJoCo camera object.
|
|
349
|
+
vis_options: The MuJoCo visualization options.
|
|
350
|
+
step_control: The simulation step control object.
|
|
351
|
+
ux_state: The UX state object.
|
|
352
|
+
cam_speed: The current camera speed.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
A tuple of (handled, updated_cam_speed).
|
|
356
|
+
"""
|
|
357
|
+
if imgui.GetIO().WantCaptureKeyboard:
|
|
358
|
+
return False, cam_speed
|
|
359
|
+
|
|
360
|
+
is_freecam_wasd = ux_state.camera_index == ux.FREE_CAMERA_IDX
|
|
361
|
+
if handle_step_control_keyboard_events(step_control, ux_state):
|
|
362
|
+
return True, cam_speed
|
|
363
|
+
|
|
364
|
+
if handle_reset_keyboard_events(model, data):
|
|
365
|
+
return True, cam_speed
|
|
366
|
+
|
|
367
|
+
if handle_camera_select_keyboard_events(model, camera, ux_state):
|
|
368
|
+
return True, cam_speed
|
|
369
|
+
|
|
370
|
+
if handle_vis_options_keyboard_events(vis_options, is_freecam_wasd):
|
|
371
|
+
return True, cam_speed
|
|
372
|
+
|
|
373
|
+
if is_freecam_wasd:
|
|
374
|
+
handled, cam_speed = handle_freecam_wasd_keyboard_events(
|
|
375
|
+
model, data, camera, cam_speed
|
|
376
|
+
)
|
|
377
|
+
if handled:
|
|
378
|
+
return True, cam_speed
|
|
379
|
+
|
|
380
|
+
return False, cam_speed
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def handle_camera_tracking_mouse_events(
|
|
384
|
+
model: mujoco.MjModel,
|
|
385
|
+
data: mujoco.MjData,
|
|
386
|
+
camera: mujoco.MjvCamera,
|
|
387
|
+
vis_options: mujoco.MjvOption,
|
|
388
|
+
ux_state: ux.UxState,
|
|
389
|
+
) -> None:
|
|
390
|
+
"""Handles mouse events for camera tracking."""
|
|
391
|
+
io = imgui.GetIO()
|
|
392
|
+
if imgui.GetIO().WantCaptureMouse:
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
if io.DisplaySize.x <= 0 or io.DisplaySize.y <= 0:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
mouse_x = io.MousePos.x / io.DisplaySize.x
|
|
399
|
+
mouse_y = io.MousePos.y / io.DisplaySize.y
|
|
400
|
+
aspect_ratio = io.DisplaySize.x / io.DisplaySize.y
|
|
401
|
+
|
|
402
|
+
# Right double click.
|
|
403
|
+
if imgui.IsMouseDoubleClicked(imgui.MouseButton.Right):
|
|
404
|
+
picked = ux.Pick(
|
|
405
|
+
model,
|
|
406
|
+
data,
|
|
407
|
+
camera,
|
|
408
|
+
mouse_x,
|
|
409
|
+
mouse_y,
|
|
410
|
+
aspect_ratio,
|
|
411
|
+
vis_options,
|
|
412
|
+
)
|
|
413
|
+
camera.lookat[:] = picked.point
|
|
414
|
+
if picked.body > 0 and io.KeyCtrl:
|
|
415
|
+
# Switch camera to tracking mode and track the selected body.
|
|
416
|
+
camera.type = int(mujoco.mjtCamera.mjCAMERA_TRACKING)
|
|
417
|
+
camera.trackbodyid = picked.body
|
|
418
|
+
camera.fixedcamid = -1
|
|
419
|
+
ux_state.camera_index = ux.TRACKING_CAMERA_IDX
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def handle_mouse_events(
|
|
423
|
+
model: mujoco.MjModel,
|
|
424
|
+
data: mujoco.MjData,
|
|
425
|
+
camera: mujoco.MjvCamera,
|
|
426
|
+
vis_options: mujoco.MjvOption,
|
|
427
|
+
perturb: mujoco.MjvPerturb,
|
|
428
|
+
ux_state: ux.UxState,
|
|
429
|
+
move_camera: bool = True,
|
|
430
|
+
) -> None:
|
|
431
|
+
"""Handles mouse events.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
model: The MuJoCo model.
|
|
435
|
+
data: The MuJoCo data.
|
|
436
|
+
camera: The MuJoCo camera object.
|
|
437
|
+
vis_options: The MuJoCo visualization options.
|
|
438
|
+
perturb: The MuJoCo perturbation object.
|
|
439
|
+
ux_state: The UX state object.
|
|
440
|
+
move_camera: Whether drags and scrolls move the camera. False when the
|
|
441
|
+
viewer's client moves it itself (Viewer.client_camera); picking and
|
|
442
|
+
perturbation are handled either way.
|
|
443
|
+
"""
|
|
444
|
+
io = imgui.GetIO()
|
|
445
|
+
if io.WantCaptureMouse:
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
if io.DisplaySize.x <= 0 or io.DisplaySize.y <= 0:
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
mouse_x = io.MousePos.x / io.DisplaySize.x
|
|
452
|
+
mouse_y = io.MousePos.y / io.DisplaySize.y
|
|
453
|
+
mouse_dx = io.MouseDelta.x / io.DisplaySize.x
|
|
454
|
+
mouse_dy = io.MouseDelta.y / io.DisplaySize.y
|
|
455
|
+
|
|
456
|
+
is_mouse_moving = mouse_dx != 0.0 or mouse_dy != 0.0
|
|
457
|
+
is_any_mouse_down = (
|
|
458
|
+
imgui.IsMouseDown(imgui.MouseButton.Left)
|
|
459
|
+
or imgui.IsMouseDown(imgui.MouseButton.Right)
|
|
460
|
+
or imgui.IsMouseDown(imgui.MouseButton.Middle)
|
|
461
|
+
)
|
|
462
|
+
is_mouse_dragging = is_mouse_moving and is_any_mouse_down
|
|
463
|
+
|
|
464
|
+
# If no mouse buttons are down, end any active perturbations.
|
|
465
|
+
if not is_any_mouse_down:
|
|
466
|
+
perturb.active = 0
|
|
467
|
+
|
|
468
|
+
# Handle perturbation mouse actions.
|
|
469
|
+
if is_mouse_dragging and io.KeyCtrl:
|
|
470
|
+
if perturb.select > 0:
|
|
471
|
+
action = int(mujoco.mjtMouse.mjMOUSE_NONE)
|
|
472
|
+
if imgui.IsMouseDown(imgui.MouseButton.Left):
|
|
473
|
+
if io.KeyAlt:
|
|
474
|
+
action = int(
|
|
475
|
+
mujoco.mjtMouse.mjMOUSE_MOVE_H
|
|
476
|
+
if io.KeyShift
|
|
477
|
+
else mujoco.mjtMouse.mjMOUSE_MOVE_V
|
|
478
|
+
)
|
|
479
|
+
else:
|
|
480
|
+
action = int(
|
|
481
|
+
mujoco.mjtMouse.mjMOUSE_ROTATE_H
|
|
482
|
+
if io.KeyShift
|
|
483
|
+
else mujoco.mjtMouse.mjMOUSE_ROTATE_V
|
|
484
|
+
)
|
|
485
|
+
elif imgui.IsMouseDown(imgui.MouseButton.Right):
|
|
486
|
+
action = int(
|
|
487
|
+
mujoco.mjtMouse.mjMOUSE_MOVE_H
|
|
488
|
+
if io.KeyShift
|
|
489
|
+
else mujoco.mjtMouse.mjMOUSE_MOVE_V
|
|
490
|
+
)
|
|
491
|
+
elif imgui.IsMouseDown(imgui.MouseButton.Middle):
|
|
492
|
+
action = int(mujoco.mjtMouse.mjMOUSE_ZOOM)
|
|
493
|
+
|
|
494
|
+
move_actions = (
|
|
495
|
+
int(mujoco.mjtMouse.mjMOUSE_MOVE_V),
|
|
496
|
+
int(mujoco.mjtMouse.mjMOUSE_MOVE_H),
|
|
497
|
+
)
|
|
498
|
+
active = int(
|
|
499
|
+
mujoco.mjtPertBit.mjPERT_TRANSLATE
|
|
500
|
+
if action in move_actions
|
|
501
|
+
else mujoco.mjtPertBit.mjPERT_ROTATE
|
|
502
|
+
)
|
|
503
|
+
if active != perturb.active:
|
|
504
|
+
ux.InitPerturb(model, data, camera, perturb, active)
|
|
505
|
+
ux.MovePerturb(
|
|
506
|
+
model,
|
|
507
|
+
data,
|
|
508
|
+
camera,
|
|
509
|
+
perturb,
|
|
510
|
+
action,
|
|
511
|
+
mouse_dx,
|
|
512
|
+
mouse_dy,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Camera drags and wheel, with the camera controls shared with the C++ Studio
|
|
516
|
+
# app and the web viewer (which leave Ctrl drags to the perturbation above).
|
|
517
|
+
if move_camera:
|
|
518
|
+
ux.move_camera_with_mouse(
|
|
519
|
+
model,
|
|
520
|
+
camera,
|
|
521
|
+
fly_camera=ux_state.camera_index == ux.FREE_CAMERA_IDX,
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
aspect_ratio = io.DisplaySize.x / io.DisplaySize.y
|
|
525
|
+
|
|
526
|
+
# Left double click.
|
|
527
|
+
if imgui.IsMouseDoubleClicked(imgui.MouseButton.Left):
|
|
528
|
+
picked = ux.Pick(
|
|
529
|
+
model,
|
|
530
|
+
data,
|
|
531
|
+
camera,
|
|
532
|
+
mouse_x,
|
|
533
|
+
mouse_y,
|
|
534
|
+
aspect_ratio,
|
|
535
|
+
vis_options,
|
|
536
|
+
)
|
|
537
|
+
if picked.body >= 0:
|
|
538
|
+
perturb.select = picked.body
|
|
539
|
+
perturb.flexselect = picked.flex
|
|
540
|
+
perturb.skinselect = picked.skin
|
|
541
|
+
|
|
542
|
+
# Compute the local position of the selected object in the world.
|
|
543
|
+
tmp = np.array(picked.point, dtype=np.float64) - data.xpos[picked.body]
|
|
544
|
+
xmat = np.array(data.xmat[picked.body], dtype=np.float64).reshape(3, 3)
|
|
545
|
+
perturb.localpos = xmat.T @ tmp
|
|
546
|
+
else:
|
|
547
|
+
perturb.select = 0
|
|
548
|
+
perturb.flexselect = -1
|
|
549
|
+
perturb.skinselect = -1
|
|
550
|
+
|
|
551
|
+
handle_camera_tracking_mouse_events(
|
|
552
|
+
model, data, camera, vis_options, ux_state
|
|
553
|
+
)
|