""" This module contains the functions to parse the input files, and create a ScannedObject. """ import numpy as np from utils.files.output import save_output_file class FacesNotGiven(Exception): """ Exception raised when no faces was given. """ class ResultFileNotGiven(Exception): """ Exception raised when no faces was given. """ class ScannedObject: """ This class is used to manage the data of the 3D object. :param vertices: List of verticesm Ndarray of shape (n,2) :param faces: List of faces, Ndarray of shape (n,2) :param result_file_path: Path to the result file (deprecated, used for the bruteforce discretization) :ivar vertices: List of vertices, Ndarray of shape (n,2) :ivar faces: List of faces, Ndarray of shape (n,2) :ivar result_file_path: Path to the result file (deprecated, used for the bruteforce discretization) :ivar x: List of x values of the vertices :ivar y: List of y values of the vertices :ivar z: List of z values of the vertices :static method from_xyz_file(): Creates a ScannedObject from a .xyz file :static method from_obj_file(): Creates a ScannedObject from a .obj file :method get_x(): Returns the x values of the vertices :method get_y(): Returns the y values of the vertices :method get_z(): Returns the z values of the vertices :method get_vertices(): Returns the vertices :method get_faces(): Returns the faces :method get_discrete_vertices(): Returns the discrete vertices :method get_data(): Returns the data :method export: Exports the data to a file :raises FacesNotGiven: If no faces was given :raises ResultFileNotGiven: If no result file was given :Example: >>> from utils.files.input import ScannedObject >>> vertices = [(0,0,0), (1,0,0), (1,1,0), (0,1,0), (0,0,1), (1,0,1), (1,1,1), (0,1,1)] >>> faces = [(0,1,2), (0,2,3), (4,5,6), (4,6,7), (0,1,5), (0,4,5), (1,2,6), (1,5,6), (2,3,7), (2,6,7), (3,0,4), (3,7,4)] >>> obj = ScannedObject(vertices, faces) >>> obj.get_x() [0, 1, 1, 0, 0, 1, 1, 0] >>> obj.get_y() [0, 0, 1, 1, 0, 0, 1, 1] >>> obj.get_z() [0, 0, 0, 0, 1, 1, 1, 1] >>> obj.get_vertices() [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)] >>> obj.get_faces() [(0, 1, 2), (0, 2, 3), (4, 5, 6), (4, 6, 7), (0, 1, 5), (0, 4, 5), (1, 2, 6), (1, 5, 6), (2, 3, 7), (2, 6, 7), (3, 0, 4), (3, 7, 4)] >>> obj.get_discrete_vertices() [[(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)]],[ (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]] #TODO Add and test exemples """ def __init__(self, vertices, faces=None, result_file_path=None): self.vertices = np.asarray(vertices) self.faces = np.asarray(faces) # Deprecated self.result_file_path = result_file_path self.bruteforce_discretization_result = None # self.x = np.asarray([vertex[0] for vertex in vertices]) self.y = np.asarray([vertex[1] for vertex in vertices]) self.z = np.asarray([vertex[2] for vertex in vertices]) @staticmethod def from_obj_file(file_path:str, result_file_path:str = None, ratio:float = 1,normalised:str = '')->'ScannedObject': """ Create an Object from an OBJ file. :param file_path: Path to the OBJ file :param result_file_path: Path to the result file :param ratio: Ratio to apply to the vertices :param normalised: the axis to normalise :return: A ScannedObject """ with open(file_path, 'r') as f: x, y, z = [], [], [] triangles = [] data = f.readlines() for line in data : if line.startswith('f'): if "//" in line: triangles.append([int(line.split()[1].split("//")[0])-1, int(line.split()[2].split("//")[0])-1, int(line.split()[3].split("//")[0])-1]) else: triangles.append([int(line.split()[1])-1, int(line.split()[2])-1, int(line.split()[3])-1]) elif line.startswith('v'): x.append(float(line.split()[1]) * ratio) y.append(float(line.split()[2]) * ratio) z.append(float(line.split()[3]) * ratio) if 'x' in normalised: xmin = min(x) for count, value in enumerate(x): x[count] -= xmin if 'y' in normalised: ymin = min(y) for count, value in enumerate(y): y[count] -= ymin if 'z' in normalised: zmin = min(z) for count, value in enumerate(z): z[count] -= zmin return ScannedObject(list(zip(x,y,z)), triangles, result_file_path) @staticmethod def from_xyz_file(file_path: str, result_file_path:str = None, delimiter: str = ' ',normalised:str = '')->'ScannedObject': """ Create an Object from an XYZ file. :param file_path: Path to the XYZ file :param result_file_path: Path to the result file :param delimiter: The delimiter used in the xyz file. :param normalised: the axis to normalise :return: A ScannedObject """ x , y , z = [], [], [] with open(file_path, 'r') as f: data = f.readlines() for line in data: x.append(float(line.split(delimiter)[0])) y.append(float(line.split(delimiter)[1])) z.append(float(line.split(delimiter)[2])) if 'x' in normalised: xmin = min(x) for count, value in enumerate(x): x[count] -= xmin if 'y' in normalised: ymin = min(y) for count, value in enumerate(y): y[count] -= ymin if 'z' in normalised: zmin = min(z) for count, value in enumerate(z): z[count] -= zmin return ScannedObject(list(zip(x,y,z)), result_file_path=result_file_path) def get_x(self)->list: """ Get the x coordinates of the object. return: x coordinates """ return self.x def get_y(self)->list: """ Get the y coordinates of the object. return: y coordinates """ return self.y def get_z(self)->list: """ Get the z coordinates of the object. return: z coordinates """ return self.z def get_vertices(self, sort:bool = False): """ Get the vertices of the object. :param sort: Sort the vertices by z coordinate :return: vertices """ vertices = self.vertices if not sort else sorted(self.vertices, key=lambda vertex: vertex[2]) return vertices def get_discrete_vertices(self, step:float = 1): """ Discretize the vertices of the object. :param step: Step of the discretization :return: Discretized vertices """ current_interval = int(min(self.get_z())) splitted_data = [[]] for line in self.get_vertices(sort=True): # TODO check distance instead of equality if line[2] >= current_interval + step: splitted_data.append([]) current_interval += step splitted_data[-1].append(line) return splitted_data def get_discrete_vertices2(self, step:float = 1): """ Deprecated """ cpt = 0 L = [[]] for vertex in self.get_vertices(sort=True): step = 1 L[-1].append(vertex) if vertex[2] > cpt + step: cpt += step L.append([]) return L def get_discrete_vertices3(self, step:float = 1): """ Deprecated """ cpt = 0 L = [[]] z = min(self.get_z()) sorted = self.get_vertices(sort=True) for index in range(len(sorted)): L[-1].append(sorted[index]) if sorted[index][2] - z > step: z = sorted[index][2] L.append([]) return L def get_faces(self,resolved:bool = False)->list: """ Get the faces of the object. :return: faces """ if self.faces is None: raise FacesNotGiven('No faces was given') if resolved: return self.vertices[self.faces] return self.faces def update_from_faces(self,faces:list): """ Update the object from the faces. :param faces: Faces to update the object from """ cpt = 0 vertex_dict = {} new_vertices = [] new_faces = [] for face in faces: new_faces.append([]) for vertex in face: vertex = tuple(vertex) if vertex not in vertex_dict: vertex_dict[vertex] = cpt cpt += 1 new_vertices.append(vertex) new_faces[-1].append(vertex_dict[vertex]) self.vertices = np.asarray(new_vertices) self.faces = np.asarray(new_faces) self.x = self.vertices[:,0] self.y = self.vertices[:,1] self.z = self.vertices[:,2] self.normalise() def normalise(self, axis:str = 'z'): """ Normalise the object. :param axis: Axis to normalise """ if 'x' in axis: self.x -= min(self.x) if 'y' in axis: self.y -= min(self.y) if 'z' in axis: self.z -= min(self.z) self.vertices = np.asarray(list(zip(self.x,self.y,self.z))) def get_data(self)->dict: """ Get the data of the object. :return: Data of the object """ return {'verticies': self.vertices, 'faces': self.faces, 'x': self.x, 'y': self.y, 'z': self.z} def bruteforce_discretization(self): """ Deprecated TODO Remove this when its not needed anymore """ if self.bruteforce_discretization_result: return self.bruteforce_discretization_result if self.result_file_path is None: raise ResultFileNotGiven("No result file was given") L = [] splitted_data = [[]] moyx, moyy, moyz = parse_result_file(self.result_file_path) moy = moyx verticies = self.get_vertices(sort=True) position = 0 while position < len(verticies): print('progression :',position/len(verticies)*100,end="\r") x = verticies[position][0] y = verticies[position][1] z = verticies[position][2] L.append(x) splitted_data[-1].append(verticies[position]) m = np.mean(L) if len(moy) > 0 and abs(m - moy[0]) < 0.000001: moy.pop(0) L = [] splitted_data.append([]) copyposition = position while int(verticies[copyposition][2]) == int(verticies[copyposition-1][2]): copyposition -= 1 position += 1 print(50*" ") self.bruteforce_discretization_result = splitted_data return splitted_data def export_xyz(self, file_path:str,separator:str="\t"): """ Export the object in a file. :param file_path: Path of the file :param separator: chars used to separate the values """ string = '' with open(file_path, "w") as f: for vertex in self.get_vertices(sort=True): x = round(vertex[0], 6) y = round(vertex[1], 6) z = round(vertex[2], 6) string+=f"{x}{separator}{y}{separator}{z}\n" save_output_file(file_path,string) def export_obj(self,file_path): """ Export the object in a file. :param file_path: Path of the file """ string = '' with open(file_path, "w") as f: for vertex in self.get_vertices(): x = round(vertex[0], 6) y = round(vertex[1], 6) z = round(vertex[2], 6) string+=f"v {x} {y} {z}\n" for face in self.get_faces(): string+="f " for vertex in face: string+=f"{vertex+1} " string+="\n" save_output_file(file_path,string) def parse_result_file(file_path: str, separator: str = "\t")-> tuple: """ This functions parses the discretised output file to retreive the first three colunms. It is used to extract the means of x y z for the consistency check and the bruteforce_discretisation :param file_path: Path of the file :param separator: chars used to separate the values :return: x, y, z :Example: >>> parse_result_file("test.txt") ([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], [1.0, 2.0, 3.0]) """ lines = [] x, y, z = [], [], [] with open(file_path, "r") as f: lines = f.readlines()[1:] for line in lines: line = line.replace(",", ".") values = line.split(separator) x.append(float(values[0])) y.append(float(values[1])) z.append(float(values[2])) return x, y, z