View difference between Paste ID: sm7NY40N and U3Q3ddxY
SHOW: | | - or go back to the newest paste.
1
# bitcpy
2
# CaveTroll
3
4
bl_info = {
5
    "name": "Legend of Grimrock Mesh Format (.mesh)",
6
    "author": "",
7
    "version": (1, 0, 1),
8
    "blender": (2, 5, 7),
9
    "api": 36339,
10
    "location": "File > Import > Legend of Grimrock Mesh (.mesh)",
11
    "description": "Import Legend of Grimrock Meshes (.mesh)",
12
    "warning": "",
13
    "wiki_url": "",
14
    "tracker_url": "",
15
    "category": "Import-Export"}
16
17
import os
18
import struct
19
20
import bpy
21
from mathutils import *
22
23
from bpy.props import *
24
from bpy_extras.io_utils import ExportHelper, ImportHelper
25
from bpy_extras.image_utils import load_image
26
27
class _mesh_material(object):
28
    __slots__ = (
29
        "bl_mat",
30
        "bl_image",
31
        "name",
32
        "index_offset",
33
        "num_primitives",
34
        )
35
        
36
    def __init__(self):
37
        self.bl_mat = None
38
        self.bl_image = None
39
        self.name = "<Empty>"
40
        self.index_offset = 0
41
        self.num_primitives = 0
42
43
# Reads file magic from file
44
def read_magic(file_object, endian = '<'):
45
    data = struct.unpack(endian+'4s', file.read(4))[0]
46
    return data;
47
48
# Read signed integer from file
49
def read_int(file_object, endian = '<'):
50
    data = struct.unpack(endian+'i', file_object.read(4))[0]
51
    return data
52
53
def read_int2(file_object, endian = '<'):
54
    data = struct.unpack(endian+'ii', file_object.read(8))
55
    return data
56
    
57
def read_int3(file_object, endian = '<'):
58
    data = struct.unpack(endian+'iii', file_object.read(12))
59
    return data
60
    
61
def read_int4(file_object, endian = '<'):
62
    data = struct.unpack(endian+'iiii', file_object.read(16))
63
    return data
64
65
# Read floating point number from file
66
def read_float(file_object, endian = '<'):
67
    data = struct.unpack(endian+'f', file_object.read(4))[0]
68
    return data
69
70
def read_float2(file_object, endian = '<'):
71
    data = struct.unpack(endian+'ff', file_object.read(8))
72
    return data
73
74
def read_float3(file_object, endian = '<'):
75
    data = struct.unpack(endian+'fff', file_object.read(12))
76
    return data
77
78
def read_float4(file_object, endian = '<'):
79
    data = struct.unpack(endian+'ffff', file_object.read(16))
80
    return data
81
82
# Read unsigned bytes from file
83
def read_byte(file_object, endian = '<'):
84
    data = struct.unpack(endian+'B', file_object.read(1))[0]
85
    return data
86
87
def read_byte2(file_object, endian = '<'):
88
    data = struct.unpack(endian+'BB', file_object.read(2))
89
    return data
90
91
def read_byte3(file_object, endian = '<'):
92
    data = struct.unpack(endian+'BBB', file_object.read(3))
93
    return data
94
95
def read_byte4(file_object, endian = '<'):
96
    data = struct.unpack(endian+'BBBB', file_object.read(4))
97
    return data
98
99
# Read string from file
100
def read_string(file_object, num, endian = '<'):
101
    raw_string = struct.unpack(endian+str(num)+'s', file_object.read(num))[0]
102
    data = raw_string.decode("utf-8", "ignore")
103
    return data
104
105
106
# Build content base path
107
def build_assets_path(filename):
108
    #filename = "C:/grimrock/assets/models/wall_sets/dungeon/floor_01.mesh"
109
    pathname = os.path.dirname(filename)
110
    
111
    index = pathname.rfind("assets")
112
    if index < 0:
113
        return pathname
114
    
115
    return pathname[0:index]
116
117
118
# Parse the '/assets/materials/default.materials' file
119
def parse_material_file( path_assets, find_material ):
120
121
    # diffuse, normal, specular
122
    texture_names = ["", "", ""]
123
124
    filename = os.path.join(path_assets, "assets/materials/default.materials")
125
    if os.path.exists(filename) == False:
126
        return texture_names
127
        
128
    file = open(filename, 'r')
129
130
    # Parse material (very primitive ...)
131
    material_scope = False
132
    material_found = False
133
    for line in file.readlines():
134
        tokens = line.split()
135
        num = len(tokens)
136
        if num <= 0:
137
            continue
138
        token0 = tokens[0].strip()
139
        if material_scope == True:
140
            if token0 == "}":
141
                if material_found == True:
142
                    break
143
                material_scope = False
144
                continue
145
            if num < 3:
146
                continue
147
            token1 = tokens[1].strip()
148
            token2 = tokens[2].strip()
149
            
150
            if material_found == True:
151
                if (token0 == "DiffuseMap" or token0 == "SpecularMap" or token0 == "NormalMap") and token1 == "=":
152
                    if token2.endswith(','):
153
                        token2 = token2[0:-1]
154
                    if token2.startswith('"') and token2.endswith('"'):
155
                        token2 = token2[1:-1]
156
                    if token0 == "DiffuseMap":
157
                        texture_names[0] = token2
158
                    elif token0 == "NormalMap":
159
                        texture_names[1] = token2
160
                    elif token0 == "SpecularMap":
161
                        texture_names[2] = token2
162
            else:
163
                if token0 == "Name" and token1 == "=":
164
                    if token2.endswith(','):
165
                        token2 = token2[0:-1]
166
                    if token2.startswith('"') and token2.endswith('"'):
167
                        token2 = token2[1:-1]
168
                    if token2 == find_material:
169
                        material_found = True
170
        else:
171
            if token0 == "material{":
172
                material_scope = True
173
            elif num > 1 and (token0 == "material" and tokens[1] == "{"):
174
                material_scope = True
175
    file.close()
176
    
177
    for i in range(len(texture_names)):
178
        texture_name = texture_names[i]
179
        if len(texture_name) <= 0:
180
            continue
181
            
182
        # Check if source exists (.tga) or whatever was given in material file
183
        source_name = os.path.join(path_assets, texture_name)
184
        if os.path.exists(source_name):
185
            texture_name[i] = source_name
186
            continue
187
        
188
        # Check for .d3d9_texture instead
189
        base_name, ext = os.path.splitext(source_name)
190
        dxt_name = base_name + ".d3d9_texture"
191
        
192
        if os.path.exists(dxt_name):
193
            texture_names[i] = dxt_name
194
            continue
195
            
196
        # Didn't find file, just remove it
197
        texture_names[i] = ""
198
    
199
    return texture_names
200
201
202
def load_mesh(filename, context):
203
    # Dig out file base name and extension
204
    name, ext = os.path.splitext(os.path.basename(filename))
205
    
206
    path_assets = build_assets_path(filename)
207
    
208
    print("Opening file: " + filename)
209
    file = open(filename, 'rb')
210
    try:
211
        magic = struct.unpack("<4s", file.read(4))[0]
212
    except:
213
        print("Error parsing file header!")
214
        file.close()
215
        return
216
        
217
    # Figure out if it's a valid mesh file
218
    if magic != b'MESH':
219
        print("Not a valid mesh model!")
220
        file.close()
221
        return
222
223
    mesh_unknown = read_int(file)
224
    num_vertices = read_int(file)
225
    
226
    # This will store all vertices and index info
227
    vertex_set = []
228
    indices = []
229
    materials = []
230
    
231
    for i in range(15):
232
        # Assumption that this is what it actually means ...
233
        data_type = read_int(file)
234
        num_comp = read_int(file)
235
        byte_width = read_int(file)
236
        
237
        print( "Vertex Attrib %d" % i )
238
        print( "\tData Type: %d" % data_type )
239
        print( "\tNum Components: %d" % num_comp )
240
        print( "\tByte Width: %d" % byte_width )
241
        
242
        # Skip empty
243
        if data_type == 0 and num_comp == 0 and byte_width == 0:
244
            # Add empty set
245
            vertex_set.append([])
246
            continue
247
248
        # Report unknown data types (?)
249
        type_size = 0
250
        if data_type == 0:
251
            type_size = 1   # byte ?
252
        elif data_type == 2:
253
            type_size = 4   # int32 ?
254
        elif data_type == 3:
255
            type_size = 4   # float
256
        else:
257
            print("Valid mesh, but not supported vertex data: data_type = %d" % data_type)
258
            file.close()
259
            return
260
        
261
        # byte_width should be type_size*num_comp
262
        if byte_width != (num_comp*type_size):
263
            print("Valid mesh, but not supported vertex data: data_size = %d" % data_size)
264
            file.close()
265
            return
266
        
267
        vertex_data = []
268
        for j in range(num_vertices):
269
            data = []
270
            if num_comp == 2:
271
                if data_type == 0:
272
                    data = read_byte2(file)
273
                elif data_type == 2:
274
                    data = read_int2(file)
275
                elif data_type == 3:
276
                    data = read_float2(file)
277
            elif num_comp == 3:
278
                if data_type == 0:
279
                    data = read_byte3(file)
280
                elif data_type == 2:
281
                    data = read_int3(file)
282
                elif data_type == 3:
283
                    data = read_float3(file)
284
            elif num_comp == 4:
285
                if data_type == 0:
286
                    data = read_byte4(file)
287
                elif data_type == 2:
288
                    data = read_int4(file)
289
                elif data_type == 3:
290
                    data = read_float4(file)
291
            vertex_data.append(data)
292
        
293
        vertex_set.append(vertex_data)
294
295
    num_indices = read_int(file)
296
    for i in range(num_indices):
297
        index = read_int(file)
298
        indices.append(index)
299
300
    num_materials = read_int(file)
301
    for i in range(num_materials):
302
        name_length = read_int(file)
303
        material_name = read_string(file, name_length)
304
        print("Material Name: " + material_name)
305
        
306
        material_unknown = read_int(file)
307
        #if pre_material != 2:
308
        #    print("Valid mesh, but not supported material data: pre_material = " + str(pre_material))
309
        #    file.close()
310
        #    return
311
         
312
        index_offset = read_int(file)
313
        num_primitives = read_int(file)
314
        
315
        material = _mesh_material()
316
        material.name = material_name
317
        material.index_offset = index_offset
318
        material.num_primitives = num_primitives
319
        
320
        materials.append(material)
321
    
322
    # Origin x,y,z (?)
323
    origin = read_float3(file)
324
    
325
    # ?
326
    read_float(file)
327
    
328
    # Bounds min x,y,z (?)
329
    bounds_min = read_float3( file )
330
    
331
    # Bounds max x,y,z (?)
332
    bounds_max = read_float3( file )
333
    
334
    file.close()
335
    
336
    # Build the blender object
337
    build_objects(name, vertex_set, indices, materials, path_assets)
338
    
339
    
340
def add_material_texture( bl_material, type, filename ):
341
    # Create texture
342
    bl_texture = bpy.data.textures.new(name=type, type="IMAGE")
343
    
344
    # Try to load image
345
    image = load_image(filename)
346
    if image:
347
        bl_texture.image = image
348
    
349
    # Create texture slot and link to 'uvset1'
350
    mtex = bl_material.texture_slots.add()
351
    mtex.texture = bl_texture
352
    mtex.texture_coords = "UV"
353
    
354
    # Ensure diffuse/specular/normal uses proper types
355
    mtex.use_map_color_diffuse = (type == "diffuse")
356
    mtex.use_map_color_spec = (type == "specular")
357
    mtex.use_map_normal = (type == "normal")
358
    
359
    return image
360
    
361
362
def create_material( path_assets, material ):
363
    # Parse material file for the actual texture images
364
    texture_names = parse_material_file(path_assets, material.name)
365
    
366
    # Create blender material
367
    bl_material = bpy.data.materials.new(material.name)
368
    
369
    # Create textures and texture slots on material
370
    image_diffuse = add_material_texture(bl_material, "diffuse", texture_names[0])
371
    image_normal = add_material_texture(bl_material, "normal", texture_names[1])
372
    image_specular = add_material_texture(bl_material, "specular", texture_names[2])
373
    
374
    # Assign material
375
    material.bl_mat = bl_material
376
    
377
    # Assign preview display image for material
378
    material.bl_image = image_diffuse
379
380
381
def build_objects(name, vertex_set, indices, materials, path_assets):
382
    print("Building Blender data")
383-
    print("Vertex sets: %d" % len(vertex_set))
383+
384
    vertices = vertex_set[0]
385
    normals = vertex_set[1]
386
    tangents = vertex_set[2]
387
    bitangents = vertex_set[3]
388
    byte_colors = vertex_set[4]
389
    texcoords = vertex_set[5]
390
391
    num_vertices = len(vertices)
392
    num_indices = len(indices)
393
    num_primitives = int(num_indices / 3)
394
    num_materials = len(materials)
395
396
    print("Num vertices: " + str(num_vertices))
397
    print("Num indices: " + str(num_indices))
398
    print("Num primitives: " + str(num_primitives))
399
    print("Num materials: " + str(num_materials))
400
    
401
    # Convert color attribute from [0, 255] to [0, 1] range
402
    float_colors = []
403
    if len(byte_colors) > 0:
404
        for i in range(num_vertices):
405
            r = byte_colors[i][0] / 255.0
406
            g = byte_colors[i][1] / 255.0
407
            b = byte_colors[i][2] / 255.0
408
            a = byte_colors[i][3] / 255.0
409
            float_colors.append([r, g, b, a])
410
    
411-
        print(float_colors)
411+
412
    if bpy.ops.object.mode_set.poll():
413
        bpy.ops.object.mode_set(mode='OBJECT')
414
415
    # Create mesh
416
    me = bpy.data.meshes.new(name)
417
    
418
    # Add vertices
419
    me.vertices.add(num_vertices)
420
    for i in range(num_vertices):
421
        # Flip y-z component so it looks more natural in blender
422
        me.vertices[i].co[0] = vertices[i][0]
423
        me.vertices[i].co[1] = vertices[i][2]
424
        me.vertices[i].co[2] = vertices[i][1]
425
426
    # Add faces
427
    me.faces.add(num_primitives)
428
    for fi in range(num_primitives):
429
        idx = fi * 3
430
        for i in range(3):
431
            # Flip indices because of vertex flip
432
            me.faces[fi].vertices_raw[2-i] = indices[idx+i]
433
434
    # Add vertex colors
435
    if len(float_colors) > 0:
436
        clrmap = me.vertex_colors.new("clrset1")
437
        for fi in range(num_primitives):
438
            clrf = clrmap.data[fi]
439
            clrf.color1 = float_colors[indices[(fi*3)+2]][0:3]
440
            clrf.color2 = float_colors[indices[(fi*3)+1]][0:3]
441-
            clrf.color1 = float_colors[indices[(fi*3)+0]][0:3]
441+
442-
            clrf.color2 = float_colors[indices[(fi*3)+0]][0:3]
442+
443
    # Add uv map
444
    uvmap = me.uv_textures.new("uvset1")
445
    for fi in range(num_primitives):
446
        uvf = uvmap.data[fi]
447
        uvf.uv1 = texcoords[indices[(fi*3)+2]]
448
        uvf.uv2 = texcoords[indices[(fi*3)+1]]
449-
        uvf.uv1 = texcoords[indices[(fi*3)+0]]
449+
        uvf.uv3 = texcoords[indices[(fi*3)+0]]
450
        
451-
        uvf.uv3 = texcoords[indices[(fi*3)+2]]
451+
452
        uvf.uv1.y = 1.0 - uvf.uv1.y
453
        uvf.uv2.y = 1.0 - uvf.uv2.y
454
        uvf.uv3.y = 1.0 - uvf.uv3.y
455
456
    # Create object and link with scene
457
    ob = bpy.data.objects.new(name, me)
458
    bpy.context.scene.objects.link(ob)
459
460
    # Add/create all our materials
461
    for material in materials:
462
        create_material(path_assets, material)
463
464
    # Assign materials to mesh
465
    for mi in range(num_materials):
466
        material = materials[mi]
467
        me.materials.append(material.bl_mat)
468
        
469
        face_offset = int(material.index_offset / 3)
470
        for fi in range(material.num_primitives):
471
            me.faces[face_offset+fi].material_index = mi
472
            
473
            uvf = uvmap.data[face_offset+fi]
474
            uvf.image = material.bl_image
475
476
    # Update mesh and scene
477
    me.update()
478
    bpy.context.scene.update()
479
480
481
class IMPORT_OT_mesh(bpy.types.Operator, ImportHelper):
482
    # Import Mesh Operator.
483
    bl_idname = "import_scene.mesh"
484
    bl_label = "Import Mesh"
485
    bl_description = "Import a Legend of Grimrock mesh"
486
    bl_options = { 'REGISTER', 'UNDO' }
487
    
488
    filepath = StringProperty(name="File Path", description="Filepath used for importing the mesh file.", maxlen=1024, default="")
489
    
490
    def execute(self, context):
491
        load_mesh(self.filepath, context)
492
        return {'FINISHED'}
493
494
    def invoke(self, context, event):
495
        wm = context.window_manager
496
        wm.fileselect_add(self)
497
        return {'RUNNING_MODAL'}
498
499
500
def menu_func(self, context):
501
    self.layout.operator(IMPORT_OT_mesh.bl_idname, text="Legend of Grimrock Mesh (.mesh)")
502
503
504
def register():
505
    bpy.utils.register_module(__name__)
506
    bpy.types.INFO_MT_file_import.append(menu_func)
507
508
509
def unregister():
510
    bpy.utils.unregister_module(__name__)
511
    bpy.types.INFO_MT_file_import.remove(menu_func)
512
513
514
if __name__ == "__main__":
515
    register()