fix(app): split renderings into two @GPU callbacks; lower defaults
Browse files- Split generate_renderings into generate_videos + generate_glb.
Each gets its own @GPU callback so the heavy mesh simplification
in GLB export does not blow the 120 s ZeroGPU lease.
- Lower default num_cam / num_hdri from 36 β 24.
- Lower default texture_size from 2048 β 1024.
- UI: make both buttons visible and wire them independently.
app.py
CHANGED
|
@@ -534,7 +534,7 @@ def load_slat_file(
|
|
| 534 |
|
| 535 |
@GPU
|
| 536 |
@torch.no_grad()
|
| 537 |
-
def
|
| 538 |
asset_state: Dict[str, Any],
|
| 539 |
hdri_file_obj: Any,
|
| 540 |
hdri_rot: float,
|
|
@@ -545,29 +545,98 @@ def generate_renderings(
|
|
| 545 |
pitch: float,
|
| 546 |
fov: float,
|
| 547 |
radius: float,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
simplify: float,
|
| 549 |
texture_size: int,
|
| 550 |
req: gr.Request,
|
| 551 |
progress: gr.Progress = gr.Progress(track_tqdm=True),
|
| 552 |
-
) -> tuple[str, str
|
| 553 |
-
"""
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
|
|
|
| 557 |
"""
|
| 558 |
slat_path = _require_slat(asset_state)
|
| 559 |
hdri_path = _require_hdri(hdri_file_obj)
|
| 560 |
session_dir = CACHE_DIR / str(req.session_hash)
|
| 561 |
|
| 562 |
-
# ββ Load NeAR once (fresh CUDA context) βββββββββββββββββββββββββββ
|
| 563 |
progress(0.05, desc="Loading NeAR on GPU")
|
| 564 |
pipe = _ensure_near_on_cuda()
|
| 565 |
|
| 566 |
-
progress(0.
|
| 567 |
slat = pipe.load_slat(slat_path)
|
| 568 |
hdri_np = _load_hdri_resized(pipe, hdri_path)
|
| 569 |
|
| 570 |
-
# ββ Prepare base mesh (optional, for GLB export) ββββββββββββββββββββ
|
| 571 |
mesh_path = asset_state.get("mesh_path")
|
| 572 |
base_mesh: Optional[trimesh.Trimesh] = None
|
| 573 |
if mesh_path and os.path.isfile(mesh_path):
|
|
@@ -579,46 +648,7 @@ def generate_renderings(
|
|
| 579 |
else:
|
| 580 |
print("[NeAR] no mesh_path β will use SLaT decoder mesh for GLB", flush=True)
|
| 581 |
|
| 582 |
-
|
| 583 |
-
progress(0.12, desc="Rendering camera-orbit videoβ¦")
|
| 584 |
-
with torch.no_grad():
|
| 585 |
-
cam_frames = pipe.render_camera_path_video(
|
| 586 |
-
slat, hdri_np,
|
| 587 |
-
num_views=int(num_cam),
|
| 588 |
-
fov=float(fov), radius=float(radius),
|
| 589 |
-
hdri_rot_deg=float(hdri_rot),
|
| 590 |
-
full_video=True,
|
| 591 |
-
shadow_video=True,
|
| 592 |
-
bg_color=(1, 1, 1),
|
| 593 |
-
verbose=True,
|
| 594 |
-
)
|
| 595 |
-
p_cam = session_dir / "video_camera_orbit.mp4"
|
| 596 |
-
imageio.mimsave(p_cam, cam_frames, fps=int(fps))
|
| 597 |
-
del cam_frames
|
| 598 |
-
_free_cuda() # Free GPU mem temporarily while still in same CUDA context
|
| 599 |
-
|
| 600 |
-
progress(0.55, desc="Rendering HDRI-rotation videoβ¦")
|
| 601 |
-
with torch.no_grad():
|
| 602 |
-
roll_frames, hdri_frames = pipe.render_hdri_rotation_video(
|
| 603 |
-
slat, hdri_np,
|
| 604 |
-
num_frames=int(num_hdri),
|
| 605 |
-
yaw_deg=float(yaw), pitch_deg=float(pitch),
|
| 606 |
-
fov=float(fov), radius=float(radius),
|
| 607 |
-
full_video=True,
|
| 608 |
-
shadow_video=True,
|
| 609 |
-
bg_color=(1, 1, 1),
|
| 610 |
-
verbose=True,
|
| 611 |
-
)
|
| 612 |
-
p_hdri = session_dir / "video_hdri_rotation.mp4"
|
| 613 |
-
p_roll = session_dir / "video_env_roll.mp4"
|
| 614 |
-
imageio.mimsave(p_hdri, hdri_frames, fps=int(fps))
|
| 615 |
-
imageio.mimsave(p_roll, roll_frames, fps=int(fps))
|
| 616 |
-
del hdri_frames, roll_frames
|
| 617 |
-
_free_cuda()
|
| 618 |
-
|
| 619 |
-
# ββ Export PBR GLB ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 620 |
-
# (renderer/tone_mapper still valid in same CUDA context)
|
| 621 |
-
progress(0.85, desc="Baking PBR GLBβ¦")
|
| 622 |
glb = pipe.export_glb_from_slat(
|
| 623 |
slat, hdri_np,
|
| 624 |
hdri_rot_deg=float(hdri_rot),
|
|
@@ -634,15 +664,10 @@ def generate_renderings(
|
|
| 634 |
glb.export(out_path)
|
| 635 |
del glb
|
| 636 |
|
| 637 |
-
# ββ Cleanup: tear down NeAR before exiting CUDA callback ββββββββββββ
|
| 638 |
_teardown_near()
|
| 639 |
|
| 640 |
-
msg = (
|
| 641 |
-
|
| 642 |
-
f"`{Path(p_hdri).name}`, `{Path(p_roll).name}` \n\n"
|
| 643 |
-
f"**β£ PBR GLB ready** β `{Path(out_path).name}`"
|
| 644 |
-
)
|
| 645 |
-
return str(p_cam), str(p_hdri), str(p_roll), str(out_path), msg
|
| 646 |
|
| 647 |
|
| 648 |
def _hunyuan_mesh_to_renderer_space(mesh: trimesh.Trimesh) -> trimesh.Trimesh:
|
|
@@ -821,12 +846,12 @@ def build_app() -> gr.Blocks:
|
|
| 821 |
hdri_rot = gr.Slider(0, 360, value=0, step=1, label="HDRI rotation Β°")
|
| 822 |
|
| 823 |
gr.HTML('<p class="section-kicker" style="margin:2px 0 2px;padding:0">Actions</p>')
|
| 824 |
-
btn_videos = gr.Button("β’
|
| 825 |
-
btn_glb = gr.Button("β£ Export PBR GLB", variant="primary"
|
| 826 |
with gr.Accordion("Video / export settings", open=False):
|
| 827 |
fps = gr.Slider(8, 48, value=24, step=1, label="FPS")
|
| 828 |
-
num_cam = gr.Slider(8, 96, value=
|
| 829 |
-
num_hdri = gr.Slider(8, 96, value=
|
| 830 |
with gr.Row():
|
| 831 |
yaw = gr.Slider(0, 360, value=0, step=1, label="Yaw Β°")
|
| 832 |
pitch = gr.Slider(-90, 90, value=0, step=1, label="Pitch Β°")
|
|
@@ -834,7 +859,7 @@ def build_app() -> gr.Blocks:
|
|
| 834 |
fov = gr.Slider(10, 70, value=40, step=1, label="FoV")
|
| 835 |
radius = gr.Slider(1.0, 4.0, value=2.0, step=0.05, label="Radius")
|
| 836 |
simplify = gr.Slider(0.80, 0.99, value=0.95, step=0.01, label="GLB simplify ratio")
|
| 837 |
-
tex_size = gr.Slider(512,
|
| 838 |
btn_clear = gr.Button("Clear session cache", variant="secondary")
|
| 839 |
|
| 840 |
# ββ CENTER: linear output area ββββββββββββββββββββββββββββββββββββ
|
|
@@ -888,10 +913,12 @@ def build_app() -> gr.Blocks:
|
|
| 888 |
[asset_state, status])
|
| 889 |
btn_load_slat.click(load_slat_file, [slat_upload, slat_path_txt],
|
| 890 |
[asset_state, status])
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
[
|
| 894 |
-
|
|
|
|
|
|
|
| 895 |
btn_clear.click(clear_cache, [], [status])
|
| 896 |
|
| 897 |
return demo
|
|
|
|
| 534 |
|
| 535 |
@GPU
|
| 536 |
@torch.no_grad()
|
| 537 |
+
def generate_videos(
|
| 538 |
asset_state: Dict[str, Any],
|
| 539 |
hdri_file_obj: Any,
|
| 540 |
hdri_rot: float,
|
|
|
|
| 545 |
pitch: float,
|
| 546 |
fov: float,
|
| 547 |
radius: float,
|
| 548 |
+
req: gr.Request,
|
| 549 |
+
progress: gr.Progress = gr.Progress(track_tqdm=True),
|
| 550 |
+
) -> tuple[str, str, str, str]:
|
| 551 |
+
"""β’ Render camera-orbit + HDRI-rotation videos.
|
| 552 |
+
|
| 553 |
+
Kept as a separate @GPU callback so it fits inside the 120 s ZeroGPU lease.
|
| 554 |
+
Returns: (cam_video, hdri_video, roll_video, status_msg)
|
| 555 |
+
"""
|
| 556 |
+
slat_path = _require_slat(asset_state)
|
| 557 |
+
hdri_path = _require_hdri(hdri_file_obj)
|
| 558 |
+
session_dir = CACHE_DIR / str(req.session_hash)
|
| 559 |
+
|
| 560 |
+
progress(0.05, desc="Loading NeAR on GPU")
|
| 561 |
+
pipe = _ensure_near_on_cuda()
|
| 562 |
+
|
| 563 |
+
progress(0.20, desc="Loading SLaT / HDRI")
|
| 564 |
+
slat = pipe.load_slat(slat_path)
|
| 565 |
+
hdri_np = _load_hdri_resized(pipe, hdri_path)
|
| 566 |
+
|
| 567 |
+
# ββ Camera-orbit video ββββββββββββββββββββββββββββββββββββββββββββ
|
| 568 |
+
progress(0.30, desc="Rendering camera-orbit videoβ¦")
|
| 569 |
+
cam_frames = pipe.render_camera_path_video(
|
| 570 |
+
slat, hdri_np,
|
| 571 |
+
num_views=int(num_cam),
|
| 572 |
+
fov=float(fov), radius=float(radius),
|
| 573 |
+
hdri_rot_deg=float(hdri_rot),
|
| 574 |
+
full_video=True,
|
| 575 |
+
shadow_video=True,
|
| 576 |
+
bg_color=(1, 1, 1),
|
| 577 |
+
verbose=True,
|
| 578 |
+
)
|
| 579 |
+
p_cam = session_dir / "video_camera_orbit.mp4"
|
| 580 |
+
imageio.mimsave(p_cam, cam_frames, fps=int(fps))
|
| 581 |
+
del cam_frames
|
| 582 |
+
_free_cuda()
|
| 583 |
+
|
| 584 |
+
# ββ HDRI-rotation videos ββββββββββββββββββββββββββββββββββββββββββ
|
| 585 |
+
progress(0.65, desc="Rendering HDRI-rotation videoβ¦")
|
| 586 |
+
roll_frames, hdri_frames = pipe.render_hdri_rotation_video(
|
| 587 |
+
slat, hdri_np,
|
| 588 |
+
num_frames=int(num_hdri),
|
| 589 |
+
yaw_deg=float(yaw), pitch_deg=float(pitch),
|
| 590 |
+
fov=float(fov), radius=float(radius),
|
| 591 |
+
full_video=True,
|
| 592 |
+
shadow_video=True,
|
| 593 |
+
bg_color=(1, 1, 1),
|
| 594 |
+
verbose=True,
|
| 595 |
+
)
|
| 596 |
+
p_hdri = session_dir / "video_hdri_rotation.mp4"
|
| 597 |
+
p_roll = session_dir / "video_env_roll.mp4"
|
| 598 |
+
imageio.mimsave(p_hdri, hdri_frames, fps=int(fps))
|
| 599 |
+
imageio.mimsave(p_roll, roll_frames, fps=int(fps))
|
| 600 |
+
del hdri_frames, roll_frames, slat, hdri_np
|
| 601 |
+
_free_cuda()
|
| 602 |
+
|
| 603 |
+
_teardown_near()
|
| 604 |
+
|
| 605 |
+
msg = (
|
| 606 |
+
f"**β’ Videos ready** β `{Path(p_cam).name}`, "
|
| 607 |
+
f"`{Path(p_hdri).name}`, `{Path(p_roll).name}`"
|
| 608 |
+
)
|
| 609 |
+
return str(p_cam), str(p_hdri), str(p_roll), msg
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
@GPU
|
| 613 |
+
@torch.no_grad()
|
| 614 |
+
def generate_glb(
|
| 615 |
+
asset_state: Dict[str, Any],
|
| 616 |
+
hdri_file_obj: Any,
|
| 617 |
+
hdri_rot: float,
|
| 618 |
simplify: float,
|
| 619 |
texture_size: int,
|
| 620 |
req: gr.Request,
|
| 621 |
progress: gr.Progress = gr.Progress(track_tqdm=True),
|
| 622 |
+
) -> tuple[str, str]:
|
| 623 |
+
"""β£ Export PBR GLB (baked texture + simplified mesh).
|
| 624 |
+
|
| 625 |
+
Separate @GPU callback so the heavy mesh simplification does not
|
| 626 |
+
blow the 120 s budget shared with video rendering.
|
| 627 |
+
Returns: (pbr_glb, status_msg)
|
| 628 |
"""
|
| 629 |
slat_path = _require_slat(asset_state)
|
| 630 |
hdri_path = _require_hdri(hdri_file_obj)
|
| 631 |
session_dir = CACHE_DIR / str(req.session_hash)
|
| 632 |
|
|
|
|
| 633 |
progress(0.05, desc="Loading NeAR on GPU")
|
| 634 |
pipe = _ensure_near_on_cuda()
|
| 635 |
|
| 636 |
+
progress(0.15, desc="Loading SLaT / HDRI / mesh")
|
| 637 |
slat = pipe.load_slat(slat_path)
|
| 638 |
hdri_np = _load_hdri_resized(pipe, hdri_path)
|
| 639 |
|
|
|
|
| 640 |
mesh_path = asset_state.get("mesh_path")
|
| 641 |
base_mesh: Optional[trimesh.Trimesh] = None
|
| 642 |
if mesh_path and os.path.isfile(mesh_path):
|
|
|
|
| 648 |
else:
|
| 649 |
print("[NeAR] no mesh_path β will use SLaT decoder mesh for GLB", flush=True)
|
| 650 |
|
| 651 |
+
progress(0.30, desc="Baking PBR GLBβ¦")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
glb = pipe.export_glb_from_slat(
|
| 653 |
slat, hdri_np,
|
| 654 |
hdri_rot_deg=float(hdri_rot),
|
|
|
|
| 664 |
glb.export(out_path)
|
| 665 |
del glb
|
| 666 |
|
|
|
|
| 667 |
_teardown_near()
|
| 668 |
|
| 669 |
+
msg = f"**β£ PBR GLB ready** β `{Path(out_path).name}`"
|
| 670 |
+
return str(out_path), msg
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
|
| 672 |
|
| 673 |
def _hunyuan_mesh_to_renderer_space(mesh: trimesh.Trimesh) -> trimesh.Trimesh:
|
|
|
|
| 846 |
hdri_rot = gr.Slider(0, 360, value=0, step=1, label="HDRI rotation Β°")
|
| 847 |
|
| 848 |
gr.HTML('<p class="section-kicker" style="margin:2px 0 2px;padding:0">Actions</p>')
|
| 849 |
+
btn_videos = gr.Button("β’ Generate Videos", variant="primary")
|
| 850 |
+
btn_glb = gr.Button("β£ Export PBR GLB", variant="primary")
|
| 851 |
with gr.Accordion("Video / export settings", open=False):
|
| 852 |
fps = gr.Slider(8, 48, value=24, step=1, label="FPS")
|
| 853 |
+
num_cam = gr.Slider(8, 96, value=24, step=4, label="Camera-orbit frames")
|
| 854 |
+
num_hdri = gr.Slider(8, 96, value=24, step=4, label="HDRI-rotation frames")
|
| 855 |
with gr.Row():
|
| 856 |
yaw = gr.Slider(0, 360, value=0, step=1, label="Yaw Β°")
|
| 857 |
pitch = gr.Slider(-90, 90, value=0, step=1, label="Pitch Β°")
|
|
|
|
| 859 |
fov = gr.Slider(10, 70, value=40, step=1, label="FoV")
|
| 860 |
radius = gr.Slider(1.0, 4.0, value=2.0, step=0.05, label="Radius")
|
| 861 |
simplify = gr.Slider(0.80, 0.99, value=0.95, step=0.01, label="GLB simplify ratio")
|
| 862 |
+
tex_size = gr.Slider(512, 2048, value=1024, step=512, label="Texture resolution")
|
| 863 |
btn_clear = gr.Button("Clear session cache", variant="secondary")
|
| 864 |
|
| 865 |
# ββ CENTER: linear output area ββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 913 |
[asset_state, status])
|
| 914 |
btn_load_slat.click(load_slat_file, [slat_upload, slat_path_txt],
|
| 915 |
[asset_state, status])
|
| 916 |
+
btn_videos.click(generate_videos,
|
| 917 |
+
[asset_state, hdri_file, hdri_rot, fps, num_cam, num_hdri, yaw, pitch, fov, radius],
|
| 918 |
+
[vid_cam, vid_hdri, vid_roll, status])
|
| 919 |
+
btn_glb.click(generate_glb,
|
| 920 |
+
[asset_state, hdri_file, hdri_rot, simplify, tex_size],
|
| 921 |
+
[glb_view, status])
|
| 922 |
btn_clear.click(clear_cache, [], [status])
|
| 923 |
|
| 924 |
return demo
|