File size: 4,544 Bytes
0e805d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""
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)