from dataclasses import dataclass import sys @dataclass class VertexPosition: x: float y: float z: float @dataclass class VertexNormal: x: float y: float z: float @dataclass class VertexTexture: x: float y: float z: float @dataclass class ObjectEvent: name: str @dataclass class FacePTN: position: int texture: int normal: int @dataclass class Face: ptn: list[FacePTN] @dataclass class Object: name: str faces: list[Face] def __init__(self, name): self.name = name self.faces = [] @dataclass class ObjFile: position: list[VertexPosition] normal: list[VertexNormal] texture: list[VertexTexture] objects: list[Object] def __init__(self): self.position = [] self.normal = [] self.texture = [] self.objects = [] def parse_float(s): if '.' not in s: return int(s, 10) i, f = s.split('.') f_digits = len(f) i = int(i, 10) f = int(f, 10) return i + (f / 10 ** f_digits) def parse_vertex(line, n, type): assert len(line) == n vs = [parse_float(line[i]) for i in range(n)] if len(vs) < 3: vs.append(0) return type(*vs) def parse_vertex_position(line): return parse_vertex(line, 3, VertexPosition) def parse_vertex_normal(line): return parse_vertex(line, 3, VertexNormal) def parse_vertex_texture(line): return parse_vertex(line, 2, VertexTexture) def parse_face_indices(indices): assert "/" in indices indices = indices.split("/") assert len(indices) == 3, indices def face_ix(s): i = int(s, 10) assert i >= 1 return i - 1 return FacePTN(*(face_ix(i) for i in indices)) def parse_face(line): return Face([parse_face_indices(indices) for indices in line]) def parse_object_event(line): assert len(line) == 1 name, = line return ObjectEvent(name) def parse_line(line): t, *line = line.split(' ') if t == '#': return None if t == 'usemtl': return None if t == 'mtllib': return None if t == 'o': return parse_object_event(line) if t == 'v': return parse_vertex_position(line) if t == 'vn': return parse_vertex_normal(line) if t == 'vt': return parse_vertex_texture(line) if t == 'f': return parse_face(line) if t == 's': # smooth shading return None assert False, (t, line) def parse_obj_lines(lines): file = ObjFile() object = Object(None) for line in lines: x = parse_line(line) if x is None: continue elif type(x) is VertexPosition: file.position.append(x) elif type(x) is VertexNormal: file.normal.append(x) elif type(x) is VertexTexture: file.texture.append(x) elif type(x) is ObjectEvent: if object.faces: assert object.name != None file.objects.append(object) object = Object(x.name) elif type(x) is Face: object.faces.append(x) else: assert False, x if object.faces: assert object.name != None file.objects.append(object) return file def parse_obj_file(filename): with open(filename, "r") as f: lines = f.read().strip().split("\n") file = parse_obj_lines(lines) return file if __name__ == "__main__": file = parse_obj_file(sys.argv[1]) from pprint import pprint pprint(file)