open3dforge / src /ui_helpers.py
Reverb's picture
Fix state persistence across ZeroGPU subprocess boundary
299ba9b
Raw
History Blame Contribute Delete
3.78 kB
"""
UI helpers β€” shared functions across Gradio tabs.
Keeps `app.py` focused on layout, not formatting logic.
"""
from __future__ import annotations
from . import quota, workspace
def get_status_bar() -> str:
"""Build the global status bar shown at the bottom of every tab.
Markdown-formatted single line with quota + workspace info.
"""
state = workspace.get_state()
parts = [
quota.format_status(),
f"πŸ“ Workspace: {workspace.current_size_mb():.1f} MB",
f"πŸ“¦ Exports: {workspace.export_count()}",
]
if state.model_used:
parts.append(f"πŸ”§ Last: {state.model_used}")
return " Β· ".join(parts)
def get_asset_summary() -> str:
"""Multi-line summary of what's currently in `workspace/current/`.
Used in the viewer panel to show pipeline progress.
"""
state = workspace.get_state()
lines = [f"**Asset:** `{state.asset_name}`"]
if state.face_count:
lines.append(f"- Faces: {state.face_count:,}")
lines.append(f"- Vertices: {state.vertex_count:,}")
stages = []
if state.high_poly_glb:
stages.append("βœ“ Generated")
if state.cleaned_glb:
stages.append("βœ“ Cleaned")
if state.low_poly_glb:
stages.append("βœ“ Decimated")
if state.unwrapped_glb:
stages.append("βœ“ UV unwrapped")
if state.normal_dx_png or state.normal_gl_png:
stages.append("βœ“ Normal baked")
if state.albedo_png:
stages.append("βœ“ Albedo baked")
if state.orm_png or (state.roughness_png and state.metallic_png):
stages.append("βœ“ PBR maps")
if state.lod_glbs:
stages.append(f"βœ“ LODs ({len(state.lod_glbs)})")
if state.collision_glb:
stages.append("βœ“ Collision")
if state.rigged_glb or state.rigged_fbx:
stages.append("βœ“ Rigged")
if stages:
lines.append("")
lines.append("**Progress:**")
for s in stages:
lines.append(f"- {s}")
else:
lines.append("")
lines.append("*No asset loaded yet. Start at the Generate tab.*")
return "\n".join(lines)
def get_viewer_model_path() -> str | None:
"""Pick the best GLB to show in the 3D viewer.
Reads the filesystem directly so it works across the ZeroGPU subprocess
boundary (the in-memory state written inside @spaces.GPU is invisible to
the parent Gradio process).
"""
from .workspace import CURRENT
# Order: most processed β†’ least processed
candidates = [
CURRENT / "rigged.glb",
CURRENT / "scaled.glb",
CURRENT / "pivoted.glb",
CURRENT / "lods" / "LOD0.glb",
CURRENT / "unwrapped.glb",
CURRENT / "low_poly.glb",
CURRENT / "cleaned.glb",
CURRENT / "repaired.glb",
CURRENT / "raw_gen.glb",
CURRENT / "high_poly.glb",
]
for path in candidates:
if path.exists():
return str(path)
return None
def quota_warning(operation: str) -> str:
"""Generate a warning if an operation would exceed the daily quota.
Returns an empty string if the operation fits comfortably.
"""
estimated = quota.estimate(operation)
state = quota.get_state()
remaining = state.remaining_seconds()
if estimated > remaining:
overage = estimated - remaining
cost = overage * quota.OVERAGE_RATE_PER_SECOND
return (
f"⚠️ **Quota warning:** This will use ~{estimated}s but you only "
f"have {remaining:.0f}s left today. "
f"Overage cost: ~${cost:.2f}"
)
elif estimated > remaining * 0.5:
return (
f"ℹ️ Estimated GPU time: ~{estimated}s "
f"({remaining:.0f}s remaining today)"
)
return ""