"""Slim TRELLIS GLBs to prop grade, in place.
/Applications/Blender.app/Contents/MacOS/Blender -b -P tools/slim_glb.py --
[max_tris] [tex_px]
TRELLIS ships ~200k-face meshes with PNG textures -- 20MB+ per prop, which is
lovely and also insane for background clutter. Per GLB: decimate every mesh
down to max_tris (default 18k), scale every image to tex_px (default 1024),
re-export with JPEG textures. Typical result is a 10x size drop with no
visible cost at prop distance. Prints "SLIM name before->after KB" per file.
"""
import os
import sys
import bpy
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
DIR = argv[0] if argv else "assets/tools"
MAX_TRIS = int(argv[1]) if len(argv) > 1 else 18000
TEX = int(argv[2]) if len(argv) > 2 else 1024
def reset():
bpy.ops.wm.read_factory_settings(use_empty=True)
def tri_count(obj):
m = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
n = len(m.loop_triangles)
obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh_clear()
return n
def slim(path):
reset()
before = os.path.getsize(path) // 1024
bpy.ops.import_scene.gltf(filepath=path)
meshes = [o for o in bpy.data.objects if o.type == "MESH"]
total = 0
for o in meshes:
o.data.calc_loop_triangles()
total += len(o.data.loop_triangles)
if total > MAX_TRIS:
ratio = MAX_TRIS / float(total)
for o in meshes:
bpy.context.view_layer.objects.active = o
mod = o.modifiers.new("slim", "DECIMATE")
mod.ratio = ratio
bpy.ops.object.modifier_apply(modifier=mod.name)
for img in bpy.data.images:
if img.size[0] > TEX or img.size[1] > TEX:
img.scale(min(img.size[0], TEX), min(img.size[1], TEX))
kwargs = {
"filepath": path,
"export_format": "GLB",
"export_image_format": "JPEG",
"export_yup": True,
}
# quality kwarg name moved between Blender versions; feed whichever exists
props = bpy.ops.export_scene.gltf.get_rna_type().properties.keys()
for k in ("export_jpeg_quality", "export_image_quality"):
if k in props:
kwargs[k] = 80
bpy.ops.export_scene.gltf(**kwargs)
print("SLIM %s %d->%dKB (%d tris)" % (
os.path.basename(path), before, os.path.getsize(path) // 1024,
min(total, MAX_TRIS)), flush=True)
for f in sorted(os.listdir(DIR)):
if f.endswith(".glb"):
try:
slim(os.path.join(DIR, f))
except Exception as e: # keep the batch alive; one dud mustn't kill it
print("SLIM_FAIL %s %s" % (f, str(e)[:160]), flush=True)
print("SLIM_COMPLETE", flush=True)