Spaces:
Running
Running
Restore docker_app.py from Make-It-Animatable
Browse files- docker_app.py +142 -0
docker_app.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import logging
|
| 3 |
+
import shutil
|
| 4 |
+
import time
|
| 5 |
+
import uuid
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
from runtime import ROOT, MODEL_ROOT, configure_storage, startup_checks
|
| 9 |
+
configure_storage()
|
| 10 |
+
logging.basicConfig(filename=ROOT/"logs/workflow.log",level=logging.INFO,
|
| 11 |
+
format="%(asctime)s %(levelname)s %(message)s")
|
| 12 |
+
runtime_report = startup_checks()
|
| 13 |
+
|
| 14 |
+
import gradio as gr
|
| 15 |
+
import torch
|
| 16 |
+
import trimesh
|
| 17 |
+
from preflight import inspect_glb
|
| 18 |
+
import upstream.app as mia
|
| 19 |
+
|
| 20 |
+
mia.init_models()
|
| 21 |
+
# Initialize existing upstream component references used by its workflow.
|
| 22 |
+
# The original UI is not launched.
|
| 23 |
+
upstream_blocks = mia.init_blocks()
|
| 24 |
+
print("MODELS_READY dedicated CUDA inference",flush=True)
|
| 25 |
+
|
| 26 |
+
def preserve_and_inspect(path):
|
| 27 |
+
if not path:
|
| 28 |
+
raise gr.Error("Upload a GLB first.")
|
| 29 |
+
folder = ROOT/"inputs"/uuid.uuid4().hex
|
| 30 |
+
folder.mkdir()
|
| 31 |
+
original = folder/"original.glb"
|
| 32 |
+
shutil.copyfile(path,original)
|
| 33 |
+
try:
|
| 34 |
+
report = inspect_glb(original)
|
| 35 |
+
except (ValueError,KeyError,IndexError,TypeError) as exc:
|
| 36 |
+
report = {"inference_allowed":False,"warning":"Invalid or unsupported GLB: "+str(exc)}
|
| 37 |
+
report.update(original=str(original),cuda_available=torch.cuda.is_available(),gpu=runtime_report["gpu"])
|
| 38 |
+
(folder/"preflight.json").write_text(json.dumps(report,indent=2))
|
| 39 |
+
return str(original),report,str(original)
|
| 40 |
+
|
| 41 |
+
def execute(original,report):
|
| 42 |
+
if not original or not report or not report.get("inference_allowed"):
|
| 43 |
+
raise gr.Error((report or {}).get("warning") or "Inspect an eligible GLB before inference.")
|
| 44 |
+
if not torch.cuda.is_available():
|
| 45 |
+
raise gr.Error("CUDA is unavailable; CPU inference is disabled.")
|
| 46 |
+
# Recheck the preserved file before expensive processing.
|
| 47 |
+
checked=inspect_glb(original)
|
| 48 |
+
if not checked["inference_allowed"]:
|
| 49 |
+
raise gr.Error(checked["warning"])
|
| 50 |
+
job=ROOT/"outputs"/uuid.uuid4().hex
|
| 51 |
+
job.mkdir()
|
| 52 |
+
working=job/"character.glb"
|
| 53 |
+
shutil.copyfile(original,working)
|
| 54 |
+
db=mia.DB()
|
| 55 |
+
started=time.time()
|
| 56 |
+
stages=("Loading mesh","Preparing coordinates","Predicting skeleton and weights",
|
| 57 |
+
"Preparing rest pose","Exporting rigged asset","Finishing")
|
| 58 |
+
try:
|
| 59 |
+
for stage,_ in zip(stages,mia._pipeline(input_path=str(working),db=db,
|
| 60 |
+
reset_to_rest=True,rest_pose_type=None)):
|
| 61 |
+
yield stage,None,None,None
|
| 62 |
+
for p in (db.anim_path,db.anim_vis_path):
|
| 63 |
+
if not p or not Path(p).is_file() or Path(p).stat().st_size==0:
|
| 64 |
+
raise RuntimeError("Upstream export did not produce a nonempty asset")
|
| 65 |
+
# Validate export parsing without changing it.
|
| 66 |
+
result=trimesh.load(db.anim_vis_path,force="scene",process=False)
|
| 67 |
+
if not result.geometry:
|
| 68 |
+
raise RuntimeError("Exported GLB contains no geometry")
|
| 69 |
+
with open(db.anim_vis_path,"rb") as stream:
|
| 70 |
+
import struct
|
| 71 |
+
stream.seek(12)
|
| 72 |
+
length,kind=struct.unpack("<II",stream.read(8))
|
| 73 |
+
doc=json.loads(stream.read(length))
|
| 74 |
+
if not doc.get("skins") or not any("skin" in n for n in doc.get("nodes",[])):
|
| 75 |
+
raise RuntimeError("Exported GLB has no skin binding")
|
| 76 |
+
evidence=dict(status="PASS",original=original,glb=db.anim_vis_path,fbx=db.anim_path,
|
| 77 |
+
elapsed_seconds=time.time()-started,export_meshes=len(result.geometry),
|
| 78 |
+
skin_count=len(doc["skins"]),runtime=runtime_report["startup_id"])
|
| 79 |
+
(job/"result.json").write_text(json.dumps(evidence,indent=2))
|
| 80 |
+
logging.info("WORKFLOW_PASS %s",json.dumps(evidence))
|
| 81 |
+
print("WORKFLOW_PASS "+json.dumps(evidence),flush=True)
|
| 82 |
+
yield "Complete: rigged GLB and FBX exported.",db.anim_vis_path,db.anim_vis_path,db.anim_path
|
| 83 |
+
except Exception as exc:
|
| 84 |
+
logging.exception("Workflow failed in %s",job)
|
| 85 |
+
(job/"error.txt").write_text(str(exc))
|
| 86 |
+
raise gr.Error("Workflow failed: "+str(exc)+". Original preserved; details in /data/logs/workflow.log.")
|
| 87 |
+
finally:
|
| 88 |
+
mia.clear(db)
|
| 89 |
+
|
| 90 |
+
def example():
|
| 91 |
+
return preserve_and_inspect(MODEL_ROOT/"data/examples/ninja.glb")
|
| 92 |
+
|
| 93 |
+
def dense_character_test():
|
| 94 |
+
# Densify an existing character only for preflight; never modify its source.
|
| 95 |
+
source=MODEL_ROOT/"data/examples/ninja.glb"
|
| 96 |
+
mesh=trimesh.load(source,force="mesh",process=False)
|
| 97 |
+
while len(mesh.faces)<=500_000:
|
| 98 |
+
mesh=mesh.subdivide()
|
| 99 |
+
folder=ROOT/"inputs"/("dense-test-"+uuid.uuid4().hex)
|
| 100 |
+
folder.mkdir()
|
| 101 |
+
path=folder/"dense-character.glb"
|
| 102 |
+
mesh.export(path)
|
| 103 |
+
report=inspect_glb(path)
|
| 104 |
+
assert not report["inference_allowed"],"Dense character must be blocked before inference"
|
| 105 |
+
report["test"]="PASS: dense character preprocessing warning"
|
| 106 |
+
report["source"]=str(source)
|
| 107 |
+
report["original"]=str(path)
|
| 108 |
+
(folder/"preflight.json").write_text(json.dumps(report,indent=2))
|
| 109 |
+
print("DENSE_CHARACTER_TEST "+json.dumps(report),flush=True)
|
| 110 |
+
return report
|
| 111 |
+
|
| 112 |
+
with gr.Blocks(title="Make-It-Animatable") as demo:
|
| 113 |
+
gr.Markdown("# Motion Rig\nUpload a character GLB, inspect it, then prepare and export its rig.")
|
| 114 |
+
original=gr.State()
|
| 115 |
+
report_state=gr.State()
|
| 116 |
+
upload=gr.File(label="Upload GLB",file_types=[".glb"],type="filepath")
|
| 117 |
+
inspect_button=gr.Button("Inspect / preflight")
|
| 118 |
+
report_view=gr.JSON(label="Mesh preflight")
|
| 119 |
+
original_download=gr.File(label="Preserved original",interactive=False)
|
| 120 |
+
run=gr.Button("Prepare and export rig",variant="primary")
|
| 121 |
+
status=gr.Textbox(label="Status",interactive=False)
|
| 122 |
+
preview=gr.Model3D(label="Export preview",interactive=False)
|
| 123 |
+
with gr.Row():
|
| 124 |
+
glb=gr.File(label="Download rigged GLB",interactive=False)
|
| 125 |
+
fbx=gr.File(label="Download rigged FBX",interactive=False)
|
| 126 |
+
with gr.Accordion("Runtime and acceptance checks",open=False):
|
| 127 |
+
gr.JSON(value=runtime_report,label="Startup GPU and dependency checks")
|
| 128 |
+
normal_test=gr.Button("Load bundled normal character for testing")
|
| 129 |
+
dense_test=gr.Button("Test dense character preflight")
|
| 130 |
+
dense_result=gr.JSON(label="Dense test result")
|
| 131 |
+
inspect_button.click(preserve_and_inspect,inputs=upload,
|
| 132 |
+
outputs=[original,report_view,original_download]).success(
|
| 133 |
+
lambda x:x,inputs=report_view,outputs=report_state)
|
| 134 |
+
normal_test.click(example,outputs=[original,report_view,original_download]).success(
|
| 135 |
+
lambda x:x,inputs=report_view,outputs=report_state)
|
| 136 |
+
run.click(execute,inputs=[original,report_state],outputs=[status,preview,glb,fbx],
|
| 137 |
+
concurrency_limit=1,concurrency_id="workflow")
|
| 138 |
+
dense_test.click(dense_character_test,outputs=dense_result,
|
| 139 |
+
concurrency_limit=1,concurrency_id="workflow")
|
| 140 |
+
demo.queue(max_size=4,default_concurrency_limit=1).launch(
|
| 141 |
+
server_name="0.0.0.0",server_port=7860,show_error=True,ssr_mode=False,
|
| 142 |
+
allowed_paths=[str(ROOT/"inputs"),str(ROOT/"outputs")])
|