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