Scripts:
[blender.git] / release / scripts / obj_import.py
1 #!BPY
2  
3 """
4 Name: 'Wavefront (.obj)...'
5 Blender: 232
6 Group: 'Import'
7 Tooltip: 'Load a Wavefront OBJ File'
8 """
9
10 __author__ = "Campbell Barton"
11 __url__ = ["blender", "elysiun"]
12 __version__ = "0.9"
13
14 __bpydoc__ = """\
15 This script imports OBJ files to Blender.
16
17 Usage:
18
19 Run this script from "File->Import" menu and then load the desired OBJ file.
20 """
21
22 # $Id$
23 #
24 # --------------------------------------------------------------------------
25 # OBJ Import v0.9 by Campbell Barton (AKA Ideasman)
26 # --------------------------------------------------------------------------
27 # ***** BEGIN GPL LICENSE BLOCK *****
28 #
29 # This program is free software; you can redistribute it and/or
30 # modify it under the terms of the GNU General Public License
31 # as published by the Free Software Foundation; either version 2
32 # of the License, or (at your option) any later version.
33 #
34 # This program is distributed in the hope that it will be useful,
35 # but WITHOUT ANY WARRANTY; without even the implied warranty of
36 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37 # GNU General Public License for more details.
38 #
39 # You should have received a copy of the GNU General Public License
40 # along with this program; if not, write to the Free Software Foundation,
41 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
42 #
43 # ***** END GPL LICENCE BLOCK *****
44 # --------------------------------------------------------------------------
45
46 NULL_MAT = '(null)' # Name for mesh's that have no mat set.
47 NULL_IMG = '(null)' # Name for mesh's that have no mat set.
48
49 MATLIMIT = 16 # This isnt about to change but probably should not be hard coded.
50
51 DIR = ''
52
53 #==============================================#
54 # Return directory, where is file              #
55 #==============================================#
56 def pathName(path,name):
57         length=len(path)
58         for CH in range(1, length):
59                 if path[length-CH:] == name:
60                         path = path[:length-CH]
61                         break
62         return path
63
64 #==============================================#
65 # Strips the slashes from the back of a string #
66 #==============================================#
67 def stripPath(path):
68         for CH in range(len(path), 0, -1):
69                 if path[CH-1] == "/" or path[CH-1] == "\\":
70                         path = path[CH:]
71                         break
72         return path
73         
74 #====================================================#
75 # Strips the prefix off the name before writing      #
76 #====================================================#
77 def stripName(name): # name is a string
78         prefixDelimiter = '.'
79         return name[ : name.find(prefixDelimiter) ]
80
81
82 from Blender import *
83
84 #==================================================================================#
85 # This gets a mat or creates one of the requested name if none exist.              #
86 #==================================================================================#
87 def getMat(matName):
88         # Make a new mat
89         try:
90                 return Material.Get(matName)
91         except:
92                 return Material.New(matName)
93
94
95 #==================================================================================#
96 # This function sets textures defined in .mtl file                                 #
97 #==================================================================================#
98 def getImg(img_fileName):
99         for i in Image.Get():
100                 if i.filename == img_fileName:
101                         return i
102         
103         # if we are this far it means the image hasnt been loaded.
104         try:
105                 return Image.Load(img_fileName)
106         except:
107                 print "unable to open", img_fileName
108                 return
109
110
111
112 #==================================================================================#
113 # This function sets textures defined in .mtl file                                 #
114 #==================================================================================#
115 def load_mat_image(mat, img_fileName, type, mesh):
116         try:
117                 image = Image.Load(img_fileName)
118         except:
119                 print "unable to open", img_fileName
120                 return
121         
122         texture = Texture.New(type)
123         texture.setType('Image')
124         texture.image = image
125         
126         # adds textures to faces (Textured/Alt-Z mode)
127         # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
128         if type == 'Kd':
129                 for f in mesh.faces:
130                         if mesh.materials[f.mat].name == mat.name:
131                         
132                                 # the inline usemat command overides the material Image
133                                 if not f.image:
134                                   f.image = image
135         
136         # adds textures for materials (rendering)
137         if type == 'Ka':
138                 mat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.CMIR)
139         if type == 'Kd':
140                 mat.setTexture(1, texture, Texture.TexCo.UV, Texture.MapTo.COL)
141         if type == 'Ks':
142                 mat.setTexture(2, texture, Texture.TexCo.UV, Texture.MapTo.SPEC)
143
144 #==================================================================================#
145 # This function loads materials from .mtl file (have to be defined in obj file)    #
146 #==================================================================================#
147 def load_mtl(dir, mtl_file, mesh):
148         # Remove ./
149         if mtl_file[:2] == './':
150                 mtl_file= mtl_file[2:]
151         
152         mtl_fileName = dir + mtl_file
153         try:
154                 fileLines= open(mtl_fileName, 'r').readlines()
155         except:
156                 print "unable to open", mtl_fileName
157                 return
158         
159         lIdx=0
160         while lIdx < len(fileLines):
161                 l = fileLines[lIdx].split()
162         
163                 # Detect a line that will be ignored
164                 if len(l) == 0:
165                         pass
166                 elif l[0] == '#' or len(l) == 0:
167                         pass
168                 elif l[0] == 'newmtl':
169                         currentMat = getMat(' '.join(l[1:]))
170                 elif l[0] == 'Ka':
171                         currentMat.setMirCol(float(l[1]), float(l[2]), float(l[3]))
172                 elif l[0] == 'Kd':
173                         currentMat.setRGBCol(float(l[1]), float(l[2]), float(l[3]))
174                 elif l[0] == 'Ks':
175                         currentMat.setSpecCol(float(l[1]), float(l[2]), float(l[3]))
176                 elif l[0] == 'Ns':
177                         currentMat.setHardness( int((float(l[1])*0.51)) )
178                 elif l[0] == 'd':
179                         currentMat.setAlpha(float(l[1]))
180                 elif l[0] == 'Tr':
181                         currentMat.setAlpha(float(l[1]))
182                 elif l[0] == 'map_Ka':
183                         img_fileName = dir + l[1]
184                         load_mat_image(currentMat, img_fileName, 'Ka', mesh)
185                 elif l[0] == 'map_Ks':
186                         img_fileName = dir + l[1]
187                         load_mat_image(currentMat, img_fileName, 'Ks', mesh)
188                 elif l[0] == 'map_Kd':
189                         img_fileName = dir + l[1]
190                         load_mat_image(currentMat, img_fileName, 'Kd', mesh)
191                 lIdx+=1
192
193 #======================================================================#
194 # Returns unique name of object (preserve overwriting existing meshes) #
195 #======================================================================#
196 def getUniqueName(name):
197         uniqueInt = 0
198         while 1:
199                 try:
200                         ob = Object.Get(name)
201                         # Okay, this is working, so lets make a new name
202                         name += '.' + str(uniqueInt)
203                         uniqueInt +=1
204                 except:
205                         if NMesh.GetRaw(name) == None:
206                                 return name
207                         else:
208                                 name += '.' + str(uniqueInt)
209                                 uniqueInt +=1
210
211 #==================================================================================#
212 # This loads data from .obj file                                                   #
213 #==================================================================================#
214 def load_obj(file):
215         time1 = sys.time()
216         def applyMat(mesh, f, mat):
217                 # Check weather the 16 mat limit has been met.
218                 if len( meshList[objectName][0].materials ) >= MATLIMIT:
219                         print 'Warning, max material limit reached, using an existing material'
220                         return meshList[objectName][0]
221                 
222                 mIdx = 0
223                 for m in meshList[objectName][0].materials:
224                         if m.getName() == mat.getName():
225                                 break
226                         mIdx+=1
227                 
228                 if mIdx == len(mesh.materials):
229                         meshList[objectName][0].addMaterial(mat)
230                 
231                 f.mat = mIdx
232                 return f
233
234         # Get the file name with no path or .obj
235         fileName = stripName( stripPath(file) )
236
237         mtl_fileName = ''
238
239         DIR = pathName(file, stripPath(file))
240
241         fileLines = open(file, 'r').readlines()
242
243
244
245         uvMapList = [(0,0)] # store tuple uv pairs here
246
247         # This dummy vert makes life a whole lot easier-
248         # pythons index system then aligns with objs, remove later
249         vertList = [NMesh.Vert(0, 0, 0)] # store tuple uv pairs here
250
251         nullMat = getMat(NULL_MAT)
252         
253         currentMat = nullMat # Use this mat.
254         currentImg = NULL_IMG # Null image is a string, otherwise this should be set to an image object.\
255         currentSmooth = 0
256         
257         #==================================================================================#
258         # Make split lines, ignore blenk lines or comments.                                #
259         #==================================================================================#
260         lIdx = 0 
261         while lIdx < len(fileLines):
262                 fileLines[lIdx] = fileLines[lIdx].split()
263                 lIdx+=1
264         
265         #==================================================================================#
266         # Load all verts first (texture verts too)                                         #
267         #==================================================================================#
268         lIdx = 0
269         print len(fileLines)
270         while lIdx < len(fileLines):
271                 
272                 l = fileLines[lIdx]
273                 if len(l) == 0:
274                         fileLines.pop(lIdx)
275                         lIdx-=1                 
276                         
277                 elif l[0] == 'v':
278                         # This is a new vert, make a new mesh
279                         vertList.append( NMesh.Vert(float(l[1]), float(l[2]), float(l[3]) ) )
280                         fileLines.pop(lIdx)
281                         lIdx-=1
282
283                 
284                 # UV COORDINATE
285                 elif l[0] == 'vt':
286                         # This is a new vert, make a new mesh
287                         uvMapList.append( (float(l[1]), float(l[2])) )
288                         fileLines.pop(lIdx)
289                         lIdx-=1
290                 lIdx+=1
291         
292         
293         # Here we store a boolean list of which verts are used or not
294         # no we know weather to add them to the current mesh
295         # This is an issue with global vertex indicies being translated to per mesh indicies
296         # like blenders, we start with a dummy just like the vert.
297         # -1 means unused, any other value refers to the local mesh index of the vert.
298
299         # objectName has a char in front of it that determins weather its a group or object.
300         # We ignore it when naming the object.
301         objectName = 'omesh' # If we cant get one, use this
302         meshList = {}
303         meshList[objectName] = (NMesh.GetRaw(), [-1]*len(vertList)) # Mesh/meshList[objectName][1]
304         meshList[objectName][0].verts.append(vertList[0])
305         meshList[objectName][0].hasFaceUV(1)
306
307         #==================================================================================#
308         # Load all faces into objects, main loop                                           #
309         #==================================================================================#
310         lIdx = 0
311         # Face and Object loading LOOP
312         while lIdx < len(fileLines):
313                 l = fileLines[lIdx]
314                 
315                 # VERTEX
316                 if l[0] == 'v':
317                         pass
318                         
319                 # Comment
320                 if l[0] == '#':
321                         pass                    
322                 
323                 # VERTEX NORMAL
324                 elif l[0] == 'vn':
325                         pass
326                 
327                 # UV COORDINATE
328                 elif l[0] == 'vt':
329                         pass
330                 
331                 # FACE
332                 elif l[0] == 'f': 
333                         # Make a face with the correct material.
334                         f = NMesh.Face()
335                         f = applyMat(meshList[objectName][0], f, currentMat)
336                         f.smooth = currentSmooth
337                         if currentImg != NULL_IMG: f.image = currentImg
338
339                         # Set up vIdxLs : Verts
340                         # Set up vtIdxLs : UV
341                         # Start with a dummy objects so python accepts OBJs 1 is the first index.
342                         vIdxLs = []
343                         vtIdxLs = []
344                         fHasUV = len(uvMapList)-1 # Assume the face has a UV until it sho it dosent, if there are no UV coords then this will start as 0.
345                         for v in l[1:]:
346                                 # OBJ files can have // or / to seperate vert/texVert/normal
347                                 # this is a bit of a pain but we must deal with it.
348                                 objVert = v.split('/', -1)
349                                 
350                                 # Vert Index - OBJ supports negative index assignment (like python)
351                                 
352                                 vIdxLs.append(int(objVert[0]))
353                                 if fHasUV:
354                                         # UV
355                                         if len(objVert) == 1:
356                                                 vtIdxLs.append(int(objVert[0])) # Sticky UV coords
357                                         elif objVert[1] != '': # Its possible that theres no texture vert just he vert and normal eg 1//2
358                                                 vtIdxLs.append(int(objVert[1])) # Seperate UV coords
359                                         else:
360                                                 fHasUV = 0
361
362                                         # Dont add a UV to the face if its larger then the UV coord list
363                                         # The OBJ file would have to be corrupt or badly written for thi to happen
364                                         # but account for it anyway.
365                                         if len(vtIdxLs) > 0:
366                                                 if vtIdxLs[-1] > len(uvMapList):
367                                                         fHasUV = 0
368                                                         print 'badly written OBJ file, invalid references to UV Texture coordinates.'
369                         
370                         # Quads only, we could import quads using the method below but it polite to import a quad as a quad.
371                         if len(vIdxLs) == 4:
372                                 for i in [0,1,2,3]:
373                                         if meshList[objectName][1][vIdxLs[i]] == -1:
374                                                 meshList[objectName][0].verts.append(vertList[vIdxLs[i]])
375                                                 f.v.append(meshList[objectName][0].verts[-1])
376                                                 meshList[objectName][1][vIdxLs[i]] = len(meshList[objectName][0].verts)-1
377                                         else:
378                                                 f.v.append(meshList[objectName][0].verts[meshList[objectName][1][vIdxLs[i]]])
379                                 
380                                 # UV MAPPING
381                                 if fHasUV:
382                                         f.uv.extend([uvMapList[ vtIdxLs[0] ],uvMapList[ vtIdxLs[1] ],uvMapList[ vtIdxLs[2] ],uvMapList[ vtIdxLs[3] ]])
383                                         #for i in [0,1,2,3]:
384                                         #       f.uv.append( uvMapList[ vtIdxLs[i] ] )
385
386                                 if f.v > 0:
387                                         f = applyMat(meshList[objectName][0], f, currentMat)
388                                         if currentImg != NULL_IMG:
389                                                 f.image = currentImg        
390                                         meshList[objectName][0].faces.append(f) # move the face onto the mesh
391                                         if len(meshList[objectName][0].faces[-1]) > 0:
392                                                 meshList[objectName][0].faces[-1].smooth = currentSmooth
393
394                         elif len(vIdxLs) >= 3: # This handles tri's and fans
395                                 for i in range(len(vIdxLs)-2):
396                                         f = NMesh.Face()
397                                         f = applyMat(meshList[objectName][0], f, currentMat)
398                                         for ii in [0, i+1, i+2]:
399                                                 
400                                                 if meshList[objectName][1][vIdxLs[ii]] == -1:
401                                                         meshList[objectName][0].verts.append(vertList[vIdxLs[ii]])
402                                                         f.v.append(meshList[objectName][0].verts[-1])
403                                                         meshList[objectName][1][vIdxLs[ii]] = len(meshList[objectName][0].verts)-1
404                                                 else:
405                                                         f.v.append(meshList[objectName][0].verts[meshList[objectName][1][vIdxLs[ii]]])
406
407                                         # UV MAPPING
408                                         if fHasUV:
409                                                 f.uv.extent([uvMapList[ vtIdxLs[0] ], uvMapList[ vtIdxLs[i+1] ], uvMapList[ vtIdxLs[i+2] ]])
410
411                                         if f.v > 0:
412                                                 f = applyMat(meshList[objectName][0], f, currentMat)
413                                                 if currentImg != NULL_IMG:
414                                                         f.image = currentImg        
415                                                 meshList[objectName][0].faces.append(f) # move the face onto the mesh
416                                                 if len(meshList[objectName][0].faces[-1]) > 0:
417                                                         meshList[objectName][0].faces[-1].smooth = currentSmooth
418                 
419                 
420                 # FACE SMOOTHING
421                 elif l[0] == 's':
422                         if l[1] == 'off': currentSmooth = 0
423                         else: currentSmooth = 1
424                         # print "smoothing", currentSmooth
425
426                 # OBJECT / GROUP
427                 elif l[0] == 'o' or l[0] == 'g':
428                         # This makes sure that if an object and a group have the same name then
429                         # they are not put into the same object.
430                         
431                         # Only make a new group.object name if the verts in the existing object have been used, this is obscure
432                         # but some files face groups seperating verts and faces which results in silly things. (no groups have names.)
433                         if len(l) == 1 and len( meshList[objectName][0].faces ) == 0:
434                                 pass
435                         
436                         else:
437                                 newObjectName = l[0] + '_'                              
438                                 
439                                 # if there is no groups name then make gp_1, gp_2, gp_100 etc
440                                 
441                                 if len(l) == 1: # No name given, make a unique name up.
442                                         
443                                         unique_count = 0
444                                         while newObjectName in meshList.keys():
445                                                 newObjectName = l[0] + '_' + str(unique_count)
446                                                 unique_count +=1
447                                 else: # The the object/group name given
448                                         newObjectName += '_'.join(l[1:])
449                                 
450                                 # Assign the new name
451                                 objectName = newObjectName
452                                         
453                                 # If we havnt written to this mesh before then do so.
454                                 # if we have then we'll just keep appending to it, this is required for soem files.
455                                 if objectName not in meshList.keys():
456                                         meshList[objectName] = (NMesh.GetRaw(), [-1]*len(vertList))
457                                         meshList[objectName][0].hasFaceUV(1)
458                                         meshList[objectName][0].verts.append( vertList[0] )
459                                 
460
461                 # MATERIAL
462                 elif l[0] == 'usemtl':
463                         if l[1] == '(null)':
464                                 currentMat = getMat(NULL_MAT)
465                         else:
466                                 currentMat = getMat(' '.join(l[1:])) # Use join in case of spaces
467                 
468                 # MATERIAL
469                 elif l[0] == 'usemat':
470                         if l[1] == '(null)':
471                                 currentImg = NULL_IMG
472                         else:
473                                 currentImg = getImg(DIR + ' '.join(l[1:])) # Use join in case of spaces 
474                 
475                 # MATERIAL FILE
476                 elif l[0] == 'mtllib':
477                         mtl_fileName = ' '.join(l[1:])
478                 
479                 lIdx+=1
480
481         
482         #==============================================#
483         # Write all meshs in the dictionary            #
484         #==============================================#  
485         for mk in meshList.keys():
486                 # Applies material properties to materials alredy on the mesh as well as Textures.
487                 if mtl_fileName != '':
488                         load_mtl(DIR, mtl_fileName, meshList[mk][0])
489                 if len(meshList[mk][0].verts) >1:
490                         meshList[mk][0].verts.pop(0)
491                         
492                         name = getUniqueName(mk)
493                         ob = NMesh.PutRaw(meshList[mk][0], name)
494                         ob.name = name
495
496         print "obj import time: ", sys.time() - time1
497
498 Window.FileSelector(load_obj, 'Import Wavefront OBJ')
499
500 '''
501 # For testing compatability
502 import os
503 for obj in os.listdir('/obj/'):
504         if obj[-3:] == 'obj':
505                 print obj
506                 newScn = Scene.New(obj)
507                 newScn.makeCurrent()
508                 load_obj('/obj/' + obj)
509         
510 '''