ckc99u commited on
Commit
265efeb
Β·
verified Β·
1 Parent(s): a388355

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -108
app.py CHANGED
@@ -12,7 +12,7 @@ from typing import List, Tuple
12
  from PIL import Image
13
  from easydict import EasyDict as edict
14
 
15
- # Add missing imports for RigNet API
16
  from gradio_client import Client, handle_file
17
  from gradio_client.exceptions import AppError
18
 
@@ -21,8 +21,7 @@ from trellis.pipelines import TrellisImageTo3DPipeline
21
  from trellis.representations import Gaussian, MeshExtractResult
22
  from trellis.utils import render_utils, postprocessing_utils
23
 
24
- RIGNET_SPACE = os.getenv("RIGNET_SPACE", "ckc99u/spaceB")
25
- RIGNET_AVAILABLE = True
26
 
27
  # Configuration
28
  MAX_SEED = np.iinfo(np.int32).max
@@ -97,7 +96,7 @@ def unpack_state(state: dict) -> Tuple[Gaussian, edict]:
97
  gs._scaling = torch.tensor(state['gaussian']['_scaling'], device='cuda')
98
  gs._rotation = torch.tensor(state['gaussian']['_rotation'], device='cuda')
99
  gs._opacity = torch.tensor(state['gaussian']['_opacity'], device='cuda')
100
-
101
  mesh = edict(
102
  vertices=torch.tensor(state['mesh']['vertices'], device='cuda'),
103
  faces=torch.tensor(state['mesh']['faces'], device='cuda'),
@@ -108,43 +107,51 @@ def get_seed(randomize_seed: bool, seed: int) -> int:
108
  """Get random seed for generation"""
109
  return np.random.randint(0, MAX_SEED) if randomize_seed else seed
110
 
111
- def call_rignet_api(obj_path: str, bandwidth: float = 0.0429, rignet_space: str = "ckc99u/spaceB") -> Tuple[str, str]:
112
  """
113
- Call RigNet space API to generate rigging from OBJ mesh
114
-
115
  Args:
116
- obj_path: Path to OBJ file (must be 1K-5K vertices)
117
- bandwidth: Rigging bandwidth parameter (0.01-0.1)
118
- rignet_space: RigNet space identifier (e.g., "username/space_name")
119
-
120
  Returns:
121
- Tuple of (rig_file_path, rig_info_text)
122
- - rig_file_path: Path to generated TXT rigging file
123
- - rig_info_text: Info about rigging (joint count, etc.)
 
124
  """
125
  try:
126
- print(f"🦴 Connecting to RigNet API ({rignet_space})...")
127
- rignet_client = Client(rignet_space)
128
-
129
- print("πŸ“€ Uploading OBJ to RigNet...")
130
- rig_result = rignet_client.predict(
131
- obj_path=handle_file(obj_path),
132
- bandwidth=float(bandwidth),
133
  api_name="/predict"
134
  )
135
-
136
- # RigNet returns (rig_file_path, info_text)
137
- rig_file = rig_result[0]
138
- rig_info = rig_result[1]
139
- print("βœ… RigNet generation successful!")
140
- return rig_file, rig_info
141
-
 
 
 
 
 
 
 
 
 
142
  except AppError as e:
143
  error_msg = str(e)
144
- print(f"⚠️ RigNet error: {error_msg}")
145
  raise
146
  except Exception as e:
147
- print(f"⚠️ RigNet API error: {str(e)}")
148
  raise
149
 
150
  @spaces.GPU(duration=180)
@@ -158,18 +165,17 @@ def generate_3d_with_rigging(
158
  mesh_simplify: float,
159
  texture_size: int,
160
  req: gr.Request,
161
- ) -> Tuple[dict, str, str, str, str, str]:
162
  """
163
- Complete pipeline: Image -> 3D Model (TRELLIS) -> OBJ -> Rigging (RigNet on CPU)
164
- All processing happens in this single GPU-decorated function to avoid quota issues.
165
  """
166
  try:
167
  user_dir = os.path.join(TMP_DIR, str(req.session_hash))
168
-
169
  # ============ STEP 1: TRELLIS 3D GENERATION ============
170
  print("🎨 Generating 3D model with TRELLIS...")
171
  init_pipeline()
172
-
173
  outputs = pipeline.run(
174
  image,
175
  seed=seed,
@@ -184,25 +190,26 @@ def generate_3d_with_rigging(
184
  "cfg_strength": slat_guidance_strength,
185
  },
186
  )
187
-
188
  # Extract Gaussian and Mesh
189
  gs = outputs['gaussian'][0]
190
  mesh = outputs['mesh'][0]
191
-
192
  # ============ STEP 2: RENDER VIDEO ============
193
  print("πŸ“Ή Rendering 360Β° preview video...")
194
  video = render_utils.render_video(gs, num_frames=120)['color']
195
  video_geo = render_utils.render_video(mesh, num_frames=120)['normal']
196
  video = [np.concatenate([video[i], video_geo[i]], axis=1) for i in range(len(video))]
 
197
  video_path = os.path.join(user_dir, 'sample.mp4')
198
  imageio.mimsave(video_path, video, fps=15)
199
-
200
  # ============ STEP 3: EXTRACT GLB ============
201
  print("🎁 Extracting GLB with textures...")
202
  glb = postprocessing_utils.to_glb(gs, mesh, simplify=mesh_simplify, texture_size=texture_size, verbose=False)
203
  glb_path = os.path.join(user_dir, 'sample.glb')
204
  glb.export(glb_path)
205
-
206
  # ============ STEP 4: CONVERT GLB TO OBJ ============
207
  print("πŸ”„ Converting GLB to OBJ format...")
208
  obj_path = os.path.join(user_dir, "model.obj")
@@ -210,7 +217,7 @@ def generate_3d_with_rigging(
210
  original_vertices = len(mesh_trimesh.vertices)
211
  original_faces = len(mesh_trimesh.faces)
212
  mesh_trimesh.export(obj_path)
213
-
214
  mesh_info = f"""
215
  πŸ“Š Mesh Statistics:
216
  β€’ Vertices: {original_vertices:,}
@@ -218,50 +225,56 @@ def generate_3d_with_rigging(
218
  β€’ Texture Size: {texture_size}px
219
  β€’ Status: βœ“ Ready for rigging
220
  """
221
-
222
- # ============ STEP 5: RIGNET RIGGING (CPU) ============
223
- print("🦴 Calling RigNet API for automatic rigging...")
224
  rig_info = ""
225
  rig_file = None
226
-
 
227
  try:
228
- # Call RigNet space API
229
- rig_result_path, rig_info_text = call_rignet_api(
230
  obj_path=obj_path,
231
- bandwidth=0.0429,
232
- rignet_space="ckc99u/spaceB" # ← Update with your RigNet space
233
  )
234
-
235
  if rig_result_path and os.path.exists(rig_result_path):
236
- # Copy rigging file to user directory
237
- rig_file = os.path.join(user_dir, 'rig_result.txt')
238
  shutil.copy(rig_result_path, rig_file)
239
- rig_info = f"""βœ… RigNet Rigging Generated:
 
 
 
 
 
 
240
  {rig_info_text}
241
 
242
- πŸ“₯ Download rig_result.txt to import into Blender/Maya
 
 
 
 
243
  """
244
- else:
245
- # Partial failure: Create fallback
246
- rig_file = os.path.join(user_dir, 'rig_result.txt')
247
- with open(rig_file, 'w') as f:
248
- f.write(rig_info_text)
249
- rig_info = rig_info_text
250
-
251
  except Exception as e:
252
- print(f"⚠️ RigNet API error: {str(e)}")
253
  # Create error file with instructions
254
- rig_file = os.path.join(user_dir, 'rig_result.txt')
255
  with open(rig_file, 'w') as f:
256
- f.write(f"RigNet Error: {str(e)}\n\n")
257
  f.write("Workaround: Download OBJ and rig manually in Blender.")
258
- rig_info = f"⚠️ RigNet API unavailable: {str(e)}\n\n**Solution:** Download OBJ and use Blender Rigify add-on"
259
-
 
 
260
  # ============ STEP 6: PACK RESULTS ============
261
  print("πŸ“¦ Packaging results...")
262
  state = pack_state(gs, mesh)
263
  torch.cuda.empty_cache()
264
-
265
  combined_info = f"""
266
  🎨 TRELLIS Generation:
267
  β€’ Seed: {seed}
@@ -278,23 +291,24 @@ def generate_3d_with_rigging(
278
  βœ“ Video preview (360Β° rotation)
279
  βœ“ GLB file (textured 3D model)
280
  βœ“ OBJ file (standard 3D format)
281
- βœ“ Rigging data (TXT)
 
282
 
283
  πŸ”§ Next Steps:
284
- 1. Download OBJ file
285
  2. Import into Blender/Maya/C4D
286
- 3. Apply automatic rigging (Rigify, etc.)
287
- 4. Rig and animate your model
288
 
289
  πŸ’‘ Pro Tips:
290
- β€’ Use GLB for quick preview
291
- β€’ Use OBJ for professional workflows
292
- β€’ The model is optimized for animation
293
  """
294
-
295
  print("βœ… All processing complete!")
296
- return state, video_path, glb_path, obj_path, rig_file, combined_info
297
-
298
  except Exception as e:
299
  import traceback
300
  error_detail = traceback.format_exc()
@@ -313,32 +327,32 @@ def extract_gaussian(state: dict, req: gr.Request) -> Tuple[str, str]:
313
  return gaussian_path, gaussian_path
314
 
315
  # ============ GRADIO UI ============
316
- with gr.Blocks(title="Image to Rigged 3D Model (Integrated)", delete_cache=(600, 600)) as demo:
317
  gr.Markdown("""
318
- # 🎭 Image β†’ 3D β†’ Rigging (Integrated Pipeline)
319
 
320
- **Skip API calls, run everything locally in one space!**
321
 
322
  This unified pipeline combines:
323
  - **TRELLIS** (Image-to-3D, Microsoft Research)
324
- - **RigNet** (Auto-rigging, SIGGRAPH 2020)
325
 
326
  ### πŸš€ Workflow:
327
  1. πŸ“€ Upload image of object/character
328
  2. 🎨 TRELLIS generates high-quality 3D mesh (GPU)
329
  3. πŸ”„ Convert to OBJ format
330
- 4. 🦴 RigNet generates skeletal rigging (CPU)
331
- 5. πŸ’Ύ Download mesh + rigging for animation
332
 
333
  ### ✨ Benefits:
334
- - βœ… No API quota errors (runs directly in space)
335
- - βœ… Faster processing (no inter-space communication)
336
- - βœ… Complete control over parameters
337
  - βœ… Production-ready output for Blender/Maya
 
338
 
339
  ⏱️ **Estimated time:** 2-5 minutes
340
  """)
341
-
342
  with gr.Row():
343
  with gr.Column(scale=1):
344
  gr.Markdown("### πŸ“₯ Input")
@@ -349,11 +363,11 @@ This unified pipeline combines:
349
  type="pil",
350
  height=300
351
  )
352
-
353
  with gr.Accordion("βš™οΈ TRELLIS Parameters", open=False):
354
  seed = gr.Slider(0, MAX_SEED, label="Seed", value=0, step=1)
355
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
356
-
357
  gr.Markdown("**Stage 1: Sparse Structure**")
358
  ss_guidance = gr.Slider(
359
  0.0, 10.0,
@@ -367,7 +381,7 @@ This unified pipeline combines:
367
  value=12,
368
  step=1
369
  )
370
-
371
  gr.Markdown("**Stage 2: Structured Latent**")
372
  slat_guidance = gr.Slider(
373
  0.0, 10.0,
@@ -381,7 +395,7 @@ This unified pipeline combines:
381
  value=12,
382
  step=1
383
  )
384
-
385
  with gr.Accordion("βš™οΈ Output Settings", open=False):
386
  mesh_simplify = gr.Slider(
387
  0.9, 0.98,
@@ -395,21 +409,21 @@ This unified pipeline combines:
395
  value=1024,
396
  step=512
397
  )
398
-
399
  generate_btn = gr.Button(
400
  "πŸš€ Generate Rigged Model",
401
  variant="primary",
402
  size="lg"
403
  )
404
-
405
  extract_gs_btn = gr.Button(
406
  "πŸ“₯ Extract Gaussian (PLY)",
407
  interactive=False
408
  )
409
-
410
  with gr.Column(scale=1):
411
  gr.Markdown("### πŸ“€ Outputs")
412
-
413
  with gr.Tabs():
414
  with gr.Tab("πŸ“Ή Preview"):
415
  video_output = gr.Video(
@@ -418,13 +432,13 @@ This unified pipeline combines:
418
  loop=True,
419
  height=300
420
  )
421
-
422
  with gr.Tab("🎨 3D Viewer"):
423
  model_output = gr.Model3D(
424
  label="GLB Viewer",
425
  height=400
426
  )
427
-
428
  with gr.Tab("πŸ“¦ Files"):
429
  glb_download = gr.DownloadButton(
430
  label="πŸ“₯ Download GLB",
@@ -435,34 +449,38 @@ This unified pipeline combines:
435
  interactive=False
436
  )
437
  rig_download = gr.DownloadButton(
438
- label="🦴 Download RIG (TXT)",
 
 
 
 
439
  interactive=False
440
  )
441
  gs_download = gr.DownloadButton(
442
  label="✨ Download Gaussian (PLY)",
443
  interactive=False
444
  )
445
-
446
  with gr.Tab("ℹ️ Info"):
447
  info_output = gr.Textbox(
448
  label="Pipeline Information",
449
  lines=20,
450
  max_lines=30
451
  )
452
-
453
  # State management
454
  output_buf = gr.State()
455
-
456
  # Event handlers
457
  demo.load(start_session)
458
  demo.unload(end_session)
459
-
460
  input_image.upload(
461
  preprocess_image,
462
  inputs=[input_image],
463
  outputs=[input_image],
464
  )
465
-
466
  generate_btn.click(
467
  get_seed,
468
  inputs=[randomize_seed, seed],
@@ -475,27 +493,29 @@ This unified pipeline combines:
475
  slat_guidance, slat_steps,
476
  mesh_simplify, texture_size
477
  ],
478
- outputs=[output_buf, video_output, model_output, obj_download, rig_download, info_output],
479
  ).then(
480
  lambda: (
481
  gr.Button(interactive=True),
482
  gr.DownloadButton(interactive=True),
483
  gr.DownloadButton(interactive=True),
484
  gr.DownloadButton(interactive=True),
 
485
  ),
486
- outputs=[extract_gs_btn, glb_download, obj_download, rig_download],
487
  )
488
-
489
  video_output.clear(
490
  lambda: (
491
  gr.Button(interactive=False),
492
  gr.DownloadButton(interactive=False),
493
  gr.DownloadButton(interactive=False),
494
  gr.DownloadButton(interactive=False),
 
495
  ),
496
- outputs=[extract_gs_btn, glb_download, obj_download, rig_download],
497
  )
498
-
499
  extract_gs_btn.click(
500
  extract_gaussian,
501
  inputs=[output_buf],
@@ -507,4 +527,4 @@ This unified pipeline combines:
507
 
508
  if __name__ == "__main__":
509
  init_pipeline()
510
- demo.launch(mcp_server=True)
 
12
  from PIL import Image
13
  from easydict import EasyDict as edict
14
 
15
+ # Add missing imports for MagicArticulate API
16
  from gradio_client import Client, handle_file
17
  from gradio_client.exceptions import AppError
18
 
 
21
  from trellis.representations import Gaussian, MeshExtractResult
22
  from trellis.utils import render_utils, postprocessing_utils
23
 
24
+ MAGIC_ARTICULATE_URL = "https://62157d4fdd84d3addb.gradio.live/"
 
25
 
26
  # Configuration
27
  MAX_SEED = np.iinfo(np.int32).max
 
96
  gs._scaling = torch.tensor(state['gaussian']['_scaling'], device='cuda')
97
  gs._rotation = torch.tensor(state['gaussian']['_rotation'], device='cuda')
98
  gs._opacity = torch.tensor(state['gaussian']['_opacity'], device='cuda')
99
+
100
  mesh = edict(
101
  vertices=torch.tensor(state['mesh']['vertices'], device='cuda'),
102
  faces=torch.tensor(state['mesh']['faces'], device='cuda'),
 
107
  """Get random seed for generation"""
108
  return np.random.randint(0, MAX_SEED) if randomize_seed else seed
109
 
110
+ def call_magic_articulate_api(obj_path: str, api_url: str = MAGIC_ARTICULATE_URL) -> Tuple[str, str, str]:
111
  """
112
+ Call MagicArticulate Colab API to generate rigging and skeleton from OBJ mesh
113
+
114
  Args:
115
+ obj_path: Path to OBJ file
116
+ api_url: MagicArticulate Colab gradio URL
117
+
 
118
  Returns:
119
+ Tuple of (rig_pred_path, skeleton_obj_path, info_text)
120
+ - rig_pred_path: Path to generated rig prediction TXT file
121
+ - skeleton_obj_path: Path to generated skeleton OBJ file
122
+ - info_text: Info about rigging results
123
  """
124
  try:
125
+ print(f"🦴 Connecting to MagicArticulate API ({api_url})...")
126
+ magic_client = Client(api_url)
127
+
128
+ print("πŸ“€ Uploading OBJ to MagicArticulate...")
129
+ result = magic_client.predict(
130
+ input_mesh=handle_file(obj_path),
 
131
  api_name="/predict"
132
  )
133
+
134
+ # MagicArticulate returns (rig_pred.txt, skeleton.obj, normalized_mesh.obj)
135
+ rig_pred_file = result[0]
136
+ skeleton_file = result[1]
137
+
138
+ print("βœ… MagicArticulate generation successful!")
139
+
140
+ # Read skeleton info
141
+ info_text = "Skeleton generated with hierarchical bone ordering"
142
+ if skeleton_file and os.path.exists(skeleton_file):
143
+ skeleton_mesh = trimesh.load(skeleton_file, force='mesh')
144
+ num_vertices = len(skeleton_mesh.vertices)
145
+ info_text = f"Joints: {num_vertices // 2}, Hierarchical structure"
146
+
147
+ return rig_pred_file, skeleton_file, info_text
148
+
149
  except AppError as e:
150
  error_msg = str(e)
151
+ print(f"⚠️ MagicArticulate error: {error_msg}")
152
  raise
153
  except Exception as e:
154
+ print(f"⚠️ MagicArticulate API error: {str(e)}")
155
  raise
156
 
157
  @spaces.GPU(duration=180)
 
165
  mesh_simplify: float,
166
  texture_size: int,
167
  req: gr.Request,
168
+ ) -> Tuple[dict, str, str, str, str, str, str]:
169
  """
170
+ Complete pipeline: Image -> 3D Model (TRELLIS) -> OBJ -> Rigging (MagicArticulate)
 
171
  """
172
  try:
173
  user_dir = os.path.join(TMP_DIR, str(req.session_hash))
174
+
175
  # ============ STEP 1: TRELLIS 3D GENERATION ============
176
  print("🎨 Generating 3D model with TRELLIS...")
177
  init_pipeline()
178
+
179
  outputs = pipeline.run(
180
  image,
181
  seed=seed,
 
190
  "cfg_strength": slat_guidance_strength,
191
  },
192
  )
193
+
194
  # Extract Gaussian and Mesh
195
  gs = outputs['gaussian'][0]
196
  mesh = outputs['mesh'][0]
197
+
198
  # ============ STEP 2: RENDER VIDEO ============
199
  print("πŸ“Ή Rendering 360Β° preview video...")
200
  video = render_utils.render_video(gs, num_frames=120)['color']
201
  video_geo = render_utils.render_video(mesh, num_frames=120)['normal']
202
  video = [np.concatenate([video[i], video_geo[i]], axis=1) for i in range(len(video))]
203
+
204
  video_path = os.path.join(user_dir, 'sample.mp4')
205
  imageio.mimsave(video_path, video, fps=15)
206
+
207
  # ============ STEP 3: EXTRACT GLB ============
208
  print("🎁 Extracting GLB with textures...")
209
  glb = postprocessing_utils.to_glb(gs, mesh, simplify=mesh_simplify, texture_size=texture_size, verbose=False)
210
  glb_path = os.path.join(user_dir, 'sample.glb')
211
  glb.export(glb_path)
212
+
213
  # ============ STEP 4: CONVERT GLB TO OBJ ============
214
  print("πŸ”„ Converting GLB to OBJ format...")
215
  obj_path = os.path.join(user_dir, "model.obj")
 
217
  original_vertices = len(mesh_trimesh.vertices)
218
  original_faces = len(mesh_trimesh.faces)
219
  mesh_trimesh.export(obj_path)
220
+
221
  mesh_info = f"""
222
  πŸ“Š Mesh Statistics:
223
  β€’ Vertices: {original_vertices:,}
 
225
  β€’ Texture Size: {texture_size}px
226
  β€’ Status: βœ“ Ready for rigging
227
  """
228
+
229
+ # ============ STEP 5: MAGIC ARTICULATE RIGGING ============
230
+ print("🦴 Calling MagicArticulate API for automatic skeleton generation...")
231
  rig_info = ""
232
  rig_file = None
233
+ skeleton_file = None
234
+
235
  try:
236
+ # Call MagicArticulate Colab API
237
+ rig_result_path, skeleton_result_path, rig_info_text = call_magic_articulate_api(
238
  obj_path=obj_path,
239
+ api_url=MAGIC_ARTICULATE_URL
 
240
  )
241
+
242
  if rig_result_path and os.path.exists(rig_result_path):
243
+ # Copy rig prediction file to user directory
244
+ rig_file = os.path.join(user_dir, 'rig_pred.txt')
245
  shutil.copy(rig_result_path, rig_file)
246
+
247
+ if skeleton_result_path and os.path.exists(skeleton_result_path):
248
+ # Copy skeleton file to user directory
249
+ skeleton_file = os.path.join(user_dir, 'skeleton.obj')
250
+ shutil.copy(skeleton_result_path, skeleton_file)
251
+
252
+ rig_info = f"""βœ… MagicArticulate Skeleton Generated:
253
  {rig_info_text}
254
 
255
+ πŸ“₯ Downloads:
256
+ β€’ rig_pred.txt - Joint positions & bone hierarchy
257
+ β€’ skeleton.obj - 3D skeleton visualization
258
+
259
+ πŸ”§ Import into Blender/Maya for animation
260
  """
261
+
 
 
 
 
 
 
262
  except Exception as e:
263
+ print(f"⚠️ MagicArticulate API error: {str(e)}")
264
  # Create error file with instructions
265
+ rig_file = os.path.join(user_dir, 'rig_pred.txt')
266
  with open(rig_file, 'w') as f:
267
+ f.write(f"MagicArticulate Error: {str(e)}\n\n")
268
  f.write("Workaround: Download OBJ and rig manually in Blender.")
269
+
270
+ rig_info = f"⚠️ MagicArticulate API unavailable: {str(e)}\n\n**Solution:** Download OBJ and use Blender Rigify add-on"
271
+ skeleton_file = None
272
+
273
  # ============ STEP 6: PACK RESULTS ============
274
  print("πŸ“¦ Packaging results...")
275
  state = pack_state(gs, mesh)
276
  torch.cuda.empty_cache()
277
+
278
  combined_info = f"""
279
  🎨 TRELLIS Generation:
280
  β€’ Seed: {seed}
 
291
  βœ“ Video preview (360Β° rotation)
292
  βœ“ GLB file (textured 3D model)
293
  βœ“ OBJ file (standard 3D format)
294
+ βœ“ Rig prediction (TXT)
295
+ βœ“ Skeleton (OBJ)
296
 
297
  πŸ”§ Next Steps:
298
+ 1. Download OBJ + Skeleton files
299
  2. Import into Blender/Maya/C4D
300
+ 3. Apply rigging from rig_pred.txt
301
+ 4. Animate your model
302
 
303
  πŸ’‘ Pro Tips:
304
+ β€’ Skeleton shows joint hierarchy visually
305
+ β€’ Rig prediction contains exact joint coordinates
306
+ β€’ Model is optimized for animation workflow
307
  """
308
+
309
  print("βœ… All processing complete!")
310
+ return state, video_path, glb_path, obj_path, rig_file, skeleton_file, combined_info
311
+
312
  except Exception as e:
313
  import traceback
314
  error_detail = traceback.format_exc()
 
327
  return gaussian_path, gaussian_path
328
 
329
  # ============ GRADIO UI ============
330
+ with gr.Blocks(title="Image to Rigged 3D Model (MagicArticulate)", delete_cache=(600, 600)) as demo:
331
  gr.Markdown("""
332
+ # 🎭 Image β†’ 3D β†’ Rigging (MagicArticulate Pipeline)
333
 
334
+ **Automated 3D generation with hierarchical skeleton rigging!**
335
 
336
  This unified pipeline combines:
337
  - **TRELLIS** (Image-to-3D, Microsoft Research)
338
+ - **MagicArticulate** (Auto-skeleton generation, CVPR 2025)
339
 
340
  ### πŸš€ Workflow:
341
  1. πŸ“€ Upload image of object/character
342
  2. 🎨 TRELLIS generates high-quality 3D mesh (GPU)
343
  3. πŸ”„ Convert to OBJ format
344
+ 4. 🦴 MagicArticulate generates hierarchical skeleton
345
+ 5. πŸ’Ύ Download mesh + rigging + skeleton for animation
346
 
347
  ### ✨ Benefits:
348
+ - βœ… Hierarchical bone ordering for better animation
349
+ - βœ… Automatic joint placement and bone connections
 
350
  - βœ… Production-ready output for Blender/Maya
351
+ - βœ… Visual skeleton + rig data included
352
 
353
  ⏱️ **Estimated time:** 2-5 minutes
354
  """)
355
+
356
  with gr.Row():
357
  with gr.Column(scale=1):
358
  gr.Markdown("### πŸ“₯ Input")
 
363
  type="pil",
364
  height=300
365
  )
366
+
367
  with gr.Accordion("βš™οΈ TRELLIS Parameters", open=False):
368
  seed = gr.Slider(0, MAX_SEED, label="Seed", value=0, step=1)
369
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
370
+
371
  gr.Markdown("**Stage 1: Sparse Structure**")
372
  ss_guidance = gr.Slider(
373
  0.0, 10.0,
 
381
  value=12,
382
  step=1
383
  )
384
+
385
  gr.Markdown("**Stage 2: Structured Latent**")
386
  slat_guidance = gr.Slider(
387
  0.0, 10.0,
 
395
  value=12,
396
  step=1
397
  )
398
+
399
  with gr.Accordion("βš™οΈ Output Settings", open=False):
400
  mesh_simplify = gr.Slider(
401
  0.9, 0.98,
 
409
  value=1024,
410
  step=512
411
  )
412
+
413
  generate_btn = gr.Button(
414
  "πŸš€ Generate Rigged Model",
415
  variant="primary",
416
  size="lg"
417
  )
418
+
419
  extract_gs_btn = gr.Button(
420
  "πŸ“₯ Extract Gaussian (PLY)",
421
  interactive=False
422
  )
423
+
424
  with gr.Column(scale=1):
425
  gr.Markdown("### πŸ“€ Outputs")
426
+
427
  with gr.Tabs():
428
  with gr.Tab("πŸ“Ή Preview"):
429
  video_output = gr.Video(
 
432
  loop=True,
433
  height=300
434
  )
435
+
436
  with gr.Tab("🎨 3D Viewer"):
437
  model_output = gr.Model3D(
438
  label="GLB Viewer",
439
  height=400
440
  )
441
+
442
  with gr.Tab("πŸ“¦ Files"):
443
  glb_download = gr.DownloadButton(
444
  label="πŸ“₯ Download GLB",
 
449
  interactive=False
450
  )
451
  rig_download = gr.DownloadButton(
452
+ label="🦴 Download Rig Prediction (TXT)",
453
+ interactive=False
454
+ )
455
+ skeleton_download = gr.DownloadButton(
456
+ label="🦴 Download Skeleton (OBJ)",
457
  interactive=False
458
  )
459
  gs_download = gr.DownloadButton(
460
  label="✨ Download Gaussian (PLY)",
461
  interactive=False
462
  )
463
+
464
  with gr.Tab("ℹ️ Info"):
465
  info_output = gr.Textbox(
466
  label="Pipeline Information",
467
  lines=20,
468
  max_lines=30
469
  )
470
+
471
  # State management
472
  output_buf = gr.State()
473
+
474
  # Event handlers
475
  demo.load(start_session)
476
  demo.unload(end_session)
477
+
478
  input_image.upload(
479
  preprocess_image,
480
  inputs=[input_image],
481
  outputs=[input_image],
482
  )
483
+
484
  generate_btn.click(
485
  get_seed,
486
  inputs=[randomize_seed, seed],
 
493
  slat_guidance, slat_steps,
494
  mesh_simplify, texture_size
495
  ],
496
+ outputs=[output_buf, video_output, model_output, obj_download, rig_download, skeleton_download, info_output],
497
  ).then(
498
  lambda: (
499
  gr.Button(interactive=True),
500
  gr.DownloadButton(interactive=True),
501
  gr.DownloadButton(interactive=True),
502
  gr.DownloadButton(interactive=True),
503
+ gr.DownloadButton(interactive=True),
504
  ),
505
+ outputs=[extract_gs_btn, glb_download, obj_download, rig_download, skeleton_download],
506
  )
507
+
508
  video_output.clear(
509
  lambda: (
510
  gr.Button(interactive=False),
511
  gr.DownloadButton(interactive=False),
512
  gr.DownloadButton(interactive=False),
513
  gr.DownloadButton(interactive=False),
514
+ gr.DownloadButton(interactive=False),
515
  ),
516
+ outputs=[extract_gs_btn, glb_download, obj_download, rig_download, skeleton_download],
517
  )
518
+
519
  extract_gs_btn.click(
520
  extract_gaussian,
521
  inputs=[output_buf],
 
527
 
528
  if __name__ == "__main__":
529
  init_pipeline()
530
+ demo.launch()