Spaces:
Running
Running
Restore preflight.py from Make-It-Animatable
Browse files- preflight.py +78 -0
preflight.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Bounded GLB metadata preflight; does not load textures or allocate mesh arrays."""
|
| 2 |
+
import json
|
| 3 |
+
import struct
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
MAX_FACES = 500_000
|
| 7 |
+
MAX_VERTICES = 500_000
|
| 8 |
+
MAX_JSON = 16 * 1024 * 1024
|
| 9 |
+
|
| 10 |
+
def inspect_glb(path):
|
| 11 |
+
path = Path(path)
|
| 12 |
+
size = path.stat().st_size
|
| 13 |
+
with path.open("rb") as stream:
|
| 14 |
+
header = stream.read(20)
|
| 15 |
+
if len(header) != 20:
|
| 16 |
+
raise ValueError("Truncated GLB header")
|
| 17 |
+
magic, version, length, json_length, chunk = struct.unpack("<4sIIII", header)
|
| 18 |
+
if magic != b"glTF" or version != 2 or length != size or chunk != 0x4E4F534A:
|
| 19 |
+
raise ValueError("Invalid GLB 2.0 header or file length")
|
| 20 |
+
if json_length > MAX_JSON or json_length > size - 20:
|
| 21 |
+
raise ValueError("GLB metadata is too large or truncated")
|
| 22 |
+
doc = json.loads(stream.read(json_length))
|
| 23 |
+
meshes = doc.get("meshes", [])
|
| 24 |
+
accessors = doc.get("accessors", [])
|
| 25 |
+
vertices = faces = 0
|
| 26 |
+
unsupported = False
|
| 27 |
+
for mesh in meshes:
|
| 28 |
+
for primitive in mesh.get("primitives", []):
|
| 29 |
+
if primitive.get("mode", 4) != 4:
|
| 30 |
+
unsupported = True
|
| 31 |
+
continue
|
| 32 |
+
position = primitive.get("attributes", {}).get("POSITION")
|
| 33 |
+
if position is None:
|
| 34 |
+
raise ValueError("Mesh primitive has no POSITION accessor")
|
| 35 |
+
count = int(accessors[position]["count"])
|
| 36 |
+
index = primitive.get("indices")
|
| 37 |
+
indices = int(accessors[index]["count"]) if index is not None else count
|
| 38 |
+
if count < 0 or indices < 0 or indices % 3:
|
| 39 |
+
raise ValueError("Invalid vertex or triangle count")
|
| 40 |
+
vertices += count
|
| 41 |
+
faces += indices // 3
|
| 42 |
+
# Count node instances because upstream merges transformed scene geometry.
|
| 43 |
+
nodes = doc.get("nodes", [])
|
| 44 |
+
instances = {}
|
| 45 |
+
for node in nodes:
|
| 46 |
+
if "mesh" in node:
|
| 47 |
+
instances[node["mesh"]] = instances.get(node["mesh"], 0) + 1
|
| 48 |
+
instantiated_faces = 0
|
| 49 |
+
instantiated_vertices = 0
|
| 50 |
+
for i, mesh in enumerate(meshes):
|
| 51 |
+
multiplicity = max(1, instances.get(i, 0))
|
| 52 |
+
for primitive in mesh.get("primitives", []):
|
| 53 |
+
pos = primitive.get("attributes", {}).get("POSITION")
|
| 54 |
+
if pos is None:
|
| 55 |
+
continue
|
| 56 |
+
count = int(accessors[pos]["count"])
|
| 57 |
+
index = primitive.get("indices")
|
| 58 |
+
n = int(accessors[index]["count"]) if index is not None else count
|
| 59 |
+
instantiated_vertices += count * multiplicity
|
| 60 |
+
if primitive.get("mode", 4) == 4:
|
| 61 |
+
instantiated_faces += n // 3 * multiplicity
|
| 62 |
+
required_extensions = doc.get("extensionsRequired", [])
|
| 63 |
+
unsupported = unsupported or any(x in required_extensions for x in
|
| 64 |
+
("KHR_draco_mesh_compression", "EXT_meshopt_compression"))
|
| 65 |
+
dense = instantiated_faces > MAX_FACES or instantiated_vertices > MAX_VERTICES or size > 512 * 1024**2
|
| 66 |
+
warning = ("Preprocessing/decimation required: this input exceeds the conservative "
|
| 67 |
+
"500,000-face or 500,000-vertex limit. The original is preserved; "
|
| 68 |
+
"inference has not started.") if dense else ""
|
| 69 |
+
if unsupported:
|
| 70 |
+
warning = "Preprocessing required: export uncompressed triangle geometry before inference."
|
| 71 |
+
return dict(file_size_bytes=size, mesh_count=len(meshes), vertices=vertices,
|
| 72 |
+
triangles=faces, instantiated_vertices=instantiated_vertices,
|
| 73 |
+
instantiated_triangles=instantiated_faces,
|
| 74 |
+
materials=len(doc.get("materials", [])),
|
| 75 |
+
textures_present=bool(doc.get("textures") or doc.get("images")),
|
| 76 |
+
density_risk="high" if dense else "within conservative limit",
|
| 77 |
+
inference_allowed=bool(meshes) and faces > 0 and not dense and not unsupported,
|
| 78 |
+
warning=warning)
|