forked from bartvdbraak/blender
ee07502a04
small update of ideasman's obj import/export scripts (added support for smooth faces)
372 lines
12 KiB
Python
372 lines
12 KiB
Python
#!BPY
|
|
|
|
"""
|
|
Name: 'Wavefront (.obj)...'
|
|
Blender: 232
|
|
Group: 'Import'
|
|
Tooltip: 'Load a Wavefront OBJ File'
|
|
"""
|
|
|
|
# $Id$
|
|
#
|
|
# --------------------------------------------------------------------------
|
|
# OBJ Import v0.9 by Campbell Barton (AKA Ideasman)
|
|
# --------------------------------------------------------------------------
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
# --------------------------------------------------------------------------
|
|
|
|
NULL_MAT = '(null)' # Name for mesh's that have no mat set.
|
|
NULL_IMG = '(null)' # Name for mesh's that have no mat set.
|
|
|
|
MATLIMIT = 16 # This isnt about to change but probably should not be hard coded.
|
|
|
|
DIR = ''
|
|
|
|
#==============================================#
|
|
# Return directory, where is file #
|
|
#==============================================#
|
|
def pathName(path,name):
|
|
length=len(path)
|
|
for CH in range(1, length):
|
|
if path[length-CH:] == name:
|
|
path = path[:length-CH]
|
|
break
|
|
return path
|
|
|
|
#==============================================#
|
|
# Strips the slashes from the back of a string #
|
|
#==============================================#
|
|
def stripPath(path):
|
|
for CH in range(len(path), 0, -1):
|
|
if path[CH-1] == "/" or path[CH-1] == "\\":
|
|
path = path[CH:]
|
|
break
|
|
return path
|
|
|
|
#====================================================#
|
|
# Strips the prefix off the name before writing #
|
|
|
|
#====================================================#
|
|
def stripName(name): # name is a string
|
|
prefixDelimiter = '.'
|
|
return name[ : name.find(prefixDelimiter) ]
|
|
|
|
|
|
from Blender import *
|
|
|
|
#==================================================================================#
|
|
# This gets a mat or creates one of the requested name if none exist. #
|
|
#==================================================================================#
|
|
def getMat(matName):
|
|
# Make a new mat
|
|
try:
|
|
return Material.Get(matName)
|
|
except:
|
|
return Material.New(matName)
|
|
|
|
|
|
#==================================================================================#
|
|
# This function sets textures defined in .mtl file #
|
|
#==================================================================================#
|
|
def getImg(img_fileName):
|
|
for i in Image.Get():
|
|
if i.filename == img_fileName:
|
|
return i
|
|
|
|
# if we are this far it means the image hasnt been loaded.
|
|
try:
|
|
return Image.Load(img_fileName)
|
|
except:
|
|
print "unable to open", img_fileName
|
|
return
|
|
|
|
|
|
|
|
#==================================================================================#
|
|
# This function sets textures defined in .mtl file #
|
|
#==================================================================================#
|
|
def load_mat_image(mat, img_fileName, type, mesh):
|
|
try:
|
|
image = Image.Load(img_fileName)
|
|
except:
|
|
print "unable to open", img_fileName
|
|
return
|
|
|
|
texture = Texture.New(type)
|
|
texture.setType('Image')
|
|
texture.image = image
|
|
|
|
# adds textures to faces (Textured/Alt-Z mode)
|
|
# Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
|
|
if type == 'Kd':
|
|
for f in mesh.faces:
|
|
if mesh.materials[f.mat].name == mat.name:
|
|
|
|
# the inline usemat command overides the material Image
|
|
if not f.image:
|
|
f.image = image
|
|
|
|
# adds textures for materials (rendering)
|
|
if type == 'Ka':
|
|
mat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.CMIR)
|
|
if type == 'Kd':
|
|
mat.setTexture(1, texture, Texture.TexCo.UV, Texture.MapTo.COL)
|
|
if type == 'Ks':
|
|
mat.setTexture(2, texture, Texture.TexCo.UV, Texture.MapTo.SPEC)
|
|
|
|
#==================================================================================#
|
|
# This function loads materials from .mtl file (have to be defined in obj file) #
|
|
#==================================================================================#
|
|
def load_mtl(dir, mtl_file, mesh):
|
|
mtl_fileName = dir + mtl_file
|
|
try:
|
|
fileLines= open(mtl_fileName, 'r').readlines()
|
|
except:
|
|
print "unable to open", mtl_fileName
|
|
return
|
|
|
|
lIdx=0
|
|
while lIdx < len(fileLines):
|
|
l = fileLines[lIdx].split()
|
|
|
|
# Detect a line that will be ignored
|
|
if len(l) == 0:
|
|
pass
|
|
elif l[0] == '#' or len(l) == 0:
|
|
pass
|
|
elif l[0] == 'newmtl':
|
|
currentMat = getMat(' '.join(l[1:]))
|
|
elif l[0] == 'Ka':
|
|
currentMat.setMirCol(eval(l[1]), eval(l[2]), eval(l[3]))
|
|
elif l[0] == 'Kd':
|
|
currentMat.setRGBCol(eval(l[1]), eval(l[2]), eval(l[3]))
|
|
elif l[0] == 'Ks':
|
|
currentMat.setSpecCol(eval(l[1]), eval(l[2]), eval(l[3]))
|
|
elif l[0] == 'Ns':
|
|
currentMat.setHardness( int((eval(l[1])*0.51)) )
|
|
elif l[0] == 'd':
|
|
currentMat.setAlpha(eval(l[1]))
|
|
elif l[0] == 'Tr':
|
|
currentMat.setAlpha(eval(l[1]))
|
|
elif l[0] == 'map_Ka':
|
|
img_fileName = dir + l[1]
|
|
load_mat_image(currentMat, img_fileName, 'Ka', mesh)
|
|
elif l[0] == 'map_Ks':
|
|
img_fileName = dir + l[1]
|
|
load_mat_image(currentMat, img_fileName, 'Ks', mesh)
|
|
elif l[0] == 'map_Kd':
|
|
img_fileName = dir + l[1]
|
|
load_mat_image(currentMat, img_fileName, 'Kd', mesh)
|
|
lIdx+=1
|
|
|
|
#==================================================================================#
|
|
# This loads data from .obj file #
|
|
#==================================================================================#
|
|
def load_obj(file):
|
|
def applyMat(mesh, f, mat):
|
|
# Check weather the 16 mat limit has been met.
|
|
if len( mesh.materials ) >= MATLIMIT:
|
|
print 'Warning, max material limit reached, using an existing material'
|
|
return mesh, f
|
|
|
|
mIdx = 0
|
|
for m in mesh.materials:
|
|
if m.getName() == mat.getName():
|
|
break
|
|
mIdx+=1
|
|
|
|
if mIdx == len(mesh.materials):
|
|
mesh.addMaterial(mat)
|
|
|
|
f.mat = mIdx
|
|
return mesh, f
|
|
|
|
# Get the file name with no path or .obj
|
|
fileName = stripName( stripPath(file) )
|
|
|
|
mtl_fileName = ''
|
|
|
|
DIR = pathName(file, stripPath(file))
|
|
|
|
fileLines = open(file, 'r').readlines()
|
|
|
|
mesh = NMesh.GetRaw() # new empty mesh
|
|
|
|
objectName = 'mesh' # If we cant get one, use this
|
|
|
|
uvMapList = [] # store tuple uv pairs here
|
|
|
|
nullMat = getMat(NULL_MAT)
|
|
|
|
currentMat = nullMat # Use this mat.
|
|
currentImg = NULL_IMG # Null image is a string, otherwise this should be set to an image object.
|
|
|
|
smooth = 0
|
|
|
|
# Main loop
|
|
lIdx = 0
|
|
while lIdx < len(fileLines):
|
|
l = fileLines[lIdx].split()
|
|
|
|
# Detect a line that will be idnored
|
|
if len(l) == 0:
|
|
pass
|
|
elif l[0] == '#' or len(l) == 0:
|
|
pass
|
|
# VERTEX
|
|
elif l[0] == 'v':
|
|
# This is a new vert, make a new mesh
|
|
mesh.verts.append( NMesh.Vert(eval(l[1]), eval(l[2]), eval(l[3]) ) )
|
|
|
|
elif l[0] == 'vn':
|
|
pass
|
|
|
|
elif l[0] == 'vt':
|
|
# This is a new vert, make a new mesh
|
|
uvMapList.append( (eval(l[1]), eval(l[2])) )
|
|
|
|
elif l[0] == 'f':
|
|
|
|
# Make a face with the correct material.
|
|
f = NMesh.Face()
|
|
mesh, f = applyMat(mesh, f, currentMat)
|
|
|
|
# Set up vIdxLs : Verts
|
|
# Set up vtIdxLs : UV
|
|
vIdxLs = []
|
|
vtIdxLs = []
|
|
for v in l[1:]:
|
|
# OBJ files can have // or / to seperate vert/texVert/normal
|
|
# this is a bit of a pain but we must deal with it.
|
|
# Well try // first and if that has a len of 1 then we'll try /
|
|
objVert = v.split('//', -1)
|
|
if len(objVert) == 1:
|
|
objVert = objVert[0].split('/', -1)
|
|
|
|
# VERT INDEX
|
|
vIdxLs.append(eval(objVert[0]) -1)
|
|
# UV
|
|
if len(objVert) == 1:
|
|
vtIdxLs.append(eval(objVert[0]) -1) # Sticky UV coords
|
|
else:
|
|
vtIdxLs.append(eval(objVert[1]) -1) # Seperate UV coords
|
|
|
|
# Quads only, we could import quads using the method below but it polite to import a quad as a quad.f
|
|
if len(vIdxLs) == 4:
|
|
f.v.append(mesh.verts[vIdxLs[0]])
|
|
f.v.append(mesh.verts[vIdxLs[1]])
|
|
f.v.append(mesh.verts[vIdxLs[2]])
|
|
f.v.append(mesh.verts[vIdxLs[3]])
|
|
# SMOTH FACE
|
|
f.smooth = smooth
|
|
# UV MAPPING
|
|
if uvMapList:
|
|
if vtIdxLs[0] < len(uvMapList):
|
|
f.uv.append( uvMapList[ vtIdxLs[0] ] )
|
|
if vtIdxLs[1] < len(uvMapList):
|
|
f.uv.append( uvMapList[ vtIdxLs[1] ] )
|
|
if vtIdxLs[2] < len(uvMapList):
|
|
f.uv.append( uvMapList[ vtIdxLs[2] ] )
|
|
if vtIdxLs[3] < len(uvMapList):
|
|
f.uv.append( uvMapList[ vtIdxLs[3] ] )
|
|
|
|
mesh.faces.append(f) # move the face onto the mesh
|
|
# Apply the current image to the face
|
|
if currentImg != NULL_IMG:
|
|
mesh.faces[-1].image = currentImg
|
|
|
|
elif len(vIdxLs) >= 3: # This handles tri's and fans
|
|
for i in range(len(vIdxLs)-2):
|
|
f = NMesh.Face()
|
|
mesh, f = applyMat(mesh, f, currentMat)
|
|
f.v.append(mesh.verts[vIdxLs[0]])
|
|
f.v.append(mesh.verts[vIdxLs[i+1]])
|
|
f.v.append(mesh.verts[vIdxLs[i+2]])
|
|
# SMOTH FACE
|
|
f.smooth = smooth
|
|
# UV MAPPING
|
|
if uvMapList:
|
|
if vtIdxLs[0] < len(uvMapList):
|
|
f.uv.append( uvMapList[ vtIdxLs[0] ] )
|
|
if vtIdxLs[1] < len(uvMapList):
|
|
f.uv.append( uvMapList[ vtIdxLs[i+1] ] )
|
|
if vtIdxLs[2] < len(uvMapList):
|
|
f.uv.append( uvMapList[ vtIdxLs[i+2] ] )
|
|
|
|
mesh.faces.append(f) # move the face onto the mesh
|
|
# Apply the current image to the face
|
|
if currentImg != NULL_IMG:
|
|
mesh.faces[-1].image = currentImg
|
|
|
|
|
|
# is o the only vert/face delimeter?
|
|
# if not we could be screwed.
|
|
elif l[0] == 'o':
|
|
# Some material stuff
|
|
if mtl_fileName != '':
|
|
load_mtl(DIR, mtl_fileName, mesh)
|
|
|
|
# Make sure the objects is worth puttong
|
|
if len(mesh.verts) > 0:
|
|
NMesh.PutRaw(mesh, fileName + '_' + objectName)
|
|
# Make new mesh
|
|
mesh = NMesh.GetRaw()
|
|
|
|
# New mesh name
|
|
objectName = '_'.join(l[1:]) # Use join in case of spaces
|
|
|
|
# New texture list
|
|
uvMapList = []
|
|
|
|
# setting smooth surface on or off
|
|
elif l[0] == 's':
|
|
if l[1] == 'off':
|
|
smooth = 0
|
|
else:
|
|
smooth = 1
|
|
|
|
elif l[0] == 'usemtl':
|
|
if l[1] == '(null)':
|
|
currentMat = getMat(NULL_MAT)
|
|
else:
|
|
currentMat = getMat(' '.join(l[1:])) # Use join in case of spaces
|
|
|
|
elif l[0] == 'usemat':
|
|
if l[1] == '(null)':
|
|
currentImg = NULL_IMG
|
|
else:
|
|
currentImg = getImg(DIR + ' '.join(l[1:])) # Use join in case of spaces
|
|
|
|
|
|
elif l[0] == 'mtllib':
|
|
mtl_fileName = l[1]
|
|
|
|
lIdx+=1
|
|
|
|
# Some material stuff
|
|
if mtl_fileName != '':
|
|
load_mtl(DIR, mtl_fileName, mesh)
|
|
|
|
# We need to do this to put the last object.
|
|
# All other objects will be put alredy
|
|
if len(mesh.verts) > 0:
|
|
NMesh.PutRaw(mesh, fileName + '_' + objectName)
|
|
|
|
Window.FileSelector(load_obj, 'Import OBJ')
|