Spaces:
Runtime error
Runtime error
| """ | |
| Blender optimization script for game-ready assets. | |
| Run with: blender --background --python blender_optimize.py -- input.glb output.glb | |
| """ | |
| import bpy | |
| import sys | |
| from pathlib import Path | |
| from mathutils import Vector | |
| def optimize_asset(input_path: str, output_path: str): | |
| """Optimize GLB for game engine use.""" | |
| print(f"[Blender] Optimizing: {input_path}") | |
| # Clear scene | |
| bpy.ops.object.select_all(action='SELECT') | |
| bpy.ops.object.delete() | |
| # Import GLB | |
| bpy.ops.import_scene.gltf(filepath=input_path) | |
| # Get imported object | |
| obj = bpy.context.selected_objects[0] | |
| bpy.context.view_layer.objects.active = obj | |
| # 1. Normalize scale to 2m height | |
| bbox = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] | |
| height = max(v.z for v in bbox) - min(v.z for v in bbox) | |
| if height > 0: | |
| scale_factor = 2.0 / height | |
| obj.scale = (scale_factor, scale_factor, scale_factor) | |
| bpy.ops.object.transform_apply(scale=True) | |
| # 2. Clean mesh topology | |
| bpy.ops.object.mode_set(mode='EDIT') | |
| bpy.ops.mesh.select_all(action='SELECT') | |
| bpy.ops.mesh.remove_doubles(threshold=0.0001) | |
| bpy.ops.mesh.normals_make_consistent(inside=False) | |
| bpy.ops.mesh.delete_loose() | |
| bpy.ops.mesh.dissolve_degenerate(threshold=0.0001) | |
| bpy.ops.object.mode_set(mode='OBJECT') | |
| # 3. Quad remesh for better topology | |
| mod = obj.modifiers.new(name="Remesh", type='REMESH') | |
| mod.mode = 'SHARP' | |
| mod.octree_depth = 7 # ~7,500 polygons | |
| mod.sharpness = 1.0 | |
| mod.use_smooth_shade = True | |
| bpy.ops.object.modifier_apply(modifier="Remesh") | |
| # 4. Smart UV unwrap | |
| bpy.ops.object.mode_set(mode='EDIT') | |
| bpy.ops.mesh.select_all(action='SELECT') | |
| bpy.ops.uv.smart_project( | |
| angle_limit=66.0, | |
| island_margin=0.02, | |
| area_weight=1.0, | |
| correct_aspect=True, | |
| scale_to_bounds=True | |
| ) | |
| bpy.ops.object.mode_set(mode='OBJECT') | |
| # 5. Apply smooth shading | |
| bpy.ops.object.shade_smooth() | |
| obj.data.use_auto_smooth = True | |
| obj.data.auto_smooth_angle = 0.523599 # 30 degrees | |
| # 6. Generate LOD levels | |
| lod_levels = [ | |
| ("LOD0", 1.0), # 100% - original | |
| ("LOD1", 0.5), # 50% - medium distance | |
| ("LOD2", 0.25), # 25% - far distance | |
| ] | |
| for lod_name, ratio in lod_levels: | |
| lod_obj = obj.copy() | |
| lod_obj.data = obj.data.copy() | |
| lod_obj.name = f"{obj.name}_{lod_name}" | |
| bpy.context.collection.objects.link(lod_obj) | |
| if ratio < 1.0: | |
| # Apply decimate modifier | |
| bpy.context.view_layer.objects.active = lod_obj | |
| mod = lod_obj.modifiers.new(name="Decimate", type='DECIMATE') | |
| mod.ratio = ratio | |
| bpy.ops.object.modifier_apply(modifier="Decimate") | |
| # 7. Generate collision mesh | |
| collision_obj = obj.copy() | |
| collision_obj.data = obj.data.copy() | |
| collision_obj.name = f"{obj.name}_collision" | |
| bpy.context.collection.objects.link(collision_obj) | |
| bpy.context.view_layer.objects.active = collision_obj | |
| # Simplify heavily for collision | |
| mod = collision_obj.modifiers.new(name="Decimate", type='DECIMATE') | |
| mod.ratio = 0.1 # 10% of original | |
| bpy.ops.object.modifier_apply(modifier="Decimate") | |
| # Convex hull for physics | |
| bpy.ops.object.mode_set(mode='EDIT') | |
| bpy.ops.mesh.select_all(action='SELECT') | |
| bpy.ops.mesh.convex_hull() | |
| bpy.ops.object.mode_set(mode='OBJECT') | |
| # 8. Export with Draco compression | |
| bpy.ops.export_scene.gltf( | |
| filepath=output_path, | |
| export_format='GLB', | |
| export_draco_mesh_compression_enable=True, | |
| export_draco_mesh_compression_level=6, | |
| export_draco_position_quantization=14, | |
| export_draco_normal_quantization=10, | |
| export_draco_texcoord_quantization=12, | |
| export_materials='EXPORT', | |
| export_colors=True, | |
| export_cameras=False, | |
| export_lights=False, | |
| export_apply=True, | |
| export_yup=True | |
| ) | |
| print(f"[Blender] Optimization complete: {output_path}") | |
| if __name__ == "__main__": | |
| # Parse command line arguments | |
| argv = sys.argv | |
| argv = argv[argv.index("--") + 1:] # Get args after -- | |
| if len(argv) < 2: | |
| print("Usage: blender --background --python blender_optimize.py -- input.glb output.glb") | |
| sys.exit(1) | |
| input_path = argv[0] | |
| output_path = argv[1] | |
| optimize_asset(input_path, output_path) | |