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