"""Persistent streaming mesh and skeleton viewer for Gradio 6.28.0.""" from pathlib import Path import gradio as gr HERE = Path(__file__).resolve().parent HTML = """
Motion Ready
Click the view to use WASD
WS Camera forward / backAD Left / rightShift Walk → run
1234Prompt 1
Lead 0.00 s
Drag to orbit · Scroll to zoom · Double-click to reset view
ReachedAhead◇ Target○ Input
Runtime0.0 s
Generation— fps
Playback0 fps
Buffer0.00 s
""" CSS = """ .motion-viewer { overflow: hidden; border: 1px solid #dce3e8; border-radius: 16px; background: #f8fafb; color: #233340; font-family: inherit; } .viewer-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 20px 0; } .viewer-action { font-size: 16px; font-weight: 600; } .viewer-status { color: #667782; font-size: 12px; } .viewer-stage { position: relative; width: 100%; height: clamp(300px, 48vw, 490px); overflow: hidden; } .viewer-stage canvas { display: block; width: 100%; height: 100%; outline: none; cursor: grab; touch-action: none; } .viewer-stage canvas:active { cursor: grabbing; } .viewer-stage:focus-within { box-shadow: inset 0 0 0 2px #397ad5; } .viewer-focus-hint { position: absolute; bottom: 10px; left: 0; right: 0; z-index: 1; text-align: center; pointer-events: none; font-size: 12px; color: #647582; } .viewer-stage:focus-within .viewer-focus-hint { opacity: 0; } .viewer-body-notice { position: absolute; top: 12px; right: 18px; z-index: 1; color: #647582; font-size: 11px; display: flex; align-items: center; gap: 8px; } .viewer-body-retry { border: 1px solid #ced8df; border-radius: 5px; padding: 4px 7px; background: white; color: #445b6a; cursor: pointer; font: inherit; } .viewer-body-retry[hidden] { display: none; } .viewer-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 20px; padding: 12px 18px 5px; font-size: 12px; } .viewer-key-legend { display: flex; align-items: center; flex-wrap: wrap; gap: 14px; } .viewer-key-legend > span { white-space: nowrap; } kbd { display: inline-block; min-width: 22px; margin-right: 3px; padding: 2px 5px; border: 1px solid #ced8df; border-radius: 4px; color: #445b6a; background: white; font: inherit; text-align: center; } kbd.is-down { color: white; background: #397ad5; border-color: #397ad5; } kbd[data-key="Shift"].is-down { background: #8b5cdd; border-color: #8b5cdd; } .viewer-slots { display: flex; align-items: center; gap: 5px; } .viewer-slots [data-slot] { width: 22px; height: 22px; line-height: 20px; text-align: center; border: 1px solid currentColor; border-radius: 50%; opacity: .6; } .viewer-slots [data-slot="0"] { color: #397ad5; } .viewer-slots [data-slot="1"] { color: #8b5cdd; } .viewer-slots [data-slot="2"] { color: #2d9e72; } .viewer-slots [data-slot="3"] { color: #d55a9a; } .viewer-slots [data-slot].is-selected { opacity: 1; border-width: 2px; font-weight: 700; line-height: 18px; } .viewer-slot-label { margin-left: 4px; } .viewer-lead { margin-left: auto; color: #667782; font-variant-numeric: tabular-nums; } .viewer-camera-hint { padding: 4px 18px 8px; color: #667782; font-size: 11px; } .viewer-path-legend { display: flex; gap: 16px; padding: 4px 18px 12px; color: #667782; font-size: 11px; } .viewer-path-legend span::before { content: ""; display: inline-block; width: 18px; margin-right: 5px; border-top: 2px solid #397ad5; vertical-align: middle; } .viewer-path-legend .path-reached::before { opacity: .55; } .viewer-path-legend .path-ahead::before { border-top-style: dashed; } .viewer-path-legend .path-target::before, .viewer-path-legend .path-spring::before { display: none; } .viewer-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 1px solid #e4eaee; background: #fff; } .viewer-stats > div { padding: 14px 16px; min-width: 0; } .viewer-stats span { display: block; font-size: 11px; color: #6c7b86; margin-bottom: 5px; } .viewer-stats strong { display: block; font-size: 17px; font-weight: 550; font-variant-numeric: tabular-nums; white-space: nowrap; } @media (max-width: 500px) { .viewer-heading { padding: 14px 14px 0; } .viewer-stats > div { padding: 12px 8px; } .viewer-stats strong { font-size: 14px; } .viewer-stats span { font-size: 10px; } } """ def create_viewer(*, require_body_ready: bool = False) -> gr.HTML: """Return one HTML output; yield native dictionaries to update its value. Each packet contains session_id, sequence, start_frame, frames [T,22,3], fps, generated_frames, elapsed_seconds, inference_fps, action and status. Frames use world XYZ, with Y up. A new session_id resets playback. mesh_pose contains rotations (base64 float32 LE [T,22,3,3], row-major local matrices with the world root first), origins (world pelvis [T,3]), and frame_count=T. The fixed neutral body is loaded once and deformed on each displayed frame using all skinning weights and the full body pose corrective. With require_body_ready=True, bind viewer.body_ready to enable Start only after the browser has loaded the body; loading assets consumes no GPU time. Optional mesh_topology contains faces (base64 uint16 LE), vertex_count=6890 and face_count=13776, sent once per session. Optional mesh contains vertices (base64 float16 LE, C-order [T,6890,3], relative to each supplied root), origins [T,3] in world XYZ, frame_count=T and vertex_count=6890. Mesh, joints and frame_meta share start_frame and one playback queue. Canvas fallback and packets without mesh continue to display the skeleton. Optional control_points and frame_meta retain backend coordinates unchanged. With control_points_delta=True, points are merged by frame (up to 2000); otherwise control_points is a complete replacement. New sessions reset it. Bind ``viewer.control(fn, inputs=[], outputs=[], queue=False, trigger_mode="multiple")``; an annotated gr.EventData receives the control dictionary through ``evt._data``. This event never writes ``value``. """ vendor = (HERE / "three.min.js").read_text(encoding="utf-8") body_script = (HERE / "body.js").read_text(encoding="utf-8") script = (HERE / "viewer.js").read_text(encoding="utf-8") body_manifest = HERE / "assets" / "body.json" body_data = HERE / "assets" / "body.bin" gr.set_static_paths(paths=[body_manifest, body_data]) # The local UMD build is loaded once; no CDN or file-serving setup is needed. js = "if (!globalThis.THREE) {\n" + vendor + "\n}\n" + body_script + "\n" + script return gr.HTML( value={"session_id": "", "sequence": -1, "frames": [], "status": "idle"}, html_template=HTML, css_template=CSS, js_on_load=js, apply_default_css=False, container=False, padding=False, elem_id="live-motion-viewer", body_manifest_url="/gradio_api/file=" + body_manifest.resolve().as_posix(), body_data_url="/gradio_api/file=" + body_data.resolve().as_posix(), require_body_ready=require_body_ready, )