diff --git a/integration_tests/data_test.py b/integration_tests/data_test.py index 0e3db873d53cf9943a8d3b4b1aae5eb4759d7f7b..cb833a3f6cf2ee55c593bdacccaeb6937af1d2d9 100644 --- a/integration_tests/data_test.py +++ b/integration_tests/data_test.py @@ -6,7 +6,7 @@ import time import numpy as np from main import get_discrete_data, get_raw_data from utils.files import output -from utils.files.input import ScannedObject +from utils.files.input import ScannedObject, parse_result_file def check_discrete_data(expected_file: str, actual_file: str, ndigits=7, eps=0.00001, silent: bool = False) -> dict: """ @@ -193,7 +193,7 @@ def check_consistency(obj: ScannedObject, discretised_file_path: str): :param obj: The object to check :param discretised_file_path: The discetised file output """ - obj.export("verification.txt") + obj.export_xyz("verification.txt") mean_list = [] moyx, moyy, moyz = parse_result_file(discretised_file_path) moy = moyy diff --git a/main.py b/main.py index 3eec56a1bc3b74cc86bba793908ee097cb1f94c8..6a757a5d5c56755c43a20f8c9245181a6ae2bc01 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,8 @@ from utils.math import data_extraction from utils.files import output from utils.files.input import ScannedObject +from utils.math.position_manipulation import verticalise +from utils.graph3D.visplot_render import render3D def get_raw_data(obj:ScannedObject, ndigits:int)->dict: """ @@ -46,7 +48,7 @@ def get_discrete_data(obj:ScannedObject, ndigits:int)->dict: - Rayon moyen (en mm) : list of mean radius values - Rayon ecart type (en mm) : list of radius standard deviation values """ - colones = ["X moy (en mm)", "Y moy (en mm)", "Z moy (en mm)","Rayon moyen (en mm)","Rayon ecart type (en mm)"] + colones = ["X moy (en mm)", "Y moy (en mm)", "Z moy (en mm)","Delta z(en mm)","Rayon moyen (en mm)","Rayon ecart type (en mm)"] data = {} for colone in colones: data[colone] = [] @@ -55,15 +57,19 @@ def get_discrete_data(obj:ScannedObject, ndigits:int)->dict: data["X moy (en mm)"].append(round(x, ndigits)) data["Y moy (en mm)"].append(round(y, ndigits)) data["Z moy (en mm)"].append(round(z, ndigits)) + first = discrete_values[0] + last = discrete_values[-1] + data["Delta z(en mm)"].append(round(last[2]-first[2],ndigits)) data["Rayon moyen (en mm)"].append(round(data_extraction.get_mean_radius(discrete_values), ndigits)) data["Rayon ecart type (en mm)"].append(round(data_extraction.get_radius_std(discrete_values), ndigits)) return data - def main(): # Create an object from the given file - obj = ScannedObject.from_xyz_file("test_cylindre.xyz",normalised='z') - + obj = ScannedObject.from_obj_file("datasets/Barette/1 - BARETTE.obj") + verticalise(obj) + obj.normalise() + # Calculate raw data and save it in a file data = get_raw_data(obj, 6) output.save_output_file('analyse_brute.txt', @@ -85,6 +91,7 @@ def main(): ["X moy (en mm)", "Y moy (en mm)", "Z moy (en mm)", + "Delta z(en mm)", "Rayon moyen (en mm)", "Rayon ecart type (en mm)"] )) diff --git a/requirement.txt b/requirement.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..239f8828713c028c29fcf705bbfebfa77d49d119 100644 --- a/requirement.txt +++ b/requirement.txt @@ -0,0 +1,3 @@ +vispy==0.12.2 +PyQt5==5.15.9 +numpy==1.24.2 \ No newline at end of file diff --git a/utils/files/input.py b/utils/files/input.py index 0da7d3759c172c8ca6ed25f71421b8acc8deaa0b..94412c881209295a6bbee935cdc30d2e3aad5199 100644 --- a/utils/files/input.py +++ b/utils/files/input.py @@ -60,15 +60,15 @@ class ScannedObject: #TODO Add and test exemples """ def __init__(self, vertices, faces=None, result_file_path=None): - self.vertices = vertices - self.faces = faces + 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 = [vertex[0] for vertex in vertices] - self.y = [vertex[1] for vertex in vertices] - self.z = [vertex[2] for vertex in vertices] + 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 @@ -88,8 +88,10 @@ class ScannedObject: data = f.readlines() for line in data : if line.startswith('f'): - # Face indices start at 1, not 0 - triangles.append([int(line.split()[1])-1, int(line.split()[2])-1, int(line.split()[3])-1]) + 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) @@ -223,16 +225,56 @@ class ScannedObject: L.append([]) return L - - def get_faces(self)->list: + 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. @@ -256,7 +298,7 @@ class ScannedObject: verticies = self.get_vertices(sort=True) position = 0 while position < len(verticies): - print(position/len(verticies)*100,end="\r") + print('progression :',position/len(verticies)*100,end="\r") x = verticies[position][0] y = verticies[position][1] z = verticies[position][2] @@ -275,7 +317,7 @@ class ScannedObject: self.bruteforce_discretization_result = splitted_data return splitted_data - def export(self, file_path:str,separator:str="\t"): + def export_xyz(self, file_path:str,separator:str="\t"): """ Export the object in a file. :param file_path: Path of the file @@ -290,6 +332,25 @@ class ScannedObject: 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: """ diff --git a/utils/3d/mpl_render.py b/utils/graph3D/mpl_render.py similarity index 72% rename from utils/3d/mpl_render.py rename to utils/graph3D/mpl_render.py index 316474e385fe15609e760f0d57500b044b2d740c..c4757fe36e03a2d20fa218cff61b668960bfcdda 100644 --- a/utils/3d/mpl_render.py +++ b/utils/graph3D/mpl_render.py @@ -1,16 +1,17 @@ from mpl_toolkits.mplot3d.art3d import Poly3DCollection import matplotlib.pyplot as plt import numpy as np +from utils.files.input import ScannedObject -def render3D(data:dict): +def render3D(obj:ScannedObject): """ Render a 3D model using matplotlib poly3dcollection :param data: A dict with the vertices and faces """ fig = plt.figure() ax = fig.add_subplot(projection='3d') - faces = np.array(data['faces']) - verts = np.array(list(zip(data['x'], data['y'], data['z']))) + faces = np.array(obj.get_faces()) + verts = np.array(obj.get_vertices()) mesh = Poly3DCollection(verts[faces], alpha=0.25, edgecolor='none') ax.add_collection3d(mesh) plt.show() \ No newline at end of file diff --git a/utils/graph3D/visplot_render.py b/utils/graph3D/visplot_render.py new file mode 100644 index 0000000000000000000000000000000000000000..d2cd0f24021018cb49a43ee2424a12b8c7382ec8 --- /dev/null +++ b/utils/graph3D/visplot_render.py @@ -0,0 +1,33 @@ +import numpy as np +from vispy import app, scene +from vispy.scene.visuals import Mesh +from vispy.scene import transforms +from vispy.visuals.filters import ShadingFilter, WireframeFilter +from utils.files.input import ScannedObject + + +def render3D(obj:ScannedObject): + vertices = np.asarray(obj.get_vertices()) + faces = np.asarray(obj.get_faces()) + canvas = scene.SceneCanvas(keys='interactive', bgcolor='white') + view = canvas.central_widget.add_view() + view.camera = 'arcball' + view.camera.depth_value = 1e3 + mesh = Mesh(vertices, faces, color=(.5, .7, .5, 1)) + view.add(mesh) + wireframe_filter = WireframeFilter(width=0) + shading_filter = ShadingFilter(shininess=0) + mesh.attach(wireframe_filter) + mesh.attach(shading_filter) + + def attach_headlight(view): + light_dir = (0, 1, 0, 0) + shading_filter.light_dir = light_dir[:3] + initial_light_dir = view.camera.transform.imap(light_dir) + @view.scene.transform.changed.connect + def on_transform_change(event): + transform = view.camera.transform + shading_filter.light_dir = transform.map(initial_light_dir)[:3] + attach_headlight(view) + canvas.show() + app.run() \ No newline at end of file diff --git a/utils/math/position_manipulation.py b/utils/math/position_manipulation.py new file mode 100644 index 0000000000000000000000000000000000000000..53af77c58c384a20cf78291f9daf687b8b451734 --- /dev/null +++ b/utils/math/position_manipulation.py @@ -0,0 +1,90 @@ +import numpy as np +from utils.files.input import ScannedObject +from utils.math.data_extraction import get_mean + + +def get_mass_properties(obj:ScannedObject): + ''' + Evaluate and return a tuple with the following elements: + - the volume + - the position of the center of gravity (COG) + - the inertia matrix expressed at the COG + + Documentation can be found here: + http://www.geometrictools.com/Documentation/PolyhedralMassProperties.pdf + ''' + + verts = np.asarray(obj.get_vertices()) + faces = np.asarray(obj.get_faces()) + faces = verts[faces] + x = np.asarray([[point[0] for point in faces] for faces in faces]) + y = np.asarray([[point[1] for point in faces] for faces in faces]) + z = np.asarray([[point[2] for point in faces] for faces in faces]) + + def subexpression(x): + w0, w1, w2 = x[:, 0], x[:, 1], x[:, 2] + temp0 = w0 + w1 + f1 = temp0 + w2 + temp1 = w0 * w0 + temp2 = temp1 + w1 * temp0 + f2 = temp2 + w2 * f1 + f3 = w0 * temp1 + w1 * temp2 + w2 * f2 + g0 = f2 + w0 * (f1 + w0) + g1 = f2 + w1 * (f1 + w1) + g2 = f2 + w2 * (f1 + w2) + return f1, f2, f3, g0, g1, g2 + + x0, x1, x2 = x[:, 0], x[:, 1], x[:, 2] + y0, y1, y2 = y[:, 0], y[:, 1], y[:, 2] + z0, z1, z2 = z[:, 0], z[:, 1], z[:, 2] + a1, b1, c1 = x1 - x0, y1 - y0, z1 - z0 + a2, b2, c2 = x2 - x0, y2 - y0, z2 - z0 + d0, d1, d2 = b1 * c2 - b2 * c1, a2 * c1 - a1 * c2, a1 * b2 - a2 * b1 + + f1x, f2x, f3x, g0x, g1x, g2x = subexpression(x) + f1y, f2y, f3y, g0y, g1y, g2y = subexpression(y) + f1z, f2z, f3z, g0z, g1z, g2z = subexpression(z) + + intg = np.zeros((10)) + intg[0] = sum(d0 * f1x) + intg[1:4] = sum(d0 * f2x), sum(d1 * f2y), sum(d2 * f2z) + intg[4:7] = sum(d0 * f3x), sum(d1 * f3y), sum(d2 * f3z) + intg[7] = sum(d0 * (y0 * g0x + y1 * g1x + y2 * g2x)) + intg[8] = sum(d1 * (z0 * g0y + z1 * g1y + z2 * g2y)) + intg[9] = sum(d2 * (x0 * g0z + x1 * g1z + x2 * g2z)) + intg /= np.array([6, 24, 24, 24, 60, 60, 60, 120, 120, 120]) + volume = intg[0] + cog = intg[1:4] / volume + cogsq = cog ** 2 + inertia = np.zeros((3, 3)) + inertia[0, 0] = intg[5] + intg[6] - volume * (cogsq[1] + cogsq[2]) + inertia[1, 1] = intg[4] + intg[6] - volume * (cogsq[2] + cogsq[0]) + inertia[2, 2] = intg[4] + intg[5] - volume * (cogsq[0] + cogsq[1]) + inertia[0, 1] = inertia[1, 0] = -(intg[7] - volume * cog[0] * cog[1]) + inertia[1, 2] = inertia[2, 1] = -(intg[8] - volume * cog[1] * cog[2]) + inertia[0, 2] = inertia[2, 0] = -(intg[9] - volume * cog[2] * cog[0]) + return volume, cog, inertia + +def verticalise(obj:ScannedObject): + cog = get_mass_properties(obj) + cog, inertia = get_mass_properties(obj)[1:] + [val,vect] = np.linalg.eig(inertia) + + if np.linalg.det(vect) < 0: + vect[:,2] = - vect[:,2] + + rot = vect.T + faces = obj.get_faces(True) + + theta = -np.pi/2 + + R_y = np.array([[np.cos(theta), 0, np.sin(theta)], + [0, 1, 0], + [-np.sin(theta), 0, np.cos(theta)]]) + + for face,_ in enumerate(faces): + for vertex in range(3): + faces[face][vertex] = rot@(faces[face][vertex] - cog) + faces[face][vertex] = R_y@(faces[face][vertex]) + + obj.update_from_faces(faces)