Skip to content

Add mjviser web viewer backend (--viewer=viser).#1266

Merged
kevinzakka merged 2 commits intogoogle-deepmind:mainfrom
kevinzakka:mjviser
Apr 3, 2026
Merged

Add mjviser web viewer backend (--viewer=viser).#1266
kevinzakka merged 2 commits intogoogle-deepmind:mainfrom
kevinzakka:mjviser

Conversation

@kevinzakka
Copy link
Copy Markdown
Collaborator

Adds support for mjviser as an alternative viewer backend via --viewer=viser. mjviser is a web-based MuJoCo visualizer built on viser, useful when running on headless machines or when you want browser-based visualization.

Did not add mjviser as an explicit dependency so it should be run as follows:

uv run --with mjviser mjwarp-viewer benchmarks/humanoid/humanoid.xml --viewer=viser

@thowell thowell requested a review from erikfrey March 29, 2026 10:14
Copy link
Copy Markdown
Collaborator

@erikfrey erikfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool! Would love to get this in, mostly nits below.

try:
from mjviser import Viewer as MjViserViewer
except ImportError:
raise SystemExit("mjviser required for --viewer=viser: pip install mjviser")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is mjvser dep heavy? Should we add it to dev dependencies?

[project.optional-dependencies]
dev = [
"pre-commit",
"pytest",
"pytest-xdist",
"ruff",
"pygls>=1.0.0,<2.0.0",
"lsprotocol>=2023.0.1,<2024.0.0",
"mujoco>=3.6.0.dev0",
"warp-lang>=1.11.0.dev0",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, added it to dev.



def _make_warp_step_fn(m, d, graph, ctrls=None):
ctrlid = [0]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha, it took me a moment to understand why you were creating a list here!

I think this is what nonlocal is for.

so:

ctrlid = 0

def step_fn(mjm, mjd):
  nonlocal ctrlid
  ...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!


def _make_c_step_fn(ctrls=None):
if ctrls is None:
return None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC why does this return none?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

if _ENGINE.value == EngineOptions.WARP:
step_fn = _make_warp_step_fn(m, d, graph, ctrls)
else:
step_fn = _make_c_step_fn(ctrls)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like you've gone through some effort to abstract out the step function logic which is nice

can't we reuse it with the passive viewer below?

ideally we could have something like:

step_fn = foo
viewer_fn = bar

viewer_fn(step_fn)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@kevinzakka
Copy link
Copy Markdown
Collaborator Author

Thanks @erikfrey, addressed all your review feedback.

@kevinzakka kevinzakka merged commit 867571f into google-deepmind:main Apr 3, 2026
10 checks passed
@kevinzakka kevinzakka deleted the mjviser branch April 3, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants