|
1 | | -# Copyright 2025 The Newton Developers |
| 1 | +# Copyright 2026 The Newton Developers |
2 | 2 | # |
3 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
4 | 4 | # you may not use this file except in compliance with the License. |
@@ -62,6 +62,7 @@ class EngineOptions(enum.IntEnum): |
62 | 62 | _KEYFRAME = flags.DEFINE_integer("keyframe", 0, "keyframe to initialize simulation.") |
63 | 63 | _DEVICE = flags.DEFINE_string("device", None, "override the default Warp device") |
64 | 64 | _REPLAY = flags.DEFINE_string("replay", None, "keyframe sequence to replay, keyframe name must prefix match") |
| 65 | +_VIEWER = flags.DEFINE_enum("viewer", "mujoco", ["mujoco", "viser"], "Viewer backend (mujoco native or mjviser web)") |
65 | 66 |
|
66 | 67 | _VIEWER_GLOBAL_STATE = {"running": True, "step_once": False} |
67 | 68 |
|
@@ -105,6 +106,47 @@ def _compile_step(m, d): |
105 | 106 | return capture.graph |
106 | 107 |
|
107 | 108 |
|
| 109 | +def _make_warp_step_fn(m, d, graph, ctrls=None): |
| 110 | + ctrlid = [0] |
| 111 | + |
| 112 | + def step_fn(mjm, mjd): |
| 113 | + if ctrls is not None and ctrlid[0] < len(ctrls): |
| 114 | + mjd.ctrl[:] = ctrls[ctrlid[0]] |
| 115 | + ctrlid[0] += 1 |
| 116 | + |
| 117 | + wp.copy(d.ctrl, wp.array([mjd.ctrl.astype(np.float32)])) |
| 118 | + wp.copy(d.act, wp.array([mjd.act.astype(np.float32)])) |
| 119 | + wp.copy(d.xfrc_applied, wp.array([mjd.xfrc_applied.astype(np.float32)])) |
| 120 | + wp.copy(d.qpos, wp.array([mjd.qpos.astype(np.float32)])) |
| 121 | + wp.copy(d.qvel, wp.array([mjd.qvel.astype(np.float32)])) |
| 122 | + wp.copy(d.time, wp.array([mjd.time], dtype=wp.float32)) |
| 123 | + |
| 124 | + if graph is None: |
| 125 | + mjw.step(m, d) |
| 126 | + else: |
| 127 | + wp.capture_launch(graph) |
| 128 | + wp.synchronize() |
| 129 | + |
| 130 | + mjw.get_data_into(mjd, mjm, d) |
| 131 | + |
| 132 | + return step_fn |
| 133 | + |
| 134 | + |
| 135 | +def _make_c_step_fn(ctrls=None): |
| 136 | + if ctrls is None: |
| 137 | + return None |
| 138 | + |
| 139 | + ctrlid = [0] |
| 140 | + |
| 141 | + def step_fn(mjm, mjd): |
| 142 | + if ctrlid[0] < len(ctrls): |
| 143 | + mjd.ctrl[:] = ctrls[ctrlid[0]] |
| 144 | + ctrlid[0] += 1 |
| 145 | + mujoco.mj_step(mjm, mjd) |
| 146 | + |
| 147 | + return step_fn |
| 148 | + |
| 149 | + |
108 | 150 | def _main(argv: Sequence[str]) -> None: |
109 | 151 | """Runs viewer app.""" |
110 | 152 | if len(argv) < 2: |
@@ -169,45 +211,58 @@ def _main(argv: Sequence[str]) -> None: |
169 | 211 | print(f"Data\n nworld: {d.nworld} nconmax: {int(d.naconmax / d.nworld)} njmax: {d.njmax}\n") |
170 | 212 | print(f"MuJoCo Warp simulating with dt = {m.opt.timestep.numpy()[0]:.3f}...") |
171 | 213 |
|
172 | | - with mujoco.viewer.launch_passive(mjm, mjd, key_callback=key_callback) as viewer: |
173 | | - opt = copy.copy(mjm.opt) |
174 | | - |
175 | | - while True: |
176 | | - start = time.time() |
177 | | - |
178 | | - if ctrls is not None and ctrlid < len(ctrls): |
179 | | - mjd.ctrl[:] = ctrls[ctrlid] |
180 | | - ctrlid += 1 |
181 | | - |
182 | | - if _ENGINE.value == EngineOptions.C: |
183 | | - mujoco.mj_step(mjm, mjd) |
184 | | - else: # mjwarp |
185 | | - wp.copy(d.ctrl, wp.array([mjd.ctrl.astype(np.float32)])) |
186 | | - wp.copy(d.act, wp.array([mjd.act.astype(np.float32)])) |
187 | | - wp.copy(d.xfrc_applied, wp.array([mjd.xfrc_applied.astype(np.float32)])) |
188 | | - wp.copy(d.qpos, wp.array([mjd.qpos.astype(np.float32)])) |
189 | | - wp.copy(d.qvel, wp.array([mjd.qvel.astype(np.float32)])) |
190 | | - wp.copy(d.time, wp.array([mjd.time], dtype=wp.float32)) |
191 | | - # if the user changed an option in the MuJoCo Simulate UI, go ahead and recompile the step |
192 | | - # TODO: update memory tied to option max iterations |
193 | | - if mjm.opt != opt: |
194 | | - opt = copy.copy(mjm.opt) |
195 | | - m = mjw.put_model(mjm) |
196 | | - graph = _compile_step(m, d) if wp.get_device().is_cuda else None |
197 | | - if _VIEWER_GLOBAL_STATE["running"] or _VIEWER_GLOBAL_STATE["step_once"]: |
198 | | - _VIEWER_GLOBAL_STATE["step_once"] = False |
199 | | - if graph is None: |
200 | | - mjw.step(m, d) |
201 | | - else: |
202 | | - wp.capture_launch(graph) |
203 | | - wp.synchronize() |
204 | | - mjw.get_data_into(mjd, mjm, d) |
205 | | - |
206 | | - viewer.sync() |
207 | | - |
208 | | - elapsed = time.time() - start |
209 | | - if elapsed < mjm.opt.timestep: |
210 | | - time.sleep(mjm.opt.timestep - elapsed) |
| 214 | + if _VIEWER.value == "viser": |
| 215 | + try: |
| 216 | + from mjviser import Viewer as MjViserViewer |
| 217 | + except ImportError: |
| 218 | + raise SystemExit("mjviser required for --viewer=viser: pip install mjviser") |
| 219 | + |
| 220 | + if _ENGINE.value == EngineOptions.WARP: |
| 221 | + step_fn = _make_warp_step_fn(m, d, graph, ctrls) |
| 222 | + else: |
| 223 | + step_fn = _make_c_step_fn(ctrls) |
| 224 | + |
| 225 | + MjViserViewer(mjm, mjd, step_fn=step_fn).run() |
| 226 | + else: |
| 227 | + with mujoco.viewer.launch_passive(mjm, mjd, key_callback=key_callback) as viewer: |
| 228 | + opt = copy.copy(mjm.opt) |
| 229 | + |
| 230 | + while True: |
| 231 | + start = time.time() |
| 232 | + |
| 233 | + if ctrls is not None and ctrlid < len(ctrls): |
| 234 | + mjd.ctrl[:] = ctrls[ctrlid] |
| 235 | + ctrlid += 1 |
| 236 | + |
| 237 | + if _ENGINE.value == EngineOptions.C: |
| 238 | + mujoco.mj_step(mjm, mjd) |
| 239 | + else: # mjwarp |
| 240 | + wp.copy(d.ctrl, wp.array([mjd.ctrl.astype(np.float32)])) |
| 241 | + wp.copy(d.act, wp.array([mjd.act.astype(np.float32)])) |
| 242 | + wp.copy(d.xfrc_applied, wp.array([mjd.xfrc_applied.astype(np.float32)])) |
| 243 | + wp.copy(d.qpos, wp.array([mjd.qpos.astype(np.float32)])) |
| 244 | + wp.copy(d.qvel, wp.array([mjd.qvel.astype(np.float32)])) |
| 245 | + wp.copy(d.time, wp.array([mjd.time], dtype=wp.float32)) |
| 246 | + # if the user changed an option in the MuJoCo Simulate UI, go ahead and recompile the step |
| 247 | + # TODO: update memory tied to option max iterations |
| 248 | + if mjm.opt != opt: |
| 249 | + opt = copy.copy(mjm.opt) |
| 250 | + m = mjw.put_model(mjm) |
| 251 | + graph = _compile_step(m, d) if wp.get_device().is_cuda else None |
| 252 | + if _VIEWER_GLOBAL_STATE["running"] or _VIEWER_GLOBAL_STATE["step_once"]: |
| 253 | + _VIEWER_GLOBAL_STATE["step_once"] = False |
| 254 | + if graph is None: |
| 255 | + mjw.step(m, d) |
| 256 | + else: |
| 257 | + wp.capture_launch(graph) |
| 258 | + wp.synchronize() |
| 259 | + mjw.get_data_into(mjd, mjm, d) |
| 260 | + |
| 261 | + viewer.sync() |
| 262 | + |
| 263 | + elapsed = time.time() - start |
| 264 | + if elapsed < mjm.opt.timestep: |
| 265 | + time.sleep(mjm.opt.timestep - elapsed) |
211 | 266 |
|
212 | 267 |
|
213 | 268 | def main(): |
|
0 commit comments