- UI SFX v3: procedurally synthesized one-shots (Tools/synth_ui_sfx.py) wired to buttons, rewards, errors and red dots via UiSfx, gated by the SFX setting. - Five formal enemies (Hornlet, Dashlet, Shellhorn, HornCaptain, GreatHorn): Hunyuan 50k -> 2000-tri XPbr, DGXA TokenRig, Idle/Walk/Attack, nested per type in PrototypeArena; PrototypeBattle.Enemy gains presentation-only Moving/Strikes. - Formal partners for all ten pets in the arena and portraits, including new VineSprite, ThunderLeopard and TideWhale. - Pipeline fixes: reject local TokenRig fallback, drop loose mesh islands, configurable Unity run_script timeout, companion static stage. - Also includes concurrent work from another tool in this working tree (FlameLion, enemy combat FX, challenge mode, home explorer, server updates). - Resources registered in Doc/ResDesc_*.md; verified on isolated :18081. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39 lines
2.5 KiB
Python
39 lines
2.5 KiB
Python
"""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))
|