import types import os class WFObj(object): def __init__(self, source, fileloader=None): self.fileloader = fileloader self.vertices = [] self.texcoords = [] self.normals = [] self.faces = {} self.materials = {} if type(source) == types.StringType: self.source_path = source if self.fileloader is not None: fh = self.fileloader(source) self.load(fh) else: fh = open(source, 'r') self.load(fh) fh.close() else: self.source_path = '' self.load(source) def load(self, fh): lineno = 0 current_material_name = '' def add_face(*vrefs): def split_vref(vref): parts = map(int, vref.split('/')) while len(parts) < 3: parts.append(0) return parts vrefs_split = map(split_vref, vrefs) for vs in vrefs_split: if vs[0] < 1 or vs[0] > len(self.vertices): raise ValueError('Vertex index %d out of range on line %d' % (vs[0], lineno)) if vs[1] < 0 or vs[0] > len(self.texcoords): raise ValueError('Texture coordinate index %d out of range on line %d' % (vs[1], lineno)) if vs[2] < 0 or vs[2] > len(self.normals): raise ValueError('Normal index %d out of range on line %d' % (vs[2], lineno)) for i in range(3): vs[i] -= 1 self.faces[current_material_name].append(vrefs_split) for line in iter(fh.readline, ''): lineno += 1 line = line.strip() if line.startswith('#'): continue parts = line.split() if len(parts) == 0: continue if parts[0] == 'v': # process vertex if len(parts[1:]) < 3: raise ValueError('Not enough components to vertex definition on line %d' % lineno) self.vertices.append(map(float, parts[1:])) elif parts[0] == 'vt': # process texture coordinates if len(parts[1:]) < 2: raise ValueError('Not enough components to texture coordinates on line %d' % lineno) self.texcoords.append(map(float, parts[1:])) elif parts[0] == 'vn': # process normal if len(parts[1:]) < 3: raise ValueError('Not enough components to normal vector on line %d' % lineno) self.normals.append(map(float, parts[1:])) elif parts[0] == 'f': if current_material_name not in self.faces: self.faces[current_material_name] = [] if len(parts[1:]) == 3: # face is a triangle add_face(*parts[1:]) elif len(parts[1:]) > 4: # split the quad into two triangles add_face(*parts[1:3]) add_face(parts[1], parts[3], parts[4]) else: raise ValueError('Faces can only have 3 or 4 vertices on line %d' % lineno) elif parts[0] == 'mtllib': if len(parts[1:]) < 1: raise ValueError('mtllib directive requires material library name') self.load_material_library(parts[1]) elif parts[0] == 'usemtl': if len(parts[1:]) < 1: raise ValueError('usemtl directive requires material name') current_material_name = parts[1] elif parts[0] == 'o': # ignore pass elif parts[0] == 'g': # ignore group name pass elif parts[0] == 's': # ignore smoothing pass else: raise ValueError('Unknown .obj directive "%s" on line %d' % (parts[0], lineno)) class Material(object): def __init__(self): self.texture = '' self.ambient = (1, 1, 1, 1) self.diffuse = (1, 1, 1, 1) self.specular = (1, 1, 1, 1) self.shininess = 50 def load_material_library(self, name): dn = os.path.dirname(self.source_path) if dn != '': name = dn + os.path.sep + name if self.fileloader is not None: fh = self.fileloader(name) else: fh = open(name, 'r') material = None material_name = '' for line in iter(fh.readline, ''): line = line.strip() if line.startswith('#'): next parts = line.split() if len(parts) < 1: next if parts[0] == 'newmtl': if len(parts[1:]) < 1: raise ValueError('newmtl directive requires argument') material = Material() material_name = parts[1] self.materials[material_name] = material else: if material is None: raise ValueError('Material directive preceeding "newmtl"') if parts[0] == 'Ns': material.shininess = float(parts[1]) elif parts[0] == 'Ka': if len(parts[1:]) < 3: raise ValueError('Ka requires 3 arguments') material.ambient = map(float, parts[1:]) if len(material.ambient) < 4: material.ambient.append(1.0) elif parts[0] == 'Kd': if len(parts[1:]) < 3: raise ValueError('Kd requires 3 arguments') material.diffuse = map(float, parts[1:]) if len(material.diffuse) < 4: material.diffuse.append(1.0) elif parts[0] == 'Ks': if len(parts[1:]) < 3: raise ValueError('Ks requires 3 arguments') material.specular = map(float, parts[1:]) if len(material.specular) < 4: material.specular.append(1.0) elif parts[0] == 'Ni': # ignore pass elif parts[0] == 'd': # ignore pass elif parts[0] == 'illum': # ignore pass elif parts[0] == 'map_Kd': material.texture = parts[1] else: raise ValueError('Unknown material directive "%s"' % parts[0]) if self.fileloader is None: fh.close()