WavefrontHelper

 1# wavefront.py
 2import numpy as np
 3
 4
 5class WavefrontOBJ:
 6    def __init__( self, default_mtl='default_mtl' ):
 7        self.path      = None               # path of loaded object
 8        self.mtllibs   = []                 # .mtl files references via mtllib
 9        self.mtls      = [ default_mtl ]    # materials referenced
10        self.mtlid     = []                 # indices into self.mtls for each polygon
11        self.vertices  = []                 # vertices as an Nx3 or Nx6 array (per vtx colors)
12        self.normals   = []                 # normals
13        self.texcoords = []                 # texture coordinates
14        self.polygons  = []                 # M*Nv*3 array, Nv=# of vertices, stored as vid,tid,nid (-1 for N/A)
15
16
17def load_obj( filename: str, default_mtl='default_mtl', triangulate=False ) -> WavefrontOBJ:
18    """Reads a .obj file from disk and returns a WavefrontOBJ instance
19
20    Handles only very rudimentary reading and contains no error handling!
21
22    Does not handle:
23        - relative indexing
24        - subobjects or groups
25        - lines, splines, beziers, etc.
26    """
27    # parses a vertex record as either vid, vid/tid, vid//nid or vid/tid/nid
28    # and returns a 3-tuple where unparsed values are replaced with -1
29    def parse_vertex( vstr ):
30        vals = vstr.split('/')
31        vid = int(vals[0])-1
32        tid = int(vals[1])-1 if len(vals) > 1 and vals[1] else -1
33        nid = int(vals[2])-1 if len(vals) > 2 else -1
34        return (vid,tid,nid)
35
36    with open( filename, 'r' ) as objf:
37        obj = WavefrontOBJ(default_mtl=default_mtl)
38        obj.path = filename
39        cur_mat = obj.mtls.index(default_mtl)
40        for line in objf:
41            toks = line.split()
42            if not toks:
43                continue
44            if toks[0] == 'v':
45                obj.vertices.append( [ float(v) for v in toks[1:]] )
46            elif toks[0] == 'vn':
47                obj.normals.append( [ float(v) for v in toks[1:]] )
48            elif toks[0] == 'vt':
49                obj.texcoords.append( [ float(v) for v in toks[1:]] )
50            elif toks[0] == 'f':
51                poly = [ parse_vertex(vstr) for vstr in toks[1:] ]
52                if triangulate:
53                    for i in range(2,len(poly)):
54                        obj.mtlid.append( cur_mat )
55                        obj.polygons.append( (poly[0], poly[i-1], poly[i] ) )
56                else:
57                    obj.mtlid.append(cur_mat)
58                    obj.polygons.append( poly )
59            elif toks[0] == 'mtllib':
60                obj.mtllibs.append( toks[1] )
61            elif toks[0] == 'usemtl':
62                if toks[1] not in obj.mtls:
63                    obj.mtls.append(toks[1])
64                cur_mat = obj.mtls.index( toks[1] )
65        return obj
66
67
68def save_obj( obj: WavefrontOBJ, filename: str ):
69    """Saves a WavefrontOBJ object to a file
70
71    Warning: Contains no error checking!
72
73    """
74    with open( filename, 'w' ) as ofile:
75        for mlib in obj.mtllibs:
76            ofile.write('mtllib {}\n'.format(mlib))
77        for vtx in obj.vertices:
78            ofile.write('v '+' '.join(['{}'.format(v) for v in vtx])+'\n')
79        for tex in obj.texcoords:
80            ofile.write('vt '+' '.join(['{}'.format(vt) for vt in tex])+'\n')
81        for nrm in obj.normals:
82            ofile.write('vn '+' '.join(['{}'.format(vn) for vn in nrm])+'\n')
83        if not obj.mtlid:
84            obj.mtlid = [-1] * len(obj.polygons)
85        poly_idx = np.argsort( np.array( obj.mtlid ) )
86        cur_mat = -1
87        for pid in poly_idx:
88            if obj.mtlid[pid] != cur_mat:
89                cur_mat = obj.mtlid[pid]
90                ofile.write('usemtl {}\n'.format(obj.mtls[cur_mat]))
91            pstr = 'f '
92            for v in obj.polygons[pid]:
93                # UGLY!
94                vstr = '{}/{}/{} '.format(v[0]+1,v[1]+1 if v[1] >= 0 else 'X', v[2]+1 if v[2] >= 0 else 'X' )
95                vstr = vstr.replace('/X/','//').replace('/X ', ' ')
96                pstr += vstr
97            ofile.write( pstr+'\n')
class WavefrontOBJ:
 6class WavefrontOBJ:
 7    def __init__( self, default_mtl='default_mtl' ):
 8        self.path      = None               # path of loaded object
 9        self.mtllibs   = []                 # .mtl files references via mtllib
10        self.mtls      = [ default_mtl ]    # materials referenced
11        self.mtlid     = []                 # indices into self.mtls for each polygon
12        self.vertices  = []                 # vertices as an Nx3 or Nx6 array (per vtx colors)
13        self.normals   = []                 # normals
14        self.texcoords = []                 # texture coordinates
15        self.polygons  = []                 # M*Nv*3 array, Nv=# of vertices, stored as vid,tid,nid (-1 for N/A)
WavefrontOBJ(default_mtl='default_mtl')
 7    def __init__( self, default_mtl='default_mtl' ):
 8        self.path      = None               # path of loaded object
 9        self.mtllibs   = []                 # .mtl files references via mtllib
10        self.mtls      = [ default_mtl ]    # materials referenced
11        self.mtlid     = []                 # indices into self.mtls for each polygon
12        self.vertices  = []                 # vertices as an Nx3 or Nx6 array (per vtx colors)
13        self.normals   = []                 # normals
14        self.texcoords = []                 # texture coordinates
15        self.polygons  = []                 # M*Nv*3 array, Nv=# of vertices, stored as vid,tid,nid (-1 for N/A)
path
mtllibs
mtls
mtlid
vertices
normals
texcoords
polygons
def load_obj( filename: str, default_mtl='default_mtl', triangulate=False) -> WavefrontHelper.WavefrontOBJ:
18def load_obj( filename: str, default_mtl='default_mtl', triangulate=False ) -> WavefrontOBJ:
19    """Reads a .obj file from disk and returns a WavefrontOBJ instance
20
21    Handles only very rudimentary reading and contains no error handling!
22
23    Does not handle:
24        - relative indexing
25        - subobjects or groups
26        - lines, splines, beziers, etc.
27    """
28    # parses a vertex record as either vid, vid/tid, vid//nid or vid/tid/nid
29    # and returns a 3-tuple where unparsed values are replaced with -1
30    def parse_vertex( vstr ):
31        vals = vstr.split('/')
32        vid = int(vals[0])-1
33        tid = int(vals[1])-1 if len(vals) > 1 and vals[1] else -1
34        nid = int(vals[2])-1 if len(vals) > 2 else -1
35        return (vid,tid,nid)
36
37    with open( filename, 'r' ) as objf:
38        obj = WavefrontOBJ(default_mtl=default_mtl)
39        obj.path = filename
40        cur_mat = obj.mtls.index(default_mtl)
41        for line in objf:
42            toks = line.split()
43            if not toks:
44                continue
45            if toks[0] == 'v':
46                obj.vertices.append( [ float(v) for v in toks[1:]] )
47            elif toks[0] == 'vn':
48                obj.normals.append( [ float(v) for v in toks[1:]] )
49            elif toks[0] == 'vt':
50                obj.texcoords.append( [ float(v) for v in toks[1:]] )
51            elif toks[0] == 'f':
52                poly = [ parse_vertex(vstr) for vstr in toks[1:] ]
53                if triangulate:
54                    for i in range(2,len(poly)):
55                        obj.mtlid.append( cur_mat )
56                        obj.polygons.append( (poly[0], poly[i-1], poly[i] ) )
57                else:
58                    obj.mtlid.append(cur_mat)
59                    obj.polygons.append( poly )
60            elif toks[0] == 'mtllib':
61                obj.mtllibs.append( toks[1] )
62            elif toks[0] == 'usemtl':
63                if toks[1] not in obj.mtls:
64                    obj.mtls.append(toks[1])
65                cur_mat = obj.mtls.index( toks[1] )
66        return obj

Reads a .obj file from disk and returns a WavefrontOBJ instance

Handles only very rudimentary reading and contains no error handling!

Does not handle: - relative indexing - subobjects or groups - lines, splines, beziers, etc.

def save_obj(obj: WavefrontHelper.WavefrontOBJ, filename: str):
69def save_obj( obj: WavefrontOBJ, filename: str ):
70    """Saves a WavefrontOBJ object to a file
71
72    Warning: Contains no error checking!
73
74    """
75    with open( filename, 'w' ) as ofile:
76        for mlib in obj.mtllibs:
77            ofile.write('mtllib {}\n'.format(mlib))
78        for vtx in obj.vertices:
79            ofile.write('v '+' '.join(['{}'.format(v) for v in vtx])+'\n')
80        for tex in obj.texcoords:
81            ofile.write('vt '+' '.join(['{}'.format(vt) for vt in tex])+'\n')
82        for nrm in obj.normals:
83            ofile.write('vn '+' '.join(['{}'.format(vn) for vn in nrm])+'\n')
84        if not obj.mtlid:
85            obj.mtlid = [-1] * len(obj.polygons)
86        poly_idx = np.argsort( np.array( obj.mtlid ) )
87        cur_mat = -1
88        for pid in poly_idx:
89            if obj.mtlid[pid] != cur_mat:
90                cur_mat = obj.mtlid[pid]
91                ofile.write('usemtl {}\n'.format(obj.mtls[cur_mat]))
92            pstr = 'f '
93            for v in obj.polygons[pid]:
94                # UGLY!
95                vstr = '{}/{}/{} '.format(v[0]+1,v[1]+1 if v[1] >= 0 else 'X', v[2]+1 if v[2] >= 0 else 'X' )
96                vstr = vstr.replace('/X/','//').replace('/X ', ' ')
97                pstr += vstr
98            ofile.write( pstr+'\n')

Saves a WavefrontOBJ object to a file

Warning: Contains no error checking!