diff --git a/model.py b/model.py new file mode 100644 index 0000000..cf5ee3d --- /dev/null +++ b/model.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +class Vertex: + def __init__(self, x = None, y = None, z = None): + self.x = x + self.y = y + self.z = z + + def from_array(self, arr): + self.x = arr[0] if len(arr) > 0 else None + self.y = arr[1] if len(arr) > 1 else None + self.z = arr[2] if len(arr) > 2 else None + return self + +Normal = Vertex +TexCoord = Vertex + +class FaceVertex: + def __init__(self, vertex = None, texture = None, normal = None): + self.vertex = vertex + self.texture = texture + self.normal = normal + + def from_array(self, arr): + self.vertex = arr[0] if len(arr) > 0 else None + self.texture = arr[1] if len(arr) > 1 else None + self.normal = arr[2] if len(arr) > 2 else None + return self + +class Face: + def __init__(self, a = None, b = None, c = None, mtl = None): + self.a = a + self.b = b + self.c = c + self.mtl = mtl + + # Expects array of array + def from_array(self, arr): + self.a = FaceVertex().from_array(arr[0]) + self.b = FaceVertex().from_array(arr[1]) + self.c = FaceVertex().from_array(arr[2]) + return self + +class ModelParser: + + def __init__(self): + self.vertices = [] + self.normals = [] + self.tex_coords = [] + self.faces = [] + + def add_vertex(self, vertex): + self.vertices.append(vertex) + + def add_tex_coord(self, tex_coord): + self.tex_coords.append(tex_coord) + + def add_normal(self, normal): + self.normals.append(normal) + + def addFace(self, face): + self.faces.append(face) + + def parse_line(self, string): + pass + + def parse_file(self, path): + with open(path) as f: + for line in f.readlines(): + line = line.rstrip() + self.parse_line(line) + +class Exporter: + def __init__(self, model): + self.model = model + diff --git a/modelconverter.py b/modelconverter.py deleted file mode 100755 index fad8973..0000000 --- a/modelconverter.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from functools import reduce - -class Vertex: - def __init__(self, x = None, y = None, z = None): - self.x = x - self.y = y - self.z = z - - def from_array(self, arr): - self.x = arr[0] if len(arr) > 0 else None - self.y = arr[1] if len(arr) > 1 else None - self.z = arr[2] if len(arr) > 2 else None - return self - -Normal = Vertex -TexCoord = Vertex - -class FaceVertex: - def __init__(self, vertex = None, texture = None, normal = None): - self.vertex = vertex - self.texture = texture - self.normal = normal - - def from_array(self, arr): - self.vertex = arr[0] if len(arr) > 0 else None - self.texture = arr[1] if len(arr) > 1 else None - self.normal = arr[2] if len(arr) > 2 else None - return self - -class Face: - def __init__(self, a = None, b = None, c = None, mtl = None): - self.a = a - self.b = b - self.c = c - self.mtl = mtl - - # Expects array of array - def from_array(self, arr): - self.a = FaceVertex().from_array(arr[0]) - self.b = FaceVertex().from_array(arr[1]) - self.c = FaceVertex().from_array(arr[2]) - return self - -class ModelParser: - - def __init__(self): - self.vertices = [] - self.normals = [] - self.tex_coords = [] - self.faces = [] - - def add_vertex(self, vertex): - self.vertices.append(vertex) - - def add_tex_coord(self, tex_coord): - self.tex_coords.append(tex_coord) - - def add_normal(self, normal): - self.normals.append(normal) - - def addFace(self, face): - self.faces.append(face) - - def parse_line(self, string): - pass - - def parse_file(self, path): - with open(path) as f: - for line in f.readlines(): - line = line.rstrip() - self.parse_line(line) - -class OBJParser(ModelParser): - - def __init__(self): - super().__init__() - self.materials = [] - - def parse_line(self, string): - split = string.split(' ') - first = split[0] - split = split[1:] - - if first == 'usemtl': - self.currentMaterial = split[0] - elif first == 'v': - self.add_vertex(Vertex().from_array(split)) - elif first == 'vn': - self.add_normal(Normal().from_array(split)) - elif first == 'vt': - self.add_tex_coord(TexCoord().from_array(split)) - elif first == 'f': - splits = list(map(lambda x: x.split('/'), split)) - - for i in range(len(splits)): - for j in range(len(splits[i])): - if splits[i][j] is not '': - splits[i][j] = str(int(splits[i][j]) - 1) - - self.addFace(Face().from_array(splits)) - -class Exporter: - def __init__(self, model): - self.model = model - -class OBJExporter(Exporter): - def __init__(self, model): - super().__init__(model) - - def __str__(self): - string = "" - - for vertex in self.model.vertices: - string += "n " + ' '.join([vertex.x, vertex.y, vertex.z]) + "\n" - - string += "\n" - - for tex_coord in self.model.tex_coords: - string += "vt " + ' '.join([tex_coord.x, tex_coord.y]) + "\n" - - string += "\n" - - for normal in self.model.normals: - string += "vn " + ' '.join([normal.x, normal.y, normal.z]) + "\n" - - string += "\n" - - for face in self.model.faces: - string += "f " - arr = [] - for v in [face.a, face.b, face.c]: - sub_arr = [] - sub_arr.append(v.vertex) - if v.normal is None: - if v.texture is not None: - sub_arr.append('') - sub_arr.append(v.texture) - elif v.texture is not None: - sub_arr.append(v.texture) - if v.normal is not None: - sub_arr.append(v.normal) - arr.append('/'.join(sub_arr)) - - string += ' '.join(arr) + '\n' - return string - -class PLYExporter(Exporter): - def __init__(self, model): - super().__init__(model) - - def __str__(self): - # Header - string = "ply\nformat ascii 1.0\ncomment Automatically gnerated by model-converter\n" - - # Types : vertices - string += "element vertex " + str(len(self.model.vertices)) +"\n" - string += "property float32 x\nproperty float32 y\nproperty float32 z\n" - - # Types : faces - string += "element face " + str(len(self.model.faces)) + "\n" - string += "property list uint8 int32 vertex_indices\n" - - # End header - string += "end_header\n" - - # Content of the model - for vertex in self.model.vertices: - string += vertex.x + " " + vertex.y + " " + vertex.z + "\n" - - for face in self.model.faces: - string += "3 " + face.a.vertex + " " + face.b.vertex + " " + face.c.vertex + "\n" - - return string - -if __name__ == '__main__': - - parser = OBJParser() - parser.parse_file('./examples/cube.obj') - - exporter = PLYExporter(parser) - string = str(exporter) - - print(string) - - diff --git a/obj.py b/obj.py new file mode 100644 index 0000000..8b086b0 --- /dev/null +++ b/obj.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +from model import ModelParser, Exporter, Vertex, TexCoord, Normal, FaceVertex, Face +from functools import reduce + +class OBJParser(ModelParser): + + def __init__(self): + super().__init__() + self.materials = [] + + def parse_line(self, string): + split = string.split(' ') + first = split[0] + split = split[1:] + + if first == 'usemtl': + self.currentMaterial = split[0] + elif first == 'v': + self.add_vertex(Vertex().from_array(split)) + elif first == 'vn': + self.add_normal(Normal().from_array(split)) + elif first == 'vt': + self.add_tex_coord(TexCoord().from_array(split)) + elif first == 'f': + splits = list(map(lambda x: x.split('/'), split)) + + for i in range(len(splits)): + for j in range(len(splits[i])): + if splits[i][j] is not '': + splits[i][j] = str(int(splits[i][j]) - 1) + + self.addFace(Face().from_array(splits)) + +class OBJExporter(Exporter): + def __init__(self, model): + super().__init__(model) + + def __str__(self): + string = "" + + for vertex in self.model.vertices: + string += "n " + ' '.join([vertex.x, vertex.y, vertex.z]) + "\n" + + string += "\n" + + for tex_coord in self.model.tex_coords: + string += "vt " + ' '.join([tex_coord.x, tex_coord.y]) + "\n" + + string += "\n" + + for normal in self.model.normals: + string += "vn " + ' '.join([normal.x, normal.y, normal.z]) + "\n" + + string += "\n" + + for face in self.model.faces: + string += "f " + arr = [] + for v in [face.a, face.b, face.c]: + sub_arr = [] + sub_arr.append(v.vertex) + if v.normal is None: + if v.texture is not None: + sub_arr.append('') + sub_arr.append(v.texture) + elif v.texture is not None: + sub_arr.append(v.texture) + if v.normal is not None: + sub_arr.append(v.normal) + arr.append('/'.join(sub_arr)) + + string += ' '.join(arr) + '\n' + return string + diff --git a/obj2ply.py b/obj2ply.py index b87ffdd..3c782c7 100755 --- a/obj2ply.py +++ b/obj2ply.py @@ -3,7 +3,8 @@ import argparse import os -from modelconverter import OBJParser, PLYExporter +from obj import OBJParser +from ply import PLYExporter from functools import partial def check_path(path, should_exist): diff --git a/ply.py b/ply.py new file mode 100644 index 0000000..89258eb --- /dev/null +++ b/ply.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +from model import ModelParser, Exporter, Vertex, Face + +class PLYParser(ModelParser): + + def __init__(self): + super().__init__() + self.counter = 0 + self.elements = [] + self.inner_parser = PLYHeaderParser(self) + + def parse_line(self, string): + self.inner_parser.parse_line(self, string) + +class PLYHeaderParser: + def __init__(self, parent): + self.current_element = None + self.parent = parent + + def parse_line(self, string): + split = string.split(' ') + if string == 'ply': + return + + elif split[0] == 'format': + if split[1] != 'ascii' or split[2] != '1.0': + print('Only ascii 1.0 format is supported', file=sys.stderr) + sys.exit(-1) + + elif split[0] == 'element': + self.current_element = PLYElement(split[1], int(split[2])) + self.parent.elements.append(self.current_element) + + elif split[0] == 'property': + self.current_element.add_property(split[2], split[1]) + + elif split[0] == 'end_header': + self.parent.inner_parser = PLYContentParser(self.parent) + + +class PLYElement: + def __init__(self, name, number): + self.name = name + self.number = number + self.properties = [] + + def add_property(name, type): + self.properties.append((name, type)) + +class PLYContentParser: + def __init__(self, parent): + self.parent = parent + self.element_index = 0 + self.counter = 0 + self.current_element = self.parent.elements[0] + + def parse_line(self, string): + self.counter += 1 + + split = string.split(' ') + + if self.current_element.name == 'vertex': + self.parent.add_vertex(Vertex.from_array(split)) + elif self.current_element.name == 'face': + self.parent.add_face(Face(split[1], split[2], split[3])) + + self.counter += 1 + + def next_element(self): + self.element_index += 1 + self.current_element = self.parent.elements[self.element_index] + +class PLYExporter(Exporter): + def __init__(self, model): + super().__init__(model) + + def __str__(self): + # Header + string = "ply\nformat ascii 1.0\ncomment Automatically gnerated by model-converter\n" + + # Types : vertices + string += "element vertex " + str(len(self.model.vertices)) +"\n" + string += "property float32 x\nproperty float32 y\nproperty float32 z\n" + + # Types : faces + string += "element face " + str(len(self.model.faces)) + "\n" + string += "property list uint8 int32 vertex_indices\n" + + # End header + string += "end_header\n" + + # Content of the model + for vertex in self.model.vertices: + string += vertex.x + " " + vertex.y + " " + vertex.z + "\n" + + for face in self.model.faces: + string += "3 " + face.a.vertex + " " + face.b.vertex + " " + face.c.vertex + "\n" + + return string + +