commit 88da9df256c0a23bd36fa0f0bf29b6363226fda1 Author: Thomas Forgione Date: Mon Dec 7 17:57:45 2020 +0100 Initial commit diff --git a/obja.py b/obja.py new file mode 100755 index 0000000..ecdf0e2 --- /dev/null +++ b/obja.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 + +import sys + +""" +obja parser for python. +""" + +class Vertex: + """ + The class that holds the x, y, and z coordinates of a vertex. + """ + def __init__(self, array): + """ + Initializes a vertex from an array of string representing floats. + """ + self.set(array) + + def set(self, array): + """ + Sets a vertex from an array of string representing floats. + """ + self.x = float(array[0]) + self.y = float(array[1]) + self.z = float(array[2]) + + def translate(self, array): + """ + Translates a vertex from an array of string representing floats. + """ + self.x += float(array[0]) + self.y += float(array[1]) + self.z += float(array[2]) + +class Face: + """ + The class that holds a, b, and c, the indices of the vertices of the face. + """ + def __init__(self, array): + """ + Initializes a face from an array of strings representing vertex indices (starting at 1) + """ + self.set(array) + self.visible = True + + def set(self, array): + """ + Sets a face from an array of strings representing vertex indices (starting at 1) + """ + self.a = int(array[0]) - 1 + self.b = int(array[1]) - 1 + self.c = int(array[2]) - 1 + + def test(self, vertices, line = "unknown"): + """ + Tests if a face references only vertices that exist when the face is declared. + """ + if self.a >= len(vertices): + raise VertexError(self.a + 1, line) + if self.b >= len(vertices): + raise VertexError(self.b + 1, line) + if self.c >= len(vertices): + raise VertexError(self.c + 1, line) + +class VertexError(Exception): + """ + An operation references a vertex that does not exist. + """ + def __init__(self, index, line): + """ + Creates the error from index of the referenced vertex and the line where the error occured. + """ + self.line = line + self.index = index + super().__init__() + + def __str__(self): + """ + Pretty prints the error. + """ + return f'There is no vertex {self.index} (line {self.line})' + +class FaceError(Exception): + """ + An operation references a face that does not exist. + """ + def __init__(self, index, line): + """ + Creates the error from index of the referenced face and the line where the error occured. + """ + self.line = line + self.index = index + super().__init__() + + def __str__(self): + """ + Pretty prints the error. + """ + return f'There is no face {self.index} (line {self.line})' + +class FaceVertexError(Exception): + """ + An operation references a face vertex that does not exist. + """ + def __init__(self, index, line): + """ + Creates the error from index of the referenced face vertex and the line where the error occured. + """ + self.line = line + self.index = index + super().__init__() + + def __str__(self): + """ + Pretty prints the error. + """ + return f'Face has no vertex {self.index} (line {self.line})' + +class UnknownInstruction(Exception): + """ + An instruction is unknown. + """ + def __init__(self, instruction, line): + """ + Creates the error from instruction and the line where the error occured. + """ + self.line = line + self.instruction = instruction + super().__init__() + + def __str__(self): + """ + Pretty prints the error. + """ + return f'Instruction {self.instruction} unknown (line {self.line})' + +class Parser: + """ + The OBJA parser. + """ + def __init__(self): + """ + Intializes an empty parser. + """ + self.vertices = [] + self.faces = [] + self.line = 0 + + def get_vertex(self, string): + """ + Gets a vertex from a string representing the index of the vertex, starting at 1. + """ + index = int(string) - 1 + if index >= len(self.vertices): + raise FaceError(index + 1, self.line) + return self.vertices[index] + + def get_face(self, string): + """ + Gets a face from a string representing the index of the face, starting at 1. + """ + index = int(string) - 1 + if index >= len(self.faces): + raise FaceError(index + 1, self.line) + return self.faces[index] + + def parse_line(self, line): + """ + Parses a line of obja file. + """ + self.line += 1 + + split = line.split() + + if len(split) == 0: + return + + if split[0] == "v": + self.vertices.append(Vertex(split[1:])) + + elif split[0] == "ev": + self.get_vertex(split[1]).set(split[2:]) + + elif split[0] == "tv": + self.get_vertex(split[1]).translate(split[2:]) + + elif split[0] == "f": + face = Face(split[1:]) + face.test(self.vertices, self.line) + self.faces.append(face) + + elif split[0] == "ts": + for i in range(1, len(split) - 2): + if i % 2 == 1: + face = Face([split[i], split[i + 1], split[i + 2]]) + else: + face = Face([split[i], split[i + 2], split[i + 1]]) + face.test(self.vertices, self.line) + self.faces.append(face) + + elif split[0] == "tf": + for i in range(1, len(split) - 2): + face = Face(split[i:i+3]) + face.test(self.vertices, self.line) + self.faces.append(face) + + elif split[0] == "ef": + self.get_face(split[1]).set(split[2:]) + + elif split[0] == "efv": + face = self.get_face(split[1]) + vertex = int(split[2]) + new_index = int(split[3]) - 1 + if vertex == 1: + face.a = new_index + elif vertex == 2: + face.b = new_index + elif vertex == 3: + face.c = new_index + else: + raise FaceVertexError(vertex, self.line) + + elif split[0] == "df": + self.get_face(split[1]).visible = False + + elif split[0] == "#": + return + + else: + raise UnknownInstruction(split[0], self.line) + +def main(): + if len(sys.argv) == 1: + print("obja needs a path to an obja file") + return + + parser = Parser() + with open(sys.argv[1], "r") as file: + for line in file.readlines(): + parser.parse_line(line) + +if __name__ == "__main__": + main() diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..de57189 --- /dev/null +++ b/test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +return_code=0 + +for file in tests/*.obja; do + echo -n "Testing $file... " + ./obja.py $file > /dev/null 2>&1 + + if [ $? -eq 0 ]; then + echo -e '\033[32mSUCCESS\033[0m' + else + echo -e '\033[31mFAILURE\033[0m' + return_code=1 + fi +done + +# objb files should fail +for file in tests/*.objb; do + echo -n "Testing $file... " + ./obja.py $file > /dev/null 2>&1 + + if [ $? -eq 0 ]; then + echo -e '\033[31mFAILURE\033[0m' + return_code=1 + else + echo -e '\033[32mSUCCESS\033[0m' + fi +done + +exit $return_code diff --git a/tests/1.obja b/tests/1.obja new file mode 100644 index 0000000..0cabc1d --- /dev/null +++ b/tests/1.obja @@ -0,0 +1,4 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +f 1 2 3 diff --git a/tests/1.objb b/tests/1.objb new file mode 100644 index 0000000..9eda781 --- /dev/null +++ b/tests/1.objb @@ -0,0 +1,4 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +f 1 2 4 diff --git a/tests/2.obja b/tests/2.obja new file mode 100644 index 0000000..1135858 --- /dev/null +++ b/tests/2.obja @@ -0,0 +1,2 @@ +v 0.0 0.0 0.0 +ev 1 1.0 1.0 1.0 diff --git a/tests/2.objb b/tests/2.objb new file mode 100644 index 0000000..2b99040 --- /dev/null +++ b/tests/2.objb @@ -0,0 +1,2 @@ +v 0.0 0.0 0.0 +ev 2 1.0 1.0 1.0 diff --git a/tests/3.obja b/tests/3.obja new file mode 100644 index 0000000..de564fd --- /dev/null +++ b/tests/3.obja @@ -0,0 +1,2 @@ +v 1.0 2.0 3.0 +tv 1 1.0 1.0 1.0 diff --git a/tests/3.objb b/tests/3.objb new file mode 100644 index 0000000..eb5ae73 --- /dev/null +++ b/tests/3.objb @@ -0,0 +1,2 @@ +v 1.0 2.0 3.0 +tv 2 1.0 1.0 1.0 diff --git a/tests/4.obja b/tests/4.obja new file mode 100644 index 0000000..d4fd1bc --- /dev/null +++ b/tests/4.obja @@ -0,0 +1,6 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 +f 1 2 3 +ef 1 1 2 4 diff --git a/tests/4.objb b/tests/4.objb new file mode 100644 index 0000000..0ce7fc8 --- /dev/null +++ b/tests/4.objb @@ -0,0 +1,6 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 +f 1 2 3 +ef 2 1 2 3 diff --git a/tests/5.obja b/tests/5.obja new file mode 100644 index 0000000..9a2d93e --- /dev/null +++ b/tests/5.obja @@ -0,0 +1,6 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 +f 1 2 3 +efv 1 3 4 diff --git a/tests/5.objb b/tests/5.objb new file mode 100644 index 0000000..5e87233 --- /dev/null +++ b/tests/5.objb @@ -0,0 +1,6 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 +f 1 2 3 +efv 2 3 4 diff --git a/tests/6.obja b/tests/6.obja new file mode 100644 index 0000000..264269e --- /dev/null +++ b/tests/6.obja @@ -0,0 +1,6 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 +f 1 2 3 +df 1 diff --git a/tests/6.objb b/tests/6.objb new file mode 100644 index 0000000..9dce655 --- /dev/null +++ b/tests/6.objb @@ -0,0 +1,6 @@ +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 +f 1 2 3 +df 2 diff --git a/tests/7.obja b/tests/7.obja new file mode 100644 index 0000000..d88ec22 --- /dev/null +++ b/tests/7.obja @@ -0,0 +1,6 @@ +v -1.0 0.0 0.0 +v -0.5 1.0 0.0 +v 0.0 0.0 0.0 +v 0.5 1.0 0.0 +v 1.0 0.0 0.0 +ts 1 2 3 4 5 diff --git a/tests/7.objb b/tests/7.objb new file mode 100644 index 0000000..7ff387b --- /dev/null +++ b/tests/7.objb @@ -0,0 +1,6 @@ +v -1.0 0.0 0.0 +v -0.5 1.0 0.0 +v 0.0 0.0 0.0 +v 0.5 1.0 0.0 +v 1.0 0.0 0.0 +ts 1 2 3 4 6 diff --git a/tests/8.obja b/tests/8.obja new file mode 100644 index 0000000..a2e3aa9 --- /dev/null +++ b/tests/8.obja @@ -0,0 +1,7 @@ +v 0.0 0.0 0.0 +v -1.0 0.0 0.0 +v -0.707 0.707 0.0 +v 0.0 1.0 0.0 +v 0.707 0.707 0.0 +v 1.0 0.0 0.0 +tf 1 2 3 4 5 6 diff --git a/tests/8.objb b/tests/8.objb new file mode 100644 index 0000000..b70c148 --- /dev/null +++ b/tests/8.objb @@ -0,0 +1,7 @@ +v 0.0 0.0 0.0 +v -1.0 0.0 0.0 +v -0.707 0.707 0.0 +v 0.0 1.0 0.0 +v 0.707 0.707 0.0 +v 1.0 0.0 0.0 +tf 1 2 3 4 5 7