File size: 3,776 Bytes
a54a5ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299ba9b
a54a5ca
299ba9b
 
 
a54a5ca
299ba9b
a54a5ca
299ba9b
 
 
 
 
 
 
 
 
 
 
 
a54a5ca
299ba9b
a54a5ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""
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 ""