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