New OBJ, Import/Export, UPDATE USE MATHUTILS

The interface, modeling, 3d editing tools, import/export, feature requests, etc

Moderators: jesterKing, stiv

ideasman
Posts: 0
Joined: Tue Feb 25, 2003 2:37 pm

New OBJ, Import/Export, UPDATE USE MATHUTILS

Post by ideasman » Sun Jun 06, 2004 1:25 pm

Here are new OBJ importer and exporters.
They dont require any python modules.

Import-

Code: Select all

#!BPY 
 
""" 
Name: 'Wavefront (*.obj)' 
Blender: 232 
Group: 'Import' 
Tooltip: 'Load a Wavefront OBJ File' 
 """ 

# -------------------------------------------------------------------------- 
# 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 ***** 
# -------------------------------------------------------------------------- 
 
WHITESPACE = [' ', '\n', '\r', '\t', '\f', '\v'] # used for the split function. 
NULL_MAT = '(null)' # Name for mesh's that have no mat set. 

MATLIMIT = 16 

#==============================================# 
# 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) ] 

#================================================================# 
# Replace module deps 'string' for join and split #              # 
# - Split splits a string into a list, and join does the reverse # 
#================================================================# 
def split(splitString, WHITESPACE): 
   splitList = [] 
   charIndex = 0 
   while charIndex < len(splitString): 
      # Skip white space 
      while charIndex < len(splitString): 
         if splitString[charIndex] in WHITESPACE:          
            charIndex += 1 
         else: 
            break 
       
      # Gather text that is not white space and append to splitList 
      startWordCharIndex = charIndex 
      while charIndex < len(splitString): 
         if splitString[charIndex] in  WHITESPACE: break 
         charIndex += 1 
          
      # Now we have the first and last chars we can append the word to the list 
      if charIndex != startWordCharIndex: 
         splitList.append(splitString[startWordCharIndex:charIndex]) 
             
   return splitList 

#===============================# 
# Join list items into a string # 
#===============================# 
def join(joinList): 
   joinedString = "" 
   for listItem in joinList: 
      joinedString = joinedString + ' ' + str(listItem) 

   # Remove the first space 
   joinedString = joinedString[1:] 
   return joinedString 


from Blender import * 

def load_obj(file): 
   # 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) 
    
   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) ) 
    
   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. 
    
   # Main loop 
   lIdx = 0 
   while lIdx < len(fileLines): 
      l = split(fileLines[lIdx], WHITESPACE) 
       
      # 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:]: 
            objVert = split( v, ['/'] )
             
            # 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]])
            # 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

         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]])
               # 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
       
      # is o the only vert/face delimeter?
      # if not we could be screwed.
      elif l[0] == 'o':
         # 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 = []
       
      elif l[0] == 'usemtl':
         if l[1] == '(null)':
            currentMat = getMat(NULL_MAT)
         else:
            currentMat = getMat(join(l[1:])) # Use join in case of spaces
             
      lIdx+=1 

   # 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, 'SELECT OBJ FILE') 


Export -

Code: Select all

#!BPY 

""" 
Name: 'Wavefront (*.obj)' 
Blender: 232 
Group: 'Export'
Tooltip: 'Save a Wavefront OBJ File' 
""" 

# -------------------------------------------------------------------------- 
# OBJ Export 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 ***** 
# -------------------------------------------------------------------------- 

#================================================# 
# Gets the world matrix of an object             # 
# by multiplying by parents mat's recursively    # 
# This only works in some simple situations,     # 
# needs work....                                 # 
#================================================# 
def getWorldMat(ob): 
   mat = ob.getMatrix() 
   p = ob.getParent() 
   if p != None: 
      mat = mat + getWorldMat(p) 
   return mat 
    
#==================# 
# Apply Transform  # 
#==================# 
def apply_transform(verts, matrix):
	verts.resize4D()
	return Mathutils.VecMultMat(verts, matrix)

#====================================================# 
# Return a 6 deciaml point floating point value      # 
# as a string that dosent have any python chars      # 
#====================================================#  
def saneFloat(float): 
  #return '%(float)b' % vars()  # 6 fp as house.hqx 
  return str('%f' % float) + ' ' 


from Blender import * 

NULL_MAT = '(null)' 

def save_obj(filename): 
    
   file = open(filename, "w") 
    
   # Write Header 
   file.write('# Blender OBJ File: ' + Get('filename') + ' \n') 
   file.write('# www.blender.org\n') 
    
   # Get all meshs 
   for ob in Object.Get(): 
      if ob.getType() == 'Mesh': 
         m = ob.getData() 
         if len(m.verts) > 0: # Make sure there is somthing to write. 
             
            # Set the default mat 
            currentMatName = '' 
             
            file.write('o ' + ob.getName() + '_' + m.name + '\n') # Write Object name 
             
            # Dosent work properly, 
            matrix = getWorldMat(ob) 
             
            # Vert 
            for v in m.verts: 
               # Transform the vert 
               vTx = apply_transform(v.co, matrix) 
                
               file.write('v ') 
               file.write(saneFloat(vTx[0])) 
               file.write(saneFloat(vTx[1])) 
               file.write(saneFloat(vTx[2]) + '\n') 
             
            # UV 
            for f in m.faces: 
               for uvIdx in range(len(f.v)): 
                  file.write('vt ') 
                  if f.uv: 
                     file.write(saneFloat(f.uv[uvIdx][0])) 
                     file.write(saneFloat(f.uv[uvIdx][1])) 
                  else: 
                     file.write('0.0 ') 
                     file.write('0.0 ') 
                      
                  file.write('0.0' + '\n') 
             
            # NORMAL 
            for f in m.faces: 
               for v in f.v: 
                  # Transform the normal 
                  noTx = apply_transform(v.no, matrix) 
                  noTx.normalize()
                  file.write('vn ') 
                  file.write(saneFloat(noTx[0])) 
                  file.write(saneFloat(noTx[1])) 
                  file.write(saneFloat(noTx[2]) + '\n') 
                               
            uvIdx = 0 
            for f in m.faces: 
               # Check material and change if needed. 
               if len(m.materials) > f.mat: 
                  if currentMatName != m.materials[f.mat].getName(): 
                     currentMatName = m.materials[f.mat].getName() 
                     file.write('usemtl ' + currentMatName + '\n') 
                   
               elif currentMatName != NULL_MAT: 
                  currentMatName = NULL_MAT 
                  file.write('usemtl ' + currentMatName + '\n') 
                
               file.write('f ') 
               for v in f.v: 
                  file.write( str(m.verts.index(v) +1) + '/') # Vert IDX 
                  file.write( str(uvIdx +1) + '/') # UV IDX 
                  file.write( str(uvIdx +1) + ' ') # NORMAL IDX 
                  uvIdx+=1 
               file.write('\n') 
                
   file.close() 

Window.FileSelector(save_obj, 'SELECT OBJ FILE') 
Last edited by ideasman on Tue Jun 08, 2004 7:27 am, edited 3 times in total.

oin
Posts: 0
Joined: Fri Apr 02, 2004 6:34 pm

Post by oin » Sun Jun 06, 2004 3:37 pm

Thanks! Is always very good to have more than one :)

ideasman
Posts: 0
Joined: Tue Feb 25, 2003 2:37 pm

Post by ideasman » Sun Jun 06, 2004 3:48 pm

Just updated them to fix material bugs... Id like to here if people have any issue with them.

Monkeyboi
Posts: 251
Joined: Tue Nov 26, 2002 1:24 pm
Location: Copenhagen, Denmark
Contact:

Post by Monkeyboi » Sun Jun 06, 2004 4:47 pm

These scripts are great! It even tesselates faces with more than four edges! Get these included with the Blender download fast, they are really superb. Good work.

Manuel
Posts: 56
Joined: Wed Oct 16, 2002 12:48 am
Location: Italy
Contact:

Post by Manuel » Mon Jun 07, 2004 12:15 am

I've this error when I try to import a deformed sphere obj previously exported with your export script:

Traceback (most recent call last):
File "obj_import.py", line 166, in load_obj
mesh, f = applyMat(mesh, f, currentMat)
File "obj_import.py", line 120, in applyMat
mesh.addMaterial(mat)
TypeError: expected Blender Material PyObject

ciao,

Manuel

ideasman
Posts: 0
Joined: Tue Feb 25, 2003 2:37 pm

Post by ideasman » Mon Jun 07, 2004 2:12 am

Fixed, needed to use

change

currentMat = NULL_MAT
to...
currentMat = getMat(NULL_MAT)

Because NULL_MAT is a string.

hanzo
Posts: 69
Joined: Mon Oct 14, 2002 9:56 am

Post by hanzo » Thu Jun 10, 2004 1:30 am

:shock: hey I need this tool, to make somerealy owesome blender games.. model texture ik in maya and export to blender...

can you tell me what this exporter can actualy do? other then meshes

ideasman
Posts: 0
Joined: Tue Feb 25, 2003 2:37 pm

Post by ideasman » Fri Jun 11, 2004 7:51 am

AFAIK obj dosent support that many features.

eg, no documentation shows things like lights/bones etc.

Try it- if somthing dosent import/exort

gimmi an example file and Ill have a look and try get it working.

EDIT- I dont think OBJ supports IK- no docs I have read reference this feature.

pildanovak
Posts: 18
Joined: Fri Oct 25, 2002 9:32 am
Contact:

Post by pildanovak » Fri Jun 11, 2004 1:20 pm

it works great for objs exported from SI-xsi, they're just rotated 90 degrees around the x axis. Thanks much

oin
Posts: 0
Joined: Fri Apr 02, 2004 6:34 pm

Post by oin » Fri Jun 11, 2004 2:17 pm

Thanks to you Ideasman :) I have not tested, but I'll surely use it a lot :)

I'm all the time using OBJ as my workflow is based in complex workflows, not a single software (Wings3d, Ultimate Unwrap, Character Fx, Anim8or, Blender, etc)

And for what i have seen as an artist , OBJ support :

-smoothing normals info. (I think smooth groups but also an autosmooth value (like90º, 75º, 60º, etc), the angle threshold that decides if an edge is creased in the shading or not )

-complex Materials : difusse, specular, bump, opacity, maps(textures) and its settings. Material color, (not vertex colors) , emisive values (like a self ilumination material setting) , diffuse color, ambient color. UV coords.
All this are specified in an ascii file called *.mtl. Is a simple collection of values for settings. Also , OBJ are ascii files. usually a coords list , with some like flags "vt" (textured vertices) , s (smooth group I think, all the following coords will be possible an smooth group) , g (for group) , etc.

It does not support bones(so neither weights.No either any form of animation). Not lights. I am almost sure neither cameras (one I can remeber that does is ASE, Ascii Scene Exporter for max, and possibly VRML2 ) Not vertex colors. But supports quads (while x, wrml doesn't)
It supports large geometry files.

IMHo, OBJ is one of the most standard and solid static formats out there. A life saver for conversions. Usually is the most problem free. So, cheers for all the OBJ plugins for Blender :)

matt_e
Posts: 410
Joined: Mon Oct 14, 2002 4:32 am
Location: Sydney, Australia
Contact:

Post by matt_e » Fri Jun 11, 2004 2:53 pm

Works great importing from ZBrush 1.55, too. Again it's rotated, but I can deal with that :). It seems a bit slow though - that mesh (although it's quite large - 95,000 verts) took about 2 minutes to import on this G4, 1.25Ghz / 1GB RAM.

halibut
Posts: 0
Joined: Fri Apr 25, 2003 2:02 pm
Contact:

Post by halibut » Fri Jun 11, 2004 3:11 pm

It's rotated blender uses the z axis as upwards (height), whereas most you it as going backwards (depth), at least I think that's the case, or something similar.

jiri
Posts: 0
Joined: Thu May 20, 2004 12:12 pm
Location: Czech Republic

Post by jiri » Fri Jun 11, 2004 6:35 pm

Hi,
it doesn't import/export mtl file! Why?

soletread
Posts: 0
Joined: Fri Jan 10, 2003 7:11 pm

Post by soletread » Fri Jun 11, 2004 10:08 pm

I am looking forward to using this script, unfortunately it crashes blender if I try to export an object that is in a scene shared with other objects.

If I have only one object in the scene then it exports nicely...

Windows XP
Blender 2.33

cheers....

ideasman
Posts: 0
Joined: Tue Feb 25, 2003 2:37 pm

Post by ideasman » Sat Jun 12, 2004 6:34 am

Yeah, I have had segfults for no apparent resion.

The string splitting for the importing has been rewritten to use pythons internal split function. (I wasnt aware of it at the time of writing)
About MTL files- SMTD (Show Me The Docs)

Could sombody please send some complex OBJ scenes with as many odd OBJ functions as possible.

I havnt been able to find an obj sample on the net that even uses an MTL file.

- Cam

Post Reply