change python scripts so modules which register with blender have a register() functi...
[blender.git] / release / scripts / io / import_scene_3ds.py
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 __author__= ['Bob Holcomb', 'Richard L?rk?ng', 'Damien McGinnes', 'Campbell Barton', 'Mario Lapin']
22 __url__ = ("blenderartists.org", "www.blender.org", "www.gametutorials.com", "lib3ds.sourceforge.net/")
23 __version__= '0.996'
24 __bpydoc__= '''\
25
26 3ds Importer
27
28 This script imports a 3ds file and the materials into Blender for editing.
29
30 Loader is based on 3ds loader from www.gametutorials.com (Thanks DigiBen).
31
32 0.996 by Mario Lapin (mario.lapin@gmail.com) 13/04/200 <br>
33  - Implemented workaround to correct association between name, geometry and materials of
34    imported meshes.
35
36    Without this patch, version 0.995 of this importer would associate to each mesh object the
37    geometry and the materials of the previously parsed mesh object. By so, the name of the
38    first mesh object would be thrown away, and the name of the last mesh object would be
39    automatically merged with a '.001' at the end. No object would desappear, however object's
40    names and materials would be completely jumbled.
41
42 0.995 by Campbell Barton<br>
43 - workaround for buggy mesh vert delete
44 - minor tweaks
45
46 0.99 by Bob Holcomb<br>
47 - added support for floating point color values that previously broke on import.
48
49 0.98 by Campbell Barton<br>
50 - import faces and verts to lists instead of a mesh, convert to a mesh later
51 - use new index mapping feature of mesh to re-map faces that were not added.
52
53 0.97 by Campbell Barton<br>
54 - Strip material names of spaces
55 - Added import as instance to import the 3ds into its own
56   scene and add a group instance to the current scene
57 - New option to scale down imported objects so they are within a limited bounding area.
58
59 0.96 by Campbell Barton<br>
60 - Added workaround for bug in setting UV's for Zero vert index UV faces.
61 - Removed unique name function, let blender make the names unique.
62
63 0.95 by Campbell Barton<br>
64 - Removed workarounds for Blender 2.41
65 - Mesh objects split by material- many 3ds objects used more then 16 per mesh.
66 - Removed a lot of unneeded variable creation.
67
68 0.94 by Campbell Barton<br>
69 - Face import tested to be about overall 16x speedup over 0.93.
70 - Material importing speedup.
71 - Tested with more models.
72 - Support some corrupt models.
73
74 0.93 by Campbell Barton<br>
75 - Tested with 400 3ds files from turbosquid and samples.
76 - Tactfully ignore faces that used the same verts twice.
77 - Rollback to 0.83 sloppy un-reorganized code, this broke UV coord loading.
78 - Converted from NMesh to Mesh.
79 - Faster and cleaner new names.
80 - Use external comprehensive image loader.
81 - Re intergrated 0.92 and 0.9 changes
82 - Fixes for 2.41 compat.
83 - Non textured faces do not use a texture flag.
84
85 0.92<br>
86 - Added support for diffuse, alpha, spec, bump maps in a single material
87
88 0.9<br>
89 - Reorganized code into object/material block functions<br>
90 - Use of Matrix() to copy matrix data<br>
91 - added support for material transparency<br>
92
93 0.83 2005-08-07: Campell Barton
94 -  Aggressive image finding and case insensitivy for posisx systems.
95
96 0.82a 2005-07-22
97 - image texture loading (both for face uv and renderer)
98
99 0.82 - image texture loading (for face uv)
100
101 0.81a (fork- not 0.9) Campbell Barton 2005-06-08
102 - Simplified import code
103 - Never overwrite data
104 - Faster list handling
105 - Leaves import selected
106
107 0.81 Damien McGinnes 2005-01-09
108 - handle missing images better
109
110 0.8 Damien McGinnes 2005-01-08
111 - copies sticky UV coords to face ones
112 - handles images better
113 - Recommend that you run 'RemoveDoubles' on each imported mesh after using this script
114
115 '''
116
117 # ***** BEGIN GPL LICENSE BLOCK *****
118 #
119 # Script copyright (C) Bob Holcomb
120 #
121 # This program is free software; you can redistribute it and/or
122 # modify it under the terms of the GNU General Public License
123 # as published by the Free Software Foundation; either version 2
124 # of the License, or (at your option) any later version.
125 #
126 # This program is distributed in the hope that it will be useful,
127 # but WITHOUT ANY WARRANTY; without even the implied warranty of
128 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
129 # GNU General Public License for more details.
130 #
131 # You should have received a copy of the GNU General Public License
132 # along with this program; if not, write to the Free Software Foundation,
133 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
134 #
135 # ***** END GPL LICENCE BLOCK *****
136 # --------------------------------------------------------------------------
137
138 # Importing modules
139
140 import os
141 import time
142 import struct
143
144 from import_scene_obj import unpack_face_list, load_image
145
146 import bpy
147 import Mathutils
148
149 BOUNDS_3DS = []
150
151
152 ######################################################
153 # Data Structures
154 ######################################################
155
156 #Some of the chunks that we will see
157 #----- Primary Chunk, at the beginning of each file
158 PRIMARY = int('0x4D4D',16)
159
160 #------ Main Chunks
161 OBJECTINFO   =      int('0x3D3D',16);      #This gives the version of the mesh and is found right before the material and object information
162 VERSION      =      int('0x0002',16);      #This gives the version of the .3ds file
163 EDITKEYFRAME=      int('0xB000',16);      #This is the header for all of the key frame info
164
165 #------ sub defines of OBJECTINFO
166 MATERIAL = 45055                #0xAFFF                         // This stored the texture info
167 OBJECT = 16384          #0x4000                         // This stores the faces, vertices, etc...
168
169 #>------ sub defines of MATERIAL
170 #------ sub defines of MATERIAL_BLOCK
171 MAT_NAME                =       int('0xA000',16)        # This holds the material name
172 MAT_AMBIENT             =       int('0xA010',16)        # Ambient color of the object/material
173 MAT_DIFFUSE             =       int('0xA020',16)        # This holds the color of the object/material
174 MAT_SPECULAR    =       int('0xA030',16)        # SPecular color of the object/material
175 MAT_SHINESS             =       int('0xA040',16)        # ??
176 MAT_TRANSPARENCY=       int('0xA050',16)        # Transparency value of material
177 MAT_SELF_ILLUM  =       int('0xA080',16)        # Self Illumination value of material
178 MAT_WIRE                =       int('0xA085',16)        # Only render's wireframe
179
180 MAT_TEXTURE_MAP =       int('0xA200',16)        # This is a header for a new texture map
181 MAT_SPECULAR_MAP=       int('0xA204',16)        # This is a header for a new specular map
182 MAT_OPACITY_MAP =       int('0xA210',16)        # This is a header for a new opacity map
183 MAT_REFLECTION_MAP=     int('0xA220',16)        # This is a header for a new reflection map
184 MAT_BUMP_MAP    =       int('0xA230',16)        # This is a header for a new bump map
185 MAT_MAP_FILENAME =      int('0xA300',16)      # This holds the file name of the texture
186
187 MAT_FLOAT_COLOR = int ('0x0010', 16) #color defined as 3 floats
188 MAT_24BIT_COLOR = int ('0x0011', 16) #color defined as 3 bytes
189
190 #>------ sub defines of OBJECT
191 OBJECT_MESH  =      int('0x4100',16);      # This lets us know that we are reading a new object
192 OBJECT_LAMP =      int('0x4600',16);      # This lets un know we are reading a light object
193 OBJECT_LAMP_SPOT = int('0x4610',16);            # The light is a spotloght.
194 OBJECT_LAMP_OFF = int('0x4620',16);             # The light off.
195 OBJECT_LAMP_ATTENUATE = int('0x4625',16);
196 OBJECT_LAMP_RAYSHADE = int('0x4627',16);
197 OBJECT_LAMP_SHADOWED = int('0x4630',16);
198 OBJECT_LAMP_LOCAL_SHADOW = int('0x4640',16);
199 OBJECT_LAMP_LOCAL_SHADOW2 = int('0x4641',16);
200 OBJECT_LAMP_SEE_CONE = int('0x4650',16);
201 OBJECT_LAMP_SPOT_RECTANGULAR = int('0x4651',16);
202 OBJECT_LAMP_SPOT_OVERSHOOT = int('0x4652',16);
203 OBJECT_LAMP_SPOT_PROJECTOR = int('0x4653',16);
204 OBJECT_LAMP_EXCLUDE = int('0x4654',16);
205 OBJECT_LAMP_RANGE = int('0x4655',16);
206 OBJECT_LAMP_ROLL = int('0x4656',16);
207 OBJECT_LAMP_SPOT_ASPECT = int('0x4657',16);
208 OBJECT_LAMP_RAY_BIAS = int('0x4658',16);
209 OBJECT_LAMP_INNER_RANGE = int('0x4659',16);
210 OBJECT_LAMP_OUTER_RANGE = int('0x465A',16);
211 OBJECT_LAMP_MULTIPLIER = int('0x465B',16);
212 OBJECT_LAMP_AMBIENT_LIGHT = int('0x4680',16);
213
214
215
216 OBJECT_CAMERA=      int('0x4700',16);      # This lets un know we are reading a camera object
217
218 #>------ sub defines of CAMERA
219 OBJECT_CAM_RANGES=   int('0x4720',16);      # The camera range values
220
221 #>------ sub defines of OBJECT_MESH
222 OBJECT_VERTICES =   int('0x4110',16);      # The objects vertices
223 OBJECT_FACES    =   int('0x4120',16);      # The objects faces
224 OBJECT_MATERIAL =   int('0x4130',16);      # This is found if the object has a material, either texture map or color
225 OBJECT_UV       =   int('0x4140',16);      # The UV texture coordinates
226 OBJECT_TRANS_MATRIX  =   int('0x4160',16); # The Object Matrix
227
228 global scn
229 scn = None
230
231 #the chunk class
232 class chunk:
233     ID = 0
234     length = 0
235     bytes_read = 0
236
237     #we don't read in the bytes_read, we compute that
238     binary_format='<HI'
239
240     def __init__(self):
241         self.ID = 0
242         self.length = 0
243         self.bytes_read = 0
244
245     def dump(self):
246         print('ID: ', self.ID)
247         print('ID in hex: ', hex(self.ID))
248         print('length: ', self.length)
249         print('bytes_read: ', self.bytes_read)
250
251 def read_chunk(file, chunk):
252     temp_data = file.read(struct.calcsize(chunk.binary_format))
253     data = struct.unpack(chunk.binary_format, temp_data)
254     chunk.ID = data[0]
255     chunk.length = data[1]
256     #update the bytes read function
257     chunk.bytes_read = 6
258
259     #if debugging
260     #chunk.dump()
261
262 def read_string(file):
263     #read in the characters till we get a null character
264     s = b''
265     while not s.endswith(b'\x00'):
266         s += struct.unpack('<c', file.read(1))[0]
267         #print 'string: ',s
268
269     s = str(s[:-1], 'ASCII')
270 #       print("read string", s)
271
272     #remove the null character from the string
273     return s
274 #       return s[:-1]
275
276 ######################################################
277 # IMPORT
278 ######################################################
279 def process_next_object_chunk(file, previous_chunk):
280     new_chunk = chunk()
281     temp_chunk = chunk()
282
283     while (previous_chunk.bytes_read < previous_chunk.length):
284         #read the next chunk
285         read_chunk(file, new_chunk)
286
287 def skip_to_end(file, skip_chunk):
288     buffer_size = skip_chunk.length - skip_chunk.bytes_read
289     binary_format='%ic' % buffer_size
290     temp_data = file.read(struct.calcsize(binary_format))
291     skip_chunk.bytes_read += buffer_size
292
293
294 def add_texture_to_material(image, texture, material, mapto):
295     #print('assigning %s to %s' % (texture, material))
296     
297     if mapto not in ("COLOR", "SPECULARITY", "ALPHA", "NORMAL"):
298         print('/tError:  Cannot map to "%s"\n\tassuming diffuse color. modify material "%s" later.' % (mapto, material.name))
299         mapto = "COLOR"
300
301     if image: 
302         texture.image = image
303 #       if image: texture.setImage(image) # double check its an image.
304
305     material.add_texture(texture, "UV", mapto)
306
307
308 def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH):
309     #print previous_chunk.bytes_read, 'BYTES READ'
310     contextObName = None
311     contextLamp = [None, None] # object, Data
312     contextMaterial = None
313     contextMatrix_rot = None # Blender.Mathutils.Matrix(); contextMatrix.identity()
314     #contextMatrix_tx = None # Blender.Mathutils.Matrix(); contextMatrix.identity()
315     contextMesh_vertls = None
316     contextMesh_facels = None
317     contextMeshMaterials = {} # matname:[face_idxs]
318     contextMeshUV = None
319
320     TEXTURE_DICT = {}
321     MATDICT = {}
322 #       TEXMODE = Mesh.FaceModes['TEX']
323
324     # Localspace variable names, faster.
325     STRUCT_SIZE_1CHAR = struct.calcsize('c')
326     STRUCT_SIZE_2FLOAT = struct.calcsize('2f')
327     STRUCT_SIZE_3FLOAT = struct.calcsize('3f')
328     STRUCT_SIZE_UNSIGNED_SHORT = struct.calcsize('H')
329     STRUCT_SIZE_4UNSIGNED_SHORT = struct.calcsize('4H')
330     STRUCT_SIZE_4x3MAT = struct.calcsize('ffffffffffff')
331     _STRUCT_SIZE_4x3MAT = struct.calcsize('fffffffffffff')
332     # STRUCT_SIZE_4x3MAT = calcsize('ffffffffffff')
333     # print STRUCT_SIZE_4x3MAT, ' STRUCT_SIZE_4x3MAT'
334
335     def putContextMesh(myContextMesh_vertls, myContextMesh_facels, myContextMeshMaterials):
336
337         materialFaces = set() # faces that have a material. Can optimize?
338
339         # Now make copies with assigned materils.
340
341         def makeMeshMaterialCopy(matName, faces):
342             '''
343             Make a new mesh with only face the faces that use this material.
344             faces can be any iterable object - containing ints.
345             '''
346
347             faceVertUsers = [False] * len(myContextMesh_vertls)
348             ok = 0
349             for fIdx in faces:
350                 for vindex in myContextMesh_facels[fIdx]:
351                     faceVertUsers[vindex] = True
352                     if matName != None: # if matName is none then this is a set(), meaning we are using the untextured faces and do not need to store textured faces.
353                         materialFaces.add(fIdx)
354                     ok = 1
355
356             if not ok:
357                 return
358
359             myVertMapping = {}
360             vertMappingIndex = 0
361
362             vertsToUse = [i for i in range(len(myContextMesh_vertls)) if faceVertUsers[i]]
363             myVertMapping = dict( [ (ii, i) for i, ii in enumerate(vertsToUse) ] )
364
365             tempName= '%s_%s' % (contextObName, matName) # matName may be None.
366             bmesh = bpy.data.meshes.new(tempName)
367
368             if matName == None:
369                 img = None
370             else:
371                 bmat = MATDICT[matName][1]
372                 bmesh.add_material(bmat)
373 #                               bmesh.materials = [bmat]
374                 try:    img = TEXTURE_DICT[bmat.name]
375                 except: img = None
376
377 #                       bmesh_verts = bmesh.verts
378             if len(vertsToUse):
379                 bmesh.add_geometry(len(vertsToUse), 0, len(faces))
380
381                 # XXX why add extra vertex?
382 #                               bmesh_verts.extend( [Vector()] )
383                 bmesh.verts.foreach_set("co", [x for tup in [myContextMesh_vertls[i] for i in vertsToUse] for x in tup])
384 #                               bmesh_verts.extend( [myContextMesh_vertls[i] for i in vertsToUse] )
385
386                 # +1 because of DUMMYVERT
387                 bmesh.faces.foreach_set("verts_raw", unpack_face_list([[myVertMapping[vindex] for vindex in myContextMesh_facels[fIdx]] for fIdx in faces]))
388 #                               face_mapping = bmesh.faces.extend( [ [ bmesh_verts[ myVertMapping[vindex]+1] for vindex in myContextMesh_facels[fIdx]] for fIdx in faces ], indexList=True )
389
390                 if bmesh.faces and (contextMeshUV or img):
391                     bmesh.add_uv_texture()
392                     for ii, i in enumerate(faces):
393
394                         # Mapped index- faces may have not been added- if so, then map to the correct index
395                         # BUGGY API - face_mapping is not always the right length
396 #                                               map_index = face_mapping[ii]
397
398                         if 1:
399 #                                               if map_index != None:
400                             targetFace = bmesh.faces[ii]
401 #                                                       targetFace = bmesh.faces[map_index]
402
403                             uf = bmesh.active_uv_texture.data[ii]
404
405                             if contextMeshUV:
406                                 # v.index-1 because of the DUMMYVERT
407                                 uvs = [contextMeshUV[vindex] for vindex in myContextMesh_facels[i]]
408
409                                 if len(myContextMesh_facels[i]) == 3:
410                                     uf.uv1, uf.uv2, uf.uv3, uf.uv4 = uvs + [(0.0, 0.0)]
411                                 else:
412                                     uf.uv1, uf.uv2, uf.uv3, uf.uv4 = uvs
413 #                                                               targetFace.uv = [contextMeshUV[vindex] for vindex in myContextMesh_facels[i]]
414                             if img:
415                                 uf.image = img
416                                 
417                                 # to get this image to show up in 'Textured' shading mode
418                                 uf.tex = True 
419
420             # bmesh.transform(contextMatrix)
421             ob = bpy.data.objects.new(tempName, 'MESH')
422             ob.data = bmesh
423             SCN.objects.link(ob)
424 #                       ob = SCN_OBJECTS.new(bmesh, tempName)
425             '''
426             if contextMatrix_tx:
427                 ob.setMatrix(contextMatrix_tx)
428             '''
429
430             if contextMatrix_rot:
431                 # ob.matrix = [x for row in contextMatrix_rot for x in row]
432                 ob.matrix = contextMatrix_rot
433 #                               ob.setMatrix(contextMatrix_rot)
434
435             importedObjects.append(ob)
436             bmesh.update()
437 #                       bmesh.calcNormals()
438
439         for matName, faces in myContextMeshMaterials.items():
440             makeMeshMaterialCopy(matName, faces)
441
442         if len(materialFaces) != len(myContextMesh_facels):
443             # Invert material faces.
444             makeMeshMaterialCopy(None, set(range(len( myContextMesh_facels ))) - materialFaces)
445             #raise 'Some UnMaterialed faces', len(contextMesh.faces)
446
447     #a spare chunk
448     new_chunk = chunk()
449     temp_chunk = chunk()
450
451     CreateBlenderObject = False
452
453     def read_float_color(temp_chunk):
454         temp_data = file.read(struct.calcsize('3f'))
455         temp_chunk.bytes_read += 12
456         return [float(col) for col in struct.unpack('<3f', temp_data)]
457
458     def read_byte_color(temp_chunk):
459         temp_data = file.read(struct.calcsize('3B'))
460         temp_chunk.bytes_read += 3
461         return [float(col)/255 for col in struct.unpack('<3B', temp_data)] # data [0,1,2] == rgb
462
463     def read_texture(new_chunk, temp_chunk, name, mapto):
464         new_texture = bpy.data.textures.new(name)
465         new_texture.type = 'IMAGE'
466         new_texture = new_texture.recast_type()
467
468         img = None
469         while (new_chunk.bytes_read < new_chunk.length):
470             #print 'MAT_TEXTURE_MAP..while', new_chunk.bytes_read, new_chunk.length
471             read_chunk(file, temp_chunk)
472
473             if (temp_chunk.ID == MAT_MAP_FILENAME):
474                 texture_name = read_string(file)
475                 img = TEXTURE_DICT[contextMaterial.name] = load_image(texture_name, dirname)
476                 new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
477
478             else:
479                 skip_to_end(file, temp_chunk)
480
481             new_chunk.bytes_read += temp_chunk.bytes_read
482
483         # add the map to the material in the right channel
484         if img:
485             add_texture_to_material(img, new_texture, contextMaterial, mapto)
486
487     dirname = os.path.dirname(FILENAME)
488
489     #loop through all the data for this chunk (previous chunk) and see what it is
490     while (previous_chunk.bytes_read < previous_chunk.length):
491         #print '\t', previous_chunk.bytes_read, 'keep going'
492         #read the next chunk
493         #print 'reading a chunk'
494         read_chunk(file, new_chunk)
495
496         #is it a Version chunk?
497         if (new_chunk.ID == VERSION):
498             #print 'if (new_chunk.ID == VERSION):'
499             #print 'found a VERSION chunk'
500             #read in the version of the file
501             #it's an unsigned short (H)
502             temp_data = file.read(struct.calcsize('I'))
503             version = struct.unpack('<I', temp_data)[0]
504             new_chunk.bytes_read += 4 #read the 4 bytes for the version number
505             #this loader works with version 3 and below, but may not with 4 and above
506             if (version > 3):
507                 print('\tNon-Fatal Error:  Version greater than 3, may not load correctly: ', version)
508
509         #is it an object info chunk?
510         elif (new_chunk.ID == OBJECTINFO):
511             #print 'elif (new_chunk.ID == OBJECTINFO):'
512             # print 'found an OBJECTINFO chunk'
513             process_next_chunk(file, new_chunk, importedObjects, IMAGE_SEARCH)
514
515             #keep track of how much we read in the main chunk
516             new_chunk.bytes_read += temp_chunk.bytes_read
517
518         #is it an object chunk?
519         elif (new_chunk.ID == OBJECT):
520
521             if CreateBlenderObject:
522                 putContextMesh(contextMesh_vertls, contextMesh_facels, contextMeshMaterials)
523                 contextMesh_vertls = []; contextMesh_facels = []
524
525                 ## preparando para receber o proximo objeto
526                 contextMeshMaterials = {} # matname:[face_idxs]
527                 contextMeshUV = None
528                 #contextMesh.vertexUV = 1 # Make sticky coords.
529                 # Reset matrix
530                 contextMatrix_rot = None
531                 #contextMatrix_tx = None
532
533             CreateBlenderObject = True
534             tempName = read_string(file)
535             contextObName = tempName
536             new_chunk.bytes_read += len(tempName)+1
537
538         #is it a material chunk?
539         elif (new_chunk.ID == MATERIAL):
540
541 #                       print("read material")
542
543             #print 'elif (new_chunk.ID == MATERIAL):'
544             contextMaterial = bpy.data.materials.new('Material')
545
546         elif (new_chunk.ID == MAT_NAME):
547             #print 'elif (new_chunk.ID == MAT_NAME):'
548             material_name = read_string(file)
549
550 #                       print("material name", material_name)
551
552             #plus one for the null character that ended the string
553             new_chunk.bytes_read += len(material_name)+1
554
555             contextMaterial.name = material_name.rstrip() # remove trailing  whitespace
556             MATDICT[material_name]= (contextMaterial.name, contextMaterial)
557
558         elif (new_chunk.ID == MAT_AMBIENT):
559             #print 'elif (new_chunk.ID == MAT_AMBIENT):'
560             read_chunk(file, temp_chunk)
561             if (temp_chunk.ID == MAT_FLOAT_COLOR):
562                 contextMaterial.mirror_color = read_float_color(temp_chunk)
563 #                               temp_data = file.read(struct.calcsize('3f'))
564 #                               temp_chunk.bytes_read += 12
565 #                               contextMaterial.mirCol = [float(col) for col in struct.unpack('<3f', temp_data)]
566             elif (temp_chunk.ID == MAT_24BIT_COLOR):
567                 contextMaterial.mirror_color = read_byte_color(temp_chunk)
568 #                               temp_data = file.read(struct.calcsize('3B'))
569 #                               temp_chunk.bytes_read += 3
570 #                               contextMaterial.mirCol = [float(col)/255 for col in struct.unpack('<3B', temp_data)] # data [0,1,2] == rgb
571             else:
572                 skip_to_end(file, temp_chunk)
573             new_chunk.bytes_read += temp_chunk.bytes_read
574
575         elif (new_chunk.ID == MAT_DIFFUSE):
576             #print 'elif (new_chunk.ID == MAT_DIFFUSE):'
577             read_chunk(file, temp_chunk)
578             if (temp_chunk.ID == MAT_FLOAT_COLOR):
579                 contextMaterial.diffuse_color = read_float_color(temp_chunk)
580 #                               temp_data = file.read(struct.calcsize('3f'))
581 #                               temp_chunk.bytes_read += 12
582 #                               contextMaterial.rgbCol = [float(col) for col in struct.unpack('<3f', temp_data)]
583             elif (temp_chunk.ID == MAT_24BIT_COLOR):
584                 contextMaterial.diffuse_color = read_byte_color(temp_chunk)
585 #                               temp_data = file.read(struct.calcsize('3B'))
586 #                               temp_chunk.bytes_read += 3
587 #                               contextMaterial.rgbCol = [float(col)/255 for col in struct.unpack('<3B', temp_data)] # data [0,1,2] == rgb
588             else:
589                 skip_to_end(file, temp_chunk)
590
591 #                       print("read material diffuse color", contextMaterial.diffuse_color)
592
593             new_chunk.bytes_read += temp_chunk.bytes_read
594
595         elif (new_chunk.ID == MAT_SPECULAR):
596             #print 'elif (new_chunk.ID == MAT_SPECULAR):'
597             read_chunk(file, temp_chunk)
598             if (temp_chunk.ID == MAT_FLOAT_COLOR):
599                 contextMaterial.specular_color = read_float_color(temp_chunk)
600 #                               temp_data = file.read(struct.calcsize('3f'))
601 #                               temp_chunk.bytes_read += 12
602 #                               contextMaterial.mirCol = [float(col) for col in struct.unpack('<3f', temp_data)]
603             elif (temp_chunk.ID == MAT_24BIT_COLOR):
604                 contextMaterial.specular_color = read_byte_color(temp_chunk)
605 #                               temp_data = file.read(struct.calcsize('3B'))
606 #                               temp_chunk.bytes_read += 3
607 #                               contextMaterial.mirCol = [float(col)/255 for col in struct.unpack('<3B', temp_data)] # data [0,1,2] == rgb
608             else:
609                 skip_to_end(file, temp_chunk)
610             new_chunk.bytes_read += temp_chunk.bytes_read
611
612         elif (new_chunk.ID == MAT_TEXTURE_MAP):
613             read_texture(new_chunk, temp_chunk, "Diffuse", "COLOR")
614
615         elif (new_chunk.ID == MAT_SPECULAR_MAP):
616             read_texture(new_chunk, temp_chunk, "Specular", "SPECULARITY")
617
618         elif (new_chunk.ID == MAT_OPACITY_MAP):
619             read_texture(new_chunk, temp_chunk, "Opacity", "ALPHA")
620
621         elif (new_chunk.ID == MAT_BUMP_MAP):
622             read_texture(new_chunk, temp_chunk, "Bump", "NORMAL")
623
624         elif (new_chunk.ID == MAT_TRANSPARENCY):
625             #print 'elif (new_chunk.ID == MAT_TRANSPARENCY):'
626             read_chunk(file, temp_chunk)
627             temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT)
628
629             temp_chunk.bytes_read += 2
630             contextMaterial.alpha = 1-(float(struct.unpack('<H', temp_data)[0])/100)
631             new_chunk.bytes_read += temp_chunk.bytes_read
632
633
634         elif (new_chunk.ID == OBJECT_LAMP): # Basic lamp support.
635
636             temp_data = file.read(STRUCT_SIZE_3FLOAT)
637
638             x,y,z = struct.unpack('<3f', temp_data)
639             new_chunk.bytes_read += STRUCT_SIZE_3FLOAT
640
641             ob = bpy.data.objects.new("Lamp", 'LAMP')
642             ob.data = bpy.data.lamps.new("Lamp")
643             SCN.objects.link(ob)
644
645             contextLamp[1]= ob.data
646 #                       contextLamp[1]= bpy.data.lamps.new()
647             contextLamp[0]= ob
648 #                       contextLamp[0]= SCN_OBJECTS.new(contextLamp[1])
649             importedObjects.append(contextLamp[0])
650
651             #print 'number of faces: ', num_faces
652             #print x,y,z
653             contextLamp[0].location = (x, y, z)
654 #                       contextLamp[0].setLocation(x,y,z)
655
656             # Reset matrix
657             contextMatrix_rot = None
658             #contextMatrix_tx = None
659             #print contextLamp.name,
660
661         elif (new_chunk.ID == OBJECT_MESH):
662             # print 'Found an OBJECT_MESH chunk'
663             pass
664         elif (new_chunk.ID == OBJECT_VERTICES):
665             '''
666             Worldspace vertex locations
667             '''
668             # print 'elif (new_chunk.ID == OBJECT_VERTICES):'
669             temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT)
670             num_verts = struct.unpack('<H', temp_data)[0]
671             new_chunk.bytes_read += 2
672
673             # print 'number of verts: ', num_verts
674             def getvert():
675                 temp_data = struct.unpack('<3f', file.read(STRUCT_SIZE_3FLOAT))
676                 new_chunk.bytes_read += STRUCT_SIZE_3FLOAT #12: 3 floats x 4 bytes each
677                 return temp_data
678
679             #contextMesh.verts.extend( [Vector(),] ) # DUMMYVERT! - remove when blenders internals are fixed.
680             contextMesh_vertls = [getvert() for i in range(num_verts)]
681
682             #print 'object verts: bytes read: ', new_chunk.bytes_read
683
684         elif (new_chunk.ID == OBJECT_FACES):
685             # print 'elif (new_chunk.ID == OBJECT_FACES):'
686             temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT)
687             num_faces = struct.unpack('<H', temp_data)[0]
688             new_chunk.bytes_read += 2
689             #print 'number of faces: ', num_faces
690
691             def getface():
692                 # print '\ngetting a face'
693                 temp_data = file.read(STRUCT_SIZE_4UNSIGNED_SHORT)
694                 new_chunk.bytes_read += STRUCT_SIZE_4UNSIGNED_SHORT #4 short ints x 2 bytes each
695                 v1,v2,v3,dummy = struct.unpack('<4H', temp_data)
696                 return v1, v2, v3
697
698             contextMesh_facels = [ getface() for i in range(num_faces) ]
699
700
701         elif (new_chunk.ID == OBJECT_MATERIAL):
702             # print 'elif (new_chunk.ID == OBJECT_MATERIAL):'
703             material_name = read_string(file)
704             new_chunk.bytes_read += len(material_name)+1 # remove 1 null character.
705
706             temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT)
707             num_faces_using_mat = struct.unpack('<H', temp_data)[0]
708             new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
709
710             def getmat():
711                 temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT)
712                 new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
713                 return struct.unpack('<H', temp_data)[0]
714
715             contextMeshMaterials[material_name]= [ getmat() for i in range(num_faces_using_mat) ]
716
717             #look up the material in all the materials
718
719         elif (new_chunk.ID == OBJECT_UV):
720             temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT)
721             num_uv = struct.unpack('<H', temp_data)[0]
722             new_chunk.bytes_read += 2
723
724             def getuv():
725                 temp_data = file.read(STRUCT_SIZE_2FLOAT)
726                 new_chunk.bytes_read += STRUCT_SIZE_2FLOAT #2 float x 4 bytes each
727                 return Mathutils.Vector( struct.unpack('<2f', temp_data) )
728
729             contextMeshUV = [ getuv() for i in range(num_uv) ]
730
731         elif (new_chunk.ID == OBJECT_TRANS_MATRIX):
732             # How do we know the matrix size? 54 == 4x4 48 == 4x3
733             temp_data = file.read(STRUCT_SIZE_4x3MAT)
734             data = list( struct.unpack('<ffffffffffff', temp_data)  )
735             new_chunk.bytes_read += STRUCT_SIZE_4x3MAT
736
737             contextMatrix_rot = Mathutils.Matrix(\
738              data[:3] + [0],\
739              data[3:6] + [0],\
740              data[6:9] + [0],\
741              data[9:] + [1])
742
743
744             '''
745             contextMatrix_rot = Blender.Mathutils.Matrix(\
746              data[:3] + [0],\
747              data[3:6] + [0],\
748              data[6:9] + [0],\
749              [0,0,0,1])
750             '''
751
752             '''
753             contextMatrix_rot = Blender.Mathutils.Matrix(\
754              data[:3] ,\
755              data[3:6],\
756              data[6:9])
757             '''
758
759             '''
760             contextMatrix_rot = Blender.Mathutils.Matrix()
761             m = 0
762             for j in xrange(4):
763                 for i in xrange(3):
764                     contextMatrix_rot[j][i] = data[m]
765                     m += 1
766
767             contextMatrix_rot[0][3]=0;
768             contextMatrix_rot[1][3]=0;
769             contextMatrix_rot[2][3]=0;
770             contextMatrix_rot[3][3]=1;
771             '''
772
773             #contextMatrix_rot.resize4x4()
774             #print "MTX"
775             #print contextMatrix_rot
776             contextMatrix_rot.invert()
777             #print contextMatrix_rot
778             #contextMatrix_tx = Blender.Mathutils.TranslationMatrix(0.5 * Blender.Mathutils.Vector(data[9:]))
779             #contextMatrix_tx.invert()
780
781             #tx.invert()
782
783             #contextMatrix = contextMatrix * tx
784             #contextMatrix = contextMatrix  *tx
785
786         elif  (new_chunk.ID == MAT_MAP_FILENAME):
787             texture_name = read_string(file)
788             try:
789                 TEXTURE_DICT[contextMaterial.name]
790             except:
791                 #img = TEXTURE_DICT[contextMaterial.name]= BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
792                 img = TEXTURE_DICT[contextMaterial.name] = load_image(texture_name, dirname)
793 #                               img = TEXTURE_DICT[contextMaterial.name]= BPyImage.comprehensiveImageLoad(texture_name, FILENAME, PLACE_HOLDER=False, RECURSIVE=IMAGE_SEARCH)
794
795             new_chunk.bytes_read += len(texture_name)+1 #plus one for the null character that gets removed
796
797         else: #(new_chunk.ID!=VERSION or new_chunk.ID!=OBJECTINFO or new_chunk.ID!=OBJECT or new_chunk.ID!=MATERIAL):
798             # print 'skipping to end of this chunk'
799             buffer_size = new_chunk.length - new_chunk.bytes_read
800             binary_format='%ic' % buffer_size
801             temp_data = file.read(struct.calcsize(binary_format))
802             new_chunk.bytes_read += buffer_size
803
804
805         #update the previous chunk bytes read
806         # print 'previous_chunk.bytes_read += new_chunk.bytes_read'
807         # print previous_chunk.bytes_read, new_chunk.bytes_read
808         previous_chunk.bytes_read += new_chunk.bytes_read
809         ## print 'Bytes left in this chunk: ', previous_chunk.length - previous_chunk.bytes_read
810
811     # FINISHED LOOP
812     # There will be a number of objects still not added
813     if contextMesh_facels != None:
814         putContextMesh(contextMesh_vertls, contextMesh_facels, contextMeshMaterials)
815
816 def load_3ds(filename, context, IMPORT_CONSTRAIN_BOUNDS=10.0, IMAGE_SEARCH=True, APPLY_MATRIX=False):
817     global FILENAME, SCN
818 #       global FILENAME, SCN_OBJECTS
819
820     # XXX
821 #       if BPyMessages.Error_NoFile(filename):
822 #               return
823
824     print('\n\nImporting 3DS: "%s"' % (filename))
825 #       print('\n\nImporting 3DS: "%s"' % (Blender.sys.expandpath(filename)))
826
827     time1 = time.clock()
828 #       time1 = Blender.sys.time()
829
830     FILENAME = filename
831     current_chunk = chunk()
832
833     file = open(filename,'rb')
834
835     #here we go!
836     # print 'reading the first chunk'
837     read_chunk(file, current_chunk)
838     if (current_chunk.ID!=PRIMARY):
839         print('\tFatal Error:  Not a valid 3ds file: ', filename)
840         file.close()
841         return
842
843
844     # IMPORT_AS_INSTANCE = Blender.Draw.Create(0)
845 #       IMPORT_CONSTRAIN_BOUNDS = Blender.Draw.Create(10.0)
846 #       IMAGE_SEARCH = Blender.Draw.Create(1)
847 #       APPLY_MATRIX = Blender.Draw.Create(0)
848
849     # Get USER Options
850 #       pup_block = [\
851 #       ('Size Constraint:', IMPORT_CONSTRAIN_BOUNDS, 0.0, 1000.0, 'Scale the model by 10 until it reacehs the size constraint. Zero Disables.'),\
852 #       ('Image Search', IMAGE_SEARCH, 'Search subdirs for any assosiated images (Warning, may be slow)'),\
853 #       ('Transform Fix', APPLY_MATRIX, 'Workaround for object transformations importing incorrectly'),\
854 #       #('Group Instance', IMPORT_AS_INSTANCE, 'Import objects into a new scene and group, creating an instance in the current scene.'),\
855 #       ]
856
857 #       if PREF_UI:
858 #               if not Blender.Draw.PupBlock('Import 3DS...', pup_block):
859 #                       return
860
861 #       Blender.Window.WaitCursor(1)
862
863 #       IMPORT_CONSTRAIN_BOUNDS = IMPORT_CONSTRAIN_BOUNDS.val
864 #       # IMPORT_AS_INSTANCE = IMPORT_AS_INSTANCE.val
865 #       IMAGE_SEARCH = IMAGE_SEARCH.val
866 #       APPLY_MATRIX = APPLY_MATRIX.val
867
868     if IMPORT_CONSTRAIN_BOUNDS:
869         BOUNDS_3DS[:]= [1<<30, 1<<30, 1<<30, -1<<30, -1<<30, -1<<30]
870     else:
871         BOUNDS_3DS[:]= []
872
873     ##IMAGE_SEARCH
874
875     scn = context.scene
876 #       scn = bpy.data.scenes.active
877     SCN = scn
878 #       SCN_OBJECTS = scn.objects
879 #       SCN_OBJECTS.selected = [] # de select all
880
881     importedObjects = [] # Fill this list with objects
882     process_next_chunk(file, current_chunk, importedObjects, IMAGE_SEARCH)
883
884
885     # Link the objects into this scene.
886     # Layers = scn.Layers
887
888     # REMOVE DUMMYVERT, - remove this in the next release when blenders internal are fixed.
889
890
891 #       for ob in importedObjects:
892 #               if ob.type == 'MESH':
893 # #             if ob.type=='Mesh':
894 #                       me = ob.getData(mesh=1)
895 #                       me.verts.delete([me.verts[0],])
896 #                       if not APPLY_MATRIX:
897 #                               me.transform(ob.matrixWorld.copy().invert())
898
899     # Done DUMMYVERT
900     """
901     if IMPORT_AS_INSTANCE:
902         name = filename.split('\\')[-1].split('/')[-1]
903         # Create a group for this import.
904         group_scn = Scene.New(name)
905         for ob in importedObjects:
906             group_scn.link(ob) # dont worry about the layers
907
908         grp = Blender.Group.New(name)
909         grp.objects = importedObjects
910
911         grp_ob = Object.New('Empty', name)
912         grp_ob.enableDupGroup = True
913         grp_ob.DupGroup = grp
914         scn.link(grp_ob)
915         grp_ob.Layers = Layers
916         grp_ob.sel = 1
917     else:
918         # Select all imported objects.
919         for ob in importedObjects:
920             scn.link(ob)
921             ob.Layers = Layers
922             ob.sel = 1
923     """
924
925     if 0:
926 #       if IMPORT_CONSTRAIN_BOUNDS!=0.0:
927         # Set bounds from objecyt bounding box
928         for ob in importedObjects:
929             if ob.type == 'MESH':
930 #                       if ob.type=='Mesh':
931                 ob.makeDisplayList() # Why dosnt this update the bounds?
932                 for v in ob.getBoundBox():
933                     for i in (0,1,2):
934                         if v[i] < BOUNDS_3DS[i]:
935                             BOUNDS_3DS[i]= v[i] # min
936
937                         if v[i] > BOUNDS_3DS[i + 3]:
938                             BOUNDS_3DS[i + 3]= v[i] # min
939
940         # Get the max axis x/y/z
941         max_axis = max(BOUNDS_3DS[3]-BOUNDS_3DS[0], BOUNDS_3DS[4]-BOUNDS_3DS[1], BOUNDS_3DS[5]-BOUNDS_3DS[2])
942         # print max_axis
943         if max_axis < 1 << 30: # Should never be false but just make sure.
944
945             # Get a new scale factor if set as an option
946             SCALE = 1.0
947             while (max_axis * SCALE) > IMPORT_CONSTRAIN_BOUNDS:
948                 SCALE/=10
949
950             # SCALE Matrix
951             SCALE_MAT = Mathutils.Matrix([SCALE,0,0,0],[0,SCALE,0,0],[0,0,SCALE,0],[0,0,0,1])
952 #                       SCALE_MAT = Blender.Mathutils.Matrix([SCALE,0,0,0],[0,SCALE,0,0],[0,0,SCALE,0],[0,0,0,1])
953
954             for ob in importedObjects:
955                 ob.setMatrix(ob.matrixWorld * SCALE_MAT)
956
957         # Done constraining to bounds.
958
959     # Select all new objects.
960     print('finished importing: "%s" in %.4f sec.' % (filename, (time.clock()-time1)))
961 #       print('finished importing: "%s" in %.4f sec.' % (filename, (Blender.sys.time()-time1)))
962     file.close()
963
964
965 DEBUG = False
966 # For testing compatibility
967 #load_3ds('/metavr/convert/vehicle/truck_002/TruckTanker1.3DS', False)
968 #load_3ds('/metavr/archive/convert/old/arranged_3ds_to_hpx-2/only-need-engine-trains/Engine2.3DS', False)
969 '''
970
971 else:
972     import os
973     # DEBUG ONLY
974     TIME = Blender.sys.time()
975     import os
976     print 'Searching for files'
977     os.system('find /metavr/ -iname "*.3ds" > /tmp/temp3ds_list')
978     # os.system('find /storage/ -iname "*.3ds" > /tmp/temp3ds_list')
979     print '...Done'
980     file = open('/tmp/temp3ds_list', 'r')
981     lines = file.readlines()
982     file.close()
983     # sort by filesize for faster testing
984     lines_size = [(os.path.getsize(f[:-1]), f[:-1]) for f in lines]
985     lines_size.sort()
986     lines = [f[1] for f in lines_size]
987
988
989     def between(v,a,b):
990         if v <= max(a,b) and v >= min(a,b):
991             return True
992         return False
993
994     for i, _3ds in enumerate(lines):
995         if between(i, 650,800):
996             #_3ds= _3ds[:-1]
997             print 'Importing', _3ds, '\nNUMBER', i, 'of', len(lines)
998             _3ds_file= _3ds.split('/')[-1].split('\\')[-1]
999             newScn = Blender.Scene.New(_3ds_file)
1000             newScn.makeCurrent()
1001             load_3ds(_3ds, False)
1002
1003     print 'TOTAL TIME: %.6f' % (Blender.sys.time() - TIME)
1004
1005 '''
1006 from bpy.props import *
1007
1008
1009 class IMPORT_OT_autodesk_3ds(bpy.types.Operator):
1010     '''Import from 3DS file format (.3ds)'''
1011     bl_idname = "import_scene.autodesk_3ds"
1012     bl_label = 'Import 3DS'
1013
1014     # List of operator properties, the attributes will be assigned
1015     # to the class instance from the operator settings before calling.
1016
1017     path = StringProperty(name="File Path", description="File path used for importing the 3DS file", maxlen= 1024, default= "")
1018     filename = StringProperty(name="File Name", description="Name of the file.")
1019     directory = StringProperty(name="Directory", description="Directory of the file.")
1020         
1021 #       size_constraint = FloatProperty(name="Size Constraint", description="Scale the model by 10 until it reacehs the size constraint. Zero Disables.", min=0.0, max=1000.0, soft_min=0.0, soft_max=1000.0, default=10.0),
1022 #       search_images = BoolProperty(name="Image Search", description="Search subdirectories for any assosiated images (Warning, may be slow)", default=True),
1023 #       apply_matrix = BoolProperty(name="Transform Fix", description="Workaround for object transformations importing incorrectly", default=False),
1024
1025     def execute(self, context):
1026         load_3ds(self.properties.path, context, 0.0, False, False)
1027         return {'FINISHED'}
1028
1029     def invoke(self, context, event):
1030         wm = context.manager
1031         wm.add_fileselect(self)
1032         return {'RUNNING_MODAL'}
1033
1034
1035 menu_func = lambda self, context: self.layout.operator(IMPORT_OT_autodesk_3ds.bl_idname, text="3D Studio (.3ds)...")
1036
1037
1038 def register():
1039     bpy.types.register(IMPORT_OT_autodesk_3ds)
1040     bpy.types.INFO_MT_file_export.append(menu_func)
1041     
1042 def unregister():
1043     bpy.types.unregister(IMPORT_OT_autodesk_3ds)
1044     bpy.types.INFO_MT_file_export.remove(menu_func)
1045
1046 # NOTES:
1047 # why add 1 extra vertex? and remove it when done? - "Answer - eekadoodle - would need to re-order UV's without this since face order isnt always what we give blender, BMesh will solve :D"
1048 # disabled scaling to size, this requires exposing bb (easy) and understanding how it works (needs some time)