code: from .Abstractmesh import AbstractMesh
  import numpy as np
  from ..utils import IO, ObservableArray, deprecated, utilities
  from ..utils.load_operations import get_connectivity_info_surface as get_connectivity_info 
  from ..utils.load_operations import compute_vertex_normals, compute_face_normals
  from ..utils.load_operations import _compute_three_vertex_normals as compute_three_normals
  from ..utils.metrics import triangle_aspect_ratio, triangle_area
  
  
  class Trimesh(AbstractMesh):
      """
      This class represents a mesh composed of triangles. It is possible to load the mesh from a file or
      from raw geometry and topology data.
  
      Parameters:
  
          filename (string): The name of the file to load 
          vertices (Array (Nx3) type=float): The list of vertices of the mesh
          polys (Array (Nx3) type=int): The list of polygons of the mesh
          labels (Array (Nx1) type=int): The list of labels of the mesh (Optional)
  
  
      """
  
      def __init__(self, filename=None, vertices=None, polys=None, labels=None, texture=None, mtl=None, smoothness=False):
  
          super(Trimesh, self).__init__()
          
          self.vtx_normals  = None # npArray (Nx3)
          self.poly_normals = None  # npArray (Nx3)
          self.texture = texture
          self.material = {}
          self.groups = {}
          self.smoothness = smoothness
  
          self.__map_poly_indices  = []
  
  
          if mtl is not None:
              self.__load_from_file(mtl)
  
          if filename is not None:
              self.__load_from_file(filename)
              self._AbstractMesh__filename = filename.split('/')[-1]
  
          elif vertices is not None and polys is not None:
  
              vertices = np.array(vertices)
              polys = np.array(polys)
              self.vertices = ObservableArray(vertices.shape)
              self.vertices[:] = vertices
              self.vertices.attach(self)
              self._AbstractMesh__polys = ObservableArray(polys.shape, dtype=np.int64)
              self._AbstractMesh__polys[:] = polys
              self._AbstractMesh__polys.attach(self)
              self.__load_operations()
  
              if labels is not None:
                  labels = np.array(labels)
                  assert(labels.shape[0] == polys.shape[0])
                  self.labels = ObservableArray(labels.shape, dtype=np.int)
                  self.labels[:] = labels
                  self.labels.attach(self)
              else:
                  self.labels = ObservableArray(polys.shape[0], dtype=np.int)
                  self.labels[:] = np.zeros(self.labels.shape, dtype=np.int)
                  self.labels.attach(self)
          
          self._AbstractMesh__poly_size = 3
          self._AbstractMesh__finished_loading = True
      
  
      # ==================== METHODS ==================== #    
  
  
      def __load_operations(self):
          self._dont_update = True
          self._AbstractMesh__boundary_needs_update = True
          self._AbstractMesh__simplex_centroids = None
  
          
          self._AbstractMesh__edges, \
          self._AbstractMesh__adj_vtx2vtx, \
          self._AbstractMesh__adj_vtx2edge, \
          self._AbstractMesh__adj_vtx2poly, \
          self._AbstractMesh__adj_edge2vtx, \
          self._AbstractMesh__adj_edge2edge, \
          self._AbstractMesh__adj_edge2poly, \
          self._AbstractMesh__adj_poly2vtx, \
          self._AbstractMesh__adj_poly2edge, \
          self._AbstractMesh__adj_poly2poly = get_connectivity_info(self.num_vertices, self.polys)
                                                                                                             
  
          self._AbstractMesh__update_bounding_box()
          self.reset_clipping()
          self.poly_normals = compute_face_normals(self.vertices, self.polys)
          self.vtx_normals = compute_vertex_normals(self.poly_normals, self.adj_vtx2poly._NList__list)
          self.__compute_metrics()
          self._AbstractMesh__simplex_centroids = None
  
          self._dont_update = False
          self.update()
  
      def __load_from_file(self, filename):
  
          ext = filename.split('.')[-1]
  
          if ext == 'obj':
              self.vertices, self._AbstractMesh__polys, self.poly_normals, self.uvcoords, self.coor, self.groups = IO.read_obj(filename)
              # self.vertices, self.faces, self.face_normals = IO.read_obj(filename)
              self.vertices.attach(self)
              self._AbstractMesh__polys.attach(self)
              self.poly_normals.attach(self)
              self.uvcoords.attach(self)
              self.coor.attach(self)
          elif ext == 'mtl':
              self.material = IO.read_mtl(filename)
              return
  
          elif ext == 'off':
              self.vertices, self._AbstractMesh__polys = IO.read_off(filename)
              self.vertices.attach(self)
              self._AbstractMesh__polys.attach(self)
  
          elif ext == 'mesh':
              self.vertices, self._AbstractMesh__polys, labels = IO.read_mesh(filename)
              self.vertices.attach(self)
              self._AbstractMesh__polys.attach(self)
  
          else:
              raise Exception("Only .obj, .off and .mesh files are supported")
  
          self.labels = ObservableArray(self.num_polys, dtype=np.int)
          self.labels[:] = np.zeros(self.labels.shape, dtype=np.int) if ext != 'mesh' else labels
          self.labels.attach(self)
  
          self.__load_operations()
  
          return self
  
      def save_file(self, filename):
  
          """
          Save the current mesh in a file. Currently it supports the .obj extension. 
  
          Parameters:
  
              filename (string): The name of the file
  
          """
  
          ext = filename.split('.')[-1]
  
          if ext == 'obj':
              IO.save_obj(self, filename)
          elif ext == 'off':
              IO.save_off(self, filename)
          elif ext == 'mesh':
              IO.save_mesh(self, filename)
          else:
              raise Exception("Only .obj, .off and .mesh files are supported")
  
      def __compute_metrics(self):
  
          self.simplex_metrics['area'] = triangle_area(self.vertices, self.polys)
          self.simplex_metrics['aspect_ratio'] = triangle_aspect_ratio(self.vertices, self.polys)
  
      def update_metrics(self):
          self.__compute_metrics()
  
      @property
      def _map_poly_indices(self):
          return self.__map_poly_indices
  
      def boundary(self):
  
          """
          Compute the boundary of the current mesh. It only returns the faces that are inside the clipping
          """
          if (self._AbstractMesh__boundary_needs_update):
              clipping_range = super(Trimesh, self).boundary()
              self._AbstractMesh__visible_polys = clipping_range 
              self._AbstractMesh__boundary_cached = clipping_range
              self._AbstractMesh__boundary_needs_update = False
  
              self.__map_poly_indices  = []
              counter = 0
              for c in clipping_range:
                  if c:
                      self.__map_poly_indices.append(counter)
                  else:
                      counter = counter + 1
  
          return self.polys[self._AbstractMesh__boundary_cached], self._AbstractMesh__boundary_cached
  
      def as_edges_flat(self):
          # Faces inside the bounding box
          boundaries = self.boundary()[0]
          # Insert into a vertical array all the correspondences between all the vertices collapsed in one dimension
          edges = np.c_[boundaries[:, :2], boundaries[:, 1:], boundaries[:, 2], boundaries[:, 0]].flatten()
          # edges_flat = self.vertices[edges].tolist()
          return edges
  
      def _as_threejs_triangle_soup(self):
  
          tris = self.vertices[self.boundary()[0].flatten()]
          return tris.astype(np.float32), compute_three_normals(tris).astype(np.float32)
  
      def as_triangles(self):
          return self.boundary()[0].flatten().astype("uint32")
  
      def _as_threejs_colors(self, colors=None):
  
          if colors is not None:
              return np.repeat(colors, 3, axis=0)
          return np.repeat(self.boundary()[1], 3)
      
      @property
      def num_triangles(self):
          return self.num_polys
  
  
      
      def vertex_remove(self, vtx_id):
  
          """
          Remove a vertex from the current mesh. It affects the mesh geometry. 
  
          Parameters:
  
              vtx_id (int): The index of the vertex to remove 
  
          """
  
          self.vertices_remove([vtx_id])
  
      def vertices_remove(self, vtx_ids):
          """
          Remove a list of vertices from the current mesh. It affects the mesh geometry. 
  
          Parameters:
  
              vtx_ids (Array (Nx1 / 1xN) type=int): List of vertices to remove. Each vertex is in the form [int]
  
          """
          self._dont_update = True
          vtx_ids = np.array(vtx_ids)
  
          for v_id in vtx_ids:
  
              self.vertices = np.delete(self.vertices, v_id, 0)
              condition = ((self._AbstractMesh__polys[:, 0] != v_id) &
                           (self._AbstractMesh__polys[:, 1] != v_id) &
                           (self._AbstractMesh__polys[:, 2] != v_id))
  
              if self.labels is not None:
                  self.labels = self.labels[condition]
  
              self._AbstractMesh__polys = self._AbstractMesh__polys[condition]
  
              self._AbstractMesh__polys[(self._AbstractMesh__polys[:, 0] > v_id)] -= np.array([1, 0, 0])
              self._AbstractMesh__polys[(self._AbstractMesh__polys[:, 1] > v_id)] -= np.array([0, 1, 0])
              self._AbstractMesh__polys[(self._AbstractMesh__polys[:, 2] > v_id)] -= np.array([0, 0, 1])
  
              vtx_ids[vtx_ids > v_id] -= 1
  
          self.__load_operations()
  
      def poly_add(self, new_poly):
          """
          Add a new face to the current mesh. It affects the mesh topology. 
  
          Parameters:
  
              new_poly (Array (Nx1) type=int): Poly to add in the form [int, ..., int]
  
          """
          self.polys_add(new_poly)
  
      def polys_add(self, new_polys):
  
          """
          Add a list of new faces to the current mesh. It affects the mesh topology. 
  
          Parameters:
  
              new_polys (Array (NxM) type=int): List of faces to add. Each face is in the form [int, ..., int]
          """
  
          AbstractMesh.polys_add(self, new_polys)
          self.__load_operations()
          
  
  
      def poly_remove(self, poly_id):
  
          """
          Remove a poly from the current mesh. It affects the mesh topology. 
  
          Parameters:
  
              poly_id (int): The index of the face to remove 
  
          """
  
          self.polys_remove([poly_id])
  
      
      def polys_remove(self, poly_ids):
  
          """
          Remove a list of polys from the current mesh. It affects the mesh topology. 
  
          Parameters:
  
              poly_ids (Array (Nx1 / 1xN) type=int): List of polys to remove. Each face is in the form [int]
  
          """
          AbstractMesh.polys_remove(self, poly_ids)
          self.__load_operations()
      
      def tessellate(self):
          return self.polys
      
      @property
      def edge_is_manifold(self):
          val = self.edge_valence
          return np.logical_and(val > 0, val < 3)
  
      
      @property
      def poly_is_on_boundary(self):
          return np.logical_not(np.all(self.adj_poly2poly != -1, axis = 1))
      
      @property
      def edge_is_on_boundary(self):
          boundary_edges = self.adj_poly2edge[self.poly_is_on_boundary].reshape(-1)
          boundary_edges = [e for e in boundary_edges if len(self.adj_edge2poly[e]) == 1]
          bool_vec = np.zeros((self.num_edges), dtype=np.bool)
          bool_vec[boundary_edges] = True
          return bool_vec
      
      @property
      def vert_is_on_boundary(self):
          boundary_verts = self.edges[self.edge_is_on_boundary].reshape(-1)
          bool_vec = np.zeros((self.num_vertices), dtype=np.bool)
          bool_vec[boundary_verts] = True
          return bool_vec
  
      
      @property
      def area(self):
          return np.sum(self.simplex_metrics['area'][1])
  
      def normalize_area(self):
          scale_factor = 1.0/np.sqrt(self.area)
          self.transform_scale([scale_factor, scale_factor, scale_factor])
          self.simplex_metrics['area'] = triangle_area(self.vertices, self.polys)
      
      def sharp_creases(self, threshold=1.0472):
          e2p = self.adj_edge2poly.array
          indices = np.logical_not(np.all(e2p != -1, axis=1)) 
          angles = utilities.angle_between_vectors(self.poly_normals[e2p[:,0]], self.poly_normals[e2p[:,1]], True)[0]
          result = angles > threshold
          result[indices] = True
          return result
      
      def fix_poly_order():
          normals = self.poly_normals
          center  = self.mesh_centroid
          a = (normals-center)
          norm = np.linalg.norm(a, axis=1)
          norm.shape = (-1,1)
          a /= norm
          condition = np.einsum("ij,ij->i", a, normals) > 0
          self.polys[condition] = np.flip(mesh.polys[condition], axis=1)
          self.__load_operations()
      
  
      #deprecated
      @property
      @deprecated("Use the method adj_poly2poly instead")
      def face2face(self):
          return self._AbstractMesh__adj_poly2poly
  
apis: numpy.sqrt numpy.sum numpy.zeros numpy.einsum numpy.logical_and numpy.repeat numpy.all numpy.delete numpy.flip numpy.array numpy.linalg.norm