Advertisement
Ember

wfeafaaaaa

May 13th, 2014
203
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 38.93 KB | None | 0 0
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. #  This program is free software; you can redistribute it and/or
  4. #  modify it under the terms of the GNU General Public License
  5. #  as published by the Free Software Foundation; either version 2
  6. #  of the License, or (at your option) any later version.
  7. #
  8. #  This program is distributed in the hope that it will be useful,
  9. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. #  GNU General Public License for more details.
  12. #
  13. #  You should have received a copy of the GNU General Public License
  14. #  along with this program; if not, write to the Free Software Foundation,
  15. #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18.  
  19. # <pep8 compliant>
  20.  
  21. # Script copyright (C) Bob Holcomb
  22. # Contributors: Campbell Barton, Bob Holcomb, Richard Lärkäng, Damien McGinnes, Mark Stijnman
  23.  
  24. """
  25. Exporting is based on 3ds loader from www.gametutorials.com(Thanks DigiBen) and using information
  26. from the lib3ds project (http://lib3ds.sourceforge.net/) sourcecode.
  27. """
  28.  
  29. ######################################################
  30. # Data Structures
  31. ######################################################
  32.  
  33. #Some of the chunks that we will export
  34. #----- Primary Chunk, at the beginning of each file
  35. PRIMARY = 0x4D4D
  36.  
  37. #------ Main Chunks
  38. OBJECTINFO = 0x3D3D  # This gives the version of the mesh and is found right before the material and object information
  39. VERSION = 0x0002  # This gives the version of the .3ds file
  40. KFDATA = 0xB000  # This is the header for all of the key frame info
  41.  
  42. #------ sub defines of OBJECTINFO
  43. MATERIAL = 45055  # 0xAFFF // This stored the texture info
  44. OBJECT = 16384  # 0x4000 // This stores the faces, vertices, etc...
  45.  
  46. #>------ sub defines of MATERIAL
  47. MATNAME = 0xA000  # This holds the material name
  48. MATAMBIENT = 0xA010  # Ambient color of the object/material
  49. MATDIFFUSE = 0xA020  # This holds the color of the object/material
  50. MATSPECULAR = 0xA030  # SPecular color of the object/material
  51. MATSHINESS = 0xA040  # ??
  52.  
  53. MAT_DIFFUSEMAP = 0xA200  # This is a header for a new diffuse texture
  54. MAT_OPACMAP = 0xA210  # head for opacity map
  55. MAT_BUMPMAP = 0xA230  # read for normal map
  56. MAT_SPECMAP = 0xA204  # read for specularity map
  57.  
  58. #>------ sub defines of MAT_???MAP
  59. MATMAPFILE = 0xA300  # This holds the file name of a texture
  60.  
  61. MAT_MAP_TILING = 0xa351   # 2nd bit (from LSB) is mirror UV flag
  62. MAT_MAP_USCALE = 0xA354   # U axis scaling
  63. MAT_MAP_VSCALE = 0xA356   # V axis scaling
  64. MAT_MAP_UOFFSET = 0xA358  # U axis offset
  65. MAT_MAP_VOFFSET = 0xA35A  # V axis offset
  66. MAT_MAP_ANG = 0xA35C      # UV rotation around the z-axis in rad
  67.  
  68. RGB1 = 0x0011
  69. RGB2 = 0x0012
  70.  
  71. #>------ sub defines of OBJECT
  72. OBJECT_MESH = 0x4100  # This lets us know that we are reading a new object
  73. OBJECT_LIGHT = 0x4600  # This lets un know we are reading a light object
  74. OBJECT_CAMERA = 0x4700  # This lets un know we are reading a camera object
  75.  
  76. #>------ sub defines of CAMERA
  77. OBJECT_CAM_RANGES = 0x4720      # The camera range values
  78.  
  79. #>------ sub defines of OBJECT_MESH
  80. OBJECT_VERTICES = 0x4110  # The objects vertices
  81. OBJECT_FACES = 0x4120  # The objects faces
  82. OBJECT_MATERIAL = 0x4130  # This is found if the object has a material, either texture map or color
  83. OBJECT_UV = 0x4140  # The UV texture coordinates
  84. OBJECT_TRANS_MATRIX = 0x4160  # The Object Matrix
  85.  
  86. #>------ sub defines of KFDATA
  87. KFDATA_KFHDR = 0xB00A
  88. KFDATA_KFSEG = 0xB008
  89. KFDATA_KFCURTIME = 0xB009
  90. KFDATA_OBJECT_NODE_TAG = 0xB002
  91.  
  92. #>------ sub defines of OBJECT_NODE_TAG
  93. OBJECT_NODE_ID = 0xB030
  94. OBJECT_NODE_HDR = 0xB010
  95. OBJECT_PIVOT = 0xB013
  96. OBJECT_INSTANCE_NAME = 0xB011
  97. POS_TRACK_TAG = 0xB020
  98. ROT_TRACK_TAG = 0xB021
  99. SCL_TRACK_TAG = 0xB022
  100.  
  101. import struct
  102.  
  103. # So 3ds max can open files, limit names to 12 in length
  104. # this is verry annoying for filenames!
  105. name_unique = []  # stores str, ascii only
  106. name_mapping = {}  # stores {orig: byte} mapping
  107.  
  108.  
  109. def sane_name(name):
  110.     name_fixed = name_mapping.get(name)
  111.     if name_fixed is not None:
  112.         return name_fixed
  113.  
  114.     # strip non ascii chars
  115.     new_name_clean = new_name = name.encode("ASCII", "replace").decode("ASCII")[:12]
  116.     i = 0
  117.  
  118.     while new_name in name_unique:
  119.         new_name = new_name_clean + ".%.3d" % i
  120.         i += 1
  121.  
  122.     # note, appending the 'str' version.
  123.     name_unique.append(new_name)
  124.     name_mapping[name] = new_name = new_name.encode("ASCII", "replace")
  125.     return new_name
  126.  
  127.  
  128. def uv_key(uv):
  129.     return round(uv[0], 6), round(uv[1], 6)
  130.  
  131. # size defines:
  132. SZ_SHORT = 2
  133. SZ_INT = 4
  134. SZ_FLOAT = 4
  135.  
  136.  
  137. class _3ds_ushort(object):
  138.     """Class representing a short (2-byte integer) for a 3ds file.
  139.    *** This looks like an unsigned short H is unsigned from the struct docs - Cam***"""
  140.     __slots__ = ("value", )
  141.  
  142.     def __init__(self, val=0):
  143.         self.value = val
  144.  
  145.     def get_size(self):
  146.         return SZ_SHORT
  147.  
  148.     def write(self, file):
  149.         file.write(struct.pack("<H", self.value))
  150.  
  151.     def __str__(self):
  152.         return str(self.value)
  153.  
  154.  
  155. class _3ds_uint(object):
  156.     """Class representing an int (4-byte integer) for a 3ds file."""
  157.     __slots__ = ("value", )
  158.  
  159.     def __init__(self, val):
  160.         self.value = val
  161.  
  162.     def get_size(self):
  163.         return SZ_INT
  164.  
  165.     def write(self, file):
  166.         file.write(struct.pack("<I", self.value))
  167.  
  168.     def __str__(self):
  169.         return str(self.value)
  170.  
  171.  
  172. class _3ds_float(object):
  173.     """Class representing a 4-byte IEEE floating point number for a 3ds file."""
  174.     __slots__ = ("value", )
  175.  
  176.     def __init__(self, val):
  177.         self.value = val
  178.  
  179.     def get_size(self):
  180.         return SZ_FLOAT
  181.  
  182.     def write(self, file):
  183.         file.write(struct.pack("<f", self.value))
  184.  
  185.     def __str__(self):
  186.         return str(self.value)
  187.  
  188.  
  189. class _3ds_string(object):
  190.     """Class representing a zero-terminated string for a 3ds file."""
  191.     __slots__ = ("value", )
  192.  
  193.     def __init__(self, val):
  194.         assert(type(val) == bytes)
  195.         self.value = val
  196.  
  197.     def get_size(self):
  198.         return (len(self.value) + 1)
  199.  
  200.     def write(self, file):
  201.         binary_format = "<%ds" % (len(self.value) + 1)
  202.         file.write(struct.pack(binary_format, self.value))
  203.  
  204.     def __str__(self):
  205.         return self.value
  206.  
  207.  
  208. class _3ds_point_3d(object):
  209.     """Class representing a three-dimensional point for a 3ds file."""
  210.     __slots__ = "x", "y", "z"
  211.  
  212.     def __init__(self, point):
  213.         self.x, self.y, self.z = point
  214.  
  215.     def get_size(self):
  216.         return 3 * SZ_FLOAT
  217.  
  218.     def write(self, file):
  219.         file.write(struct.pack('<3f', self.x, self.y, self.z))
  220.  
  221.     def __str__(self):
  222.         return '(%f, %f, %f)' % (self.x, self.y, self.z)
  223.  
  224. # Used for writing a track
  225. '''
  226. class _3ds_point_4d(object):
  227.    """Class representing a four-dimensional point for a 3ds file, for instance a quaternion."""
  228.    __slots__ = "x","y","z","w"
  229.    def __init__(self, point=(0.0,0.0,0.0,0.0)):
  230.        self.x, self.y, self.z, self.w = point
  231.  
  232.    def get_size(self):
  233.        return 4*SZ_FLOAT
  234.  
  235.    def write(self,file):
  236.        data=struct.pack('<4f', self.x, self.y, self.z, self.w)
  237.        file.write(data)
  238.  
  239.    def __str__(self):
  240.        return '(%f, %f, %f, %f)' % (self.x, self.y, self.z, self.w)
  241. '''
  242.  
  243.  
  244. class _3ds_point_uv(object):
  245.     """Class representing a UV-coordinate for a 3ds file."""
  246.     __slots__ = ("uv", )
  247.  
  248.     def __init__(self, point):
  249.         self.uv = point
  250.  
  251.     def get_size(self):
  252.         return 2 * SZ_FLOAT
  253.  
  254.     def write(self, file):
  255.         data = struct.pack('<2f', self.uv[0], self.uv[1])
  256.         file.write(data)
  257.  
  258.     def __str__(self):
  259.         return '(%g, %g)' % self.uv
  260.  
  261.  
  262. class _3ds_rgb_color(object):
  263.     """Class representing a (24-bit) rgb color for a 3ds file."""
  264.     __slots__ = "r", "g", "b"
  265.  
  266.     def __init__(self, col):
  267.         self.r, self.g, self.b = col
  268.  
  269.     def get_size(self):
  270.         return 3
  271.  
  272.     def write(self, file):
  273.         file.write(struct.pack('<3B', int(255 * self.r), int(255 * self.g), int(255 * self.b)))
  274.  
  275.     def __str__(self):
  276.         return '{%f, %f, %f}' % (self.r, self.g, self.b)
  277.  
  278.  
  279. class _3ds_face(object):
  280.     """Class representing a face for a 3ds file."""
  281.     __slots__ = ("vindex", )
  282.  
  283.     def __init__(self, vindex):
  284.         self.vindex = vindex
  285.  
  286.     def get_size(self):
  287.         return 4 * SZ_SHORT
  288.  
  289.     # no need to validate every face vert. the oversized array will
  290.     # catch this problem
  291.  
  292.     def write(self, file):
  293.         # The last zero is only used by 3d studio
  294.         file.write(struct.pack("<4H", self.vindex[0], self.vindex[1], self.vindex[2], 0))
  295.  
  296.     def __str__(self):
  297.         return "[%d %d %d]" % (self.vindex[0], self.vindex[1], self.vindex[2])
  298.  
  299.  
  300. class _3ds_array(object):
  301.     """Class representing an array of variables for a 3ds file.
  302.  
  303.    Consists of a _3ds_ushort to indicate the number of items, followed by the items themselves.
  304.    """
  305.     __slots__ = "values", "size"
  306.  
  307.     def __init__(self):
  308.         self.values = []
  309.         self.size = SZ_SHORT
  310.  
  311.     # add an item:
  312.     def add(self, item):
  313.         self.values.append(item)
  314.         self.size += item.get_size()
  315.  
  316.     def get_size(self):
  317.         return self.size
  318.  
  319.     def validate(self):
  320.         return len(self.values) <= 65535
  321.  
  322.     def write(self, file):
  323.         _3ds_ushort(len(self.values)).write(file)
  324.         for value in self.values:
  325.             value.write(file)
  326.  
  327.     # To not overwhelm the output in a dump, a _3ds_array only
  328.     # outputs the number of items, not all of the actual items.
  329.     def __str__(self):
  330.         return '(%d items)' % len(self.values)
  331.  
  332.  
  333. class _3ds_named_variable(object):
  334.     """Convenience class for named variables."""
  335.  
  336.     __slots__ = "value", "name"
  337.  
  338.     def __init__(self, name, val=None):
  339.         self.name = name
  340.         self.value = val
  341.  
  342.     def get_size(self):
  343.         if self.value is None:
  344.             return 0
  345.         else:
  346.             return self.value.get_size()
  347.  
  348.     def write(self, file):
  349.         if self.value is not None:
  350.             self.value.write(file)
  351.  
  352.     def dump(self, indent):
  353.         if self.value is not None:
  354.             print(indent * " ",
  355.                   self.name if self.name else "[unnamed]",
  356.                   " = ",
  357.                   self.value)
  358.  
  359.  
  360. #the chunk class
  361. class _3ds_chunk(object):
  362.     """Class representing a chunk in a 3ds file.
  363.  
  364.    Chunks contain zero or more variables, followed by zero or more subchunks.
  365.    """
  366.     __slots__ = "ID", "size", "variables", "subchunks"
  367.  
  368.     def __init__(self, chunk_id=0):
  369.         self.ID = _3ds_ushort(chunk_id)
  370.         self.size = _3ds_uint(0)
  371.         self.variables = []
  372.         self.subchunks = []
  373.  
  374.     def add_variable(self, name, var):
  375.         """Add a named variable.
  376.  
  377.        The name is mostly for debugging purposes."""
  378.         self.variables.append(_3ds_named_variable(name, var))
  379.  
  380.     def add_subchunk(self, chunk):
  381.         """Add a subchunk."""
  382.         self.subchunks.append(chunk)
  383.  
  384.     def get_size(self):
  385.         """Calculate the size of the chunk and return it.
  386.  
  387.        The sizes of the variables and subchunks are used to determine this chunk\'s size."""
  388.         tmpsize = self.ID.get_size() + self.size.get_size()
  389.         for variable in self.variables:
  390.             tmpsize += variable.get_size()
  391.         for subchunk in self.subchunks:
  392.             tmpsize += subchunk.get_size()
  393.         self.size.value = tmpsize
  394.         return self.size.value
  395.  
  396.     def validate(self):
  397.         for var in self.variables:
  398.             func = getattr(var.value, "validate", None)
  399.             if (func is not None) and not func():
  400.                 return False
  401.  
  402.         for chunk in self.subchunks:
  403.             func = getattr(chunk, "validate", None)
  404.             if (func is not None) and not func():
  405.                 return False
  406.  
  407.         return True
  408.  
  409.     def write(self, file):
  410.         """Write the chunk to a file.
  411.  
  412.        Uses the write function of the variables and the subchunks to do the actual work."""
  413.         #write header
  414.         self.ID.write(file)
  415.         self.size.write(file)
  416.         for variable in self.variables:
  417.             variable.write(file)
  418.         for subchunk in self.subchunks:
  419.             subchunk.write(file)
  420.  
  421.     def dump(self, indent=0):
  422.         """Write the chunk to a file.
  423.  
  424.        Dump is used for debugging purposes, to dump the contents of a chunk to the standard output.
  425.        Uses the dump function of the named variables and the subchunks to do the actual work."""
  426.         print(indent * " ",
  427.               "ID=%r" % hex(self.ID.value),
  428.               "size=%r" % self.get_size())
  429.         for variable in self.variables:
  430.             variable.dump(indent + 1)
  431.         for subchunk in self.subchunks:
  432.             subchunk.dump(indent + 1)
  433.  
  434.  
  435. ######################################################
  436. # EXPORT
  437. ######################################################
  438.  
  439. def get_material_image_texslots(material):
  440.     # blender utility func.
  441.     if material:
  442.         return [s for s in material.texture_slots if s and s.texture.type == 'IMAGE' and s.texture.image]
  443.  
  444.     return []
  445.  
  446.     """
  447.    images = []
  448.    if material:
  449.        for mtex in material.getTextures():
  450.            if mtex and mtex.tex.type == Blender.Texture.Types.IMAGE:
  451.                image = mtex.tex.image
  452.                if image:
  453.                    images.append(image) # maye want to include info like diffuse, spec here.
  454.    return images
  455.    """
  456.  
  457.  
  458. def make_material_subchunk(chunk_id, color):
  459.     """Make a material subchunk.
  460.  
  461.    Used for color subchunks, such as diffuse color or ambient color subchunks."""
  462.     mat_sub = _3ds_chunk(chunk_id)
  463.     col1 = _3ds_chunk(RGB1)
  464.     col1.add_variable("color1", _3ds_rgb_color(color))
  465.     mat_sub.add_subchunk(col1)
  466.     # optional:
  467.     #col2 = _3ds_chunk(RGB1)
  468.     #col2.add_variable("color2", _3ds_rgb_color(color))
  469.     #mat_sub.add_subchunk(col2)
  470.     return mat_sub
  471.  
  472.  
  473. def make_material_texture_chunk(chunk_id, texslots, tess_uv_image=None):
  474.     """Make Material Map texture chunk given a seq. of `MaterialTextureSlot`'s
  475.  
  476.        `tess_uv_image` is optionally used as image source if the slots are
  477.        empty. No additional filtering for mapping modes is done, all
  478.        slots are written "as is".
  479.    """
  480.  
  481.     mat_sub = _3ds_chunk(chunk_id)
  482.     has_entry = False
  483.  
  484.     import bpy
  485.  
  486.     def add_texslot(texslot):
  487.         texture = texslot.texture
  488.         image = texture.image
  489.  
  490.         filename = bpy.path.basename(image.filepath)
  491.         mat_sub_file = _3ds_chunk(MATMAPFILE)
  492.         mat_sub_file.add_variable("mapfile", _3ds_string(sane_name(filename)))
  493.         mat_sub.add_subchunk(mat_sub_file)
  494.  
  495.         maptile = 0
  496.  
  497.         # no perfect mapping for mirror modes - 3DS only has uniform mirror w. repeat=2
  498.         if texture.extension == 'REPEAT' and (texture.use_mirror_x and texture.repeat_x > 1) \
  499.            or (texture.use_mirror_y and texture.repeat_y > 1):
  500.             maptile |= 0x2
  501.         # CLIP maps to 3DS' decal flag
  502.         elif texture.extension == 'CLIP':
  503.             maptile |= 0x10
  504.  
  505.         mat_sub_tile = _3ds_chunk(MAT_MAP_TILING)
  506.         mat_sub_tile.add_variable("maptiling", _3ds_ushort(maptile))
  507.         mat_sub.add_subchunk(mat_sub_tile)
  508.  
  509.         mat_sub_uscale = _3ds_chunk(MAT_MAP_USCALE)
  510.         mat_sub_uscale.add_variable("mapuscale", _3ds_float(texslot.scale[0]))
  511.         mat_sub.add_subchunk(mat_sub_uscale)
  512.  
  513.         mat_sub_vscale = _3ds_chunk(MAT_MAP_VSCALE)
  514.         mat_sub_vscale.add_variable("mapuscale", _3ds_float(texslot.scale[1]))
  515.         mat_sub.add_subchunk(mat_sub_vscale)
  516.  
  517.         mat_sub_uoffset = _3ds_chunk(MAT_MAP_UOFFSET)
  518.         mat_sub_uoffset.add_variable("mapuoffset", _3ds_float(texslot.offset[0]))
  519.         mat_sub.add_subchunk(mat_sub_uoffset)
  520.  
  521.         mat_sub_voffset = _3ds_chunk(MAT_MAP_VOFFSET)
  522.         mat_sub_voffset.add_variable("mapvoffset", _3ds_float(texslot.offset[1]))
  523.         mat_sub.add_subchunk(mat_sub_voffset)
  524.  
  525.     # store all textures for this mapto in order. This at least is what
  526.     # the 3DS exporter did so far, afaik most readers will just skip
  527.     # over 2nd textures.
  528.     for slot in texslots:
  529.         add_texslot(slot)
  530.         has_entry = True
  531.  
  532.     # image from tess. UV face - basically the code above should handle
  533.     # this already. No idea why its here so keep it :-)
  534.     if tess_uv_image and not has_entry:
  535.         has_entry = True
  536.  
  537.         filename = bpy.path.basename(tess_uv_image.filepath)
  538.         mat_sub_file = _3ds_chunk(MATMAPFILE)
  539.         mat_sub_file.add_variable("mapfile", _3ds_string(sane_name(filename)))
  540.         mat_sub.add_subchunk(mat_sub_file)
  541.  
  542.     return mat_sub if has_entry else None
  543.  
  544.  
  545. def make_material_chunk(material, image):
  546.     """Make a material chunk out of a blender material."""
  547.     material_chunk = _3ds_chunk(MATERIAL)
  548.     name = _3ds_chunk(MATNAME)
  549.  
  550.     name_str = material.name if material else "None"
  551.  
  552.     if image:
  553.         name_str += image.name
  554.  
  555.     name.add_variable("name", _3ds_string(sane_name(name_str)))
  556.     material_chunk.add_subchunk(name)
  557.  
  558.     if not material:
  559.         material_chunk.add_subchunk(make_material_subchunk(MATAMBIENT, (0.0, 0.0, 0.0)))
  560.         material_chunk.add_subchunk(make_material_subchunk(MATDIFFUSE, (0.8, 0.8, 0.8)))
  561.         material_chunk.add_subchunk(make_material_subchunk(MATSPECULAR, (1.0, 1.0, 1.0)))
  562.  
  563.     else:
  564.         material_chunk.add_subchunk(make_material_subchunk(MATAMBIENT, (material.ambient * material.diffuse_color)[:]))
  565.         material_chunk.add_subchunk(make_material_subchunk(MATDIFFUSE, material.diffuse_color[:]))
  566.         material_chunk.add_subchunk(make_material_subchunk(MATSPECULAR, material.specular_color[:]))
  567.  
  568.         slots = get_material_image_texslots(material)  # can be None
  569.  
  570.         if slots:
  571.  
  572.             spec = [s for s in slots if s.use_map_specular or s.use_map_color_spec]
  573.             matmap = make_material_texture_chunk(MAT_SPECMAP, spec)
  574.             if matmap:
  575.                 material_chunk.add_subchunk(matmap)
  576.  
  577.             alpha = [s for s in slots if s.use_map_alpha]
  578.             matmap = make_material_texture_chunk(MAT_OPACMAP, alpha)
  579.             if matmap:
  580.                 material_chunk.add_subchunk(matmap)
  581.  
  582.             normal = [s for s in slots if s.use_map_normal]
  583.             matmap = make_material_texture_chunk(MAT_BUMPMAP, normal)
  584.             if matmap:
  585.                 material_chunk.add_subchunk(matmap)
  586.  
  587.             # make sure no textures are lost. Everything that doesn't fit
  588.             # into a channel is exported as diffuse texture with a
  589.             # warning.
  590.             diffuse = []
  591.             for s in slots:
  592.                 if s.use_map_color_diffuse:
  593.                     diffuse.append(s)
  594.                 elif not (s in normal or s in alpha or s in spec):
  595.                     print('\nwarning: failed to map texture to 3DS map channel, assuming diffuse')
  596.                     diffuse.append(s)
  597.  
  598.             if diffuse:
  599.                 matmap = make_material_texture_chunk(MAT_DIFFUSEMAP, diffuse, image)
  600.                 if matmap:
  601.                     material_chunk.add_subchunk(matmap)
  602.  
  603.     return material_chunk
  604.  
  605.  
  606. class tri_wrapper(object):
  607.     """Class representing a triangle.
  608.  
  609.    Used when converting faces to triangles"""
  610.  
  611.     __slots__ = "vertex_index", "mat", "image", "faceuvs", "offset"
  612.  
  613.     def __init__(self, vindex=(0, 0, 0), mat=None, image=None, faceuvs=None):
  614.         self.vertex_index = vindex
  615.         self.mat = mat
  616.         self.image = image
  617.         self.faceuvs = faceuvs
  618.         self.offset = [0, 0, 0]  # offset indices
  619.  
  620.  
  621. def extract_triangles(mesh):
  622.     """Extract triangles from a mesh.
  623.  
  624.    If the mesh contains quads, they will be split into triangles."""
  625.     tri_list = []
  626.     do_uv = bool(mesh.tessface_uv_textures)
  627.  
  628.     img = None
  629.     for i, face in enumerate(mesh.tessfaces):
  630.         f_v = face.vertices
  631.  
  632.         uf = mesh.tessface_uv_textures.active.data[i] if do_uv else None
  633.  
  634.         if do_uv:
  635.             f_uv = uf.uv
  636.             img = uf.image if uf else None
  637.             if img is not None:
  638.                 img = img.name
  639.  
  640.         # if f_v[3] == 0:
  641.         if len(f_v) == 3:
  642.             new_tri = tri_wrapper((f_v[0], f_v[1], f_v[2]), face.material_index, img)
  643.             if (do_uv):
  644.                 new_tri.faceuvs = uv_key(f_uv[0]), uv_key(f_uv[1]), uv_key(f_uv[2])
  645.             tri_list.append(new_tri)
  646.  
  647.         else:  # it's a quad
  648.             new_tri = tri_wrapper((f_v[0], f_v[1], f_v[2]), face.material_index, img)
  649.             new_tri_2 = tri_wrapper((f_v[0], f_v[2], f_v[3]), face.material_index, img)
  650.  
  651.             if (do_uv):
  652.                 new_tri.faceuvs = uv_key(f_uv[0]), uv_key(f_uv[1]), uv_key(f_uv[2])
  653.                 new_tri_2.faceuvs = uv_key(f_uv[0]), uv_key(f_uv[2]), uv_key(f_uv[3])
  654.  
  655.             tri_list.append(new_tri)
  656.             tri_list.append(new_tri_2)
  657.  
  658.     return tri_list
  659.  
  660.  
  661. def remove_face_uv(verts, tri_list):
  662.     """Remove face UV coordinates from a list of triangles.
  663.  
  664.    Since 3ds files only support one pair of uv coordinates for each vertex, face uv coordinates
  665.    need to be converted to vertex uv coordinates. That means that vertices need to be duplicated when
  666.    there are multiple uv coordinates per vertex."""
  667.  
  668.     # initialize a list of UniqueLists, one per vertex:
  669.     #uv_list = [UniqueList() for i in xrange(len(verts))]
  670.     unique_uvs = [{} for i in range(len(verts))]
  671.  
  672.     # for each face uv coordinate, add it to the UniqueList of the vertex
  673.     for tri in tri_list:
  674.         for i in range(3):
  675.             # store the index into the UniqueList for future reference:
  676.             # offset.append(uv_list[tri.vertex_index[i]].add(_3ds_point_uv(tri.faceuvs[i])))
  677.  
  678.             context_uv_vert = unique_uvs[tri.vertex_index[i]]
  679.             uvkey = tri.faceuvs[i]
  680.  
  681.             offset_index__uv_3ds = context_uv_vert.get(uvkey)
  682.  
  683.             if not offset_index__uv_3ds:
  684.                 offset_index__uv_3ds = context_uv_vert[uvkey] = len(context_uv_vert), _3ds_point_uv(uvkey)
  685.  
  686.             tri.offset[i] = offset_index__uv_3ds[0]
  687.  
  688.     # At this point, each vertex has a UniqueList containing every uv coordinate that is associated with it
  689.     # only once.
  690.  
  691.     # Now we need to duplicate every vertex as many times as it has uv coordinates and make sure the
  692.     # faces refer to the new face indices:
  693.     vert_index = 0
  694.     vert_array = _3ds_array()
  695.     uv_array = _3ds_array()
  696.     index_list = []
  697.     for i, vert in enumerate(verts):
  698.         index_list.append(vert_index)
  699.  
  700.         pt = _3ds_point_3d(vert.co)  # reuse, should be ok
  701.         uvmap = [None] * len(unique_uvs[i])
  702.         for ii, uv_3ds in unique_uvs[i].values():
  703.             # add a vertex duplicate to the vertex_array for every uv associated with this vertex:
  704.             vert_array.add(pt)
  705.             # add the uv coordinate to the uv array:
  706.             # This for loop does not give uv's ordered by ii, so we create a new map
  707.             # and add the uv's later
  708.             # uv_array.add(uv_3ds)
  709.             uvmap[ii] = uv_3ds
  710.  
  711.         # Add the uv's in the correct order
  712.         for uv_3ds in uvmap:
  713.             # add the uv coordinate to the uv array:
  714.             uv_array.add(uv_3ds)
  715.  
  716.         vert_index += len(unique_uvs[i])
  717.  
  718.     # Make sure the triangle vertex indices now refer to the new vertex list:
  719.     for tri in tri_list:
  720.         for i in range(3):
  721.             tri.offset[i] += index_list[tri.vertex_index[i]]
  722.         tri.vertex_index = tri.offset
  723.  
  724.     return vert_array, uv_array, tri_list
  725.  
  726.  
  727. def make_faces_chunk(tri_list, mesh, materialDict):
  728.     """Make a chunk for the faces.
  729.  
  730.    Also adds subchunks assigning materials to all faces."""
  731.  
  732.     materials = mesh.materials
  733.     if not materials:
  734.         mat = None
  735.  
  736.     face_chunk = _3ds_chunk(OBJECT_FACES)
  737.     face_list = _3ds_array()
  738.  
  739.     if mesh.tessface_uv_textures:
  740.         # Gather materials used in this mesh - mat/image pairs
  741.         unique_mats = {}
  742.         for i, tri in enumerate(tri_list):
  743.  
  744.             face_list.add(_3ds_face(tri.vertex_index))
  745.  
  746.             if materials:
  747.                 mat = materials[tri.mat]
  748.                 if mat:
  749.                     mat = mat.name
  750.  
  751.             img = tri.image
  752.  
  753.             try:
  754.                 context_mat_face_array = unique_mats[mat, img][1]
  755.             except:
  756.                 name_str = mat if mat else "None"
  757.                 if img:
  758.                     name_str += img
  759.  
  760.                 context_mat_face_array = _3ds_array()
  761.                 unique_mats[mat, img] = _3ds_string(sane_name(name_str)), context_mat_face_array
  762.  
  763.             context_mat_face_array.add(_3ds_ushort(i))
  764.             # obj_material_faces[tri.mat].add(_3ds_ushort(i))
  765.  
  766.         face_chunk.add_variable("faces", face_list)
  767.         for mat_name, mat_faces in unique_mats.values():
  768.             obj_material_chunk = _3ds_chunk(OBJECT_MATERIAL)
  769.             obj_material_chunk.add_variable("name", mat_name)
  770.             obj_material_chunk.add_variable("face_list", mat_faces)
  771.             face_chunk.add_subchunk(obj_material_chunk)
  772.  
  773.     else:
  774.  
  775.         obj_material_faces = []
  776.         obj_material_names = []
  777.         for m in materials:
  778.             if m:
  779.                 obj_material_names.append(_3ds_string(sane_name(m.name)))
  780.                 obj_material_faces.append(_3ds_array())
  781.         n_materials = len(obj_material_names)
  782.  
  783.         for i, tri in enumerate(tri_list):
  784.             face_list.add(_3ds_face(tri.vertex_index))
  785.             if (tri.mat < n_materials):
  786.                 obj_material_faces[tri.mat].add(_3ds_ushort(i))
  787.  
  788.         face_chunk.add_variable("faces", face_list)
  789.         for i in range(n_materials):
  790.             obj_material_chunk = _3ds_chunk(OBJECT_MATERIAL)
  791.             obj_material_chunk.add_variable("name", obj_material_names[i])
  792.             obj_material_chunk.add_variable("face_list", obj_material_faces[i])
  793.             face_chunk.add_subchunk(obj_material_chunk)
  794.  
  795.     return face_chunk
  796.  
  797.  
  798. def make_vert_chunk(vert_array):
  799.     """Make a vertex chunk out of an array of vertices."""
  800.     vert_chunk = _3ds_chunk(OBJECT_VERTICES)
  801.     vert_chunk.add_variable("vertices", vert_array)
  802.     return vert_chunk
  803.  
  804.  
  805. def make_uv_chunk(uv_array):
  806.     """Make a UV chunk out of an array of UVs."""
  807.     uv_chunk = _3ds_chunk(OBJECT_UV)
  808.     uv_chunk.add_variable("uv coords", uv_array)
  809.     return uv_chunk
  810.  
  811.  
  812. def make_matrix_4x3_chunk(matrix):
  813.     matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX)
  814.     for vec in matrix.col:
  815.         for f in vec[:3]:
  816.             matrix_chunk.add_variable("matrix_f", _3ds_float(f))
  817.     return matrix_chunk
  818.  
  819.  
  820. def make_mesh_chunk(mesh, matrix, materialDict):
  821.     """Make a chunk out of a Blender mesh."""
  822.  
  823.     # Extract the triangles from the mesh:
  824.     tri_list = extract_triangles(mesh)
  825.  
  826.     if mesh.tessface_uv_textures:
  827.         # Remove the face UVs and convert it to vertex UV:
  828.         vert_array, uv_array, tri_list = remove_face_uv(mesh.vertices, tri_list)
  829.     else:
  830.         # Add the vertices to the vertex array:
  831.         vert_array = _3ds_array()
  832.         for vert in mesh.vertices:
  833.             vert_array.add(_3ds_point_3d(vert.co))
  834.         # no UV at all:
  835.         uv_array = None
  836.  
  837.     # create the chunk:
  838.     mesh_chunk = _3ds_chunk(OBJECT_MESH)
  839.  
  840.     # add vertex chunk:
  841.     mesh_chunk.add_subchunk(make_vert_chunk(vert_array))
  842.     # add faces chunk:
  843.  
  844.     mesh_chunk.add_subchunk(make_faces_chunk(tri_list, mesh, materialDict))
  845.  
  846.     # if available, add uv chunk:
  847.     if uv_array:
  848.         mesh_chunk.add_subchunk(make_uv_chunk(uv_array))
  849.  
  850.     mesh_chunk.add_subchunk(make_matrix_4x3_chunk(matrix))
  851.  
  852.     return mesh_chunk
  853.  
  854.  
  855. ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
  856. def make_kfdata(start=0, stop=0, curtime=0):
  857.    """Make the basic keyframe data chunk"""
  858.    kfdata = _3ds_chunk(KFDATA)
  859.  
  860.    kfhdr = _3ds_chunk(KFDATA_KFHDR)
  861.    kfhdr.add_variable("revision", _3ds_ushort(0))
  862.    # Not really sure what filename is used for, but it seems it is usually used
  863.    # to identify the program that generated the .3ds:
  864.    kfhdr.add_variable("filename", _3ds_string("Blender"))
  865.    kfhdr.add_variable("animlen", _3ds_uint(stop-start))
  866.  
  867.    kfseg = _3ds_chunk(KFDATA_KFSEG)
  868.    kfseg.add_variable("start", _3ds_uint(start))
  869.    kfseg.add_variable("stop", _3ds_uint(stop))
  870.  
  871.    kfcurtime = _3ds_chunk(KFDATA_KFCURTIME)
  872.    kfcurtime.add_variable("curtime", _3ds_uint(curtime))
  873.  
  874.    kfdata.add_subchunk(kfhdr)
  875.    kfdata.add_subchunk(kfseg)
  876.    kfdata.add_subchunk(kfcurtime)
  877.    return kfdata
  878.  
  879. def make_track_chunk(ID, obj):
  880.    """Make a chunk for track data.
  881.  
  882.    Depending on the ID, this will construct a position, rotation or scale track."""
  883.    track_chunk = _3ds_chunk(ID)
  884.    track_chunk.add_variable("track_flags", _3ds_ushort())
  885.    track_chunk.add_variable("unknown", _3ds_uint())
  886.    track_chunk.add_variable("unknown", _3ds_uint())
  887.    track_chunk.add_variable("nkeys", _3ds_uint(1))
  888.    # Next section should be repeated for every keyframe, but for now, animation is not actually supported.
  889.    track_chunk.add_variable("tcb_frame", _3ds_uint(0))
  890.    track_chunk.add_variable("tcb_flags", _3ds_ushort())
  891.    if obj.type=='Empty':
  892.        if ID==POS_TRACK_TAG:
  893.            # position vector:
  894.            track_chunk.add_variable("position", _3ds_point_3d(obj.getLocation()))
  895.        elif ID==ROT_TRACK_TAG:
  896.            # rotation (quaternion, angle first, followed by axis):
  897.            q = obj.getEuler().to_quaternion()  # XXX, todo!
  898.            track_chunk.add_variable("rotation", _3ds_point_4d((q.angle, q.axis[0], q.axis[1], q.axis[2])))
  899.        elif ID==SCL_TRACK_TAG:
  900.            # scale vector:
  901.            track_chunk.add_variable("scale", _3ds_point_3d(obj.getSize()))
  902.    else:
  903.        # meshes have their transformations applied before
  904.        # exporting, so write identity transforms here:
  905.        if ID==POS_TRACK_TAG:
  906.            # position vector:
  907.            track_chunk.add_variable("position", _3ds_point_3d((0.0,0.0,0.0)))
  908.        elif ID==ROT_TRACK_TAG:
  909.            # rotation (quaternion, angle first, followed by axis):
  910.            track_chunk.add_variable("rotation", _3ds_point_4d((0.0, 1.0, 0.0, 0.0)))
  911.        elif ID==SCL_TRACK_TAG:
  912.            # scale vector:
  913.            track_chunk.add_variable("scale", _3ds_point_3d((1.0, 1.0, 1.0)))
  914.  
  915.    return track_chunk
  916.  
  917. def make_kf_obj_node(obj, name_to_id):
  918.    """Make a node chunk for a Blender object.
  919.  
  920.    Takes the Blender object as a parameter. Object id's are taken from the dictionary name_to_id.
  921.    Blender Empty objects are converted to dummy nodes."""
  922.  
  923.    name = obj.name
  924.    # main object node chunk:
  925.    kf_obj_node = _3ds_chunk(KFDATA_OBJECT_NODE_TAG)
  926.    # chunk for the object id:
  927.    obj_id_chunk = _3ds_chunk(OBJECT_NODE_ID)
  928.    # object id is from the name_to_id dictionary:
  929.    obj_id_chunk.add_variable("node_id", _3ds_ushort(name_to_id[name]))
  930.  
  931.    # object node header:
  932.    obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR)
  933.    # object name:
  934.    if obj.type == 'Empty':
  935.        # Empties are called "$$$DUMMY" and use the OBJECT_INSTANCE_NAME chunk
  936.        # for their name (see below):
  937.        obj_node_header_chunk.add_variable("name", _3ds_string("$$$DUMMY"))
  938.    else:
  939.        # Add the name:
  940.        obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name)))
  941.    # Add Flag variables (not sure what they do):
  942.    obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0))
  943.    obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
  944.  
  945.    # Check parent-child relationships:
  946.    parent = obj.parent
  947.    if (parent is None) or (parent.name not in name_to_id):
  948.        # If no parent, or the parents name is not in the name_to_id dictionary,
  949.        # parent id becomes -1:
  950.        obj_node_header_chunk.add_variable("parent", _3ds_ushort(-1))
  951.    else:
  952.        # Get the parent's id from the name_to_id dictionary:
  953.        obj_node_header_chunk.add_variable("parent", _3ds_ushort(name_to_id[parent.name]))
  954.  
  955.    # Add pivot chunk:
  956.    obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT)
  957.    obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(obj.getLocation()))
  958.    kf_obj_node.add_subchunk(obj_pivot_chunk)
  959.  
  960.    # add subchunks for object id and node header:
  961.    kf_obj_node.add_subchunk(obj_id_chunk)
  962.    kf_obj_node.add_subchunk(obj_node_header_chunk)
  963.  
  964.    # Empty objects need to have an extra chunk for the instance name:
  965.    if obj.type == 'Empty':
  966.        obj_instance_name_chunk = _3ds_chunk(OBJECT_INSTANCE_NAME)
  967.        obj_instance_name_chunk.add_variable("name", _3ds_string(sane_name(name)))
  968.        kf_obj_node.add_subchunk(obj_instance_name_chunk)
  969.  
  970.    # Add track chunks for position, rotation and scale:
  971.    kf_obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, obj))
  972.    kf_obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, obj))
  973.    kf_obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, obj))
  974.  
  975.    return kf_obj_node
  976. '''
  977.  
  978.  
  979. def save(operator,
  980.          context, filepath="",
  981.          use_selection=True,
  982.          global_matrix=None,
  983.          ):
  984.  
  985.     import bpy
  986.     import mathutils
  987.  
  988.     import time
  989.     from bpy_extras.io_utils import create_derived_objects, free_derived_objects
  990.  
  991.     """Save the Blender scene to a 3ds file."""
  992.  
  993.     # Time the export
  994.     time1 = time.clock()
  995.     #Blender.Window.WaitCursor(1)
  996.  
  997.     if global_matrix is None:
  998.         global_matrix = mathutils.Matrix()
  999.  
  1000.     if bpy.ops.object.mode_set.poll():
  1001.         bpy.ops.object.mode_set(mode='OBJECT')
  1002.  
  1003.     # Initialize the main chunk (primary):
  1004.     primary = _3ds_chunk(PRIMARY)
  1005.     # Add version chunk:
  1006.     version_chunk = _3ds_chunk(VERSION)
  1007.     version_chunk.add_variable("version", _3ds_uint(3))
  1008.     primary.add_subchunk(version_chunk)
  1009.  
  1010.     # init main object info chunk:
  1011.     object_info = _3ds_chunk(OBJECTINFO)
  1012.  
  1013.     ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
  1014.    # init main key frame data chunk:
  1015.    kfdata = make_kfdata()
  1016.    '''
  1017.  
  1018.     # Make a list of all materials used in the selected meshes (use a dictionary,
  1019.     # each material is added once):
  1020.     materialDict = {}
  1021.     mesh_objects = []
  1022.  
  1023.     scene = context.scene
  1024.  
  1025.     if use_selection:
  1026.         objects = (ob for ob in scene.objects if ob.is_visible(scene) and ob.select)
  1027.     else:
  1028.         objects = (ob for ob in scene.objects if ob.is_visible(scene))
  1029.  
  1030.     for ob in objects:
  1031.         # get derived objects
  1032.         free, derived = create_derived_objects(scene, ob)
  1033.  
  1034.         if derived is None:
  1035.             continue
  1036.  
  1037.         for ob_derived, mat in derived:
  1038.             if ob.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
  1039.                 continue
  1040.  
  1041.             try:
  1042.                 data = ob_derived.to_mesh(scene, True, 'PREVIEW')
  1043.             except:
  1044.                 data = None
  1045.  
  1046.             if data:
  1047.                 matrix = global_matrix * mat
  1048.                 data.transform(matrix)
  1049.                 mesh_objects.append((ob_derived, data, matrix))
  1050.                 mat_ls = data.materials
  1051.                 mat_ls_len = len(mat_ls)
  1052.  
  1053.                 # get material/image tuples.
  1054.                 if data.tessface_uv_textures:
  1055.                     if not mat_ls:
  1056.                         mat = mat_name = None
  1057.  
  1058.                     for f, uf in zip(data.tessfaces, data.tessface_uv_textures.active.data):
  1059.                         if mat_ls:
  1060.                             mat_index = f.material_index
  1061.                             if mat_index >= mat_ls_len:
  1062.                                 mat_index = f.mat = 0
  1063.                             mat = mat_ls[mat_index]
  1064.                             mat_name = None if mat is None else mat.name
  1065.                         # else there already set to none
  1066.  
  1067.                         img = uf.image
  1068.                         img_name = None if img is None else img.name
  1069.  
  1070.                         materialDict.setdefault((mat_name, img_name), (mat, img))
  1071.  
  1072.                 else:
  1073.                     for mat in mat_ls:
  1074.                         if mat:  # material may be None so check its not.
  1075.                             materialDict.setdefault((mat.name, None), (mat, None))
  1076.  
  1077.                     # Why 0 Why!
  1078.                     for f in data.tessfaces:
  1079.                         if f.material_index >= mat_ls_len:
  1080.                             f.material_index = 0
  1081.  
  1082.         if free:
  1083.             free_derived_objects(ob)
  1084.  
  1085.     # Make material chunks for all materials used in the meshes:
  1086.     for mat_and_image in materialDict.values():
  1087.         object_info.add_subchunk(make_material_chunk(mat_and_image[0], mat_and_image[1]))
  1088.  
  1089.     # Give all objects a unique ID and build a dictionary from object name to object id:
  1090.     """
  1091.    name_to_id = {}
  1092.    for ob, data in mesh_objects:
  1093.        name_to_id[ob.name]= len(name_to_id)
  1094.    #for ob in empty_objects:
  1095.    #    name_to_id[ob.name]= len(name_to_id)
  1096.    """
  1097.  
  1098.     # Create object chunks for all meshes:
  1099.     i = 0
  1100.     for ob, blender_mesh, matrix in mesh_objects:
  1101.         # create a new object chunk
  1102.         object_chunk = _3ds_chunk(OBJECT)
  1103.  
  1104.         # set the object name
  1105.         object_chunk.add_variable("name", _3ds_string(sane_name(ob.name)))
  1106.  
  1107.         # make a mesh chunk out of the mesh:
  1108.         object_chunk.add_subchunk(make_mesh_chunk(blender_mesh, matrix, materialDict))
  1109.  
  1110.         # ensure the mesh has no over sized arrays
  1111.         # skip ones that do!, otherwise we cant write since the array size wont
  1112.         # fit into USHORT.
  1113.         if object_chunk.validate():
  1114.             object_info.add_subchunk(object_chunk)
  1115.         else:
  1116.             operator.report({'WARNING'}, "Object %r can't be written into a 3DS file")
  1117.  
  1118.         ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
  1119.        # make a kf object node for the object:
  1120.        kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
  1121.        '''
  1122.  
  1123.         if not blender_mesh.users:
  1124.             bpy.data.meshes.remove(blender_mesh)
  1125.         #blender_mesh.vertices = None
  1126.  
  1127.         i += i
  1128.  
  1129.     # Create chunks for all empties:
  1130.     ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
  1131.    for ob in empty_objects:
  1132.        # Empties only require a kf object node:
  1133.        kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
  1134.        pass
  1135.    '''
  1136.  
  1137.     # Add main object info chunk to primary chunk:
  1138.     primary.add_subchunk(object_info)
  1139.  
  1140.     ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
  1141.    # Add main keyframe data chunk to primary chunk:
  1142.    primary.add_subchunk(kfdata)
  1143.    '''
  1144.  
  1145.     # At this point, the chunk hierarchy is completely built.
  1146.  
  1147.     # Check the size:
  1148.     primary.get_size()
  1149.     # Open the file for writing:
  1150.     file = open(filepath, 'wb')
  1151.  
  1152.     # Recursively write the chunks to file:
  1153.     primary.write(file)
  1154.  
  1155.     # Close the file:
  1156.     file.close()
  1157.  
  1158.     # Clear name mapping vars, could make locals too
  1159.     del name_unique[:]
  1160.     name_mapping.clear()
  1161.  
  1162.     # Debugging only: report the exporting time:
  1163.     #Blender.Window.WaitCursor(0)
  1164.     print("3ds export time: %.2f" % (time.clock() - time1))
  1165.  
  1166.     # Debugging only: dump the chunk hierarchy:
  1167.     #primary.dump()
  1168.  
  1169.     return {'FINISHED'}
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement