Spaces:
Paused
Paused
File size: 3,374 Bytes
381de33 | 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 | import bpy
import numpy as np
from mathutils import Vector
from mathutils.bvhtree import BVHTree
import argparse
import sys
import json
import bmesh
from pathlib import Path
p = argparse.ArgumentParser()
p.add_argument("--input", required=True)
p.add_argument("--output", required=True)
a = p.parse_args(sys.argv[sys.argv.index("--") + 1 :])
out = Path(a.output)
out.mkdir(parents=True, exist_ok=True)
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.ops.import_scene.gltf(filepath=str(Path(a.input).resolve()))
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
obj.select_set(False)
objs = [o for o in bpy.context.scene.objects if o.type == "MESH"]
v = np.array([tuple(v.co) for o in objs for v in o.data.vertices])
u, inv = np.unique(np.round(v / 1e-5).astype(int), axis=0, return_inverse=True)
u = u * 1e-5
adj = [set() for _ in u]
off = 0
faces = []
for o in objs:
for p in o.data.polygons:
faces.append(inv[off + np.array(p.vertices)])
for e in o.data.edges:
a, b = inv[off + np.array(e.vertices)]
adj[a].add(b)
adj[b].add(a)
off += len(o.data.vertices)
r = set(range(len(u)))
comps = []
while r:
a = r.pop()
c = [a]
q = [a]
while q:
for b in adj[q.pop()]:
if b in r:
r.remove(b)
q.append(b)
c.append(b)
comps.append(c)
comps.sort(key=len, reverse=True)
main = set(comps[0])
tree = BVHTree.FromPolygons(u, [f for f in faces if f[0] in main], all_triangles=True)
removed = set()
report = []
for c in comps[1:]:
votes = []
for vertex in u[c]:
hitvotes = []
for d in [(1, 0.231, 0.451), (-0.21, 1, 0.323), (0.371, 0.143, 1)]:
origin = Vector(vertex)
direction = Vector(d).normalized()
count = 0
for i in range(50):
hit, normal, index, dist = tree.ray_cast(origin, direction)
if hit is None:
break
count += 1
origin = hit + direction * 1e-6
hitvotes.append(count % 2)
votes.append(sum(hitvotes) >= 2)
inside = float(np.mean(votes))
if inside == 1.0:
removed.update(c)
report.append({"vertices": len(c), "inside_fraction": inside, "removed": inside == 1.0})
print("REMOVING", len(removed), "internal vertices", flush=True)
off = 0
for obj in objs:
ids = {i for i in range(len(obj.data.vertices)) if inv[off + i] in removed}
off += len(obj.data.vertices)
bm = bmesh.new()
bm.from_mesh(obj.data)
bm.verts.ensure_lookup_table()
bmesh.ops.delete(bm, geom=[bm.verts[i] for i in ids], context="VERTS")
bm.to_mesh(obj.data)
bm.free()
bpy.ops.object.select_all(action="SELECT")
bpy.ops.export_scene.gltf(filepath=str(out / "character-static.glb"), export_format="GLB", use_selection=True)
(out / "internal-shell-cleanup.json").write_text(
json.dumps(
{
"removed_vertices": len(removed),
"components": report,
"criterion": "Every vertex inside the largest outer shell by majority parity of three ray directions",
},
indent=2,
)
)
|