Guest User

Untitled

a guest
May 1st, 2012
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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))
  384.  
  385.     vertices = vertex_set[0]
  386.     normals = vertex_set[1]
  387.     tangents = vertex_set[2]
  388.     bitangents = vertex_set[3]
  389.     byte_colors = vertex_set[4]
  390.     texcoords = vertex_set[5]
  391.  
  392.     num_vertices = len(vertices)
  393.     num_indices = len(indices)
  394.     num_primitives = int(num_indices / 3)
  395.     num_materials = len(materials)
  396.  
  397.     print("Num vertices: " + str(num_vertices))
  398.     print("Num indices: " + str(num_indices))
  399.     print("Num primitives: " + str(num_primitives))
  400.     print("Num materials: " + str(num_materials))
  401.    
  402.     # Convert color attribute from [0, 255] to [0, 1] range
  403.     float_colors = []
  404.     if len(byte_colors) > 0:
  405.         for i in range(num_vertices):
  406.             r = byte_colors[i][0] / 255.0
  407.             g = byte_colors[i][1] / 255.0
  408.             b = byte_colors[i][2] / 255.0
  409.             a = byte_colors[i][3] / 255.0
  410.             float_colors.append([r, g, b, a])
  411.         print(float_colors)
  412.    
  413.     # Before adding any meshes or armatures go into Object mode.
  414.     if bpy.ops.object.mode_set.poll():
  415.         bpy.ops.object.mode_set(mode='OBJECT')
  416.  
  417.     # Create mesh
  418.     me = bpy.data.meshes.new(name)
  419.    
  420.     # Add vertices
  421.     me.vertices.add(num_vertices)
  422.     for i in range(num_vertices):
  423.         # Flip y-z component so it looks more natural in blender
  424.         me.vertices[i].co[0] = vertices[i][0]
  425.         me.vertices[i].co[1] = vertices[i][2]
  426.         me.vertices[i].co[2] = vertices[i][1]
  427.  
  428.     # Add faces
  429.     me.faces.add(num_primitives)
  430.     for fi in range(num_primitives):
  431.         idx = fi * 3
  432.         for i in range(3):
  433.             # Flip indices because of vertex flip
  434.             me.faces[fi].vertices_raw[2-i] = indices[idx+i]
  435.  
  436.     # Add vertex colors
  437.     if len(float_colors) > 0:
  438.         clrmap = me.vertex_colors.new("clrset1")
  439.         for fi in range(num_primitives):
  440.             clrf = clrmap.data[fi]
  441.             clrf.color1 = float_colors[indices[(fi*3)+0]][0:3]
  442.             clrf.color2 = float_colors[indices[(fi*3)+0]][0:3]
  443.             clrf.color3 = float_colors[indices[(fi*3)+0]][0:3]
  444.  
  445.     # Add uv map
  446.     uvmap = me.uv_textures.new("uvset1")
  447.     for fi in range(num_primitives):
  448.         uvf = uvmap.data[fi]
  449.         uvf.uv1 = texcoords[indices[(fi*3)+0]]
  450.         uvf.uv2 = texcoords[indices[(fi*3)+1]]
  451.         uvf.uv3 = texcoords[indices[(fi*3)+2]]
  452.        
  453.         # Flip uv coordinates
  454.         uvf.uv1.y = 1.0 - uvf.uv1.y
  455.         uvf.uv2.y = 1.0 - uvf.uv2.y
  456.         uvf.uv3.y = 1.0 - uvf.uv3.y
  457.  
  458.     # Create object and link with scene
  459.     ob = bpy.data.objects.new(name, me)
  460.     bpy.context.scene.objects.link(ob)
  461.  
  462.     # Add/create all our materials
  463.     for material in materials:
  464.         create_material(path_assets, material)
  465.  
  466.     # Assign materials to mesh
  467.     for mi in range(num_materials):
  468.         material = materials[mi]
  469.         me.materials.append(material.bl_mat)
  470.        
  471.         face_offset = int(material.index_offset / 3)
  472.         for fi in range(material.num_primitives):
  473.             me.faces[face_offset+fi].material_index = mi
  474.            
  475.             uvf = uvmap.data[face_offset+fi]
  476.             uvf.image = material.bl_image
  477.  
  478.     # Update mesh and scene
  479.     me.update()
  480.     bpy.context.scene.update()
  481.  
  482.  
  483. class IMPORT_OT_mesh(bpy.types.Operator, ImportHelper):
  484.     # Import Mesh Operator.
  485.     bl_idname = "import_scene.mesh"
  486.     bl_label = "Import Mesh"
  487.     bl_description = "Import a Legend of Grimrock mesh"
  488.     bl_options = { 'REGISTER', 'UNDO' }
  489.    
  490.     filepath = StringProperty(name="File Path", description="Filepath used for importing the mesh file.", maxlen=1024, default="")
  491.    
  492.     def execute(self, context):
  493.         load_mesh(self.filepath, context)
  494.         return {'FINISHED'}
  495.  
  496.     def invoke(self, context, event):
  497.         wm = context.window_manager
  498.         wm.fileselect_add(self)
  499.         return {'RUNNING_MODAL'}
  500.  
  501.  
  502. def menu_func(self, context):
  503.     self.layout.operator(IMPORT_OT_mesh.bl_idname, text="Legend of Grimrock Mesh (.mesh)")
  504.  
  505.  
  506. def register():
  507.     bpy.utils.register_module(__name__)
  508.     bpy.types.INFO_MT_file_import.append(menu_func)
  509.  
  510.  
  511. def unregister():
  512.     bpy.utils.unregister_module(__name__)
  513.     bpy.types.INFO_MT_file_import.remove(menu_func)
  514.  
  515.  
  516. if __name__ == "__main__":
  517.     register()
Advertisement
Add Comment
Please, Sign In to add comment