jainarham commited on
Commit
2a8a4fd
·
verified ·
1 Parent(s): 21a10cd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +366 -250
app.py CHANGED
@@ -18,62 +18,62 @@ TRELLIS_SPACE = "microsoft/TRELLIS.2"
18
  TEMP_DIR = os.path.join(tempfile.gettempdir(), "trellis_3d")
19
  os.makedirs(TEMP_DIR, exist_ok=True)
20
 
 
 
 
 
21
 
22
- def text_to_image(prompt, seed=0):
23
- try:
24
- from gradio_client import Client
25
 
26
- logger.info(f"Generating image from text: {prompt}")
27
-
28
- spaces = [
29
- {
30
- "space": "stabilityai/stable-diffusion-3.5-large-turbo",
31
- "args": [prompt, "", seed, True, 1024, 1024, 4, 0],
32
- "endpoint": "/infer"
33
- },
34
- {
35
- "space": "black-forest-labs/FLUX.1-schnell",
36
- "args": [prompt, seed, True, 1024, 1024, 4],
37
- "endpoint": "/infer"
38
- },
39
- ]
40
-
41
- for space_config in spaces:
42
- try:
43
- logger.info(f"Trying: {space_config['space']}")
44
- img_client = Client(space_config["space"])
45
- result = img_client.predict(
46
- *space_config["args"],
47
- api_name=space_config["endpoint"]
48
- )
49
-
50
- img_path = None
51
- if isinstance(result, str) and os.path.exists(result):
52
- img_path = result
53
- elif isinstance(result, tuple) and len(result) > 0:
54
- first = result[0]
55
- if isinstance(first, str) and os.path.exists(first):
56
- img_path = first
57
- elif isinstance(first, dict) and "path" in first:
58
- img_path = first["path"]
59
- elif isinstance(result, dict) and "path" in result:
60
- img_path = result["path"]
61
- elif hasattr(result, "path"):
62
- img_path = result.path
63
-
64
- if img_path and os.path.exists(img_path):
65
- logger.info(f"Image generated: {img_path}")
66
- return img_path
67
-
68
- except Exception as e:
69
- logger.warning(f"{space_config['space']} failed: {e}")
70
- continue
71
 
72
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- except Exception as e:
75
- logger.error(f"Text to image failed: {e}")
76
- return None
77
 
78
 
79
  def generate_3d_from_image(
@@ -101,124 +101,172 @@ def generate_3d_from_image(
101
  logger.info("Starting 3D generation pipeline")
102
  logger.info(f"Image: {image_path}")
103
 
104
- client = Client(TRELLIS_SPACE)
105
- logger.info("Connected to TRELLIS.2")
106
 
107
- # Step 1: Start Session
108
- logger.info("Step 1/5: Starting session...")
109
- try:
110
- client.predict(api_name="/start_session")
111
- logger.info("Session started")
112
- except Exception as e:
113
- logger.warning(f"Start session note: {e}")
 
 
114
 
115
- # Step 2: Preprocess Image
116
- logger.info("Step 2/5: Preprocessing image...")
117
- processed_path = image_path
118
- try:
119
- preprocessed = client.predict(
120
- handle_file(image_path),
121
- api_name="/preprocess_image"
122
- )
123
- logger.info(f"Preprocessed result: {preprocessed}")
124
 
125
- if preprocessed:
126
- if isinstance(preprocessed, dict) and "path" in preprocessed:
127
- processed_path = preprocessed["path"]
128
- elif isinstance(preprocessed, str) and os.path.exists(preprocessed):
129
- processed_path = preprocessed
130
- elif hasattr(preprocessed, "path"):
131
- processed_path = preprocessed.path
132
 
133
- logger.info(f"Using image: {processed_path}")
 
 
 
 
 
 
 
134
 
135
- except Exception as e:
136
- logger.warning(f"Preprocess note: {e}")
 
 
 
 
 
137
 
138
- # Step 3: Get Seed
139
- logger.info("Step 3/5: Getting seed...")
140
- actual_seed = seed
141
- try:
142
- actual_seed = client.predict(
143
- randomize_seed,
144
- seed,
145
- api_name="/get_seed"
146
- )
147
- logger.info(f"Seed: {actual_seed}")
148
- except Exception as e:
149
- logger.warning(f"Get seed note: {e}")
150
-
151
- # Step 4: Image to 3D
152
- logger.info("Step 4/5: Generating 3D model (1-3 minutes)...")
153
-
154
- if isinstance(processed_path, str):
155
- image_input = handle_file(processed_path)
156
- else:
157
- image_input = processed_path
158
-
159
- preview_result = client.predict(
160
- image_input,
161
- actual_seed,
162
- resolution,
163
- ss_guidance_strength,
164
- ss_guidance_rescale,
165
- ss_sampling_steps,
166
- ss_rescale_t,
167
- shape_slat_guidance_strength,
168
- shape_slat_guidance_rescale,
169
- shape_slat_sampling_steps,
170
- shape_slat_rescale_t,
171
- tex_slat_guidance_strength,
172
- tex_slat_guidance_rescale,
173
- tex_slat_sampling_steps,
174
- tex_slat_rescale_t,
175
- api_name="/image_to_3d"
176
- )
177
 
178
- logger.info(f"3D result type: {type(preview_result)}")
179
- logger.info(f"3D result: {str(preview_result)[:500]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- # Step 5: Extract GLB
182
- logger.info("Step 5/5: Extracting GLB file...")
183
 
184
- glb_result = client.predict(
185
- decimation_target,
186
- texture_size,
187
- api_name="/extract_glb"
188
- )
189
 
190
- logger.info(f"GLB result type: {type(glb_result)}")
191
- logger.info(f"GLB result: {glb_result}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
- glb_path = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
  if isinstance(glb_result, tuple):
196
  for item in glb_result:
197
- if isinstance(item, str) and os.path.exists(item):
198
- glb_path = item
199
- break
200
- elif isinstance(item, dict) and "path" in item:
201
- if os.path.exists(item["path"]):
202
- glb_path = item["path"]
203
- break
204
- elif hasattr(item, "path") and os.path.exists(item.path):
205
- glb_path = item.path
206
- break
207
- elif isinstance(glb_result, str) and os.path.exists(glb_result):
208
- glb_path = glb_result
209
- elif isinstance(glb_result, dict) and "path" in glb_result:
210
- glb_path = glb_result["path"]
211
- elif hasattr(glb_result, "path"):
212
- glb_path = glb_result.path
213
-
214
- if glb_path and os.path.exists(glb_path):
215
- file_id = uuid.uuid4().hex[:8]
216
- output_path = os.path.join(TEMP_DIR, f"model_{file_id}.glb")
217
- shutil.copy2(glb_path, output_path)
218
- logger.info(f"GLB saved to: {output_path}")
219
- return output_path
220
-
221
- logger.error(f"Could not extract GLB from: {glb_result}")
222
  return None
223
 
224
 
@@ -258,16 +306,15 @@ def handle_image_to_3d(
258
  elif hasattr(image, "save"):
259
  image.save(img_path, "PNG")
260
  else:
261
- raise gr.Error("Invalid image")
262
  except gr.Error:
263
  raise
264
  except Exception as e:
265
  raise gr.Error(f"Image error: {e}")
266
 
267
- progress(0.1, desc="Connecting to TRELLIS...")
268
-
269
  try:
270
- progress(0.15, desc="Generating 3D model... (1-3 min)")
 
271
 
272
  glb_path = generate_3d_from_image(
273
  image_path=img_path,
@@ -290,11 +337,14 @@ def handle_image_to_3d(
290
  texture_size=texture_size
291
  )
292
 
293
- progress(0.95, desc="Almost done...")
294
  duration = time.time() - start_time
295
 
296
  if glb_path and os.path.exists(glb_path):
297
- status = f"Generated in {duration:.1f}s"
 
 
 
 
298
  progress(1.0, desc="Done!")
299
  return glb_path, glb_path, status
300
  else:
@@ -303,7 +353,7 @@ def handle_image_to_3d(
303
  except gr.Error:
304
  raise
305
  except Exception as e:
306
- logger.error(f"Generation error: {e}", exc_info=True)
307
  raise gr.Error(f"Generation failed: {str(e)}")
308
 
309
 
@@ -333,17 +383,19 @@ def handle_text_to_3d(
333
 
334
  start_time = time.time()
335
 
336
- progress(0.05, desc="Generating image from text...")
337
 
338
  img_path = text_to_image(prompt, seed)
339
 
340
  if img_path is None:
341
  raise gr.Error(
342
  "Could not generate image from text. "
343
- "Try using the Image to 3D tab instead."
 
 
344
  )
345
 
346
- progress(0.3, desc="Image ready! Now generating 3D...")
347
 
348
  try:
349
  glb_path = generate_3d_from_image(
@@ -367,11 +419,15 @@ def handle_text_to_3d(
367
  texture_size=texture_size
368
  )
369
 
370
- progress(0.95, desc="Almost done...")
371
  duration = time.time() - start_time
372
 
373
  if glb_path and os.path.exists(glb_path):
374
- status = f"Generated in {duration:.1f}s from prompt: {prompt}"
 
 
 
 
 
375
  progress(1.0, desc="Done!")
376
  return img_path, glb_path, glb_path, status
377
  else:
@@ -388,19 +444,29 @@ def check_status():
388
  try:
389
  from gradio_client import Client
390
 
 
391
  client = Client(TRELLIS_SPACE)
 
392
  api_str = client.view_api(return_format="str")
393
 
394
  return (
395
  "## Connected to TRELLIS.2\n\n"
396
  f"**Space:** `{TRELLIS_SPACE}`\n\n"
 
397
  "**Status:** Online and Ready\n\n"
398
- "### Pipeline Steps:\n"
399
- "1. `/start_session` - Initialize\n"
400
- "2. `/preprocess_image` - Clean image\n"
401
- "3. `/get_seed` - Prepare seed\n"
402
- "4. `/image_to_3d` - Generate 3D (15 params)\n"
403
- "5. `/extract_glb` - Download GLB file\n\n"
 
 
 
 
 
 
 
404
  "### Raw API:\n"
405
  f"```\n{api_str}\n```"
406
  )
@@ -409,10 +475,11 @@ def check_status():
409
  return (
410
  "## Connection Failed\n\n"
411
  f"**Error:** `{str(e)}`\n\n"
412
- "The TRELLIS space might be:\n"
413
- "- Sleeping (wait 2-3 min, it auto-wakes)\n"
414
  "- At capacity (try again later)\n"
415
- "- Under maintenance"
 
416
  )
417
 
418
 
@@ -428,10 +495,6 @@ def cleanup():
428
  return f"Error: {e}"
429
 
430
 
431
- # ============================================
432
- # GRADIO UI
433
- # ============================================
434
-
435
  css = """
436
  .gradio-container {
437
  max-width: 1200px !important;
@@ -464,6 +527,26 @@ css = """
464
  -webkit-text-fill-color: transparent;
465
  font-size: 2.2em !important;
466
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  """
468
 
469
  with gr.Blocks(
@@ -481,17 +564,23 @@ with gr.Blocks(
481
  '<div class="header-area">'
482
  "<h1>TRELLIS 3D Generator</h1>"
483
  '<p style="color: #888; font-size: 15px;">'
484
- "Generate 3D models from images or text - Powered by Microsoft TRELLIS 2"
485
  "</p>"
486
  "</div>"
487
  )
488
 
489
  with gr.Tabs():
490
 
491
- # ==========================================
492
- # TAB 1: IMAGE TO 3D
493
- # ==========================================
494
  with gr.Tab("Image to 3D"):
 
 
 
 
 
 
 
 
 
495
  with gr.Row():
496
 
497
  with gr.Column(scale=2):
@@ -524,57 +613,69 @@ with gr.Blocks(
524
 
525
  img_resolution = gr.Radio(
526
  choices=["512", "1024", "1536"],
527
- value="1024",
528
- label="Resolution"
529
  )
530
 
531
  with gr.Accordion("Advanced: Sparse Structure", open=False):
532
  img_ss_guidance = gr.Slider(
533
- 0, 20, 7.5, step=0.1, label="Guidance Strength"
 
534
  )
535
  img_ss_rescale = gr.Slider(
536
- 0, 1, 0.7, step=0.05, label="Guidance Rescale"
 
537
  )
538
  img_ss_steps = gr.Slider(
539
- 1, 50, 12, step=1, label="Sampling Steps"
 
540
  )
541
  img_ss_rescale_t = gr.Slider(
542
- 0, 10, 5.0, step=0.1, label="Rescale T"
 
543
  )
544
 
545
  with gr.Accordion("Advanced: Shape Latent", open=False):
546
  img_shape_guidance = gr.Slider(
547
- 0, 20, 7.5, step=0.1, label="Guidance Strength"
 
548
  )
549
  img_shape_rescale = gr.Slider(
550
- 0, 1, 0.5, step=0.05, label="Guidance Rescale"
 
551
  )
552
  img_shape_steps = gr.Slider(
553
- 1, 50, 12, step=1, label="Sampling Steps"
 
554
  )
555
  img_shape_rescale_t = gr.Slider(
556
- 0, 10, 3.0, step=0.1, label="Rescale T"
 
557
  )
558
 
559
  with gr.Accordion("Advanced: Texture Latent", open=False):
560
  img_tex_guidance = gr.Slider(
561
- 0, 20, 1.0, step=0.1, label="Guidance Strength"
 
562
  )
563
  img_tex_rescale = gr.Slider(
564
- 0, 1, 0.0, step=0.05, label="Guidance Rescale"
 
565
  )
566
  img_tex_steps = gr.Slider(
567
- 1, 50, 12, step=1, label="Sampling Steps"
 
568
  )
569
  img_tex_rescale_t = gr.Slider(
570
- 0, 10, 3.0, step=0.1, label="Rescale T"
 
571
  )
572
 
573
  with gr.Accordion("Advanced: Export Settings", open=False):
574
  img_decimation = gr.Slider(
575
  10000, 1000000, 300000,
576
  step=10000,
577
- label="Mesh Faces (Decimation Target)"
578
  )
579
  img_texture_size = gr.Slider(
580
  512, 4096, 2048,
@@ -595,7 +696,10 @@ with gr.Blocks(
595
 
596
  with gr.Column(scale=3):
597
  gr.Markdown("### 3D Model Result")
598
- img_model = gr.Model3D(label="3D Viewer", height=500)
 
 
 
599
  img_download = gr.File(label="Download GLB")
600
  gr.Markdown(
601
  "Drag = Rotate | Scroll = Zoom | Right-drag = Pan"
@@ -627,16 +731,15 @@ with gr.Blocks(
627
  show_progress="full"
628
  )
629
 
630
- # ==========================================
631
- # TAB 2: TEXT TO 3D
632
- # ==========================================
633
  with gr.Tab("Text to 3D"):
634
 
635
- gr.Markdown(
636
- "> **Note:** Text to 3D works in 2 steps. "
637
- "First generates an image from your text using Stable Diffusion, "
638
- "then converts that image to 3D using TRELLIS. "
639
- "This may take 2-5 minutes total."
 
 
640
  )
641
 
642
  with gr.Row():
@@ -646,7 +749,7 @@ with gr.Blocks(
646
 
647
  txt_prompt = gr.Textbox(
648
  label="Text Prompt",
649
- placeholder="A cute cat sitting, white background, single object, 3D render style...",
650
  lines=3
651
  )
652
 
@@ -689,7 +792,7 @@ with gr.Blocks(
689
 
690
  gr.Markdown(
691
  "**Tip:** Add 'white background, 3D render, single object' "
692
- "to your prompt for better results."
693
  )
694
 
695
  with gr.Row():
@@ -707,50 +810,62 @@ with gr.Blocks(
707
 
708
  txt_resolution = gr.Radio(
709
  choices=["512", "1024", "1536"],
710
- value="1024",
711
- label="Resolution"
712
  )
713
 
714
  with gr.Accordion("Advanced: Sparse Structure", open=False):
715
  txt_ss_guidance = gr.Slider(
716
- 0, 20, 7.5, step=0.1, label="Guidance Strength"
 
717
  )
718
  txt_ss_rescale = gr.Slider(
719
- 0, 1, 0.7, step=0.05, label="Guidance Rescale"
 
720
  )
721
  txt_ss_steps = gr.Slider(
722
- 1, 50, 12, step=1, label="Sampling Steps"
 
723
  )
724
  txt_ss_rescale_t = gr.Slider(
725
- 0, 10, 5.0, step=0.1, label="Rescale T"
 
726
  )
727
 
728
  with gr.Accordion("Advanced: Shape Latent", open=False):
729
  txt_shape_guidance = gr.Slider(
730
- 0, 20, 7.5, step=0.1, label="Guidance Strength"
 
731
  )
732
  txt_shape_rescale = gr.Slider(
733
- 0, 1, 0.5, step=0.05, label="Guidance Rescale"
 
734
  )
735
  txt_shape_steps = gr.Slider(
736
- 1, 50, 12, step=1, label="Sampling Steps"
 
737
  )
738
  txt_shape_rescale_t = gr.Slider(
739
- 0, 10, 3.0, step=0.1, label="Rescale T"
 
740
  )
741
 
742
  with gr.Accordion("Advanced: Texture Latent", open=False):
743
  txt_tex_guidance = gr.Slider(
744
- 0, 20, 1.0, step=0.1, label="Guidance Strength"
 
745
  )
746
  txt_tex_rescale = gr.Slider(
747
- 0, 1, 0.0, step=0.05, label="Guidance Rescale"
 
748
  )
749
  txt_tex_steps = gr.Slider(
750
- 1, 50, 12, step=1, label="Sampling Steps"
 
751
  )
752
  txt_tex_rescale_t = gr.Slider(
753
- 0, 10, 3.0, step=0.1, label="Rescale T"
 
754
  )
755
 
756
  with gr.Accordion("Advanced: Export", open=False):
@@ -777,13 +892,12 @@ with gr.Blocks(
777
  )
778
 
779
  with gr.Column(scale=3):
780
- gr.Markdown("### Generated Image then 3D Model")
781
 
782
  txt_gen_image = gr.Image(
783
  label="Generated Image (Step 1)",
784
  height=200
785
  )
786
-
787
  txt_model = gr.Model3D(
788
  label="3D Model (Step 2)",
789
  height=400
@@ -824,16 +938,15 @@ with gr.Blocks(
824
  show_progress="full"
825
  )
826
 
827
- # ==========================================
828
- # TAB 3: STATUS AND API
829
- # ==========================================
830
  with gr.Tab("Status and API"):
 
831
  with gr.Row():
832
 
833
  with gr.Column():
834
- gr.Markdown("### Connection")
 
835
  status_display = gr.Markdown(
836
- "Click Check Connection to start."
837
  )
838
 
839
  with gr.Row():
@@ -861,23 +974,26 @@ with gr.Blocks(
861
  gr.Markdown("### How It Works")
862
  gr.Markdown(
863
  "**Text to 3D Pipeline:**\n\n"
864
- "Text Prompt -> Stable Diffusion (Image) -> TRELLIS (3D) -> GLB File\n\n"
865
  "**Image to 3D Pipeline:**\n\n"
866
- "Image Upload -> TRELLIS (3D) -> GLB File\n\n"
867
- "### TRELLIS Pipeline Steps\n\n"
868
- "1. `start_session` - Initialize\n"
869
- "2. `preprocess_image` - Clean image\n"
870
- "3. `get_seed` - Prepare seed\n"
871
- "4. `image_to_3d` - Generate 3D (15 params)\n"
872
- "5. `extract_glb` - Download GLB file\n\n"
873
- "### API Usage (Python)\n\n"
 
 
874
  "```python\n"
875
- "from gradio_client import Client, handle_file\n\n"
876
- "client = Client('YOUR-SPACE-URL')\n\n"
877
- "# Image to 3D\n"
878
- "model, file = client.predict(\n"
 
879
  " handle_file('image.png'),\n"
880
- " 0, True, '1024',\n"
881
  " 7.5, 0.7, 12, 5.0,\n"
882
  " 7.5, 0.5, 12, 3.0,\n"
883
  " 1.0, 0.0, 12, 3.0,\n"
@@ -888,10 +1004,10 @@ with gr.Blocks(
888
  )
889
 
890
  gr.Markdown(
891
- "<div style='text-align:center; padding:15px; opacity:0.5; font-size:12px;'>"
892
- "TRELLIS 3D Generator - "
893
- "<a href='https://github.com/microsoft/TRELLIS.2'>Microsoft TRELLIS 2</a> - "
894
- "Built with Gradio"
895
  "</div>"
896
  )
897
 
 
18
  TEMP_DIR = os.path.join(tempfile.gettempdir(), "trellis_3d")
19
  os.makedirs(TEMP_DIR, exist_ok=True)
20
 
21
+ FALLBACK_3D_SPACES = [
22
+ "TencentARC/InstantMesh",
23
+ "sudo-ai/zero123plus",
24
+ ]
25
 
 
 
 
26
 
27
+ def text_to_image(prompt, seed=0):
28
+ from gradio_client import Client
29
+
30
+ logger.info(f"Generating image from text: {prompt}")
31
+
32
+ spaces = [
33
+ {
34
+ "space": "stabilityai/stable-diffusion-3.5-large-turbo",
35
+ "args": [prompt, "", seed, True, 1024, 1024, 4, 0],
36
+ "endpoint": "/infer"
37
+ },
38
+ {
39
+ "space": "black-forest-labs/FLUX.1-schnell",
40
+ "args": [prompt, seed, True, 1024, 1024, 4],
41
+ "endpoint": "/infer"
42
+ },
43
+ ]
44
+
45
+ for space_config in spaces:
46
+ try:
47
+ logger.info(f"Trying image gen: {space_config['space']}")
48
+ img_client = Client(space_config["space"])
49
+ result = img_client.predict(
50
+ *space_config["args"],
51
+ api_name=space_config["endpoint"]
52
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ img_path = None
55
+ if isinstance(result, str) and os.path.exists(result):
56
+ img_path = result
57
+ elif isinstance(result, tuple) and len(result) > 0:
58
+ first = result[0]
59
+ if isinstance(first, str) and os.path.exists(first):
60
+ img_path = first
61
+ elif isinstance(first, dict) and "path" in first:
62
+ img_path = first["path"]
63
+ elif isinstance(result, dict) and "path" in result:
64
+ img_path = result["path"]
65
+ elif hasattr(result, "path"):
66
+ img_path = result.path
67
+
68
+ if img_path and os.path.exists(img_path):
69
+ logger.info(f"Image generated: {img_path}")
70
+ return img_path
71
+
72
+ except Exception as e:
73
+ logger.warning(f"{space_config['space']} failed: {e}")
74
+ continue
75
 
76
+ return None
 
 
77
 
78
 
79
  def generate_3d_from_image(
 
101
  logger.info("Starting 3D generation pipeline")
102
  logger.info(f"Image: {image_path}")
103
 
104
+ max_retries = 5
105
+ retry_delays = [10, 30, 60, 90, 120]
106
 
107
+ for attempt in range(max_retries):
108
+ try:
109
+ if attempt > 0:
110
+ wait_time = retry_delays[min(attempt, len(retry_delays) - 1)]
111
+ logger.info(
112
+ f"Retry {attempt + 1}/{max_retries} "
113
+ f"after {wait_time}s wait..."
114
+ )
115
+ time.sleep(wait_time)
116
 
117
+ client = Client(TRELLIS_SPACE)
118
+ logger.info("Connected to TRELLIS.2")
 
 
 
 
 
 
 
119
 
120
+ logger.info("Step 1/5: Starting session...")
121
+ try:
122
+ client.predict(api_name="/start_session")
123
+ logger.info("Session started")
124
+ except Exception as e:
125
+ logger.warning(f"Start session note: {e}")
 
126
 
127
+ logger.info("Step 2/5: Preprocessing image...")
128
+ processed_path = image_path
129
+ try:
130
+ preprocessed = client.predict(
131
+ handle_file(image_path),
132
+ api_name="/preprocess_image"
133
+ )
134
+ logger.info(f"Preprocessed: {preprocessed}")
135
 
136
+ if preprocessed:
137
+ if isinstance(preprocessed, dict) and "path" in preprocessed:
138
+ processed_path = preprocessed["path"]
139
+ elif isinstance(preprocessed, str) and os.path.exists(preprocessed):
140
+ processed_path = preprocessed
141
+ elif hasattr(preprocessed, "path"):
142
+ processed_path = preprocessed.path
143
 
144
+ except Exception as e:
145
+ logger.warning(f"Preprocess note: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
+ logger.info("Step 3/5: Getting seed...")
148
+ actual_seed = seed
149
+ try:
150
+ actual_seed = client.predict(
151
+ randomize_seed,
152
+ seed,
153
+ api_name="/get_seed"
154
+ )
155
+ logger.info(f"Seed: {actual_seed}")
156
+ except Exception as e:
157
+ logger.warning(f"Seed note: {e}")
158
+
159
+ logger.info("Step 4/5: Generating 3D (1-3 min)...")
160
+
161
+ if isinstance(processed_path, str):
162
+ image_input = handle_file(processed_path)
163
+ else:
164
+ image_input = processed_path
165
+
166
+ preview_result = client.predict(
167
+ image_input,
168
+ actual_seed,
169
+ resolution,
170
+ ss_guidance_strength,
171
+ ss_guidance_rescale,
172
+ ss_sampling_steps,
173
+ ss_rescale_t,
174
+ shape_slat_guidance_strength,
175
+ shape_slat_guidance_rescale,
176
+ shape_slat_sampling_steps,
177
+ shape_slat_rescale_t,
178
+ tex_slat_guidance_strength,
179
+ tex_slat_guidance_rescale,
180
+ tex_slat_sampling_steps,
181
+ tex_slat_rescale_t,
182
+ api_name="/image_to_3d"
183
+ )
184
 
185
+ logger.info(f"3D result: {str(preview_result)[:300]}")
 
186
 
187
+ logger.info("Step 5/5: Extracting GLB...")
 
 
 
 
188
 
189
+ glb_result = client.predict(
190
+ decimation_target,
191
+ texture_size,
192
+ api_name="/extract_glb"
193
+ )
194
+
195
+ logger.info(f"GLB result: {glb_result}")
196
+
197
+ glb_path = extract_glb_path(glb_result)
198
+
199
+ if glb_path and os.path.exists(glb_path):
200
+ file_id = uuid.uuid4().hex[:8]
201
+ output_path = os.path.join(TEMP_DIR, f"model_{file_id}.glb")
202
+ shutil.copy2(glb_path, output_path)
203
+ logger.info(f"GLB saved: {output_path}")
204
+ return output_path
205
+
206
+ logger.error(f"No GLB from: {glb_result}")
207
+ return None
208
+
209
+ except Exception as e:
210
+ error_msg = str(e)
211
+ logger.error(f"Attempt {attempt + 1} failed: {error_msg}")
212
+
213
+ is_quota_error = any(
214
+ phrase in error_msg.lower()
215
+ for phrase in [
216
+ "gpu quota",
217
+ "exceeded",
218
+ "queue",
219
+ "too many",
220
+ "rate limit",
221
+ "capacity",
222
+ "busy"
223
+ ]
224
+ )
225
 
226
+ if is_quota_error and attempt < max_retries - 1:
227
+ logger.info("GPU quota issue. Will retry...")
228
+ continue
229
+ elif is_quota_error:
230
+ raise Exception(
231
+ "GPU quota exceeded on TRELLIS. "
232
+ "The free GPU is busy right now. "
233
+ "Please try again in 2-5 minutes. "
234
+ "Tip: Use resolution 512 for faster processing."
235
+ )
236
+ else:
237
+ raise
238
+
239
+ return None
240
+
241
+
242
+ def extract_glb_path(glb_result):
243
+ if glb_result is None:
244
+ return None
245
 
246
  if isinstance(glb_result, tuple):
247
  for item in glb_result:
248
+ path = extract_single_path(item)
249
+ if path:
250
+ return path
251
+
252
+ return extract_single_path(glb_result)
253
+
254
+
255
+ def extract_single_path(item):
256
+ if item is None:
257
+ return None
258
+
259
+ if isinstance(item, str) and os.path.exists(item):
260
+ return item
261
+
262
+ if isinstance(item, dict) and "path" in item:
263
+ if os.path.exists(item["path"]):
264
+ return item["path"]
265
+
266
+ if hasattr(item, "path"):
267
+ if isinstance(item.path, str) and os.path.exists(item.path):
268
+ return item.path
269
+
 
 
 
270
  return None
271
 
272
 
 
306
  elif hasattr(image, "save"):
307
  image.save(img_path, "PNG")
308
  else:
309
+ raise gr.Error("Invalid image format")
310
  except gr.Error:
311
  raise
312
  except Exception as e:
313
  raise gr.Error(f"Image error: {e}")
314
 
 
 
315
  try:
316
+ progress(0.1, desc="Connecting to TRELLIS (free GPU)...")
317
+ progress(0.15, desc="Generating 3D... This takes 1-3 min. If GPU is busy it will auto-retry...")
318
 
319
  glb_path = generate_3d_from_image(
320
  image_path=img_path,
 
337
  texture_size=texture_size
338
  )
339
 
 
340
  duration = time.time() - start_time
341
 
342
  if glb_path and os.path.exists(glb_path):
343
+ file_size = os.path.getsize(glb_path) / (1024 * 1024)
344
+ status = (
345
+ f"Done! Generated in {duration:.1f}s | "
346
+ f"File size: {file_size:.1f} MB"
347
+ )
348
  progress(1.0, desc="Done!")
349
  return glb_path, glb_path, status
350
  else:
 
353
  except gr.Error:
354
  raise
355
  except Exception as e:
356
+ logger.error(f"Error: {e}", exc_info=True)
357
  raise gr.Error(f"Generation failed: {str(e)}")
358
 
359
 
 
383
 
384
  start_time = time.time()
385
 
386
+ progress(0.05, desc="Step 1: Generating image from text...")
387
 
388
  img_path = text_to_image(prompt, seed)
389
 
390
  if img_path is None:
391
  raise gr.Error(
392
  "Could not generate image from text. "
393
+ "The image AI spaces might be busy. "
394
+ "Try the Image to 3D tab instead - "
395
+ "generate an image with any AI tool and upload it."
396
  )
397
 
398
+ progress(0.3, desc="Step 2: Image ready! Generating 3D model...")
399
 
400
  try:
401
  glb_path = generate_3d_from_image(
 
419
  texture_size=texture_size
420
  )
421
 
 
422
  duration = time.time() - start_time
423
 
424
  if glb_path and os.path.exists(glb_path):
425
+ file_size = os.path.getsize(glb_path) / (1024 * 1024)
426
+ status = (
427
+ f"Done! Generated in {duration:.1f}s | "
428
+ f"Prompt: {prompt} | "
429
+ f"File: {file_size:.1f} MB"
430
+ )
431
  progress(1.0, desc="Done!")
432
  return img_path, glb_path, glb_path, status
433
  else:
 
444
  try:
445
  from gradio_client import Client
446
 
447
+ start = time.time()
448
  client = Client(TRELLIS_SPACE)
449
+ connect_time = time.time() - start
450
  api_str = client.view_api(return_format="str")
451
 
452
  return (
453
  "## Connected to TRELLIS.2\n\n"
454
  f"**Space:** `{TRELLIS_SPACE}`\n\n"
455
+ f"**Connection time:** {connect_time:.1f}s\n\n"
456
  "**Status:** Online and Ready\n\n"
457
+ "### How it works (all FREE):\n\n"
458
+ "| Step | Endpoint | What it does |\n"
459
+ "|------|----------|-------------|\n"
460
+ "| 1 | /start_session | Initialize |\n"
461
+ "| 2 | /preprocess_image | Clean image |\n"
462
+ "| 3 | /get_seed | Prepare seed |\n"
463
+ "| 4 | /image_to_3d | Generate 3D |\n"
464
+ "| 5 | /extract_glb | Get GLB file |\n\n"
465
+ "### Tips for free GPU:\n"
466
+ "- Use resolution **512** for fastest results\n"
467
+ "- If GPU quota error, wait 2-5 min and retry\n"
468
+ "- Auto-retry is built in (up to 5 attempts)\n"
469
+ "- Off-peak hours (night/early morning) work best\n\n"
470
  "### Raw API:\n"
471
  f"```\n{api_str}\n```"
472
  )
 
475
  return (
476
  "## Connection Failed\n\n"
477
  f"**Error:** `{str(e)}`\n\n"
478
+ "**Possible reasons:**\n"
479
+ "- Space is sleeping (wait 2-3 min)\n"
480
  "- At capacity (try again later)\n"
481
+ "- Under maintenance\n\n"
482
+ "Click Check Connection again to retry."
483
  )
484
 
485
 
 
495
  return f"Error: {e}"
496
 
497
 
 
 
 
 
498
  css = """
499
  .gradio-container {
500
  max-width: 1200px !important;
 
527
  -webkit-text-fill-color: transparent;
528
  font-size: 2.2em !important;
529
  }
530
+
531
+ .tip-box {
532
+ background: #e8f5e9;
533
+ border: 1px solid #4caf50;
534
+ border-radius: 8px;
535
+ padding: 10px 15px;
536
+ margin: 8px 0;
537
+ color: #2e7d32;
538
+ font-size: 14px;
539
+ }
540
+
541
+ .warn-box {
542
+ background: #fff3e0;
543
+ border: 1px solid #ff9800;
544
+ border-radius: 8px;
545
+ padding: 10px 15px;
546
+ margin: 8px 0;
547
+ color: #e65100;
548
+ font-size: 14px;
549
+ }
550
  """
551
 
552
  with gr.Blocks(
 
564
  '<div class="header-area">'
565
  "<h1>TRELLIS 3D Generator</h1>"
566
  '<p style="color: #888; font-size: 15px;">'
567
+ "Generate 3D models from images or text | 100% Free | Powered by Microsoft TRELLIS 2"
568
  "</p>"
569
  "</div>"
570
  )
571
 
572
  with gr.Tabs():
573
 
 
 
 
574
  with gr.Tab("Image to 3D"):
575
+
576
+ gr.HTML(
577
+ '<div class="tip-box">'
578
+ "<strong>Tip:</strong> Use resolution 512 for fastest results. "
579
+ "If you get a GPU quota error, the app will auto-retry up to 5 times. "
580
+ "Best results during off-peak hours."
581
+ "</div>"
582
+ )
583
+
584
  with gr.Row():
585
 
586
  with gr.Column(scale=2):
 
613
 
614
  img_resolution = gr.Radio(
615
  choices=["512", "1024", "1536"],
616
+ value="512",
617
+ label="Resolution (512 = fastest, less GPU usage)"
618
  )
619
 
620
  with gr.Accordion("Advanced: Sparse Structure", open=False):
621
  img_ss_guidance = gr.Slider(
622
+ 0, 20, 7.5, step=0.1,
623
+ label="Guidance Strength"
624
  )
625
  img_ss_rescale = gr.Slider(
626
+ 0, 1, 0.7, step=0.05,
627
+ label="Guidance Rescale"
628
  )
629
  img_ss_steps = gr.Slider(
630
+ 1, 50, 12, step=1,
631
+ label="Sampling Steps"
632
  )
633
  img_ss_rescale_t = gr.Slider(
634
+ 0, 10, 5.0, step=0.1,
635
+ label="Rescale T"
636
  )
637
 
638
  with gr.Accordion("Advanced: Shape Latent", open=False):
639
  img_shape_guidance = gr.Slider(
640
+ 0, 20, 7.5, step=0.1,
641
+ label="Guidance Strength"
642
  )
643
  img_shape_rescale = gr.Slider(
644
+ 0, 1, 0.5, step=0.05,
645
+ label="Guidance Rescale"
646
  )
647
  img_shape_steps = gr.Slider(
648
+ 1, 50, 12, step=1,
649
+ label="Sampling Steps"
650
  )
651
  img_shape_rescale_t = gr.Slider(
652
+ 0, 10, 3.0, step=0.1,
653
+ label="Rescale T"
654
  )
655
 
656
  with gr.Accordion("Advanced: Texture Latent", open=False):
657
  img_tex_guidance = gr.Slider(
658
+ 0, 20, 1.0, step=0.1,
659
+ label="Guidance Strength"
660
  )
661
  img_tex_rescale = gr.Slider(
662
+ 0, 1, 0.0, step=0.05,
663
+ label="Guidance Rescale"
664
  )
665
  img_tex_steps = gr.Slider(
666
+ 1, 50, 12, step=1,
667
+ label="Sampling Steps"
668
  )
669
  img_tex_rescale_t = gr.Slider(
670
+ 0, 10, 3.0, step=0.1,
671
+ label="Rescale T"
672
  )
673
 
674
  with gr.Accordion("Advanced: Export Settings", open=False):
675
  img_decimation = gr.Slider(
676
  10000, 1000000, 300000,
677
  step=10000,
678
+ label="Mesh Faces"
679
  )
680
  img_texture_size = gr.Slider(
681
  512, 4096, 2048,
 
696
 
697
  with gr.Column(scale=3):
698
  gr.Markdown("### 3D Model Result")
699
+ img_model = gr.Model3D(
700
+ label="3D Viewer",
701
+ height=500
702
+ )
703
  img_download = gr.File(label="Download GLB")
704
  gr.Markdown(
705
  "Drag = Rotate | Scroll = Zoom | Right-drag = Pan"
 
731
  show_progress="full"
732
  )
733
 
 
 
 
734
  with gr.Tab("Text to 3D"):
735
 
736
+ gr.HTML(
737
+ '<div class="warn-box">'
738
+ "<strong>Note:</strong> Text to 3D works in 2 steps: "
739
+ "First generates an image from your text (Stable Diffusion), "
740
+ "then converts that image to 3D (TRELLIS). "
741
+ "Takes 2-5 minutes. 100% free."
742
+ "</div>"
743
  )
744
 
745
  with gr.Row():
 
749
 
750
  txt_prompt = gr.Textbox(
751
  label="Text Prompt",
752
+ placeholder="A cute cat sitting, white background, single object, 3D render...",
753
  lines=3
754
  )
755
 
 
792
 
793
  gr.Markdown(
794
  "**Tip:** Add 'white background, 3D render, single object' "
795
+ "for better results."
796
  )
797
 
798
  with gr.Row():
 
810
 
811
  txt_resolution = gr.Radio(
812
  choices=["512", "1024", "1536"],
813
+ value="512",
814
+ label="Resolution (512 = fastest)"
815
  )
816
 
817
  with gr.Accordion("Advanced: Sparse Structure", open=False):
818
  txt_ss_guidance = gr.Slider(
819
+ 0, 20, 7.5, step=0.1,
820
+ label="Guidance Strength"
821
  )
822
  txt_ss_rescale = gr.Slider(
823
+ 0, 1, 0.7, step=0.05,
824
+ label="Guidance Rescale"
825
  )
826
  txt_ss_steps = gr.Slider(
827
+ 1, 50, 12, step=1,
828
+ label="Sampling Steps"
829
  )
830
  txt_ss_rescale_t = gr.Slider(
831
+ 0, 10, 5.0, step=0.1,
832
+ label="Rescale T"
833
  )
834
 
835
  with gr.Accordion("Advanced: Shape Latent", open=False):
836
  txt_shape_guidance = gr.Slider(
837
+ 0, 20, 7.5, step=0.1,
838
+ label="Guidance Strength"
839
  )
840
  txt_shape_rescale = gr.Slider(
841
+ 0, 1, 0.5, step=0.05,
842
+ label="Guidance Rescale"
843
  )
844
  txt_shape_steps = gr.Slider(
845
+ 1, 50, 12, step=1,
846
+ label="Sampling Steps"
847
  )
848
  txt_shape_rescale_t = gr.Slider(
849
+ 0, 10, 3.0, step=0.1,
850
+ label="Rescale T"
851
  )
852
 
853
  with gr.Accordion("Advanced: Texture Latent", open=False):
854
  txt_tex_guidance = gr.Slider(
855
+ 0, 20, 1.0, step=0.1,
856
+ label="Guidance Strength"
857
  )
858
  txt_tex_rescale = gr.Slider(
859
+ 0, 1, 0.0, step=0.05,
860
+ label="Guidance Rescale"
861
  )
862
  txt_tex_steps = gr.Slider(
863
+ 1, 50, 12, step=1,
864
+ label="Sampling Steps"
865
  )
866
  txt_tex_rescale_t = gr.Slider(
867
+ 0, 10, 3.0, step=0.1,
868
+ label="Rescale T"
869
  )
870
 
871
  with gr.Accordion("Advanced: Export", open=False):
 
892
  )
893
 
894
  with gr.Column(scale=3):
895
+ gr.Markdown("### Generated Image to 3D Model")
896
 
897
  txt_gen_image = gr.Image(
898
  label="Generated Image (Step 1)",
899
  height=200
900
  )
 
901
  txt_model = gr.Model3D(
902
  label="3D Model (Step 2)",
903
  height=400
 
938
  show_progress="full"
939
  )
940
 
 
 
 
941
  with gr.Tab("Status and API"):
942
+
943
  with gr.Row():
944
 
945
  with gr.Column():
946
+ gr.Markdown("### Connection Status")
947
+
948
  status_display = gr.Markdown(
949
+ "Click Check Connection to test."
950
  )
951
 
952
  with gr.Row():
 
974
  gr.Markdown("### How It Works")
975
  gr.Markdown(
976
  "**Text to 3D Pipeline:**\n\n"
977
+ "Text -> Stable Diffusion (free) -> Image -> TRELLIS (free) -> GLB\n\n"
978
  "**Image to 3D Pipeline:**\n\n"
979
+ "Image -> TRELLIS (free) -> GLB\n\n"
980
+ "---\n\n"
981
+ "### Free GPU Tips\n\n"
982
+ "- Use resolution **512** for fastest/most reliable results\n"
983
+ "- If GPU quota error appears, app retries up to 5 times\n"
984
+ "- Best time: late night or early morning (less traffic)\n"
985
+ "- Each generation uses about 60-120s of GPU time\n"
986
+ "- The free quota resets every few minutes\n\n"
987
+ "---\n\n"
988
+ "### API Usage\n\n"
989
  "```python\n"
990
+ "from gradio_client import Client, handle_file\n"
991
+ "\n"
992
+ "client = Client('YOUR-SPACE-URL')\n"
993
+ "\n"
994
+ "result = client.predict(\n"
995
  " handle_file('image.png'),\n"
996
+ " 0, True, '512',\n"
997
  " 7.5, 0.7, 12, 5.0,\n"
998
  " 7.5, 0.5, 12, 3.0,\n"
999
  " 1.0, 0.0, 12, 3.0,\n"
 
1004
  )
1005
 
1006
  gr.Markdown(
1007
+ "<div style='text-align:center; padding:15px; "
1008
+ "opacity:0.5; font-size:12px;'>"
1009
+ "TRELLIS 3D Generator | 100% Free | "
1010
+ "Powered by Microsoft TRELLIS 2 | Built with Gradio"
1011
  "</div>"
1012
  )
1013