4
Name: 'Wavefront (.obj)...'
7
Tooltip: 'Load a Wavefront OBJ File'
10
__author__ = "Campbell Barton"
11
__url__ = ["blender", "elysiun"]
15
This script imports OBJ files to Blender.
19
Run this script from "File->Import" menu and then load the desired OBJ file.
22
# $Id: obj_import.py,v 1.20 2005/06/12 05:54:15 ianwill Exp $
24
# --------------------------------------------------------------------------
25
# OBJ Import v1.0 by Campbell Barton (AKA Ideasman)
26
# --------------------------------------------------------------------------
27
# ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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.
43
# ***** END GPL LICENCE BLOCK *****
44
# --------------------------------------------------------------------------
46
ABORT_MENU = 'Failed Reading OBJ%t|File is probably another type|if not send this file to|cbarton@metavr.com|with MTL and image files for further testing.'
48
NULL_MAT = '(null)' # Name for mesh's that have no mat set.
49
NULL_IMG = '(null)' # Name for mesh's that have no mat set.
51
MATLIMIT = 16 # This isnt about to change but probably should not be hard coded.
55
#==============================================#
56
# Return directory, where the file is #
57
#==============================================#
59
lastSlash = max(path.rfind('\\'), path.rfind('/'))
61
path = path[:lastSlash]
62
return '%s%s' % (path, sys.sep)
64
#==============================================#
65
# Strips the slashes from the back of a string #
66
#==============================================#
68
return path.split('/')[-1].split('\\')[-1]
70
#====================================================#
71
# Strips the prefix off the name before writing #
72
#====================================================#
73
def stripExt(name): # name is a string
74
return name[ : name.rfind('.') ]
80
#==================================================================================#
81
# This function sets textures defined in .mtl file #
82
#==================================================================================#
83
def getImg(img_fileName):
85
if stripPath(i.filename) == stripPath(img_fileName):
88
# if we are this far it means the image hasnt been loaded.
90
return Image.Load(img_fileName)
92
print '\tunable to open image file: "%s"' % img_fileName
96
#==================================================================================#
97
# This function sets textures defined in .mtl file #
98
#==================================================================================#
99
def loadMaterialImage(mat, img_fileName, type, meshDict):
100
TEX_ON_FLAG = NMesh.FaceModes['TEX']
102
texture = Texture.New(type)
103
texture.setType('Image')
105
image = getImg(img_fileName)
107
texture.image = image
109
# adds textures to faces (Textured/Alt-Z mode)
110
# Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
111
if image and type == 'Kd':
112
for meshPair in meshDict.values():
113
for f in meshPair[0].faces:
114
#print meshPair[0].materials[f.mat].name, mat.name
115
if meshPair[0].materials[f.mat].name == mat.name:
116
# the inline usemat command overides the material Image
118
f.mode |= TEX_ON_FLAG
121
# adds textures for materials (rendering)
123
mat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.CMIR)
125
mat.setTexture(1, texture, Texture.TexCo.UV, Texture.MapTo.COL)
127
mat.setTexture(2, texture, Texture.TexCo.UV, Texture.MapTo.SPEC)
129
#==================================================================================#
130
# This function loads materials from .mtl file (have to be defined in obj file) #
131
#==================================================================================#
132
def load_mtl(dir, mtl_file, meshDict, materialDict):
134
#===============================================================================#
135
# This gets a mat or creates one of the requested name if none exist. #
136
#===============================================================================#
137
def getMat(matName, materialDict):
140
return materialDict[matName]
142
# Do we realy need to keep the dict up to date?, not realy but keeps consuistant.
143
materialDict[matName] = Material.New(matName)
144
return materialDict[matName]
146
mtl_file = stripPath(mtl_file)
147
mtl_fileName = dir + mtl_file
150
fileLines= open(mtl_fileName, 'r').readlines()
152
print '\tunable to open referenced material file: "%s"' % mtl_fileName
157
while lIdx < len(fileLines):
158
l = fileLines[lIdx].split()
160
# Detect a line that will be ignored
163
elif l[0] == '#' or len(l) == 0:
165
elif l[0] == 'newmtl':
166
currentMat = getMat('_'.join(l[1:]), materialDict) # Material should alredy exist.
168
currentMat.setMirCol(float(l[1]), float(l[2]), float(l[3]))
170
currentMat.setRGBCol(float(l[1]), float(l[2]), float(l[3]))
172
currentMat.setSpecCol(float(l[1]), float(l[2]), float(l[3]))
174
currentMat.setHardness( int((float(l[1])*0.51)) )
176
currentMat.setAlpha(float(l[1]))
178
currentMat.setAlpha(float(l[1]))
179
elif l[0] == 'map_Ka':
180
img_fileName = dir + ' '.join(l[1:])
181
loadMaterialImage(currentMat, img_fileName, 'Ka', meshDict)
182
elif l[0] == 'map_Ks':
183
img_fileName = dir + ' '.join(l[1:])
184
loadMaterialImage(currentMat, img_fileName, 'Ks', meshDict)
185
elif l[0] == 'map_Kd':
186
img_fileName = dir + ' '.join(l[1:])
187
loadMaterialImage(currentMat, img_fileName, 'Kd', meshDict)
190
print '\tERROR: Unable to parse MTL file.'
191
#===========================================================================#
192
# Returns unique name of object/mesh (preserve overwriting existing meshes) #
193
#===========================================================================#
194
def getUniqueName(name):
199
ob = Object.Get(newName)
200
# Okay, this is working, so lets make a new name
201
newName = '%s.%d' % (name, uniqueInt)
203
except AttributeError:
204
if newName not in NMesh.GetNames():
207
newName = '%s.%d' % (name, uniqueInt)
210
#==================================================================================#
211
# This loads data from .obj file #
212
#==================================================================================#
216
# Deselect all objects in the scene.
217
# do this first so we dont have to bother, with objects we import
218
for ob in Scene.GetCurrent().getChildren():
221
TEX_OFF_FLAG = ~NMesh.FaceModes['TEX']
223
# Get the file name with no path or .obj
224
fileName = stripExt( stripPath(file) )
228
DIR = stripFile(file)
230
tempFile = open(file, 'r')
231
fileLines = tempFile.readlines()
234
uvMapList = [(0,0)] # store tuple uv pairs here
236
# This dummy vert makes life a whole lot easier-
237
# pythons index system then aligns with objs, remove later
238
vertList = [None] # Could havea vert but since this is a placeholder theres no Point
241
# Store all imported images in a dict, names are key
244
# This stores the index that the current mesh has for the current material.
245
# if the mesh does not have the material then set -1
246
contextMeshMatIdx = -1
248
# Keep this out of the dict for easy accsess.
249
nullMat = Material.New(NULL_MAT)
251
currentMat = nullMat # Use this mat.
252
currentImg = None # Null image is a string, otherwise this should be set to an image object.\
253
currentSmooth = False
255
# Store a list of unnamed names
256
currentUnnamedGroupIdx = 1
257
currentUnnamedObjectIdx = 1
259
quadList = (0, 1, 2, 3)
261
faceQuadVList = [None, None, None, None]
262
faceTriVList = [None, None, None]
264
#==================================================================================#
265
# Load all verts first (texture verts too) #
266
#==================================================================================#
267
nonVertFileLines = []
269
materialDict = {} # Store all imported materials as unique dict, names are key
271
print '\tfile length: %d' % len(fileLines)
273
while lIdx < len(fileLines):
274
# Ignore vert normals
275
if fileLines[lIdx].startswith('vn'):
279
# Dont Bother splitting empty or comment lines.
280
if len(fileLines[lIdx]) == 0 or\
281
fileLines[lIdx][0] == '\n' or\
282
fileLines[lIdx][0] == '#':
286
fileLines[lIdx] = fileLines[lIdx].split()
294
vertList.append( NMesh.Vert(float(l[1]), float(l[2]), float(l[3]) ) )
298
uvMapList.append( (float(l[1]), float(l[2])) )
300
# Smoothing groups, make a list of unique.
303
smoothingGroups['_'.join(l[1:])] = None # Can we assign something more usefull? cant use sets yet
305
# Keep Smoothing group line
306
nonVertFileLines.append(l)
308
# Smoothing groups, make a list of unique.
309
elif l[0] == 'usemtl':
311
materialDict['_'.join(l[1:])] = None # Can we assign something more usefull? cant use sets yet
313
# Keep Smoothing group line
314
nonVertFileLines.append(l)
317
nonVertFileLines.append(l)
321
print Draw.PupMenu(ABORT_MENU)
325
fileLines = nonVertFileLines
328
# Only want unique keys anyway
329
smoothingGroups['(null)'] = None # Make sure we have at least 1.
330
smoothingGroups = smoothingGroups.keys()
331
print '\tfound %d smoothing groups.' % (len(smoothingGroups) -1)
333
# Add materials to Blender for later is in teh OBJ
334
for k in materialDict.keys():
335
materialDict[k] = Material.New(k)
338
# Make a list of all unused vert indicies that we can copy from
339
VERT_USED_LIST = [0]*len(vertList)
341
# Here we store a boolean list of which verts are used or not
342
# no we know weather to add them to the current mesh
343
# This is an issue with global vertex indicies being translated to per mesh indicies
344
# like blenders, we start with a dummy just like the vert.
345
# -1 means unused, any other value refers to the local mesh index of the vert.
347
# currentObjectName has a char in front of it that determins weather its a group or object.
348
# We ignore it when naming the object.
349
currentObjectName = 'unnamed_obj_0' # If we cant get one, use this
351
meshDict = {} # The 3 variables below are stored in a tuple within this dict for each mesh
352
currentMesh = NMesh.GetRaw() # The NMesh representation of the OBJ group/Object
353
currentUsedVertList = {} # A Dict of smooth groups, each smooth group has a list of used verts and they are generated on demand so as to save memory.
354
currentMaterialMeshMapping = {} # Used to store material indicies so we dont have to search the mesh for materials every time.
356
# Every mesh has a null smooth group, this is used if there are no smooth groups in the OBJ file.
357
# and when for faces where no smooth group is used.
358
currentSmoothGroup = '(null)' # The Name of the current smooth group
360
# For direct accsess to the Current Meshes, Current Smooth Groups- Used verts.
361
# This is of course context based and changes on the fly.
362
currentUsedVertListSmoothGroup = VERT_USED_LIST[:]
364
# Set the initial '(null)' Smooth group, every mesh has one.
365
currentUsedVertList[currentSmoothGroup] = currentUsedVertListSmoothGroup
368
# 0:NMesh, 1:SmoothGroups[UsedVerts[0,0,0,0]], 2:materialMapping['matname':matIndexForThisNMesh]
369
meshDict[currentObjectName] = (currentMesh, currentUsedVertList, currentMaterialMeshMapping)
376
currentMesh.verts.append(vertList[0]) # So we can sync with OBJ indicies where 1 is the first item.
377
if len(uvMapList) > 1:
378
currentMesh.hasFaceUV(1) # Turn UV's on if we have ANY texture coords in this obj file.
381
#==================================================================================#
382
# Load all faces into objects, main loop #
383
#==================================================================================#
386
# Face and Object loading LOOP
387
while lIdx < len(fileLines):
392
# Make a face with the correct material.
394
# Add material to mesh
395
if contextMeshMatIdx == -1:
396
tmpMatLs = currentMesh.materials
398
if len(tmpMatLs) == MATLIMIT:
399
contextMeshMatIdx = 0 # Use first material
400
print 'material overflow, attempting to use > 16 materials. defaulting to first.'
402
contextMeshMatIdx = len(tmpMatLs)
403
currentMaterialMeshMapping[currentMat.name] = contextMeshMatIdx
404
currentMesh.addMaterial(currentMat)
406
# Set up vIdxLs : Verts
407
# Set up vtIdxLs : UV
408
# Start with a dummy objects so python accepts OBJs 1 is the first index.
411
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.
413
# OBJ files can have // or / to seperate vert/texVert/normal
414
# this is a bit of a pain but we must deal with it.
415
objVert = v.split('/')
417
# Vert Index - OBJ supports negative index assignment (like python)
419
vIdxLs.append(int(objVert[0]))
422
if len(objVert) == 1:
423
#vtIdxLs.append(int(objVert[0])) # replace with below.
424
vtIdxLs.append(vIdxLs[-1]) # Sticky UV coords
425
elif objVert[1]: # != '' # Its possible that theres no texture vert just he vert and normal eg 1//2
426
vtIdxLs.append(int(objVert[1])) # Seperate UV coords
430
# Dont add a UV to the face if its larger then the UV coord list
431
# The OBJ file would have to be corrupt or badly written for thi to happen
432
# but account for it anyway.
434
if vtIdxLs[-1] > len(uvMapList):
436
print 'badly written OBJ file, invalid references to UV Texture coordinates.'
438
# Quads only, we could import quads using the method below but it polite to import a quad as a quad.
442
for i in quadList: # quadList == [0,1,2,3]
443
if currentUsedVertListSmoothGroup[vIdxLs[i]] == 0:
444
v = vertList[vIdxLs[i]]
445
currentMesh.verts.append(v)
447
currentUsedVertListSmoothGroup[vIdxLs[i]] = len(currentMesh.verts)-1
449
f.v.append(currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[i]]])
451
if currentUsedVertListSmoothGroup[vIdxLs[0]] == 0:
452
faceQuadVList[0] = vertList[vIdxLs[0]]
453
currentUsedVertListSmoothGroup[vIdxLs[0]] = len(currentMesh.verts)
455
faceQuadVList[0] = currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[0]]]
457
if currentUsedVertListSmoothGroup[vIdxLs[1]] == 0:
458
faceQuadVList[1] = vertList[vIdxLs[1]]
459
currentUsedVertListSmoothGroup[vIdxLs[1]] = len(currentMesh.verts)+1
461
faceQuadVList[1] = currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[1]]]
463
if currentUsedVertListSmoothGroup[vIdxLs[2]] == 0:
464
faceQuadVList[2] = vertList[vIdxLs[2]]
465
currentUsedVertListSmoothGroup[vIdxLs[2]] = len(currentMesh.verts)+2
467
faceQuadVList[2] = currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[2]]]
469
if currentUsedVertListSmoothGroup[vIdxLs[3]] == 0:
470
faceQuadVList[3] = vertList[vIdxLs[3]]
471
currentUsedVertListSmoothGroup[vIdxLs[3]] = len(currentMesh.verts)+3
473
faceQuadVList[3] = currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[3]]]
475
currentMesh.verts.extend(faceQuadVList)
476
f = NMesh.Face(faceQuadVList)
480
f.uv = [uvMapList[ vtIdxLs[0] ],uvMapList[ vtIdxLs[1] ],uvMapList[ vtIdxLs[2] ],uvMapList[ vtIdxLs[3] ]]
484
f.mode &= TEX_OFF_FLAG
486
f.mat = contextMeshMatIdx
487
f.smooth = currentSmooth
488
currentMesh.faces.append(f) # move the face onto the mesh
490
elif len(vIdxLs) >= 3: # This handles tri's and fans
491
for i in range(len(vIdxLs)-2):
494
for ii in [0, i+1, i+2]:
495
if currentUsedVertListSmoothGroup[vIdxLs[ii]] == 0:
496
v = vertList[vIdxLs[ii]]
497
currentMesh.verts.append(v)
499
currentUsedVertListSmoothGroup[vIdxLs[ii]] = len(currentMesh.verts)-1
501
f.v.append(currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[ii]]])
505
if currentUsedVertListSmoothGroup[vIdxLs[0]] == 0:
506
faceTriVList[0] = vertList[vIdxLs[0]]
507
currentUsedVertListSmoothGroup[vIdxLs[0]] = len(currentMesh.verts)
509
faceTriVList[0] = currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[0]]]
511
if currentUsedVertListSmoothGroup[vIdxLs[i+1]] == 0:
512
faceTriVList[1] = vertList[vIdxLs[i+1]]
513
currentUsedVertListSmoothGroup[vIdxLs[i+1]] = len(currentMesh.verts)+1
515
faceTriVList[1] = currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[i+1]]]
517
if currentUsedVertListSmoothGroup[vIdxLs[i+2]] == 0:
518
faceTriVList[2] = vertList[vIdxLs[i+2]]
519
currentUsedVertListSmoothGroup[vIdxLs[i+2]] = len(currentMesh.verts)+2
521
faceTriVList[2] = currentMesh.verts[currentUsedVertListSmoothGroup[vIdxLs[i+2]]]
523
currentMesh.verts.extend(faceTriVList)
524
f = NMesh.Face(faceTriVList)
529
f.uv = [uvMapList[vtIdxLs[0]], uvMapList[vtIdxLs[i+1]], uvMapList[vtIdxLs[i+2]]]
533
f.mode &= TEX_OFF_FLAG
535
f.mat = contextMeshMatIdx
536
f.smooth = currentSmooth
537
currentMesh.faces.append(f) # move the face onto the mesh
541
# No value? then turn on.
544
currentSmoothGroup = '(null)'
546
currentUsedVertListSmoothGroup = currentUsedVertList[currentSmoothGroup]
548
currentUsedVertListSmoothGroup = VERT_USED_LIST[:]
549
currentUsedVertList[currentSmoothGroup] = currentUsedVertListSmoothGroup
553
currentSmooth = False
554
currentSmoothGroup = '(null)'
555
# We all have a null group so dont need to try
556
currentUsedVertListSmoothGroup = currentUsedVertList['(null)']
559
currentSmoothGroup = '_'.join(l[1:])
562
elif l[0] == 'o' or l[0] == 'g':
564
# Forget about the current image
567
# This makes sure that if an object and a group have the same name then
568
# they are not put into the same object.
570
# Only make a new group.object name if the verts in the existing object have been used, this is obscure
571
# but some files face groups seperating verts and faces which results in silly things. (no groups have names.)
573
currentObjectName = '_'.join(l[1:])
574
else: # No name given
575
# Make a new empty name
576
if l[0] == 'g': # Make a blank group name
577
currentObjectName = 'unnamed_grp_%d' % currentUnnamedGroupIdx
578
currentUnnamedGroupIdx +=1
579
else: # is an object.
580
currentObjectName = 'unnamed_ob_%d' % currentUnnamedObjectIdx
581
currentUnnamedObjectIdx +=1
584
# If we havnt written to this mesh before then do so.
585
# if we have then we'll just keep appending to it, this is required for soem files.
587
# If we are new, or we are not yet in the list of added meshes
588
# then make us new mesh.
589
if len(l) == 1 or currentObjectName not in meshDict.keys():
590
currentMesh = NMesh.GetRaw()
592
currentUsedVertList = {}
595
currentSmoothGroup = '(null)'
596
currentUsedVertListSmoothGroup = VERT_USED_LIST[:]
597
currentUsedVertList[currentSmoothGroup] = currentUsedVertListSmoothGroup
598
currentMaterialMeshMapping = {}
600
meshDict[currentObjectName] = (currentMesh, currentUsedVertList, currentMaterialMeshMapping)
601
currentMesh.hasFaceUV(1)
602
currentMesh.verts.append( vertList[0] )
603
contextMeshMatIdx = -1
606
# Since we have this in Blender then we will check if the current Mesh has the material.
607
# set the contextMeshMatIdx to the meshs index but only if we have it.
608
currentMesh, currentUsedVertList, currentMaterialMeshMapping = meshDict[currentObjectName]
609
#getMeshMaterialIndex(currentMesh, currentMat)
612
contextMeshMatIdx = currentMaterialMeshMapping[currentMat.name] #getMeshMaterialIndex(currentMesh, currentMat)
616
# For new meshes switch smoothing groups to null
617
currentSmoothGroup = '(null)'
618
currentUsedVertListSmoothGroup = currentUsedVertList[currentSmoothGroup]
621
elif l[0] == 'usemtl':
622
if len(l) == 1 or l[1] == NULL_MAT:
623
currentMat = nullMat # We know we have a null mat.
625
currentMat = materialDict['_'.join(l[1:])]
627
contextMeshMatIdx = currentMaterialMeshMapping[currentMat.name]
629
contextMeshMatIdx = -1 #getMeshMaterialIndex(currentMesh, currentMat)
632
elif l[0] == 'usemat' or l[0] == 'usemap':
633
if len(l) == 1 or l[1] == '(null)' or l[1] == 'off':
637
newImgName = stripPath(' '.join(l[1:])) # Use space since its a file name.
640
# Assume its alredy set in the dict (may or maynot be loaded)
641
currentImg = imageDict[newImgName]
643
except KeyError: # Not in dict, add for first time.
644
try: # Image has not been added, Try and load the image
645
currentImg = Image.Load( '%s%s' % (DIR, newImgName) ) # Use join in case of spaces
646
imageDict[newImgName] = currentImg
648
except IOError: # Cant load, just set blank.
649
imageDict[newImgName] = None
653
elif l[0] == 'mtllib':
654
mtl_fileName = ' '.join(l[1:]) # SHOULD SUPPORT MULTIPLE MTL?
657
# Applies material properties to materials alredy on the mesh as well as Textures.
659
load_mtl(DIR, mtl_fileName, meshDict, materialDict)
663
for mk in meshDict.keys():
664
meshDict[mk][0].verts.pop(0)
666
# Ignore no vert meshes.
667
if not meshDict[mk][0].verts:
670
name = getUniqueName(mk)
671
ob = NMesh.PutRaw(meshDict[mk][0], name)
674
importedObjects.append(ob)
676
# Select all imported objects.
677
for ob in importedObjects:
680
print "obj import time: ", sys.time() - time1
683
print Draw.PupMenu(ABORT_MENU)
687
def load_obj_callback(file):
688
# Try/Fails should realy account for these, but if somthing realy bad happens then Popup error.
692
print Draw.PupMenu(ABORT_MENU)
694
Window.FileSelector(load_obj_callback, 'Import Wavefront OBJ')
696
# For testing compatibility
700
for obj in os.listdir('/obj/'):
701
if obj.lower().endswith('obj'):
703
newScn = Scene.New(obj)
705
load_obj('/obj/' + obj)
707
print "TOTAL IMPORT TIME: ", sys.time() - TIME
709
#load_obj('/obj/foot_bones.obj')
710
#load_obj('/obj/mba1.obj')
711
#load_obj('/obj/PixZSphere50.OBJ')