# coding: utf-8 # ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### # __author__ = ["Campbell Barton", "Bob Holcomb", "Richard Lärkäng", "Damien McGinnes", "Mark Stijnman"] __url__ = ("blenderartists.org", "www.blender.org", "www.gametutorials.com", "lib3ds.sourceforge.net/") __version__ = "0.90a" __bpydoc__ = """\ 3ds Exporter This script Exports a 3ds file. Exporting is based on 3ds loader from www.gametutorials.com(Thanks DigiBen) and using information from the lib3ds project (http://lib3ds.sourceforge.net/) sourcecode. """ # ***** BEGIN GPL LICENSE BLOCK ***** # # Script copyright (C) Bob Holcomb # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** # -------------------------------------------------------------------------- ###################################################### # Importing modules ###################################################### import struct import os import time import bpy # import Blender # from BPyMesh import getMeshFromObject # from BPyObject import getDerivedObjects # try: # import struct # except: # struct = None # also used by X3D exporter # return a tuple (free, object list), free is True if memory should be freed later with free_derived_objects() def create_derived_objects(scene, ob): if ob.parent and ob.parent.dupli_type != 'NONE': return False, None if ob.dupli_type != 'NONE': ob.create_dupli_list(scene) return True, [(dob.object, dob.matrix) for dob in ob.dupli_list] else: return False, [(ob, ob.matrix_world)] # also used by X3D exporter def free_derived_objects(ob): ob.free_dupli_list() # So 3ds max can open files, limit names to 12 in length # this is verry annoying for filenames! name_unique = [] name_mapping = {} def sane_name(name): name_fixed = name_mapping.get(name) if name_fixed != None: return name_fixed if len(name) > 12: new_name = name[:12] else: new_name = name i = 0 while new_name in name_unique: new_name = new_name[:-4] + '.%.3d' % i i+=1 name_unique.append(new_name) name_mapping[name] = new_name return new_name ###################################################### # Data Structures ###################################################### #Some of the chunks that we will export #----- Primary Chunk, at the beginning of each file PRIMARY= int("0x4D4D",16) #------ Main Chunks OBJECTINFO = int("0x3D3D",16); #This gives the version of the mesh and is found right before the material and object information VERSION = int("0x0002",16); #This gives the version of the .3ds file KFDATA = int("0xB000",16); #This is the header for all of the key frame info #------ sub defines of OBJECTINFO MATERIAL=45055 #0xAFFF // This stored the texture info OBJECT=16384 #0x4000 // This stores the faces, vertices, etc... #>------ sub defines of MATERIAL MATNAME = int("0xA000",16); # This holds the material name MATAMBIENT = int("0xA010",16); # Ambient color of the object/material MATDIFFUSE = int("0xA020",16); # This holds the color of the object/material MATSPECULAR = int("0xA030",16); # SPecular color of the object/material MATSHINESS = int("0xA040",16); # ?? MATMAP = int("0xA200",16); # This is a header for a new material MATMAPFILE = int("0xA300",16); # This holds the file name of the texture RGB1= int("0x0011",16) RGB2= int("0x0012",16) #>------ sub defines of OBJECT OBJECT_MESH = int("0x4100",16); # This lets us know that we are reading a new object OBJECT_LIGHT = int("0x4600",16); # This lets un know we are reading a light object OBJECT_CAMERA= int("0x4700",16); # This lets un know we are reading a camera object #>------ sub defines of CAMERA OBJECT_CAM_RANGES= int("0x4720",16); # The camera range values #>------ sub defines of OBJECT_MESH OBJECT_VERTICES = int("0x4110",16); # The objects vertices OBJECT_FACES = int("0x4120",16); # The objects faces OBJECT_MATERIAL = int("0x4130",16); # This is found if the object has a material, either texture map or color OBJECT_UV = int("0x4140",16); # The UV texture coordinates OBJECT_TRANS_MATRIX = int("0x4160",16); # The Object Matrix #>------ sub defines of KFDATA KFDATA_KFHDR = int("0xB00A",16); KFDATA_KFSEG = int("0xB008",16); KFDATA_KFCURTIME = int("0xB009",16); KFDATA_OBJECT_NODE_TAG = int("0xB002",16); #>------ sub defines of OBJECT_NODE_TAG OBJECT_NODE_ID = int("0xB030",16); OBJECT_NODE_HDR = int("0xB010",16); OBJECT_PIVOT = int("0xB013",16); OBJECT_INSTANCE_NAME = int("0xB011",16); POS_TRACK_TAG = int("0xB020",16); ROT_TRACK_TAG = int("0xB021",16); SCL_TRACK_TAG = int("0xB022",16); def uv_key(uv): return round(uv[0], 6), round(uv[1], 6) # return round(uv.x, 6), round(uv.y, 6) # size defines: SZ_SHORT = 2 SZ_INT = 4 SZ_FLOAT = 4 class _3ds_short(object): '''Class representing a short (2-byte integer) for a 3ds file. *** This looks like an unsigned short H is unsigned from the struct docs - Cam***''' __slots__ = 'value' def __init__(self, val=0): self.value=val def get_size(self): return SZ_SHORT def write(self,file): file.write(struct.pack("= mat_ls_len: mat_index = f.mat = 0 mat = mat_ls[mat_index] if mat: mat_name = mat.name else: mat_name = None # else there already set to none img = uf.image # img = f.image if img: img_name = img.name else: img_name = None materialDict.setdefault((mat_name, img_name), (mat, img) ) else: for mat in mat_ls: if mat: # material may be None so check its not. materialDict.setdefault((mat.name, None), (mat, None) ) # Why 0 Why! for f in data.faces: if f.material_index >= mat_ls_len: # if f.mat >= mat_ls_len: f.material_index = 0 # f.mat = 0 if free: free_derived_objects(ob) # Make material chunks for all materials used in the meshes: for mat_and_image in materialDict.values(): object_info.add_subchunk(make_material_chunk(mat_and_image[0], mat_and_image[1])) # Give all objects a unique ID and build a dictionary from object name to object id: """ name_to_id = {} for ob, data in mesh_objects: name_to_id[ob.name]= len(name_to_id) #for ob in empty_objects: # name_to_id[ob.name]= len(name_to_id) """ # Create object chunks for all meshes: i = 0 for ob, blender_mesh in mesh_objects: # create a new object chunk object_chunk = _3ds_chunk(OBJECT) # set the object name object_chunk.add_variable("name", _3ds_string(sane_name(ob.name))) # make a mesh chunk out of the mesh: object_chunk.add_subchunk(make_mesh_chunk(blender_mesh, materialDict)) object_info.add_subchunk(object_chunk) ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX # make a kf object node for the object: kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id)) ''' if not blender_mesh.users: bpy.data.meshes.remove(blender_mesh) # blender_mesh.verts = None i+=i # Create chunks for all empties: ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX for ob in empty_objects: # Empties only require a kf object node: kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id)) pass ''' # Add main object info chunk to primary chunk: primary.add_subchunk(object_info) ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX # Add main keyframe data chunk to primary chunk: primary.add_subchunk(kfdata) ''' # At this point, the chunk hierarchy is completely built. # Check the size: primary.get_size() # Open the file for writing: file = open( filename, 'wb' ) # Recursively write the chunks to file: primary.write(file) # Close the file: file.close() # Debugging only: report the exporting time: # Blender.Window.WaitCursor(0) print("3ds export time: %.2f" % (time.clock() - time1)) # print("3ds export time: %.2f" % (Blender.sys.time() - time1)) # Debugging only: dump the chunk hierarchy: #primary.dump() # if __name__=='__main__': # if struct: # Blender.Window.FileSelector(save_3ds, "Export 3DS", Blender.sys.makename(ext='.3ds')) # else: # Blender.Draw.PupMenu("Error%t|This script requires a full python installation") # # save_3ds('/test_b.3ds') from bpy.props import * class Export3DS(bpy.types.Operator): '''Export to 3DS file format (.3ds)''' bl_idname = "export.autodesk_3ds" bl_label = 'Export 3DS' # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. filepath = StringProperty(name="File Path", description="Filepath used for exporting the 3DS file", maxlen= 1024, default= "") check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) def execute(self, context): save_3ds(self.properties.filepath, context) return {'FINISHED'} def invoke(self, context, event): wm = context.manager wm.add_fileselect(self) return {'RUNNING_MODAL'} def poll(self, context): # Poll isnt working yet return context.active_object != None # Add to a menu def menu_func(self, context): default_path = os.path.splitext(bpy.data.filepath)[0] + ".3ds" self.layout.operator(Export3DS.bl_idname, text="3D Studio (.3ds)").filepath = default_path def register(): bpy.types.register(Export3DS) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): bpy.types.unregister(Export3DS) bpy.types.INFO_MT_file_export.remove(menu_func) if __name__ == "__main__": register()