objparser.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. ''' a minimal wavefront .obj file parser
  2. tested on blender 2.58 exported .obj file
  3. '''
  4. from collections import defaultdict, namedtuple
  5. Vertices = namedtuple('Vertices', 'xs ys zs')
  6. class Mesh(object):
  7. def __init__(self, fname=None):
  8. self.objects = defaultdict(list) # key: object/material name, value: list of triangles
  9. self.materials = {}
  10. self.lnums = {}
  11. self.objfile = None
  12. self.subobj2text = None
  13. self.vertices = Vertices([], [], [])
  14. if fname is not None:
  15. self.parseObjFile(fname)
  16. def parseObjFile(self, fname):
  17. self.objfile = fname
  18. self.subobj2text = None
  19. self.objects.clear()
  20. xs, ys, zs = [], [], [] # hold coords of vertices
  21. lines = open(fname).read().strip().split('\n')
  22. # initialize
  23. objname = 'default'
  24. currobj = self.objects[objname]
  25. self.materials[objname] = 'default'
  26. self.lnums[objname] = 0
  27. for lnum, l in enumerate(lines):
  28. if l.startswith('o '): # a new object
  29. objname = l[2:].strip()
  30. currobj = self.objects[objname]
  31. self.lnums[objname] = lnum
  32. elif l.startswith('usemtl '):
  33. matname = l[7:].strip()
  34. self.materials[objname] = matname
  35. #~ currobj = self.objects[objname]
  36. elif l.startswith('v '): # a vertex
  37. x, y, z = tuple(float(e) for e in l[2:].strip().split())
  38. xs.append(x)
  39. ys.append(y)
  40. zs.append(z)
  41. elif l.startswith('f '): # a face
  42. coords = tuple(int(e.split("/")[0]) for e in l[2:].strip().split())
  43. if len(coords) == 3:
  44. v1, v2, v3 = coords
  45. face = v1-1, v2-1, v3-1
  46. currobj.append(face)
  47. elif len(coords) == 4:
  48. v1, v2, v3, v4 = coords
  49. face1 = v1-1, v2-1, v3-1
  50. face2 = v3-1, v4-1, v1-1
  51. currobj.extend((face1, face2))
  52. else:
  53. #~ print 'skipping face:\n %s' % l
  54. pass
  55. # remove empty objects
  56. for name, triangles in self.objects.items():
  57. if len(triangles) == 0:
  58. self.objects.pop(name)
  59. self.lnums.pop(name)
  60. self.materials.pop(name)
  61. self.vertices = Vertices(xs, ys, zs)
  62. def itertriangles(self, objname=None):
  63. ''' iterate over all triangles of total mesh or of given object
  64. returns 3 points in 3d space
  65. '''
  66. if objname is not None:
  67. vs = self.objects[objname]
  68. else:
  69. vs = []
  70. for _vs in self.objects.values():
  71. vs.extend(_vs)
  72. xs = self.vertices.xs
  73. ys = self.vertices.ys
  74. zs = self.vertices.zs
  75. for v in vs:
  76. p1 = (xs[v[0]], ys[v[0]], zs[v[0]])
  77. p2 = (xs[v[1]], ys[v[1]], zs[v[1]])
  78. p3 = (xs[v[2]], ys[v[2]], zs[v[2]])
  79. # return triangle given by 3 points
  80. yield p1, p2, p3
  81. def splitToText(self):
  82. if self.subobj2text is None:
  83. obj2text = {}
  84. lines = self.objfile.lines()
  85. lnums = list(sorted(self.lnums.items(), key=lambda e: e[1]))
  86. for (objname1, l1), (objname2, l2) in zip(lnums, lnums[1:] + [('END', None)]):
  87. s = '\n'.join('v %s %s %s' % e for e in zip(*self.vertices))
  88. s += '\n'
  89. s += '\n'.join(e.strip() for e in lines[l1:l2] if e.startswith('f '))
  90. obj2text[objname1] = s
  91. self.subobj2text = obj2text
  92. else:
  93. obj2text = self.subobj2text
  94. return obj2text
  95. #~ obj2fnames = {}
  96. #~ vertices = zip(*mesh.vertices)
  97. #~ for objname, faces in mesh.objects.items():
  98. #~ needed_vertices_idx = set()
  99. #~ for face in faces:
  100. #~ needed_vertices_idx.update(face)
  101. #~ vertex_lines = []
  102. #~ idx_mapping = {}
  103. #~ for i, v_idx in enumerate(needed_vertices_idx):
  104. #~ idx_mapping[v_idx] = i
  105. #~ vertex_lines.append('v %s %s %s' % vertices[v_idx])
  106. #~ face_lines = []
  107. #~ for f in faces:
  108. #~ face_lines.append('f %s %s %s' % (idx_mapping[f[0]], idx_mapping[f[1]], idx_mapping[f[2]]))
  109. #~ objfname = tmpdir.joinpath('_%s.obj' % objname)
  110. #~ objfname.write_text('\n'.join(vertex_lines + face_lines))
  111. #~ obj2fnames[objname] = objfname