File size: 6,926 Bytes
ed7d799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807bba4
ed7d799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import json
import logging
import shutil
import time
import uuid
from pathlib import Path

from runtime import ROOT, MODEL_ROOT, configure_storage, startup_checks
configure_storage()
logging.basicConfig(filename=ROOT/"logs/workflow.log",level=logging.INFO,
                    format="%(asctime)s %(levelname)s %(message)s")
runtime_report = startup_checks()

import gradio as gr
import torch
import trimesh
from preflight import inspect_glb
import upstream.app as mia

mia.init_models()
# Initialize existing upstream component references used by its workflow.
# The original UI is not launched.
upstream_blocks = mia.init_blocks()
print("MODELS_READY dedicated CUDA inference",flush=True)

def preserve_and_inspect(path):
    if not path:
        raise gr.Error("Upload a GLB first.")
    folder = ROOT/"inputs"/uuid.uuid4().hex
    folder.mkdir()
    original = folder/"original.glb"
    shutil.copyfile(path,original)
    try:
        report = inspect_glb(original)
    except (ValueError,KeyError,IndexError,TypeError) as exc:
        report = {"inference_allowed":False,"warning":"Invalid or unsupported GLB: "+str(exc)}
    report.update(original=str(original),cuda_available=torch.cuda.is_available(),gpu=runtime_report["gpu"])
    (folder/"preflight.json").write_text(json.dumps(report,indent=2))
    return str(original),report,str(original)

def execute(original,report):
    if not original or not report or not report.get("inference_allowed"):
        raise gr.Error((report or {}).get("warning") or "Inspect an eligible GLB before inference.")
    if not torch.cuda.is_available():
        raise gr.Error("CUDA is unavailable; CPU inference is disabled.")
    # Recheck the preserved file before expensive processing.
    checked=inspect_glb(original)
    if not checked["inference_allowed"]:
        raise gr.Error(checked["warning"])
    job=ROOT/"outputs"/uuid.uuid4().hex
    job.mkdir()
    working=job/"character.glb"
    shutil.copyfile(original,working)
    db=mia.DB()
    started=time.time()
    stages=("Loading mesh","Preparing coordinates","Predicting skeleton and weights",
            "Preparing rest pose","Exporting rigged asset","Finishing")
    try:
        for stage,_ in zip(stages,mia._pipeline(input_path=str(working),db=db,
                                               reset_to_rest=True,rest_pose_type=None)):
            yield stage,None,None,None
        for p in (db.anim_path,db.anim_vis_path):
            if not p or not Path(p).is_file() or Path(p).stat().st_size==0:
                raise RuntimeError("Upstream export did not produce a nonempty asset")
        # Validate export parsing without changing it.
        result=trimesh.load(db.anim_vis_path,force="scene",process=False)
        if not result.geometry:
            raise RuntimeError("Exported GLB contains no geometry")
        with open(db.anim_vis_path,"rb") as stream:
            import struct
            stream.seek(12)
            length,kind=struct.unpack("<II",stream.read(8))
            doc=json.loads(stream.read(length))
        if not doc.get("skins") or not any("skin" in n for n in doc.get("nodes",[])):
            raise RuntimeError("Exported GLB has no skin binding")
        evidence=dict(status="PASS",original=original,glb=db.anim_vis_path,fbx=db.anim_path,
                      elapsed_seconds=time.time()-started,export_meshes=len(result.geometry),
                      skin_count=len(doc["skins"]),runtime=runtime_report["startup_id"])
        (job/"result.json").write_text(json.dumps(evidence,indent=2))
        logging.info("WORKFLOW_PASS %s",json.dumps(evidence))
        print("WORKFLOW_PASS "+json.dumps(evidence),flush=True)
        yield "Complete: rigged GLB and FBX exported.",db.anim_vis_path,db.anim_vis_path,db.anim_path
    except Exception as exc:
        logging.exception("Workflow failed in %s",job)
        (job/"error.txt").write_text(str(exc))
        raise gr.Error("Workflow failed: "+str(exc)+". Original preserved; details in /data/logs/workflow.log.")
    finally:
        mia.clear(db)

def example():
    return preserve_and_inspect(MODEL_ROOT/"data/examples/ninja.glb")

def dense_character_test():
    # Densify an existing character only for preflight; never modify its source.
    source=MODEL_ROOT/"data/examples/ninja.glb"
    mesh=trimesh.load(source,force="mesh",process=False)
    while len(mesh.faces)<=500_000:
        mesh=mesh.subdivide()
    folder=ROOT/"inputs"/("dense-test-"+uuid.uuid4().hex)
    folder.mkdir()
    path=folder/"dense-character.glb"
    mesh.export(path)
    report=inspect_glb(path)
    assert not report["inference_allowed"],"Dense character must be blocked before inference"
    report["test"]="PASS: dense character preprocessing warning"
    report["source"]=str(source)
    report["original"]=str(path)
    (folder/"preflight.json").write_text(json.dumps(report,indent=2))
    print("DENSE_CHARACTER_TEST "+json.dumps(report),flush=True)
    return report

with gr.Blocks(title="Make-It-Animatable") as demo:
    gr.Markdown("# Motion Rig\nUpload a character GLB, inspect it, then prepare and export its rig.")
    original=gr.State()
    report_state=gr.State()
    upload=gr.File(label="Upload GLB",file_types=[".glb"],type="filepath")
    inspect_button=gr.Button("Inspect / preflight")
    report_view=gr.JSON(label="Mesh preflight")
    original_download=gr.File(label="Preserved original",interactive=False)
    run=gr.Button("Prepare and export rig",variant="primary")
    status=gr.Textbox(label="Status",interactive=False)
    preview=gr.Model3D(label="Export preview",interactive=False)
    with gr.Row():
        glb=gr.File(label="Download rigged GLB",interactive=False)
        fbx=gr.File(label="Download rigged FBX",interactive=False)
    with gr.Accordion("Runtime and acceptance checks",open=False):
        gr.JSON(value=runtime_report,label="Startup GPU and dependency checks")
        normal_test=gr.Button("Load bundled normal character for testing")
        dense_test=gr.Button("Test dense character preflight")
        dense_result=gr.JSON(label="Dense test result")
    inspect_button.click(preserve_and_inspect,inputs=upload,
                         outputs=[original,report_view,original_download]).success(
        lambda x:x,inputs=report_view,outputs=report_state)
    normal_test.click(example,outputs=[original,report_view,original_download]).success(
        lambda x:x,inputs=report_view,outputs=report_state)
    run.click(execute,inputs=[original,report_state],outputs=[status,preview,glb,fbx],
              concurrency_limit=1,concurrency_id="workflow")
    dense_test.click(dense_character_test,outputs=dense_result,
                     concurrency_limit=1,concurrency_id="workflow")
demo.queue(max_size=4,default_concurrency_limit=1).launch(
    server_name="0.0.0.0",server_port=7860,show_error=True,ssr_mode=False,
    allowed_paths=[str(ROOT/"inputs"),str(ROOT/"outputs")])