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