cs686 commited on
Commit
209d2fa
·
verified ·
1 Parent(s): a21baa5

Deploy ARDY ZeroGPU Blender motion API

Browse files
Files changed (1) hide show
  1. app.py +50 -141
app.py CHANGED
@@ -526,17 +526,37 @@ def generate_blender_motion(
526
  randomize_seed,
527
  None,
528
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  motion_tensor = torch.from_numpy(motion_np).to("cuda")
530
- out = _decode_motion("human", motion_tensor)
531
- r = RIGS["human"]
532
  skeleton = r["skeleton"]
533
  joint_names = list(skeleton.bone_order_names)
534
  parents = list(r["parents"])
535
  rest_positions = to_numpy(skeleton.neutral_joints)
536
 
537
  output_dir = Path(tempfile.mkdtemp(prefix="ardy_blender_"))
538
- npz_path = output_dir / "ardy_motion.npz"
539
- bvh_path = output_dir / "ardy_motion.bvh"
540
  metadata_path = output_dir / "metadata.json"
541
 
542
  np.savez_compressed(
@@ -545,7 +565,7 @@ def generate_blender_motion(
545
  fps=np.asarray(r["fps"], dtype=np.float32),
546
  text=np.asarray(prompt),
547
  seed=np.asarray(seed, dtype=np.int64),
548
- rig=np.asarray("human"),
549
  joint_names=np.asarray(joint_names),
550
  parents=np.asarray(parents, dtype=np.int32),
551
  rest_positions=np.asarray(rest_positions, dtype=np.float32),
@@ -562,15 +582,23 @@ def generate_blender_motion(
562
  metadata_path.write_text(
563
  json.dumps(
564
  {
565
- "model": "nvidia/ARDY-Core-RP-20FPS-Horizon40",
566
- "rig": "human",
567
- "prompt": prompt,
568
- "seed": seed,
 
 
 
 
569
  "fps": r["fps"],
570
  "frames": int(out["posed_joints"].shape[0]),
 
 
 
571
  "joints": joint_names,
572
  "parents": parents,
573
  "coordinate_system": "Y-up, metric",
 
574
  "post_processing": False,
575
  },
576
  ensure_ascii=False,
@@ -578,100 +606,18 @@ def generate_blender_motion(
578
  ),
579
  encoding="utf-8",
580
  )
581
- return str(bvh_path), str(npz_path), str(metadata_path), seed
582
 
583
 
584
  @spaces.GPU
585
- def generate_timeline_motion(
586
- timeline: list[list[object]],
587
- diffusion_steps: int = NUM_BASE_STEPS,
588
- cfg_weight: float = 2.0,
589
  seed: int = 0,
590
- randomize_seed: bool = True,
591
  ) -> tuple[str, str, str, int]:
592
- """Generate sequential prompts as one autoregressively continuous motion."""
593
- from timeline_utils import normalize_timeline
594
-
595
- r = RIGS["human"]
596
- segments = normalize_timeline(timeline, fps=float(r["fps"]))
597
- motion_np = None
598
- resolved_seed = int(seed)
599
-
600
- for index, segment in enumerate(segments):
601
- start_frame = 0 if motion_np is None else int(motion_np.shape[1])
602
- _, resolved_seed, motion_np = _core_generate(
603
- "human",
604
- segment["prompt"],
605
- segment["duration_seconds"],
606
- diffusion_steps,
607
- cfg_weight,
608
- resolved_seed,
609
- bool(randomize_seed) if index == 0 else False,
610
- motion_np,
611
- )
612
- end_frame = int(motion_np.shape[1])
613
- segment["start_frame"] = start_frame
614
- segment["end_frame"] = end_frame
615
- segment["duration_seconds"] = (end_frame - start_frame) / float(r["fps"])
616
-
617
- if motion_np is None:
618
- raise gr.Error("The timeline did not contain any motion segments.")
619
-
620
- motion_tensor = torch.from_numpy(motion_np).to("cuda")
621
- out = _decode_motion("human", motion_tensor)
622
- skeleton = r["skeleton"]
623
- joint_names = list(skeleton.bone_order_names)
624
- parents = list(r["parents"])
625
- rest_positions = to_numpy(skeleton.neutral_joints)
626
-
627
- output_dir = Path(tempfile.mkdtemp(prefix="ardy_timeline_"))
628
- npz_path = output_dir / "ardy_timeline_motion.npz"
629
- bvh_path = output_dir / "ardy_timeline_motion.bvh"
630
- metadata_path = output_dir / "timeline_metadata.json"
631
-
632
- timeline_json = json.dumps(segments, ensure_ascii=False)
633
- np.savez_compressed(
634
- npz_path,
635
- **out,
636
- fps=np.asarray(r["fps"], dtype=np.float32),
637
- text=np.asarray(timeline_json),
638
- seed=np.asarray(resolved_seed, dtype=np.int64),
639
- rig=np.asarray("human"),
640
- joint_names=np.asarray(joint_names),
641
- parents=np.asarray(parents, dtype=np.int32),
642
- rest_positions=np.asarray(rest_positions, dtype=np.float32),
643
- )
644
- write_bvh(
645
- bvh_path,
646
- joint_names=joint_names,
647
- parents=parents,
648
- rest_positions=rest_positions,
649
- local_rot_mats=out["local_rot_mats"],
650
- root_positions=out["root_positions"],
651
- fps=r["fps"],
652
- )
653
- metadata_path.write_text(
654
- json.dumps(
655
- {
656
- "model": "nvidia/ARDY-Core-RP-20FPS-Horizon40",
657
- "rig": "human",
658
- "seed": resolved_seed,
659
- "fps": r["fps"],
660
- "frames": int(out["posed_joints"].shape[0]),
661
- "duration_seconds": float(out["posed_joints"].shape[0]) / float(r["fps"]),
662
- "segments": segments,
663
- "joints": joint_names,
664
- "parents": parents,
665
- "coordinate_system": "Y-up, metric",
666
- "generation_mode": "autoregressive timeline",
667
- "post_processing": False,
668
- },
669
- ensure_ascii=False,
670
- indent=2,
671
- ),
672
- encoding="utf-8",
673
- )
674
- return str(bvh_path), str(npz_path), str(metadata_path), resolved_seed
675
 
676
 
677
  # -----------------------------------------------------------------------------
@@ -1149,45 +1095,16 @@ with gr.Blocks() as demo:
1149
 
1150
  with gr.Accordion("Blender export", open=False):
1151
  gr.Markdown(
1152
- "Generate the human rig as **BVH**, full-fidelity **NPZ**, and "
1153
- "JSON metadata. Import the BVH directly in Blender."
 
1154
  )
1155
- export_blender = gr.Button("Generate Blender files", variant="secondary")
1156
  with gr.Row():
1157
  bvh_file = gr.File(label="Blender BVH")
1158
  npz_file = gr.File(label="ARDY NPZ")
1159
  metadata_file = gr.File(label="Metadata")
1160
 
1161
- with gr.Accordion("Continuous action timeline", open=True):
1162
- gr.Markdown(
1163
- "Add one short instruction per row. ARDY carries the previous pose and "
1164
- "root trajectory into the next row, producing one continuous motion. "
1165
- "Each segment is 2–4 seconds (the model's generation window is 2 seconds), "
1166
- "and the combined duration may not exceed 8 seconds."
1167
- )
1168
- timeline_input = gr.Dataframe(
1169
- headers=["Action prompt", "Duration (seconds)"],
1170
- datatype=["str", "number"],
1171
- type="array",
1172
- row_count=(4, "dynamic"),
1173
- col_count=(2, "fixed"),
1174
- value=[
1175
- ["Walk forward two steps.", 2.0],
1176
- ["Turn around smoothly.", 2.0],
1177
- ["Wave the right hand.", 2.0],
1178
- ["Sit down on the ground.", 2.0],
1179
- ],
1180
- label="Action segments",
1181
- )
1182
- generate_timeline = gr.Button(
1183
- "Generate continuous timeline",
1184
- variant="primary",
1185
- )
1186
- with gr.Row():
1187
- timeline_bvh = gr.File(label="Continuous Blender BVH")
1188
- timeline_npz = gr.File(label="Continuous ARDY NPZ")
1189
- timeline_metadata = gr.File(label="Timeline metadata")
1190
-
1191
  gr.Examples(
1192
  examples=EXAMPLES,
1193
  inputs=[prompt, rig, duration],
@@ -1217,23 +1134,15 @@ with gr.Blocks() as demo:
1217
  cont.click(fn=ui_continue, inputs=_gen_inputs + [seq_state],
1218
  outputs=[player, seed, seq_state], api_name=False)
1219
  export_blender.click(
1220
- fn=generate_blender_motion,
1221
- inputs=[prompt, duration, diffusion_steps, cfg_weight, seed, randomize_seed],
1222
  outputs=[bvh_file, npz_file, metadata_file, seed],
1223
  api_name=False,
1224
  )
1225
- generate_timeline.click(
1226
- fn=generate_timeline_motion,
1227
- inputs=[timeline_input, diffusion_steps, cfg_weight, seed, randomize_seed],
1228
- outputs=[timeline_bvh, timeline_npz, timeline_metadata, seed],
1229
- api_name=False,
1230
- concurrency_limit=1,
1231
- )
1232
 
1233
  # Clean single-shot endpoint for the HTTP API / MCP tool (no session state).
1234
  gr.api(generate_motion, api_name="generate")
1235
  gr.api(generate_blender_motion, api_name="generate_blender")
1236
- gr.api(generate_timeline_motion, api_name="generate_timeline")
1237
 
1238
  demo.queue()
1239
 
 
526
  randomize_seed,
527
  None,
528
  )
529
+ return _export_motion_files("human", motion_np, prompt, seed)
530
+
531
+
532
+ def _export_motion_files(
533
+ rig: str,
534
+ motion_np: np.ndarray,
535
+ prompt: str,
536
+ seed: int,
537
+ ) -> tuple[str, str, str, int]:
538
+ """Decode an accumulated ARDY sequence and write Blender export files."""
539
+ rig_key = _normalize_rig(rig)
540
+ if motion_np is None:
541
+ raise gr.Error(
542
+ "Generate a motion first, then use Continue to extend it before exporting."
543
+ )
544
+
545
+ motion_np = np.asarray(motion_np)
546
+ if motion_np.ndim != 3 or motion_np.shape[0] != 1 or motion_np.shape[1] < 1:
547
+ raise gr.Error(f"Invalid ARDY sequence state: shape={motion_np.shape}")
548
+
549
  motion_tensor = torch.from_numpy(motion_np).to("cuda")
550
+ out = _decode_motion(rig_key, motion_tensor)
551
+ r = RIGS[rig_key]
552
  skeleton = r["skeleton"]
553
  joint_names = list(skeleton.bone_order_names)
554
  parents = list(r["parents"])
555
  rest_positions = to_numpy(skeleton.neutral_joints)
556
 
557
  output_dir = Path(tempfile.mkdtemp(prefix="ardy_blender_"))
558
+ npz_path = output_dir / f"ardy_{rig_key}_motion.npz"
559
+ bvh_path = output_dir / f"ardy_{rig_key}_motion.bvh"
560
  metadata_path = output_dir / "metadata.json"
561
 
562
  np.savez_compressed(
 
565
  fps=np.asarray(r["fps"], dtype=np.float32),
566
  text=np.asarray(prompt),
567
  seed=np.asarray(seed, dtype=np.int64),
568
+ rig=np.asarray(rig_key),
569
  joint_names=np.asarray(joint_names),
570
  parents=np.asarray(parents, dtype=np.int32),
571
  rest_positions=np.asarray(rest_positions, dtype=np.float32),
 
582
  metadata_path.write_text(
583
  json.dumps(
584
  {
585
+ "model": (
586
+ "nvidia/ARDY-Core-RP-20FPS-Horizon40"
587
+ if rig_key == "human"
588
+ else "nvidia/ARDY-G1-RP-25FPS-Horizon52"
589
+ ),
590
+ "rig": rig_key,
591
+ "latest_prompt": prompt,
592
+ "seed": int(seed),
593
  "fps": r["fps"],
594
  "frames": int(out["posed_joints"].shape[0]),
595
+ "duration_seconds": (
596
+ float(out["posed_joints"].shape[0]) / float(r["fps"])
597
+ ),
598
  "joints": joint_names,
599
  "parents": parents,
600
  "coordinate_system": "Y-up, metric",
601
+ "source": "current accumulated Generate/Continue sequence",
602
  "post_processing": False,
603
  },
604
  ensure_ascii=False,
 
606
  ),
607
  encoding="utf-8",
608
  )
609
+ return str(bvh_path), str(npz_path), str(metadata_path), int(seed)
610
 
611
 
612
  @spaces.GPU
613
+ def export_current_sequence(
614
+ state: object,
615
+ rig: str = DEFAULT_RIG,
616
+ prompt: str = "",
617
  seed: int = 0,
 
618
  ) -> tuple[str, str, str, int]:
619
+ """Export the complete sequence currently held by Generate/Continue."""
620
+ return _export_motion_files(rig, state, prompt, seed)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
 
622
 
623
  # -----------------------------------------------------------------------------
 
1095
 
1096
  with gr.Accordion("Blender export", open=False):
1097
  gr.Markdown(
1098
+ "Export the **complete motion currently in the player**, including "
1099
+ "every segment added with **Continue ▸**, as Blender **BVH**, "
1100
+ "full-fidelity **NPZ**, and JSON metadata."
1101
  )
1102
+ export_blender = gr.Button("Export current sequence", variant="secondary")
1103
  with gr.Row():
1104
  bvh_file = gr.File(label="Blender BVH")
1105
  npz_file = gr.File(label="ARDY NPZ")
1106
  metadata_file = gr.File(label="Metadata")
1107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1108
  gr.Examples(
1109
  examples=EXAMPLES,
1110
  inputs=[prompt, rig, duration],
 
1134
  cont.click(fn=ui_continue, inputs=_gen_inputs + [seq_state],
1135
  outputs=[player, seed, seq_state], api_name=False)
1136
  export_blender.click(
1137
+ fn=export_current_sequence,
1138
+ inputs=[seq_state, rig, prompt, seed],
1139
  outputs=[bvh_file, npz_file, metadata_file, seed],
1140
  api_name=False,
1141
  )
 
 
 
 
 
 
 
1142
 
1143
  # Clean single-shot endpoint for the HTTP API / MCP tool (no session state).
1144
  gr.api(generate_motion, api_name="generate")
1145
  gr.api(generate_blender_motion, api_name="generate_blender")
 
1146
 
1147
  demo.queue()
1148