Replay `rollout.qpos/qvel` onto a fresh model from `xml_path` and return frames + nq.
(
xml_path: str | Path,
rollout: RolloutResult,
*,
width: int,
height: int,
camera: str | int,
frame_stride: int,
)
| 90 | |
| 91 | |
| 92 | def _render_frames( |
| 93 | xml_path: str | Path, |
| 94 | rollout: RolloutResult, |
| 95 | *, |
| 96 | width: int, |
| 97 | height: int, |
| 98 | camera: str | int, |
| 99 | frame_stride: int, |
| 100 | ) -> tuple[list, int]: |
| 101 | """Replay `rollout.qpos/qvel` onto a fresh model from `xml_path` and return frames + nq.""" |
| 102 | model = mujoco.MjModel.from_xml_path(str(xml_path)) |
| 103 | data = mujoco.MjData(model) |
| 104 | renderer = mujoco.Renderer(model, width=width, height=height) |
| 105 | |
| 106 | raw_nq = int(rollout.qpos.shape[1]) |
| 107 | model_nq = int(model.nq) |
| 108 | |
| 109 | frames: list = [] |
| 110 | for k in range(0, rollout.qpos.shape[0], frame_stride): |
| 111 | q = rollout.qpos[k] |
| 112 | v = rollout.qvel[k] |
| 113 | # qpos shapes can differ if the metasim model added wrapper qpos |
| 114 | # slots (rare — wrappers usually have no joint). Truncate or pad so |
| 115 | # we never crash; visual quality may suffer but the report still |
| 116 | # gets a frame instead of a blank. |
| 117 | if q.shape[0] >= model_nq: |
| 118 | data.qpos[:] = q[:model_nq] |
| 119 | else: |
| 120 | data.qpos[: q.shape[0]] = q |
| 121 | nv = int(model.nv) |
| 122 | if v.shape[0] >= nv: |
| 123 | data.qvel[:] = v[:nv] |
| 124 | else: |
| 125 | data.qvel[: v.shape[0]] = v |
| 126 | mujoco.mj_forward(model, data) |
| 127 | renderer.update_scene(data, camera=camera) |
| 128 | frames.append(renderer.render().copy()) |
| 129 | return frames, raw_nq |
| 130 | |
| 131 | |
| 132 | def side_by_side_video( |
no test coverage detected