STATICAI commited on
Commit
1fc2cc9
·
verified ·
1 Parent(s): 8f65673

Restore preflight.py from Make-It-Animatable

Browse files
Files changed (1) hide show
  1. 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)