The Settings panel is the preferred interface for persistent user preferences. Open it from
Edit > Settings..., Window > Settings, or F9. It is a dockable, non-modal panel; opening
it does not block viewport interaction.
| Variable | Values | Purpose |
|---|---|---|
MOJIVE_RENDERER |
opengl, bgfx |
Select the renderer for interactive and offscreen rendering; an explicit renderer= argument takes precedence. |
MOJIVE_BACKEND |
opengl, bgfx |
Legacy renderer setting, used when MOJIVE_RENDERER is unset. |
MOJIVE_GL |
auto, native, glfw, egl |
Select OpenGL context creation. Offscreen auto tries EGL on Linux, then hidden GLFW only when a desktop display and the main thread are available. |
MOJIVE_UI_SCALE |
positive number | Override the logical UI scale when desktop scale detection is wrong. |
MOJIVE_LANGUAGE |
en, zh_CN |
Override the UI language for the process. |
MOJIVE_CJK_FONT |
font path | Use a specific CJK fallback font. |
MOJIVE_SETTINGS |
JSON path | Override the persistent application settings file. |
MOJIVE_CONFIG_DIR |
directory path | Override the directory containing imgui.ini. |
MOJIVE_IMGUI_INI |
file path | Override the ImGui layout file directly. |
MOJIVE_OPEN_SETTINGS |
1 |
Open the Settings panel at startup; primarily used by visual acceptance. |
MOJIVE_RENDERER and its legacy alias MOJIVE_BACKEND select rendering. Model-oriented CLI
commands use --adapter (or the compatible -b/--backend alias) for the scene adapter. See the
CLI reference.
FORGE_VIEWER_BACKEND and the value forge remain accepted only as migration compatibility for
older local launch scripts. New scripts should use MOJIVE_RENDERER=opengl.
Application preferences default to these locations:
| Platform | Settings file | Layout file |
|---|---|---|
| macOS | ~/Library/Application Support/mojive/settings.json |
~/Library/Application Support/mojive/native_imgui.ini |
| Linux | ${XDG_CONFIG_HOME:-~/.config}/mojive/settings.json |
${XDG_CONFIG_HOME:-~/.config}/mojive/native_imgui.ini |
| Windows | %APPDATA%/mojive/settings.json |
%APPDATA%/mojive/native_imgui.ini |
MOJIVE_SETTINGS replaces the settings-file path. MOJIVE_IMGUI_INI replaces the layout-file
path. MOJIVE_CONFIG_DIR affects the layout file only.
The classic presentation retains imgui.ini in the same directory.
The Settings panel writes user choices to the JSON settings file and restores them at the next launch. The file is intentionally backend-neutral, so switching between OpenGL and bgfx keeps the same preference.
| Preference | Values | Settings panel |
|---|---|---|
shadow_quality |
performance, balanced, high |
Rendering > Shadows > Shadow quality |
geometry_style |
collision_color, visual_opacity, collision_opacity |
MuJoCo Visuals > Both appearance |
contact_style |
shape, color, scale, use_model_color |
MuJoCo Visuals > Contact points |
balanced is the default. performance reduces receiver filtering work, while high increases
near-cascade density and filtering quality for close inspection. Changing the preset invalidates
the shadow-map cache once; subsequent static frames reuse the rebuilt maps.
Mojive has two separate entry points: an offscreen renderer that returns scene images, and an
interactive viewer that exposes Mojive's full desktop tools. Normal Viewer construction loads
and enables every built-in panel, with the standard docking layout and interaction defaults.
Inspector, Control, Joints, Camera and Keyframes are available from the start. Users can close
panels, disable tools, or explicitly choose a reduced configuration after deciding what they need.
ViewerConfig.minimal() is an opt-in embedding preset, never the default viewer experience.
The default ViewerConfig.ui="native" uses the production workspace adopted from the native
design reference: an activity bar, Scene sidebar, tabbed Inspector, bottom Timeline/Output/Statistics,
and viewport tools. Clicking the active activity item hides its panel; clicking it again reopens it.
Scene selection supports Ctrl/Cmd toggle and Shift ranges in the visible hierarchy order.
ViewerConfig(ui="classic") selects the previous presentation for existing embeddings. Both
presentations use the same Session commands, editor transactions and renderer contracts. Their
default docking files are separate; an explicit LayoutConfig.path still chooses an exact file.
Native workspace defaults are stored in mojive.design.resources/design-spec.json and resolved
per viewer by DesignRuntime. The component painters use mojive.ui.workspace.design for
shared dimensions. They use logical points:
14-point body and numeric text, 16-point titles, 28-point controls, 6-point control corners,
12/10-point panel padding, 4-point numeric field gaps and 8-point row spacing. The application bar
is 46 points tall; timeline lanes are 36 points tall. Drawing and input geometry use the same
dimensions and receive the platform UI scale once.
Run make ui-native-design to open the native parameter editor alongside the production
workspace. The editor provides component samples, shared typography and spacing, module
overrides, and workspace layout parameters. Alt-click a production component to select its
parameters; the overlay shows its painted bounds and spacing. Edits update the production
widgets immediately. Save, undo and redo operate on the specification independently of scene
editing. The header switches between separate and embedded editor windows.
Alt-click the bottom runtime status text or dot to select status_context, the parent group
for runtime status, recording actions and operation hints. Its padding and margins move the
group together; status_hints controls spacing within the hint list. Runtime metrics retain
their separate group on the right.
make ui-design-native is a compatibility alias for the same interactive editor.
Run make ui-native-capture to capture production panels and menus under
output/ui_design/native/<backend>/ and exit. The underlying
python -m mojive.tools.native_workspace command opens a production workspace by default;
--capture selects the automated gallery.
The editor saves design-spec.json beside the editor's settings.json; its adjacent
design-spec.live.json contains the current draft. This is the Mojive user configuration
directory (~/Library/Application Support/mojive on macOS and $XDG_CONFIG_HOME/mojive,
or ~/.config/mojive, on Linux). An explicit MOJIVE_SETTINGS path also determines this
directory. Normal native viewers use the newest valid saved specification or draft there.
MOJIVE_DESIGN_SPEC=/absolute/path/spec.json selects another
specification for a viewer process, and make ui-native-design ARGS='--spec /absolute/path/spec.json'
selects it for the editor. With no saved file, viewers use the bundled defaults. Paths are resolved
from the working directory; use an absolute path when sharing a specification across launchers.
Generated files under output/ can be deleted without removing the saved design.
Use BACKEND=bgfx to run the editor with the native backend. For reproducible capture, use
MOJIVE_UI_SCALE=1 make ui-native-design ARGS='--hidden --frames 8 --capture output/workbench'.
Set MOJIVE_UI_SCALE=2 for a 2× UI, and supply matching window dimensions when passing
explicit --width and --height values.
| Use case | Entry point | What is constructed |
|---|---|---|
Replace mujoco.Renderer |
mojive.Renderer(model) |
MuJoCo scene conversion and offscreen rendering; no Viewer, panels or socket |
Migrate mujoco.viewer calls |
mojive.viewer.launch(...) or mojive.viewer.launch_passive(model, data) |
Full Mojive desktop UI by default; see the compatibility guide |
| Render programmatic or custom scenes | mojive.SceneRenderer(...) |
Scene rendering without a physics engine or editor |
| Explicitly reduce the viewer | ViewerConfig.minimal("inspector", "control", "joints", "camera") |
Viewer with those four panels; editing overlays and the debug socket start disabled |
Both offscreen renderers expose renderer.debug for retained 3D drawing and
renderer.canvas2d for grids and planar annotations. These draw into scene images and do not
add UI. RGB, depth and segmentation are rendering products, not interactive viewer surfaces.
For the normal desktop experience, omit the configuration:
from mojive import build
with build("robot.xml") as viewer:
viewer.run()The process-based mojive.launch_passive extension also loads all panels. Its caller owns the clock and
model, pose/topology edits and playback shortcuts remain unavailable; see
passive viewing for write-back support. Capability restrictions
are independent of optional UI configuration.
For an application that intentionally needs only selected tools:
from mojive import ViewerConfig, build
with build("robot.xml", config=ViewerConfig.minimal("inspector", "control", "joints", "camera")) as viewer:
# Optional features can be loaded later, from code or the Window menu.
viewer.panels.open("keyframes")
viewer.run()ViewerConfig.minimal() loads no panels. It retains navigation, selection, and debug drawing,
uses an isolated docking layout, and disables gizmo/perturbation input, scene helpers and viewport
capsules. The menu bar and status bar remain available. It is a lighter interactive viewer;
use Renderer or SceneRenderer when no UI is needed.
For independent choices, use ViewerConfig(builtin_panels=("inspector", "camera"), debug_server=False).
builtin_panels=None preserves all desktop panels; () constructs none. Only selected panel
implementations are imported and instantiated. PanelConfig still controls availability/open state
of loaded panels and applies when a panel is loaded later. Closing a panel stops its frame-data
requests but keeps its state. panels.get() is read-only; load(), open() and enable() can load a
built-in on demand. Explicit custom-only PanelManager(panels=[...]) registries remain closed.
debug_server=False disables the legacy local debug-drawing socket. In-process debug drawing
remains available. The separate control RPC server starts only when viewer.start_rpc() is called.
Use dataclasses.replace() to customize a minimal preset, including interactions and visible layers.
Embedding code can define interaction ownership before the viewer starts. The options are independent: disabling scene picking does not disable focus or camera motion, disabling fly keys does not disable orbit, and hiding the gizmo does not clear the logical selection.
from pathlib import Path
from mojive import (
CameraInputConfig,
InteractionConfig,
InputClaim,
LayoutConfig,
PanelConfig,
RecordingConfig,
SelectionInputConfig,
SelectionStyle,
ViewerConfig,
ViewportLayers,
ViewportOverlayConfig,
build,
)
config = ViewerConfig(
interactions=InteractionConfig(
camera=CameraInputConfig(fly=False),
selection=SelectionInputConfig(pick=False, clear_with_escape=False),
panel_shortcuts=False,
),
selection=SelectionStyle(highlight=True, outline=True, gizmo=False, frame=True),
panels={
"hierarchy": PanelConfig(enabled=False),
"inspector": PanelConfig(open=False),
},
# Keep this product's docking state separate from the Mojive editor.
layout=LayoutConfig(path=".policy-eval-imgui.ini"),
viewport_overlays=ViewportOverlayConfig(
playback_scale=0.85,
tool_scale=0.85,
status_duration=4.0,
movable=True,
),
layers=ViewportLayers(debug_3d=False),
recording=RecordingConfig(countdown=3.0, fps=60),
shadow_quality="high",
live_model_updates=False, # UI model edits wait for the viewport Apply action.
)
viewer = build(Path("robot.xml"), config=config)
def policy_input(input):
if input.viewport_focused and input.key_down("w"):
move_policy_target_forward()
return InputClaim(keys=frozenset({"w", "a", "s", "d"}))
viewer.set_input_handler(policy_input)
viewer.run()InputClaim is per-frame ownership, so an application may claim WASD only while a policy-control
mode is active. Broad ImGui keyboard capture from a focused panel does not block the callback;
only active text editing, modal UI, and native dialogs do. Clicking the viewport returns focus,
while pick_on_focus=False can make that first click focus-only. Actions may also be assigned to
None in the Settings shortcut editor. At
runtime, use viewer.configure_interactions(...), viewer.configure_selection(...),
viewer.configure_viewport_overlays(...), viewer.configure_layers(...),
viewer.configure_recording(...), and the
stable panel IDs through viewer.panels.open("hierarchy"), close, enable, or disable.
Explicit ViewerConfig values apply to that viewer instance. Changes made in Settings are stored
as desktop preferences for later viewers created without an explicit config. Runtime
configure_* calls are also instance-local unless passed persist=True.
Continuous Settings controls preview values while dragging or typing and save on commit.
Persistent setters apply the runtime value before saving it. If the save raises OSError, the
current session keeps that value and the previous settings file remains intact. Python and RPC
callers receive the error; the Settings panel reports that the value applies only to this session
and could not be saved. A later successful commit saves the current value.
ViewerConfig(live_model_updates=None) uses the persisted editor preference, whose default is
False. Set True for immediate UI model edits; public Session/RPC commands remain synchronous.
UI colors belong to each Viewer rather than its Session, so two windows presenting the same
scene may use different themes. Theme controls the native ImGui colors, semantic node and axis
colors, and the palette assigned to newly authored objects. ViewportChromeColors independently
controls the floating capsule surface, outline, divider, and the background and foreground for
off, hover, press, on, and disabled controls. Colors are RGBA floats; rgb8() is a convenience
for 8-bit RGB values.
from dataclasses import replace
from mojive import THEME, ViewportChromeColors, build, rgb8
chrome = replace(
THEME.viewport,
surface=rgb8(25, 28, 34, 0.94),
outline=rgb8(115, 125, 140, 0.55),
hover_background=rgb8(55, 70, 88),
press_background=rgb8(43, 56, 72),
on_background=rgb8(65, 105, 145),
on_foreground=rgb8(240, 247, 255),
)
custom_theme = replace(
THEME,
viewport=chrome,
entity_palette=(rgb8(233, 122, 92), rgb8(94, 180, 155), rgb8(104, 145, 224)),
)
viewer = build("robot.xml", theme=custom_theme)
viewer.set_theme(replace(custom_theme, viewport=replace(chrome, on_background=rgb8(125, 90, 190))))Owned MuJoCo viewers default to ViewerConfig(threaded_physics=True): physics advances on a
worker while the main thread reads completed snapshots. Use threaded_physics=False for the
serial path. This is an instance option, not a persisted desktop preference. Externally clocked
models and adapters without a concurrent driver keep their existing execution mode. Physics
commands synchronize at a native batch boundary; camera and selection interaction do not wait
for that boundary. See simulation ownership.
Input hooks and bindings use physical keys and mouse buttons. On macOS, ctrl means Control
and super means Command; Control plus left click remains a left-button gesture. Native macOS
text-editing shortcuts remain enabled inside ImGui text fields.
The Dimensions tool deliberately has no default shortcut, so it does not take another key from an embedding application. It can be selected and optionally bound from code:
from mojive import InputAction
viewer.set_gizmo_mode("dimensions")
viewer.configure_input_binding(InputAction.GIZMO_DIMENSIONS, "s")The tool edits primitive parameters rather than adding transform scale: a sphere exposes radius; a cylinder or cone exposes diameter and height; a capsule exposes diameter and shaft length; and a box or ellipsoid exposes three independent dimensions. Unsupported mesh data remains editable through its source rather than being presented as a misleading scale operation.
Drag the outlined center to resize all dimensions proportionally. XY, XZ, and YZ handles resize their corresponding body axes; finite planes expose XY only. Cylinder, cone, and capsule radial handles preserve their circular section by coupling X and Y. A sphere's center and screen-facing ring both change its radius. Dimension gestures support snapping and Undo.
Use LayoutConfig(persistence=False) for a deterministic default layout on every launch, or
LayoutConfig(reset=True) to discard stale/off-screen docking coordinates once and then keep the
new layout. A custom path isolates each embedding application from Mojive's editor layout.
Library code configures shadow quality explicitly per renderer and does not read or update the application settings file:
from mojive import Renderer, ShadowQuality
with Renderer(model, shadow_quality=ShadowQuality.HIGH) as renderer:
renderer.update_scene(data)
image = renderer.render()
renderer.set_shadow_quality(ShadowQuality.PERFORMANCE)Settings > MuJoCo Visuals > Contact points controls contact marker visibility and appearance.
The default is a red Cylinder, oriented along the contact normal to distinguish it from yellow
collision geometry. Sphere draws a solid ball; Point retains the screen-space circular marker.
Color includes opacity; Size multiplies the marker dimensions. Use model color restores
the adapter's contact color. Island visualization takes precedence over either color choice.
MuJoCo supplies the base radius and half height from visual/scale/contactwidth and
contactheight, multiplied by stat.meansize. Cylinder uses these nominal dimensions;
it does not stretch with penetration depth. Sphere uses the radius. Point uses a four-pixel
radius at size 1x. These display preferences do not change contacts or simulation parameters.
Shape, color, and size persist across desktop launches and are shared by viewport, scene captures,
video, and camera previews. Public Python callers can import ContactStyle from mojive, pass
ViewerConfig(contact_style=ContactStyle(...)), call
viewer.configure_contact_style(ContactStyle(...), persist=True), or use
scene_renderer.set_contact_style(ContactStyle(...)) before updating its frame.
Settings > Camera controls double-click focus and editor zoom. Changes apply immediately and persist across launches. Field labels follow the UI language; curve and mode names remain English.
Object focus approaches from the camera's current side of the object and looks toward its bounds center. Views below a 30-degree elevation rise to that angle; steeper views from above retain their elevation. The eye follows a straight path; heading and elevation ease toward their endpoints without reversing, and level endpoint views stay level throughout the transition. Joint focus also accounts for the joint axis and nearby occlusion when choosing an observation direction.
- Focus distance is framing padding:
1xtightly fits the object's bounds; larger values leave more space. Joint focus retains its additional space for the joint axis and controls. - Transition duration is in seconds;
0focuses immediately. Transition curve selectsLinear,Smoothstep(default),Smootherstep, orEase out cubic. Object focus applies the curve to both turning and visible approach, so they begin and end together. Travel along the straight camera path accounts for perspective scaling to avoid a late rush toward the object. - Proportional zoom changes distance by a ratio, making each step smaller near the pivot. Linear moves a fixed fraction of the last framed scene's extent per step. Zoom speed scales both wheel and drag input.
- Minimum distance and Maximum distance bound editor zoom in world units (defaults
0.001and1e6). Orthographic zoom uses its equivalent perspective distance. Limits also constrain focus framing; a maximum smaller than the distance needed to fit an object can crop it. The Camera panel uses the same limits with a logarithmic distance slider.
Programmatic viewers can use ViewerConfig(navigation=CameraNavigationConfig(...)) or
viewer.configure_navigation(CameraNavigationConfig(...), persist=False). Import
CameraNavigationConfig from mojive. Explicit configuration overrides saved preferences for
that viewer; persist=True saves it. These preferences do not edit authored scene cameras.
Open Camera > Tracking (F6), choose a body in Target, or select a scene object and
press Track selected. X-Y follows horizontal motion while holding the current camera
height; X-Y-Z also follows vertical motion. Tracking keeps the viewing direction and distance:
the target's rotation does not turn the camera. Orbit and zoom still work, and panning adjusts
the composition offset. Changing scene selection does not change the tracking target.
Smoothing is the time in seconds to halve the remaining position error. Larger values suppress
rapid motion more strongly and add following delay; 0 follows directly. The default is 0.25 s
with X-Y tracking. Smoothing uses elapsed display time, independently of simulation steps,
playback speed, and the physics owner. Axis and smoothing choices persist across launches;
tracking starts disabled in a new viewer. Choose Off to stop at the current view. Selecting a
model camera, framing the scene, focusing another object, or explicitly setting a camera also
ends tracking.
from mojive import CameraTrackingConfig
viewer.configure_tracking(CameraTrackingConfig(axes="xy", smoothing=0.35))
viewer.track_body("pelvis") # Unique body name, or composed body index.
viewer.track_body(None) # Stop following without changing the current view.These methods also work on launch_passive() handles: following runs in the display process
without adding work to each physics step. For other adapters and programmatic scenes, use
viewer.track_node(node.node_id) with a node from viewer.session.nodes. ViewerConfig(tracking=...)
sets initial preferences; configure_tracking(..., persist=True) also saves desktop preferences.
Settings > MuJoCo Visuals > Both appearance saves collision color and independent Visual/Collision
opacity. The default is a muted amber collision color (0.8, 0.45, 0.12), Visual opacity 0.65,
and Collision opacity 0.35. Values range from 0 to 1. Visual opacity multiplies the authored
material alpha; collision opacity is independent of it, including invisible physics proxies.
Shared primitives with identical visual and collision geometry are drawn once with their original
material; distinct collision hulls use the comparison opacity. Collision-only view stays opaque.
These settings affect display only, preserve instanced rendering, and do not edit physics materials.
from mojive import GeometryStyle, ViewerConfig
style = GeometryStyle(collision_color=(0.3, 0.5, 0.7), visual_opacity=0.9, collision_opacity=0.2)
config = ViewerConfig(geometry_style=style)
viewer.configure_geometry_style(style, persist=True)
# Offscreen SceneRenderer uses the same settings:
scene_renderer.set_geometry_style(style)An omitted ViewerConfig.geometry_style loads the saved desktop preference. Screenshots, videos,
and the selected-camera preview follow the active style, including per-body Both overrides.
Still captures default to SCENE, at the displayed viewport's physical-pixel resolution, without
reading the full-window framebuffer. Scene images retain active render flags, MuJoCo visuals
(including inertia, scaled inertia and contacts), visual groups, geometry comparisons, labels,
coordinate frames and diagnostic views. Editor selection, gizmos and viewport controls are excluded.
VIEWPORT includes Mojive's viewport tools and overlays but crops panels and the menu bar;
WINDOW captures the complete ImGui window. An explicitly fixed render size, including record(size=...),
continues to override the default Scene output resolution.
from mojive import CaptureSurface
viewer.capture("output/raw.png")
viewer.capture("output/tutorial.png", surface=CaptureSurface.VIEWPORT)
viewer.start_recording("output/walkthrough.mp4", countdown=0)
for _ in range(120):
viewer.sync()
viewer.pause_recording()
# Update the scene without appending video frames, then continue the same file.
viewer.resume_recording()
for _ in range(120):
viewer.sync()
viewer.stop_recording()Interactive recordings default to VIEWPORT, 60 FPS, and a three-second countdown. Change these
in Settings > Recording, also reachable through View > Recording Settings.... Countdown
uses wall time, can be canceled with its button or the recording shortcut, and creates no video
file until a frame is captured. A zero-second delay starts on the next clean frame after menus
close. The recording rate is independent from display and physics rates.
Successful viewer screenshot saves automatically copy an image to the desktop clipboard;
successfully finalized videos copy a file for pasting as an attachment. The copy runs in the
background and the status receipt reports success or failure, retaining the saved file either
way. Disable this in Settings → Recording → Copy to clipboard, or
use RecordingConfig(copy_to_clipboard=False). A burst of saves leaves the newest item on the
clipboard. In-memory captures, the UI-free renderer and silent recording finalization
(report=False, including viewer shutdown) do not change the clipboard.
Linux X11 uses GTK3; native Wayland requires wl-copy (XWayland can use the X11 route).
macOS uses the system pasteboard and Windows uses file-drop/image clipboard data. The receiving
application must support pasting images or file attachments; video data is not pasted as text.
The Video encoding section applies to both live recordings and take exports. Quality priority uses CRF (0–51; lower values preserve more detail and produce larger files), while Target bitrate uses an average bitrate in Mbps, not a strict per-frame or file-size limit. Only the active mode's value is shown. Encoding speed selects Fast, Balanced, or Slow; slower encoding spends more CPU time on compression and can slow live recording. Color sampling selects compatible 4:2:0 or full-resolution 4:4:4 chroma, which requires a compatible player. Defaults remain H.264, CRF 25, Balanced, and 4:2:0. Hover over controls for short guidance.
Encoding settings are captured when recording is requested, including its countdown; changes
apply to the next video, not a paused recording being resumed. Configure them programmatically
with viewer.configure_recording(RecordingConfig(crf=18, encoder_preset="fast")), or
RecordingConfig(rate_control="bitrate", bitrate_mbps=12). GUI changes persist as preferences;
API changes remain instance-local unless persist=True is supplied. Capture the native settings
layouts with make keyframe-timeline ARGS='--recording-settings --scale 2.5 --language zh_CN'.
For a completed simulation take, Keyframes > Export Video rewinds to its first frame, waits for the countdown, records the entire take once, and saves automatically. Recording settings set the start delay and final-frame hold (one second by default); the same defaults are available in Settings > Recording. The countdown is excluded from the video; the final hold is included. The playback range and independent Loop toggle remain unchanged and are ignored for this recording. Pausing the video also pauses take playback and the final hold. Camera and Layers controls remain available while recording. Changing the take or scene ends the recording.
The equivalent API is viewer.start_take_video("output/take.mp4", countdown=3, end_hold=1);
keep calling viewer.sync() while viewer.recording.active. Take playback advances with encoded
frames at the selected playback speed. Slow rendering or encoding can extend recording wall time
without skipping motion or shortening the resulting video. Finished videos show their absolute
save path in a short viewport notice using the configured 3–5 second duration. The complete
message remains in Output: right-click it and choose Copy path. Select ordinary Output
records and use Ctrl/Cmd+C to copy their message bodies.
Use make take-video (or BACKEND=bgfx) for native controls and decoded-video acceptance.
Settings > Interaction > Perturb provides independent Drag force scale and
Twist torque scale multipliers from 0x to 20x (default 1x). They scale the physical
perturbation wrench, including damping, without changing mouse sensitivity or policy
controls. Translation also scales its moment-arm torque. Zero disables that gesture's
force or torque; the value-reset mouse binding restores 1x. Changes persist as
perturb_force_scale and perturb_torque_scale in the viewer settings file. They work
in managed and passive MuJoCo viewers; adapters without strength support retain unit
strength and show disabled controls.
Open View > Layers... or Window > Layers to control viewport content during everyday
viewing and recording. The panel docks outside the viewport. Its switches control viewport
controls, transform and joint gizmos, selection feedback, camera/light helpers, perturbation
guides, 3D debug drawings, and 2D canvas drawings. Public debug layers and Canvas2D layers can
also be hidden individually under Named drawing layers. These switches preserve the
publisher's own visibility choices and tool settings. Hidden gizmos and camera/light helpers
stop receiving pointer hits. Hiding perturbation guides keeps physical perturbation available.
Model geometry visibility remains in the Hierarchy and visual-group controls.
Viewport capture and recording follow the live choices, including changes made while recording;
there is no separate recording-only mask or extra scene render. Raw SCENE output follows the
GPU drawing layers but excludes ImGui overlays. Camera preview is part of viewport controls.
Small limited hinge joints show a faint complementary arc completing the rotation ring. Hover
or drag anywhere on that ring to manipulate the joint within its authored limits.
Use make recording-layers (or BACKEND=bgfx) for scripted live controls and encoded-video
acceptance, including representative images under output/recording-layers/.
viewer.record(...) remains the deterministic fixed-frame, UI-free rollout API. The
start_recording lifecycle is for user-driven or automated editor demonstrations. The current
phase (idle, countdown, recording, paused, or finalizing), remaining countdown seconds, surface,
output path, frame count, and duration are available through viewer.recording.
Changing the window or viewport dimensions while recording a UI surface stops that recording
with an explicit error instead of silently stretching or cropping frames.
Generated captures, recordings, reports, visual-acceptance images, and the built documentation
site belong under the repository's ignored output/ directory.
OpenGL is the default. Interactive windows require a desktop OpenGL 3.3 core profile. On Linux,
interactive GLFW normally chooses the native context API (EGL on Wayland, GLX on X11); set
MOJIVE_GL=egl PYOPENGL_PLATFORM=egl to force and verify EGL for the interactive window
(or use make egl-viewer). Set both variables before starting Python: GLFW and the ImGui
PyOpenGL backend must agree on the context API.
If EGL fails with eglInitialize failed (0x3001) in a Conda shell, check
printenv __EGL_VENDOR_LIBRARY_DIRS. A Conda activation hook can restrict EGL vendor discovery
to its Mesa libraries and exclude the system NVIDIA driver. Compare with system discovery:
env -u __EGL_VENDOR_LIBRARY_DIRS make eglIf this succeeds, remove the vendor-directory export from the affected environment's
etc/conda/activate.d/ hook and run unset __EGL_VENDOR_LIBRARY_DIRS in existing shells.
Keep intentional vendor overrides when they are required by your environment.
Offscreen Renderer has a separate context policy:
| Selection | Behavior |
|---|---|
auto or unset, Linux |
Try EGL first. If creation fails, try hidden GLFW only with DISPLAY or WAYLAND_DISPLAY set and on the main thread. Warn when fallback succeeds. |
auto or unset, macOS/Windows |
Use a hidden native GLFW window. |
egl |
Require EGL; never silently switch to GLFW. |
glfw or native |
Require a hidden native GLFW window; do not try EGL first. |
A hidden window is offscreen, not display-free. On a Linux desktop where EGL fails, try:
MOJIVE_GL=glfw uv run --no-sync python examples/mujoco_render.py assets/test_scene.xmlOn a server without X11/Wayland, keep EGL and check the GPU driver, EGL vendor libraries, and
GPU/device access inside the container. Setting a dummy DISPLAY does not create a display
server. Worker threads do not automatically fall back to GLFW. Require EGL for reproducible
headless jobs and benchmarks:
MOJIVE_GL=egl uv run --no-sync python examples/mujoco_video.py assets/test_scene.xmlContext errors retain the original initialization failure and every attempted backend. A successful GLFW fallback may select a different GPU from EGL, so the runtime emits a warning. The presence of a display variable permits an attempt; it does not guarantee a working display.
The bgfx backend requires the native extension and compiled shaders. Build with
make native-python-build; use MOJIVE_NATIVE_BUILD to select that development build.
See the native backend guide for setup and platform checks.
Use mojive backends for adapter availability and mojive probe for OpenGL capability details.
Interactive recording copies captured RGB frames directly into reusable buffers and writes them
to FFmpeg on one worker. It owns at most three RGB frames, including the previous image retained
for repetition: 3 × width × height × 3 bytes (about 74.6 MB at 3840×2160). When the buffers are
occupied, recording skips sampling without waiting for encoding or requesting its GPU readback.
The worker holds the previous image across skipped intervals, preserving video duration and
playback speed. Frame count and duration include these repeated frames; a 60 FPS output does not
guarantee 60 distinct images per second under load. GPU readback, copies and CPU encoding still
have a cost. Lowering recording FPS reduces the load. The first image is also written on the
worker; automatic simulation start waits for its success. Encoder
failures are reported even while recording is paused. A write or finalization that makes no
progress for 30 seconds aborts the encoder and reports failure.
Stopping through the UI enters finalizing: the viewer remains usable while the remaining video
timeline is encoded, including any repeated tail frames. A saved receipt appears only after
successful completion. Sustained encoder overload can extend finalization. Keep calling viewer.sync()
while viewer.recording.active to observe that completion. A new recording cannot start while
one is finalizing. viewer.stop_recording() and viewer shutdown wait for file completion.
Offline take export and direct VideoRecorder calls remain synchronous.
VideoRecorder streams frames through FFmpeg. imageio-ffmpeg is a normal Mojive dependency
and supplies executable discovery. If the executable is missing, reinstall that dependency or
set IMAGEIO_FFMPEG_EXE to a working FFmpeg binary with the requested encoder.
MP4 defaults to H.264 (libx264) and yuv420p for player compatibility. Use
VideoRecorder(..., pixel_format="yuv444p") for full chroma resolution, or codec= to select
another encoder supported by your FFmpeg. Odd input dimensions in yuv420p are edge-padded by
one pixel on the right/bottom as needed, never scaled. The recorder warns and exposes
encoded_size; size remains the required input dimensions. Choose even dimensions for exact
output dimensions with the compatible default. Not every encoder supports every pixel format.
VideoRecorder(..., crf=18, preset="slow") selects H.264 constant-quality encoding. Alternatively,
VideoRecorder(..., bitrate=12_000_000, preset="fast") requests a target average bitrate in
bits per second. CRF and bitrate are mutually exclusive; omitted options retain CRF 25 and
the medium preset for H.264. Explicit crf and preset require libx264; the default WMV
encoder remains unchanged. These controls use FFmpeg's
libx264 rate-control and preset options.
Always close the recorder (prefer a with block). Empty recordings, write failures, encoder
errors, and finalization timeouts raise errors instead of reporting success. On failure, a
partial file may remain for diagnosis. See the rollout video recipe.
Without an override, Mojive combines GLFW content scale and framebuffer scale so fonts, ImGui controls, overlays, gizmos, labels, and hit regions retain a consistent physical size. Use a fixed scale only to diagnose a desktop-reporting problem or to run visual acceptance:
MOJIVE_UI_SCALE=2 uv run --no-sync mojive editorThe UI atlas uses JetBrains Mono and a CJK fallback. Mojive searches for an installed Noto Sans SC
font and may place a checksum-verified copy in the application cache. Set MOJIVE_CJK_FONT when a
specific local font is required.
The playback capsule and vertical tool capsule share their thickness. playback_scale
is authoritative when restoring a mapping; tool_scale remains a compatibility input
when playback_scale is absent. Changing either capsule through the viewer updates both.
viewport_overlays.status_duration is clamped to 3–5 seconds (default 4) for routine transient
messages at the viewport's lower left. Explicitly longer or persistent messages retain their
requested duration; video export receipts remain for 8 seconds. File receipts show the filename
with Open folder and Copy path actions even when Output or viewport overlays are hidden.
Completed video exports also print the absolute destination to the caller's terminal.
The status bar retains running/recording state,
contextual input hints, and the existing right-hand metrics.
Selection leaves manipulation tools inactive until the user enables one. G, R and the
corresponding tool-column buttons toggle the tool; the public set_gizmo_mode method
explicitly enables the chosen mode. Entity-name editing uses the configurable
panel.edit_name pointer action (double left-click by default). Enter or focus loss
commits; Escape cancels.