use f.area where possible over python function and use len(mface) over len(mface.v)
[blender.git] / release / scripts / 3ds_import.py
1 #!BPY
2 """ 
3 Name: '3D Studio (.3ds)...'
4 Blender: 241
5 Group: 'Import'
6 Tooltip: 'Import from 3DS file format (.3ds)'
7 """
8
9 __author__= ['Bob Holcomb', 'Richard L?rk?ng', 'Damien McGinnes', 'Campbell Barton']
10 __url__= ('blender', 'elysiun', 'http://www.gametutorials.com')
11 __version__= '0.95'
12 __bpydoc__= '''\
13
14 3ds Importer
15
16 This script imports a 3ds file and the materials into Blender for editing.
17
18 Loader is based on 3ds loader from www.gametutorials.com (Thanks DigiBen).
19
20 0.95 by Campbell Barton<br>
21 - Removed workarounds for Blender 2.41
22 - Mesh objects split by material- many 3ds objects used more then 16 per mesh.
23 - Removed a lot of unneeded variable creation.
24
25 0.94 by Campbell Barton<br> 
26 - Face import tested to be about overall 16x speedup over 0.93.
27 - Material importing speedup.
28 - Tested with more models.
29 - Support some corrupt models.
30
31 0.93 by Campbell Barton<br> 
32 - Tested with 400 3ds files from turbosquid and samples.
33 - Tactfully ignore faces that used the same verts twice.
34 - Rollback to 0.83 sloppy un-reorganized code, this broke UV coord loading.
35 - Converted from NMesh to Mesh.
36 - Faster and cleaner new names.
37 - Use external comprehensive image loader.
38 - Re intergrated 0.92 and 0.9 changes
39 - Fixes for 2.41 compat.
40 - Non textured faces do not use a texture flag.
41
42 0.92<br>
43 - Added support for diffuse, alpha, spec, bump maps in a single material
44
45 0.9<br>
46 - Reorganized code into object/material block functions<br>
47 - Use of Matrix() to copy matrix data<br>
48 - added support for material transparency<br>
49
50 0.83 2005-08-07: Campell Barton
51 -  Aggressive image finding and case insensitivy for posisx systems.
52
53 0.82a 2005-07-22
54 - image texture loading (both for face uv and renderer)
55
56 0.82 - image texture loading (for face uv)
57
58 0.81a (fork- not 0.9) Campbell Barton 2005-06-08
59 - Simplified import code
60 - Never overwrite data
61 - Faster list handling
62 - Leaves import selected
63
64 0.81 Damien McGinnes 2005-01-09
65 - handle missing images better
66     
67 0.8 Damien McGinnes 2005-01-08
68 - copies sticky UV coords to face ones
69 - handles images better
70 - Recommend that you run 'RemoveDoubles' on each imported mesh after using this script
71
72 '''
73
74 # Importing modules
75
76 import Blender
77 from Blender import Mesh, Scene, Object, Material, Image, Texture, Lamp, Mathutils
78 from Blender.Mathutils import Vector
79 import BPyImage
80 reload( BPyImage )
81
82 import struct
83 from struct import calcsize, unpack
84
85 import os
86
87 # If python version is less than 2.4, try to get set stuff from module
88 import sys
89 if ( (sys.version_info[0] <= 2) and (sys.version_info[1] < 4) ):
90         from sets import Set as set
91
92 #this script imports uvcoords as sticky vertex coords
93 #this parameter enables copying these to face uv coords
94 #which shold be more useful.
95
96
97 #===========================================================================#
98 # Returns unique name of object/mesh (stops overwriting existing meshes)    #
99 #===========================================================================#
100 def getUniqueName(name):
101         count= 0
102         newname= name[:19]
103         while newname in getUniqueName.uniqueObNames:
104                 newname= '%s.%.3i' % (name[:15], count)
105                 count+=1
106         # Dont use again.
107         getUniqueName.uniqueObNames.append(newname)
108         return newname
109 getUniqueName.uniqueObNames= Blender.NMesh.GetNames() + [ob.name for ob in Object.Get()]
110
111 def createBlenderTexture(material, name, image):
112         texture= Texture.New(name)
113         texture.setType('Image')
114         texture.image= image
115         material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL)
116
117
118
119 ######################################################
120 # Data Structures
121 ######################################################
122
123 #Some of the chunks that we will see
124 #----- Primary Chunk, at the beginning of each file
125 PRIMARY= long('0x4D4D',16)
126
127 #------ Main Chunks
128 OBJECTINFO   =      long('0x3D3D',16);      #This gives the version of the mesh and is found right before the material and object information
129 VERSION      =      long('0x0002',16);      #This gives the version of the .3ds file
130 EDITKEYFRAME=      long('0xB000',16);      #This is the header for all of the key frame info
131
132 #------ sub defines of OBJECTINFO
133 MATERIAL=45055          #0xAFFF                         // This stored the texture info
134 OBJECT=16384            #0x4000                         // This stores the faces, vertices, etc...
135
136 #>------ sub defines of MATERIAL
137 #------ sub defines of MATERIAL_BLOCK
138 MAT_NAME                =       long('0xA000',16)       # This holds the material name
139 MAT_AMBIENT             =       long('0xA010',16)       # Ambient color of the object/material
140 MAT_DIFFUSE             =       long('0xA020',16)       # This holds the color of the object/material
141 MAT_SPECULAR    =       long('0xA030',16)       # SPecular color of the object/material
142 MAT_SHINESS             =       long('0xA040',16)       # ??
143 MAT_TRANSPARENCY=       long('0xA050',16)       # Transparency value of material
144 MAT_SELF_ILLUM  =       long('0xA080',16)       # Self Illumination value of material
145 MAT_WIRE                =       long('0xA085',16)       # Only render's wireframe
146
147 MAT_TEXTURE_MAP =       long('0xA200',16)       # This is a header for a new texture map
148 MAT_SPECULAR_MAP=       long('0xA204',16)       # This is a header for a new specular map
149 MAT_OPACITY_MAP =       long('0xA210',16)       # This is a header for a new opacity map
150 MAT_REFLECTION_MAP=     long('0xA220',16)       # This is a header for a new reflection map
151 MAT_BUMP_MAP    =       long('0xA230',16)       # This is a header for a new bump map
152 MAT_MAP_FILENAME =      long('0xA300',16);      # This holds the file name of the texture
153
154 #>------ sub defines of OBJECT
155 OBJECT_MESH  =      long('0x4100',16);      # This lets us know that we are reading a new object
156 OBJECT_LAMP =      long('0x4600',16);      # This lets un know we are reading a light object
157 OBJECT_LAMP_SPOT = long('0x4610',16);           # The light is a spotloght.
158 OBJECT_LAMP_OFF = long('0x4620',16);            # The light off.
159 OBJECT_LAMP_ATTENUATE = long('0x4625',16);      
160 OBJECT_LAMP_RAYSHADE = long('0x4627',16);       
161 OBJECT_LAMP_SHADOWED = long('0x4630',16);       
162 OBJECT_LAMP_LOCAL_SHADOW = long('0x4640',16);   
163 OBJECT_LAMP_LOCAL_SHADOW2 = long('0x4641',16);  
164 OBJECT_LAMP_SEE_CONE = long('0x4650',16);       
165 OBJECT_LAMP_SPOT_RECTANGULAR= long('0x4651',16);
166 OBJECT_LAMP_SPOT_OVERSHOOT= long('0x4652',16);
167 OBJECT_LAMP_SPOT_PROJECTOR= long('0x4653',16);
168 OBJECT_LAMP_EXCLUDE= long('0x4654',16);
169 OBJECT_LAMP_RANGE= long('0x4655',16);
170 OBJECT_LAMP_ROLL= long('0x4656',16);
171 OBJECT_LAMP_SPOT_ASPECT= long('0x4657',16);
172 OBJECT_LAMP_RAY_BIAS= long('0x4658',16);
173 OBJECT_LAMP_INNER_RANGE= long('0x4659',16);
174 OBJECT_LAMP_OUTER_RANGE= long('0x465A',16);
175 OBJECT_LAMP_MULTIPLIER = long('0x465B',16);
176 OBJECT_LAMP_AMBIENT_LIGHT = long('0x4680',16);
177
178
179
180 OBJECT_CAMERA=      long('0x4700',16);      # This lets un know we are reading a camera object
181
182 #>------ sub defines of CAMERA
183 OBJECT_CAM_RANGES=   long('0x4720',16);      # The camera range values
184
185 #>------ sub defines of OBJECT_MESH
186 OBJECT_VERTICES =   long('0x4110',16);      # The objects vertices
187 OBJECT_FACES    =   long('0x4120',16);      # The objects faces
188 OBJECT_MATERIAL =   long('0x4130',16);      # This is found if the object has a material, either texture map or color
189 OBJECT_UV       =   long('0x4140',16);      # The UV texture coordinates
190 OBJECT_TRANS_MATRIX  =   long('0x4160',16); # The Object Matrix
191
192 global scn
193 scn= None
194
195 #the chunk class
196 class chunk:
197         ID=0
198         length=0
199         bytes_read=0
200
201         #we don't read in the bytes_read, we compute that
202         binary_format='<HI'
203
204         def __init__(self):
205                 self.ID=0
206                 self.length=0
207                 self.bytes_read=0
208
209         def dump(self):
210                 print 'ID: ', self.ID
211                 print 'ID in hex: ', hex(self.ID)
212                 print 'length: ', self.length
213                 print 'bytes_read: ', self.bytes_read
214
215 def read_chunk(file, chunk):
216         temp_data=file.read(calcsize(chunk.binary_format))
217         data=unpack(chunk.binary_format, temp_data)
218         chunk.ID=data[0]
219         chunk.length=data[1]
220         #update the bytes read function
221         chunk.bytes_read=6
222
223         #if debugging
224         #chunk.dump()
225
226 def read_string(file):
227         #read in the characters till we get a null character
228         s=''
229         while not s.endswith('\x00'):
230                 s+=unpack( 'c', file.read(1) )[0]
231                 #print 'string: ',s
232         
233         #remove the null character from the string
234         return s[:-1]
235
236 ######################################################
237 # IMPORT
238 ######################################################
239 def process_next_object_chunk(file, previous_chunk):
240         new_chunk=chunk()
241         temp_chunk=chunk()
242
243         while (previous_chunk.bytes_read<previous_chunk.length):
244                 #read the next chunk
245                 read_chunk(file, new_chunk)
246
247 def skip_to_end(file, skip_chunk):
248         buffer_size=skip_chunk.length-skip_chunk.bytes_read
249         binary_format='%ic' % buffer_size
250         temp_data=file.read(calcsize(binary_format))
251         skip_chunk.bytes_read+=buffer_size
252
253
254 def add_texture_to_material(image, texture, material, mapto):
255         if mapto=='DIFFUSE':
256                 map=Texture.MapTo.COL
257         elif mapto=='SPECULAR':
258                 map=Texture.MapTo.SPEC
259         elif mapto=='OPACITY':
260                 map=Texture.MapTo.ALPHA
261         elif mapto=='BUMP':
262                 map=Texture.MapTo.NOR
263         else:
264                 raise '/tError:  Cannot map to ', mapto
265                 return
266         
267
268         texture.setImage(image)
269         texture_list=material.getTextures()
270         index=0
271         for tex in texture_list:
272                 if tex==None:
273                         material.setTexture(index,texture,Texture.TexCo.UV,map)
274                         return
275                 else:
276                         index+=1
277                 if index>10:
278                         print '/tError: Cannot add diffuse map.  Too many textures'
279
280 def process_next_chunk(file, previous_chunk, importedObjects):
281         #print previous_chunk.bytes_read, 'BYTES READ'
282         contextObName= None
283         contextLamp= [None, None] # object, Data
284         contextMaterial= None
285         contextMatrix= Blender.Mathutils.Matrix(); contextMatrix.identity()
286         contextMesh= None
287         contextMeshMaterials= {} # matname:[face_idxs]
288         
289         TEXTURE_DICT={}
290         MATDICT={}
291         TEXMODE= Mesh.FaceModes['TEX']
292         
293         # Localspace variable names, faster.
294         STRUCT_SIZE_1CHAR= calcsize('c')
295         STRUCT_SIZE_2FLOAT= calcsize('2f')
296         STRUCT_SIZE_3FLOAT= calcsize('3f')
297         STRUCT_SIZE_UNSIGNED_SHORT= calcsize('H')
298         STRUCT_SIZE_4UNSIGNED_SHORT= calcsize('4H')
299         STRUCT_SIZE_4x3MAT= calcsize('ffffffffffff')
300         
301         
302         def putContextMesh(myContextMesh, myContextMeshMaterials):
303                 
304                 #print 'prtting myContextMesh', myContextMesh.name
305                 INV_MAT= Blender.Mathutils.Matrix(contextMatrix)
306                 
307                 INV_MAT.invert()
308                 contextMesh.transform(INV_MAT)
309                 
310                 materialFaces= set()
311                 # Now make copies with assigned materils.
312                 
313                 def makeMeshMaterialCopy(matName, faces):                       
314                         # Make a new mesh with only face the faces that use this material.
315                         faceVertUsers = [False] * len(myContextMesh.verts)
316                         ok=0
317                         for fIdx in faces:
318                                 for v in myContextMesh.faces[fIdx].v:
319                                         faceVertUsers[v.index] = True
320                                         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.
321                                                 materialFaces.add(fIdx)
322                                         ok=1
323                         
324                         if not ok:
325                                 return
326                                         
327                         myVertMapping = {}
328                         vertMappingIndex = 0
329                         
330                         vertsToUse = [i for i in xrange(len(myContextMesh.verts)) if faceVertUsers[i]]
331                         myVertMapping = dict( [ (ii, i) for i, ii in enumerate(vertsToUse) ] )
332                         
333                         bmesh = Mesh.New(contextMesh.name)
334                         
335                         if matName != None:
336                                 bmat = MATDICT[matName][1]
337                                 try:
338                                         img= TEXTURE_DICT[bmat.name]
339                                 except:
340                                         img= None
341                                 bmesh.materials= [bmat]
342                         else:
343                                 img= None
344                                 
345                         bmesh.verts.extend( [myContextMesh.verts[i].co for i in vertsToUse] )
346                         bmesh.faces.extend( [ [ bmesh.verts[ myVertMapping[v.index]] for v in myContextMesh.faces[fIdx].v] for fIdx in faces ] )
347                         
348                         if contextMeshUV or img:
349                                 bmesh.faceUV= 1
350                                 for ii, i in enumerate(faces):
351                                         targetFace= bmesh.faces[ii]
352                                         if contextMeshUV:
353                                                 targetFace.uv= [contextMeshUV[v.index] for v in myContextMesh.faces[i].v]
354                                                 
355                                         if img:
356                                                 targetFace.image= img
357                         
358                         tempName= contextObName + '_' + str(matName) # str because we may be None
359                         bmesh.name= tempName
360                         ob = Object.New('Mesh', tempName)
361                         ob.link(bmesh)
362                         ob.setMatrix(contextMatrix)
363                         importedObjects.append(ob)
364                         ##scn.link(ob)
365                         ##ob.Layers= scn.Layers
366                         ##ob.sel= 1
367                 
368                 for matName, faces in myContextMeshMaterials.iteritems():
369                         makeMeshMaterialCopy(matName, faces)
370                         
371                 if len(materialFaces)!=len(contextMesh.faces):
372                         # Invert material faces.
373                         makeMeshMaterialCopy(None, set(range(len( contextMesh.faces ))) - materialFaces)
374                         #raise 'Some UnMaterialed faces', len(contextMesh.faces)
375                 
376         
377         #a spare chunk
378         new_chunk= chunk()
379         temp_chunk= chunk()
380
381         #loop through all the data for this chunk (previous chunk) and see what it is
382         while (previous_chunk.bytes_read<previous_chunk.length):
383                 #print '\t', previous_chunk.bytes_read, 'keep going'
384                 #read the next chunk
385                 #print 'reading a chunk'
386                 read_chunk(file, new_chunk)
387
388                 #is it a Version chunk?
389                 if (new_chunk.ID==VERSION):
390                         #print 'if (new_chunk.ID==VERSION):'
391                         #print 'found a VERSION chunk'
392                         #read in the version of the file
393                         #it's an unsigned short (H)
394                         temp_data= file.read(calcsize('I'))
395                         version,= unpack('I', temp_data)
396                         new_chunk.bytes_read+= 4 #read the 4 bytes for the version number
397                         #this loader works with version 3 and below, but may not with 4 and above
398                         if (version>3):
399                                 print '\tNon-Fatal Error:  Version greater than 3, may not load correctly: ', version
400
401                 #is it an object info chunk?
402                 elif (new_chunk.ID==OBJECTINFO):
403                         #print 'elif (new_chunk.ID==OBJECTINFO):'
404                         # print 'found an OBJECTINFO chunk'
405                         process_next_chunk(file, new_chunk, importedObjects)
406                         
407                         #keep track of how much we read in the main chunk
408                         new_chunk.bytes_read+=temp_chunk.bytes_read
409
410                 #is it an object chunk?
411                 elif (new_chunk.ID==OBJECT):
412                         tempName= read_string(file)
413                         contextObName= getUniqueName( tempName )
414                         new_chunk.bytes_read += len(tempName)+1
415                 
416                 #is it a material chunk?
417                 elif (new_chunk.ID==MATERIAL):
418                         #print 'elif (new_chunk.ID==MATERIAL):'
419                         contextMaterial= Material.New()
420                 
421                 elif (new_chunk.ID==MAT_NAME):
422                         #print 'elif (new_chunk.ID==MAT_NAME):'
423                         material_name= read_string(file)
424                         
425                         #plus one for the null character that ended the string
426                         new_chunk.bytes_read+= len(material_name)+1
427                         
428                         contextMaterial.name= material_name
429                         MATDICT[material_name]= (contextMaterial.name, contextMaterial)
430                 
431                 elif (new_chunk.ID==MAT_AMBIENT):
432                         #print 'elif (new_chunk.ID==MAT_AMBIENT):'
433                         read_chunk(file, temp_chunk)
434                         temp_data=file.read(calcsize('3B'))
435                         temp_chunk.bytes_read+= 3
436                         contextMaterial.mirCol= [float(col)/255 for col in unpack('3B', temp_data)] # data [0,1,2] == rgb
437                         new_chunk.bytes_read+= temp_chunk.bytes_read
438
439                 elif (new_chunk.ID==MAT_DIFFUSE):
440                         #print 'elif (new_chunk.ID==MAT_DIFFUSE):'
441                         read_chunk(file, temp_chunk)
442                         temp_data=file.read(calcsize('3B'))
443                         temp_chunk.bytes_read+= 3
444                         contextMaterial.rgbCol= [float(col)/255 for col in unpack('3B', temp_data)] # data [0,1,2] == rgb
445                         new_chunk.bytes_read+= temp_chunk.bytes_read
446
447                 elif (new_chunk.ID==MAT_SPECULAR):
448                         #print 'elif (new_chunk.ID==MAT_SPECULAR):'
449                         read_chunk(file, temp_chunk)
450                         temp_data= file.read(calcsize('3B'))
451                         temp_chunk.bytes_read+= 3
452                         
453                         contextMaterial.specCol= [float(col)/255 for col in unpack('3B', temp_data)] # data [0,1,2] == rgb
454                         new_chunk.bytes_read+= temp_chunk.bytes_read
455
456                 elif (new_chunk.ID==MAT_TEXTURE_MAP):
457                         #print 'elif (new_chunk.ID==MAT_TEXTURE_MAP):'
458                         new_texture= Blender.Texture.New('Diffuse')
459                         new_texture.setType('Image')
460                         while (new_chunk.bytes_read<new_chunk.length):
461                                 #print 'MAT_TEXTURE_MAP..while', new_chunk.bytes_read, new_chunk.length
462                                 read_chunk(file, temp_chunk)
463                                 
464                                 if (temp_chunk.ID==MAT_MAP_FILENAME):
465                                         texture_name=read_string(file)
466                                         img= TEXTURE_DICT[contextMaterial.name]= BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
467                                         new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
468                                         
469                                 else:
470                                         skip_to_end(file, temp_chunk)
471                                 
472                                 new_chunk.bytes_read+= temp_chunk.bytes_read
473                         
474                         #add the map to the material in the right channel
475                         add_texture_to_material(img, new_texture, contextMaterial, 'DIFFUSE')
476                         
477                 elif (new_chunk.ID==MAT_SPECULAR_MAP):
478                         #print 'elif (new_chunk.ID==MAT_SPECULAR_MAP):'
479                         new_texture= Blender.Texture.New('Specular')
480                         new_texture.setType('Image')
481                         while (new_chunk.bytes_read<new_chunk.length):
482                                 read_chunk(file, temp_chunk)
483                                 
484                                 if (temp_chunk.ID==MAT_MAP_FILENAME):
485                                         texture_name= read_string(file)
486                                         img= BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
487                                         new_chunk.bytes_read+= (len(texture_name)+1) #plus one for the null character that gets removed
488                                 else:
489                                         skip_to_end(file, temp_chunk)
490                                 
491                                 new_chunk.bytes_read+= temp_chunk.bytes_read
492                                 
493                         #add the map to the material in the right channel
494                         add_texture_to_material(img, new_texture, contextMaterial, 'SPECULAR')
495         
496                 elif (new_chunk.ID==MAT_OPACITY_MAP):
497                         #print 'new_texture=Blender.Texture.New('Opacity')'
498                         new_texture= Blender.Texture.New('Opacity')
499                         new_texture.setType('Image')
500                         while (new_chunk.bytes_read<new_chunk.length):
501                                 read_chunk(file, temp_chunk)
502                                 
503                                 if (temp_chunk.ID==MAT_MAP_FILENAME):
504                                         texture_name= read_string(file)
505                                         img= BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
506                                         new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
507                                 else:
508                                         skip_to_end(file, temp_chunk)
509                                 
510                                 new_chunk.bytes_read+= temp_chunk.bytes_read
511
512                         #add the map to the material in the right channel
513                         add_texture_to_material(img, new_texture, contextMaterial, 'OPACITY')
514
515                 elif (new_chunk.ID==MAT_BUMP_MAP):
516                         #print 'elif (new_chunk.ID==MAT_BUMP_MAP):'
517                         new_texture= Blender.Texture.New('Bump')
518                         new_texture.setType('Image')
519                         while (new_chunk.bytes_read<new_chunk.length):
520                                 read_chunk(file, temp_chunk)
521                                 
522                                 if (temp_chunk.ID==MAT_MAP_FILENAME):
523                                         texture_name= read_string(file)
524                                         img= BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
525                                         new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
526                                 else:
527                                         skip_to_end(file, temp_chunk)
528                                 
529                                 new_chunk.bytes_read+=temp_chunk.bytes_read
530                                 
531                         #add the map to the material in the right channel
532                         add_texture_to_material(img, new_texture, contextMaterial, 'BUMP')
533                         
534                 elif (new_chunk.ID==MAT_TRANSPARENCY):
535                         #print 'elif (new_chunk.ID==MAT_TRANSPARENCY):'
536                         read_chunk(file, temp_chunk)
537                         temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
538                         
539                         temp_chunk.bytes_read+=2
540                         contextMaterial.alpha= 1-(float(unpack('H', temp_data)[0])/100)
541                         new_chunk.bytes_read+=temp_chunk.bytes_read
542
543
544                 elif (new_chunk.ID==OBJECT_LAMP): # Basic lamp support.
545                         
546                         #print 'LAMP!!!!!!!!!'
547                         temp_data=file.read(STRUCT_SIZE_3FLOAT)
548                         
549                         x,y,z=unpack('3f', temp_data)
550                         new_chunk.bytes_read+=STRUCT_SIZE_3FLOAT
551                         
552                         contextLamp[0]= Object.New('Lamp')
553                         contextLamp[1]= Lamp.New()
554                         contextLamp[0].link(contextLamp[1])
555                         ##scn.link(contextLamp[0])
556                         importedObjects.append(contextLamp[0])
557                         
558                         
559                         
560                         #print 'number of faces: ', num_faces
561                         #print x,y,z
562                         contextLamp[0].setLocation(x,y,z)
563                         
564                         # Reset matrix
565                         contextMatrix= Mathutils.Matrix(); contextMatrix.identity()     
566                         #print contextLamp.name, 
567                         
568                         
569                 elif (new_chunk.ID==OBJECT_MESH):
570                         # print 'Found an OBJECT_MESH chunk'
571                         if contextMesh != None: # Write context mesh if we have one.
572                                 putContextMesh(contextMesh, contextMeshMaterials)
573                         
574                         contextMesh= Mesh.New()
575                         contextMeshMaterials= {} # matname:[face_idxs]
576                         contextMeshUV= None
577                         #contextMesh.vertexUV= 1 # Make sticky coords.
578                         # Reset matrix
579                         contextMatrix= Blender.Mathutils.Matrix(); contextMatrix.identity()
580                 
581                 elif (new_chunk.ID==OBJECT_VERTICES):
582                         # print 'elif (new_chunk.ID==OBJECT_VERTICES):'
583                         temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
584                         num_verts,=unpack('H', temp_data)
585                         new_chunk.bytes_read+=2
586                         
587                         # print 'number of verts: ', num_verts
588                         def getvert():
589                                 temp_data=file.read(STRUCT_SIZE_3FLOAT)
590                                 new_chunk.bytes_read += STRUCT_SIZE_3FLOAT #12: 3 floats x 4 bytes each
591                                 return Vector(unpack('3f', temp_data))
592                         
593                         contextMesh.verts.extend( [getvert() for i in xrange(num_verts)] )
594                         #print 'object verts: bytes read: ', new_chunk.bytes_read
595
596                 elif (new_chunk.ID==OBJECT_FACES):
597                         # print 'elif (new_chunk.ID==OBJECT_FACES):'
598                         temp_data= file.read(STRUCT_SIZE_UNSIGNED_SHORT)
599                         num_faces,= unpack('H', temp_data)
600                         new_chunk.bytes_read+= 2
601                         #print 'number of faces: ', num_faces
602                         
603                         def getface():
604                                 # print '\ngetting a face'
605                                 temp_data= file.read(STRUCT_SIZE_4UNSIGNED_SHORT)
606                                 new_chunk.bytes_read+= STRUCT_SIZE_4UNSIGNED_SHORT #4 short ints x 2 bytes each
607                                 v1,v2,v3,dummy= unpack('4H', temp_data)
608                                 if v1==v2 or v1==v3 or v2==v3:
609                                         return None
610                                 return contextMesh.verts[v1], contextMesh.verts[v2], contextMesh.verts[v3]
611                         
612                         faces= [ getface() for i in xrange(num_faces) ]
613                         facesExtend= [ f for f in faces if f ]
614                         
615                         if facesExtend:
616                                 contextMesh.faces.extend( facesExtend )
617                                 
618                                 # face mapping so duplicate faces dont mess us up.
619                                 if len(contextMesh.faces)==len(faces):
620                                         contextFaceMapping= None
621                                 else:
622                                         contextFaceMapping= {}
623                                         meshFaceOffset= 0
624                                         for i, f in enumerate(faces):
625                                                 if not f: # Face used stupid verts-
626                                                         contextFaceMapping[i]= None
627                                                         meshFaceOffset+= 1
628                                                 else:
629                                                         #print 'DOUBLE FACE', '\tfacelen', len(f), i, num_faces, (i-meshFaceOffset)
630                                                         #print i-meshFaceOffset, len(contextMesh.faces)q
631                                                         if len(contextMesh.faces) <= i-meshFaceOffset: # SHOULD NEVER HAPPEN, CORRUPS 3DS?
632                                                                 contextFaceMapping[i]= None
633                                                                 meshFaceOffset-=1
634                                                         else:
635                                                                 meshface= contextMesh.faces[i-meshFaceOffset]
636                                                                 ok= True
637                                                                 for vi in xrange(len(f)):
638                                                                         if meshface.v[vi] != f[vi]:
639                                                                                 ok=False
640                                                                                 break
641                                                                 if ok:
642                                                                         meshFaceOffset+=1
643                                                                         contextFaceMapping[i]= i-meshFaceOffset
644                                                                 else:
645                                                                         contextFaceMapping[i]= None
646                                 
647
648
649                 elif (new_chunk.ID==OBJECT_MATERIAL):
650                         # print 'elif (new_chunk.ID==OBJECT_MATERIAL):'
651                         material_name= read_string(file)
652                         new_chunk.bytes_read += len(material_name)+1 # remove 1 null character.
653                         
654                         tempMatFaceIndexList = contextMeshMaterials[material_name]= []
655                         
656                         temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
657                         num_faces_using_mat,= unpack('H', temp_data)
658                         new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
659                         
660                         #list of faces using mat
661                         for face_counter in xrange(num_faces_using_mat):
662                                 temp_data= file.read(STRUCT_SIZE_UNSIGNED_SHORT)
663                                 new_chunk.bytes_read+= STRUCT_SIZE_UNSIGNED_SHORT
664                                 faceIndex,= unpack('H', temp_data)
665                                 
666                                 # We dont have to use context face mapping.
667                                 if contextFaceMapping:
668                                         meshFaceIndex= contextFaceMapping[faceIndex]
669                                 else:
670                                         meshFaceIndex= faceIndex
671                                 
672                                 if meshFaceIndex != None:
673                                         tempMatFaceIndexList.append(meshFaceIndex)
674                         
675                         tempMatFaceIndexList.sort()
676                         del tempMatFaceIndexList
677                         #look up the material in all the materials
678
679                 elif (new_chunk.ID==OBJECT_UV):
680                         # print 'elif (new_chunk.ID==OBJECT_UV):'
681                         temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
682                         num_uv,=unpack('H', temp_data)
683                         new_chunk.bytes_read+= 2
684                         
685                         def getuv():
686                                 temp_data=file.read(STRUCT_SIZE_2FLOAT)
687                                 new_chunk.bytes_read += STRUCT_SIZE_2FLOAT #2 float x 4 bytes each
688                                 return Vector( unpack('2f', temp_data) )
689                                 
690                         contextMeshUV= [ getuv() for i in xrange(num_uv) ]
691                 
692                 elif (new_chunk.ID== OBJECT_TRANS_MATRIX):
693                         # print 'elif (new_chunk.ID== OBJECT_TRANS_MATRIX):'
694                         temp_data=file.read(STRUCT_SIZE_4x3MAT)
695                         data= list( unpack('ffffffffffff', temp_data) )
696                         new_chunk.bytes_read += STRUCT_SIZE_4x3MAT 
697                         
698                         contextMatrix= Blender.Mathutils.Matrix(\
699                          data[:3] + [0],\
700                          data[3:6] + [0],\
701                          data[6:9] + [0],\
702                          data[9:] + [1])
703                 
704                 elif  (new_chunk.ID==MAT_MAP_FILENAME):
705                         raise 'Hello--'
706                         texture_name=read_string(file)
707                         try:
708                                 TEXTURE_DICT[contextMaterial.name]
709                         except:
710                                 img= TEXTURE_DICT[contextMaterial.name]= BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
711                         
712                         new_chunk.bytes_read+= len(texture_name)+1 #plus one for the null character that gets removed
713                 
714                 else: #(new_chunk.ID!=VERSION or new_chunk.ID!=OBJECTINFO or new_chunk.ID!=OBJECT or new_chunk.ID!=MATERIAL):
715                         # print 'skipping to end of this chunk'
716                         buffer_size=new_chunk.length-new_chunk.bytes_read
717                         binary_format='%ic' % buffer_size
718                         temp_data=file.read(calcsize(binary_format))
719                         new_chunk.bytes_read+=buffer_size
720
721
722                 #update the previous chunk bytes read
723                 # print 'previous_chunk.bytes_read += new_chunk.bytes_read'
724                 # print previous_chunk.bytes_read, new_chunk.bytes_read
725                 previous_chunk.bytes_read += new_chunk.bytes_read
726                 ## print 'Bytes left in this chunk: ', previous_chunk.length-previous_chunk.bytes_read
727         
728         # FINISHED LOOP
729         # There will be a number of objects still not added
730         if contextMesh != None:
731                 putContextMesh(contextMesh, contextMeshMaterials)
732
733 def load_3ds(filename):
734         print '\n\nImporting "%s" "%s"' % (filename, Blender.sys.expandpath(filename))
735         
736         time1= Blender.sys.time()
737         
738         global FILENAME
739         FILENAME=filename
740         current_chunk=chunk()
741         
742         file=open(filename,'rb')
743         
744         #here we go!
745         # print 'reading the first chunk'
746         read_chunk(file, current_chunk)
747         if (current_chunk.ID!=PRIMARY):
748                 print '\tFatal Error:  Not a valid 3ds file: ', filename
749                 file.close()
750                 return
751         
752         
753         IMPORT_AS_INSTANCE= Blender.Draw.Create(0)
754         # Get USER Options
755         pup_block= [\
756         ('Group Instance', IMPORT_AS_INSTANCE, 'Import objects into a new scene and group, creating an instance in the current scene.'),\
757         ]
758         
759         if not Blender.Draw.PupBlock('Import 3DS...', pup_block):
760                 return
761         IMPORT_AS_INSTANCE= IMPORT_AS_INSTANCE.val
762         
763         importedObjects= [] # Fill this list with objects
764         process_next_chunk(file, current_chunk, importedObjects)
765         
766         scn= Scene.GetCurrent()
767         for ob in scn.getChildren():
768                 ob.sel= 0
769         
770         # Link the objects into this scene.
771         Layers= scn.Layers
772         if IMPORT_AS_INSTANCE:
773                 name= filename.split('\\')[-1].split('/')[-1]
774                 # Create a group for this import.
775                 group_scn= Scene.New(name)
776                 for ob in importedObjects:
777                         group_scn.link(ob) # dont worry about the layers
778                 
779                 grp= Blender.Group.New(name)
780                 grp.objects= importedObjects
781                 
782                 grp_ob= Object.New('Empty', name)
783                 grp_ob.enableDupGroup= True
784                 grp_ob.DupGroup= grp
785                 scn.link(grp_ob)
786                 grp_ob.Layers= Layers
787                 grp_ob.sel= 1
788         else:
789                 # Select all imported objects.
790                 for ob in importedObjects:
791                         scn.link(ob)
792                         ob.Layers= Layers
793                         ob.sel= 1
794         
795         
796         # Select all new objects.
797         print 'finished importing: "%s" in %.4f sec.' % (filename, (Blender.sys.time()-time1))
798         file.close()
799
800 if __name__=='__main__':
801         Blender.Window.FileSelector(load_3ds, 'Import 3DS', '*.3ds')
802
803 # For testing compatibility
804 '''
805 TIME= Blender.sys.time()
806 import os
807 print 'Searching for files'
808 os.system('find /metavr/ -iname "*.3ds" > /tmp/temp3ds_list')
809 # os.system('find /storage/ -iname "*.3ds" > /tmp/temp3ds_list')
810 print '...Done'
811 file= open('/tmp/temp3ds_list', 'r')
812 lines= file.readlines()
813 file.close()
814
815 def between(v,a,b):
816         if v <= max(a,b) and v >= min(a,b):
817                 return True
818         return False
819         
820 for i, _3ds in enumerate(lines):
821         if between(i, 600, 700):
822                 _3ds= _3ds[:-1]
823                 print 'Importing', _3ds, '\nNUMBER', i, 'of', len(lines)
824                 _3ds_file= _3ds.split('/')[-1].split('\\')[-1]
825                 newScn= Scene.New(_3ds_file)
826                 newScn.makeCurrent()
827                 load_3ds(_3ds)
828
829 print 'TOTAL TIME: %.6f' % (Blender.sys.time() - TIME)
830 '''