Reverb commited on
Commit
1fe7d3d
Β·
1 Parent(s): 00132f3

Fix viewer not updating: direct GLB output + allowed_paths

Browse files

- Moved gen_btn.click() wiring to after gr.Model3D viewer is defined
so outputs=[gen_status, viewer] is valid (viewer was not in scope
when the click was wired inside Tab 1)
- generate_hunyuan() now yields (status_str, glb_path|None) tuples;
the final yield carries str(raw_gen_path) so Gradio receives the GLB
path as a direct function output and serves it automatically β€” no
separate state lookup needed
- Added allowed_paths=[workspace.WORKSPACE] to launch() so Gradio 5
can serve files from the workspace directory (without this, local
paths returned to gr.Model3D are silently rejected)
- _gen_event.then() now only refreshes [summary, status_bar] since the
viewer is already updated by the direct output above
- pp/rig/ex still use _global_refresh for all three (viewer, summary,
status_bar) via .then()

Files changed (2) hide show
  1. app.py +38 -18
  2. src/stages/stage1_generate.py +11 -12
app.py CHANGED
@@ -82,10 +82,17 @@ def _stub(stage: str) -> str:
82
 
83
 
84
  def handle_generate(images, model, quality, seed, _steps, _octree, tex_size, _symmetry, do_rembg):
85
- """Dispatch to the correct generation backend. Yields status strings for streaming."""
 
 
 
 
 
86
  if "TRELLIS" in model:
87
- yield f"βš™οΈ **TRELLIS.2** Β· {quality}\n\nContacting remote Space..."
88
- yield generate_trellis(images, quality, int(seed), int(tex_size))
 
 
89
  return
90
  yield from generate_hunyuan(images, quality, int(seed), int(tex_size), do_rembg=bool(do_rembg))
91
 
@@ -466,13 +473,7 @@ def build_ui() -> gr.Blocks:
466
  gen_btn = gr.Button("Generate", variant="primary")
467
  with gr.Column(scale=1):
468
  gen_status = gr.Markdown("*Awaiting input.*")
469
- _gen_event = gen_btn.click(
470
- fn=handle_generate,
471
- inputs=[gen_images, gen_model, gen_quality, gen_seed,
472
- gen_steps, gen_octree, gen_tex_size, gen_symmetry,
473
- gen_rembg],
474
- outputs=gen_status,
475
- )
476
 
477
  # ============ Tab 2: Post-Process =============================
478
  with gr.Tab("2. Post-Process", id=2):
@@ -713,10 +714,22 @@ def build_ui() -> gr.Blocks:
713
  elem_classes=["status-bar"],
714
  )
715
 
716
- # --- Global refresh: every pipeline action updates the viewer, summary,
717
- # and status bar. We chain .then() onto each button so the refresh runs
718
- # only after the action completes (and even if it fails β€” Gradio still
719
- # fires .then() after exceptions inside the main handler).
 
 
 
 
 
 
 
 
 
 
 
 
720
  def _global_refresh():
721
  return (
722
  ui_helpers.get_viewer_model_path(),
@@ -724,9 +737,14 @@ def build_ui() -> gr.Blocks:
724
  ui_helpers.get_status_bar(),
725
  )
726
 
727
- # Processing buttons: refresh AFTER the operation completes (.then),
728
- # not simultaneously with it (.click).
729
- for _ev in (_gen_event, _pp_event, _rig_event, _ex_event):
 
 
 
 
 
730
  _ev.then(fn=_global_refresh, outputs=[viewer, summary, status_bar])
731
 
732
  # Utility buttons: refresh immediately on click.
@@ -749,8 +767,10 @@ demo.queue(default_concurrency_limit=1).launch(
749
  server_name="0.0.0.0",
750
  server_port=7860,
751
  show_error=True,
 
 
 
752
  # Gradio 6: show_api removed. Use footer_links instead.
753
- # Equivalent of old show_api=False:
754
  footer_links=["gradio", "settings"],
755
  theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"),
756
  css=CUSTOM_CSS,
 
82
 
83
 
84
  def handle_generate(images, model, quality, seed, _steps, _octree, tex_size, _symmetry, do_rembg):
85
+ """Dispatch to the correct generation backend.
86
+
87
+ Yields (status_markdown, viewer_path_or_None) tuples for streaming.
88
+ The viewer path is yielded only in the final tuple so Gradio can serve
89
+ the GLB directly without a separate state lookup.
90
+ """
91
  if "TRELLIS" in model:
92
+ yield f"βš™οΈ **TRELLIS.2** Β· {quality}\n\nContacting remote Space...", None
93
+ result = generate_trellis(images, quality, int(seed), int(tex_size))
94
+ viewer_path = ui_helpers.get_viewer_model_path()
95
+ yield result, viewer_path
96
  return
97
  yield from generate_hunyuan(images, quality, int(seed), int(tex_size), do_rembg=bool(do_rembg))
98
 
 
473
  gen_btn = gr.Button("Generate", variant="primary")
474
  with gr.Column(scale=1):
475
  gen_status = gr.Markdown("*Awaiting input.*")
476
+ # Click wired below, after viewer component is defined.
 
 
 
 
 
 
477
 
478
  # ============ Tab 2: Post-Process =============================
479
  with gr.Tab("2. Post-Process", id=2):
 
714
  elem_classes=["status-bar"],
715
  )
716
 
717
+ # --- Wire generate button now that viewer is in scope ---------------
718
+ # handle_generate yields (status_str, glb_path_or_None) tuples so that
719
+ # the viewer updates the instant the final mesh is ready β€” no separate
720
+ # state lookup needed.
721
+ _gen_event = gen_btn.click(
722
+ fn=handle_generate,
723
+ inputs=[gen_images, gen_model, gen_quality, gen_seed,
724
+ gen_steps, gen_octree, gen_tex_size, gen_symmetry,
725
+ gen_rembg],
726
+ outputs=[gen_status, viewer],
727
+ )
728
+
729
+ # --- Global refresh: every pipeline action updates summary + status bar.
730
+ # For gen_btn the viewer is already updated directly above; for the rest
731
+ # we include the viewer in _global_refresh so post-process / rig / export
732
+ # results also appear.
733
  def _global_refresh():
734
  return (
735
  ui_helpers.get_viewer_model_path(),
 
737
  ui_helpers.get_status_bar(),
738
  )
739
 
740
+ def _summary_refresh():
741
+ return ui_helpers.get_asset_summary(), ui_helpers.get_status_bar()
742
+
743
+ # gen_btn: viewer already updated by direct output; only refresh metadata.
744
+ _gen_event.then(fn=_summary_refresh, outputs=[summary, status_bar])
745
+
746
+ # Other processing buttons: refresh viewer + metadata after completion.
747
+ for _ev in (_pp_event, _rig_event, _ex_event):
748
  _ev.then(fn=_global_refresh, outputs=[viewer, summary, status_bar])
749
 
750
  # Utility buttons: refresh immediately on click.
 
767
  server_name="0.0.0.0",
768
  server_port=7860,
769
  show_error=True,
770
+ # Allow Gradio to serve files from the workspace directory so that
771
+ # gr.Model3D can display generated GLBs without a 403 error.
772
+ allowed_paths=[str(workspace.WORKSPACE)],
773
  # Gradio 6: show_api removed. Use footer_links instead.
 
774
  footer_links=["gradio", "settings"],
775
  theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"),
776
  css=CUSTOM_CSS,
src/stages/stage1_generate.py CHANGED
@@ -247,13 +247,13 @@ def generate_hunyuan(
247
  hdr = f"βš™οΈ **Hunyuan3D-2.1** Β· {quality}\n\n"
248
 
249
  if not images:
250
- yield "❌ Please upload at least one reference image."
251
  return
252
 
253
  # ------------------------------------------------------------------
254
  # Step 1 β€” Load images
255
  # ------------------------------------------------------------------
256
- yield hdr + "**[1/5]** Loading images..."
257
 
258
  def _open(f) -> Image.Image:
259
  path = f.name if hasattr(f, "name") else str(f)
@@ -265,14 +265,14 @@ def generate_hunyuan(
265
  # Step 2 β€” Background removal
266
  # ------------------------------------------------------------------
267
  if do_rembg:
268
- yield hdr + f"**[2/5]** Removing background ({len(pil_images)} image(s))..."
269
  try:
270
  pil_images = [_remove_background(img) for img in pil_images]
271
  except Exception as e:
272
- yield f"❌ Background removal failed:\n```\n{e}\n```"
273
  return
274
  else:
275
- yield hdr + "**[2/5]** Background removal skipped."
276
 
277
  preset = _HY_PRESETS.get(quality, _HY_PRESETS["Balanced (~60s)"])
278
 
@@ -285,12 +285,12 @@ def generate_hunyuan(
285
  # ------------------------------------------------------------------
286
  # Step 3 β€” Load pipeline
287
  # ------------------------------------------------------------------
288
- yield hdr + "**[3/5]** Loading pipeline (first run downloads ~4 GB of weights)..."
289
 
290
  try:
291
  pipeline = _get_hy_pipeline()
292
  except Exception as e:
293
- yield f"❌ Pipeline load failed:\n```\n{type(e).__name__}: {e}\n```"
294
  return
295
 
296
  # ------------------------------------------------------------------
@@ -298,7 +298,7 @@ def generate_hunyuan(
298
  # ------------------------------------------------------------------
299
  steps = preset["steps"]
300
  octree = preset["octree_resolution"]
301
- yield hdr + f"**[4/5]** Generating shape β€” {steps} diffusion steps, octree {octree}..."
302
 
303
  # Track step count via callback so we can update progress.
304
  _progress: list[int] = [0]
@@ -323,13 +323,13 @@ def generate_hunyuan(
323
  yield (
324
  f"❌ Hunyuan3D-2.1 generation failed:\n```\n{type(e).__name__}: {e}\n```\n\n"
325
  f"Tip: make sure the image has a clean subject on a white/transparent background."
326
- )
327
  return
328
 
329
  # ------------------------------------------------------------------
330
  # Step 5 β€” Save & decimate
331
  # ------------------------------------------------------------------
332
- yield hdr + "**[5/5]** Saving mesh and building working copy..."
333
 
334
  workspace.reset_current()
335
  ws = workspace.reset_state()
@@ -378,6 +378,5 @@ def generate_hunyuan(
378
  f"βœ… **Generated with Hunyuan3D-2.1** Β· {quality}\n\n"
379
  f"- Faces: {face_count:,} Β· Vertices: {vertex_count:,}\n"
380
  f"- Time: {elapsed:.1f}s\n\n"
381
- f"Scroll down to see the result in the 3D viewer. "
382
  f"High-poly saved for Stage 2 baking."
383
- )
 
247
  hdr = f"βš™οΈ **Hunyuan3D-2.1** Β· {quality}\n\n"
248
 
249
  if not images:
250
+ yield "❌ Please upload at least one reference image.", None
251
  return
252
 
253
  # ------------------------------------------------------------------
254
  # Step 1 β€” Load images
255
  # ------------------------------------------------------------------
256
+ yield hdr + "**[1/5]** Loading images...", None
257
 
258
  def _open(f) -> Image.Image:
259
  path = f.name if hasattr(f, "name") else str(f)
 
265
  # Step 2 β€” Background removal
266
  # ------------------------------------------------------------------
267
  if do_rembg:
268
+ yield hdr + f"**[2/5]** Removing background ({len(pil_images)} image(s))...", None
269
  try:
270
  pil_images = [_remove_background(img) for img in pil_images]
271
  except Exception as e:
272
+ yield f"❌ Background removal failed:\n```\n{e}\n```", None
273
  return
274
  else:
275
+ yield hdr + "**[2/5]** Background removal skipped.", None
276
 
277
  preset = _HY_PRESETS.get(quality, _HY_PRESETS["Balanced (~60s)"])
278
 
 
285
  # ------------------------------------------------------------------
286
  # Step 3 β€” Load pipeline
287
  # ------------------------------------------------------------------
288
+ yield hdr + "**[3/5]** Loading pipeline (first run downloads ~7 GB of weights)...", None
289
 
290
  try:
291
  pipeline = _get_hy_pipeline()
292
  except Exception as e:
293
+ yield f"❌ Pipeline load failed:\n```\n{type(e).__name__}: {e}\n```", None
294
  return
295
 
296
  # ------------------------------------------------------------------
 
298
  # ------------------------------------------------------------------
299
  steps = preset["steps"]
300
  octree = preset["octree_resolution"]
301
+ yield hdr + f"**[4/5]** Generating shape β€” {steps} diffusion steps, octree {octree}...", None
302
 
303
  # Track step count via callback so we can update progress.
304
  _progress: list[int] = [0]
 
323
  yield (
324
  f"❌ Hunyuan3D-2.1 generation failed:\n```\n{type(e).__name__}: {e}\n```\n\n"
325
  f"Tip: make sure the image has a clean subject on a white/transparent background."
326
+ ), None
327
  return
328
 
329
  # ------------------------------------------------------------------
330
  # Step 5 β€” Save & decimate
331
  # ------------------------------------------------------------------
332
+ yield hdr + "**[5/5]** Saving mesh and building working copy...", None
333
 
334
  workspace.reset_current()
335
  ws = workspace.reset_state()
 
378
  f"βœ… **Generated with Hunyuan3D-2.1** Β· {quality}\n\n"
379
  f"- Faces: {face_count:,} Β· Vertices: {vertex_count:,}\n"
380
  f"- Time: {elapsed:.1f}s\n\n"
 
381
  f"High-poly saved for Stage 2 baking."
382
+ ), str(raw_gen_path)