"""Drop loose mesh islands that TokenRig discards (thin floating tail tips, specks) so the baked game mesh and the rig agree. blender -b --factory-startup --python Tools/model_tools/bl_keep_main_island.py -- in.glb out.glb [--min-fraction 0.05] Islands are found after welding UV-seam duplicates (1e-4); only the welding used for the test, the export keeps the original split vertices and UVs. """ import argparse, json, sys from pathlib import Path import bmesh, bpy p = argparse.ArgumentParser(); p.add_argument('source', type=Path); p.add_argument('output', type=Path) p.add_argument('--min-fraction', type=float, default=.05, help='keep islands with at least this fraction of the largest island vertices') a = p.parse_args(sys.argv[sys.argv.index('--') + 1:]) bpy.ops.wm.read_factory_settings(use_empty=True); bpy.ops.import_scene.gltf(filepath=str(a.source.resolve())) report = [] for o in [o for o in bpy.data.objects if o.type == 'MESH']: bm = bmesh.new(); bm.from_mesh(o.data); bm.verts.ensure_lookup_table() # Union-find over original vertices, joining edges and co-located (seam-split) vertices. parent = list(range(len(bm.verts))) def find(i): while parent[i] != i: parent[i] = parent[parent[i]]; i = parent[i] return i def union(i, j): parent[find(i)] = find(j) for e in bm.edges: union(e.verts[0].index, e.verts[1].index) cells = {} for v in bm.verts: key = tuple(round(c / 1e-4) for c in v.co) if key in cells: union(v.index, cells[key]) else: cells[key] = v.index sizes = {} for v in bm.verts: r = find(v.index); sizes[r] = sizes.get(r, 0) + 1 largest = max(sizes.values()); keep = {r for r, n in sizes.items() if n >= largest * a.min_fraction} drop = [v for v in bm.verts if find(v.index) not in keep] report.append(dict(object=o.name, islands=len(sizes), kept=len(keep), droppedVertices=len(drop), droppedIslandSizes=sorted((n for r, n in sizes.items() if r not in keep), reverse=True)[:20])) bmesh.ops.delete(bm, geom=drop, context='VERTS'); bm.to_mesh(o.data); bm.free(); o.data.update() a.output.parent.mkdir(parents=True, exist_ok=True) bpy.ops.export_scene.gltf(filepath=str(a.output.resolve()), export_format='GLB') rec = dict(source=str(a.source), output=str(a.output), minFraction=a.min_fraction, objects=report, note='Only loose islands removed; remaining geometry, UVs and textures unchanged.') a.output.with_suffix('.islands.json').write_text(json.dumps(rec, indent=2), encoding='utf-8'); print(json.dumps(rec))