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