Skip to content

Latest commit

 

History

History
672 lines (546 loc) · 39 KB

configuration.md

File metadata and controls

672 lines (546 loc) · 39 KB

Configuration reference

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.

Environment variables

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.

Persistent files

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.

Choose the amount of UI to load

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.

Explicitly reduce the viewer

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.

Programmatic viewer configuration

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)

Contact point appearance

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.

Camera navigation

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: 1x tightly 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; 0 focuses immediately. Transition curve selects Linear, Smoothstep (default), Smootherstep, or Ease 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.001 and 1e6). 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.

Camera tracking

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.

Geometry comparison appearance

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.

Interactive capture and recording

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.

Render backend requirements

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 egl

If 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.xml

On 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.xml

Context 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.

Video encoding

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.

UI scale and fonts

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 editor

The 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.

Viewport chrome behavior

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.