def get_rho(orig_v, vertices, faces): orig_v = normalize_area(orig_v, faces) a1 = igl.massmatrix(orig_v, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC).diagonal() a2 = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC).diagonal() return np.divide(a1, a2)
def test_massmatrix(self): a = igl.massmatrix(self.v, self.f) b = igl.massmatrix(self.v, self.f, type=igl.MASSMATRIX_TYPE_BARYCENTRIC) self.assertTrue(a.shape == (self.v.shape[0], self.v.shape[0])) self.assertTrue(b.shape == (self.v.shape[0], self.v.shape[0])) self.assertTrue(b.dtype == np.float64) self.assertTrue(a.dtype == np.float64) self.assertTrue(type(a) == type(b) == csc.csc_matrix)
def makeLaplacianMatrixSolverIGLHard(VPos, ITris, anchorsIdx): VPosE = igl.eigen.MatrixXd(VPos) ITrisE = igl.eigen.MatrixXi(ITris) L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() M_inv = igl.eigen.SparseMatrixd() igl.cotmatrix(VPosE,ITrisE,L) igl.massmatrix(VPosE,ITrisE,igl.MASSMATRIX_TYPE_VORONOI,M) igl.invert_diag(M,M_inv) L = M_inv*L deltaCoords = L*VPosE deltaCoords = np.array(deltaCoords) #Bi-laplacian Q = L.transpose()*L
def key_pressed(viewer, key, modifier): global V global U global F global L if key == ord('r') or key == ord('R'): U = V; elif key == ord(' '): # Recompute just mass matrix on each step M = igl.eigen.SparseMatrixd() igl.massmatrix(U,F,igl.MASSMATRIX_TYPE_BARYCENTRIC,M); # Solve (M-delta*L) U = M*U S = (M - 0.001*L) solver = igl.eigen.SimplicialLLTsparse(S) U = solver.solve(M*U) # Compute centroid and subtract (also important for numerics) dblA = igl.eigen.MatrixXd() igl.doublearea(U,F,dblA) print(dblA.sum()) area = 0.5*dblA.sum() BC = igl.eigen.MatrixXd() igl.barycenter(U,F,BC) centroid = igl.eigen.MatrixXd([[0.0,0.0,0.0]]) for i in range(0,BC.rows()): centroid += 0.5*dblA[i,0]/area*BC.row(i) U -= centroid.replicate(U.rows(),1) # Normalize to unit surface area (important for numerics) U = U / math.sqrt(area) else: return False # Send new positions, update normals, recenter viewer.data.set_vertices(U) viewer.data.compute_normals() viewer.core.align_camera_center(U,F) return True
def conformal_flow(vertices, faces): vertices = normalize(np.copy(vertices)) L = igl.cotmatrix(vertices, faces) itrs = 20 time_step = .1 for i in range(itrs): vertices = normalize(vertices) # this is a lumped mass matrix M = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC) S = (M - time_step * L) b = M.dot(vertices) vertices = spsolve(S, b) vertices = normalize(vertices) print("flow error: " + str(get_error(vertices))) vertices = project_sphere(vertices) print("percentage of flipped faces: " + str(100 * get_flipped_normals(vertices, faces))) return vertices
def test_harmonic(self): l = igl.cotmatrix(self.v1, self.f1) m = igl.massmatrix(self.v1, self.f1, igl.MASSMATRIX_TYPE_VORONOI) b = np.array([1, 2, 10, 7]) bc = self.v1[b, :] k = 1 w = igl.harmonic_weights_from_laplacian_and_mass(l, m, b, bc, k) self.assertTrue(w.flags.c_contiguous)
def get_mass_matrix(v, f, area_type): if (area_type == 'voronoi') or (area_type == 'barycentric'): return igl.massmatrix(v, f, AREA_TYPES[area_type]) elif area_type == 'mayer': return mayer_area(v, f) else: raise ValueError("Please choose one of the supported mass" +f" matrix types:{list(AREA_TYPES.keys())}")
def smooth_data(v, f, w): # Smoothing = Minimize curvature # # min_{p*}{E(p*)} # # E(p*) = sum_{p in points on mesh}{A_p * ((Lp*)^2 + w(p* - p)^2)}, # where A_p is the voronoi region around point p # # Solve dE(p*)/dp* = 0 # => (L'ML + wM)p* = (wM)p ~= Ax = b # # L = M^(-1)L_w, w = cotangent[c]/uniform[u]/... # # L_c defined in slides 5 page 66 # M and L defined in slides 6 page 23 # Constructing the squared Laplacian and squared Hessian energy # NOTE L_c is sparse # L_c = igl.cotmatrix(v, f) # discrete laplacian # L_c.data[np.isnan(L_c.data)] = 0 # L_c.data[L_c.data > 1e7] = 1e7 # L_c.data[L_c.data < -1e7] = -1e7 # Uniform laplacian # adopted from here: # https://libigl.github.io/libigl-python-bindings/igl_docs/#cotmatrix adj = igl.adjacency_matrix(f) # Sum each row = number of neighbours adj_sum = np.squeeze(np.asarray(np.sum(adj, axis=1))) # Convert row sums into diagonal of sparse matrix adj_diag = sp.sparse.diags(adj_sum) # Build uniform laplacian L_u = adj - adj_diag # NOTE M is sparse M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI) # Give corrupted triangles only very little weight mask = np.logical_or(np.isnan(M.data), M.data == 0) M.data[mask] = 1e-7 M_inv = sp.sparse.diags(1 / M.diagonal()) # NOTE L_c can become very weird when dealing with corrupted meshes # L = M_inv @ L_c L = M_inv @ L_u A = L.T @ M @ L + w * M # TODO lambda * ID ? A += sp.sparse.identity(A.shape[0]) b = w * M @ v v_star = sp.sparse.linalg.spsolve(A, b) return v_star
def canonical_rotation(orig_v, vertices, faces): weights, A = IRF(orig_v, vertices, faces, 3) Q = np.vstack([-weights[3], -weights[1], weights[2]]).T u, s, vh = np.linalg.svd(Q) # product here is reversed because the vertices are row vectors (ie: A * v -> v^T * A^T) orig_v = orig_v.dot(u) vertices = vertices.dot(vh.T) # project to sphere again because of numerical issues w rotation vertices = project_sphere(vertices) # pi rotations: yz, xz, xy rx = np.diag([1, -1, -1]) ry = np.diag([-1, 1, -1]) rz = np.diag([-1, -1, 1]) M = igl.massmatrix(orig_v, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC) weights, A = IRF(orig_v, vertices, faces, 8) weights_x, A_x = IRF(orig_v.dot(rx), vertices.dot(rx), faces, 8) weights_y, A_y = IRF(orig_v.dot(ry), vertices.dot(ry), faces, 8) weights_z, A_z = IRF(orig_v.dot(rz), vertices.dot(rz), faces, 8) # l = [ # sum(weights[4])+sum(weights[5])+sum(weights[7]), # sum(weights_x[4])+sum(weights_x[5])+sum(weights_x[7]), # sum(weights_y[4])+sum(weights_y[5])+sum(weights_y[7]), # sum(weights_z[4])+sum(weights_z[5])+sum(weights_z[7]), # ] l = [ np.sum(np.sum(M.dot(A.dot(weights)), axis=0)), np.sum(np.sum(M.dot(A_x.dot(weights_x)), axis=0)), np.sum(np.sum(M.dot(A_y.dot(weights_y)), axis=0)), np.sum(np.sum(M.dot(A_z.dot(weights_z)), axis=0)), ] # print(np.sum(M.dot(A.dot(weights)), axis=0)), # print(np.sum(M.dot(A_x.dot(weights_x)), axis=0)), # print(np.sum(M.dot(A_y.dot(weights_y)), axis=0)), # print(np.sum(M.dot(A_z.dot(weights_z)), axis=0)), ind = l.index(max(l)) if ind == 0: return orig_v, vertices elif ind == 1: return orig_v.dot(rx), vertices.dot(rx) elif ind == 2: return orig_v.dot(ry), vertices.dot(ry) elif ind == 3: return orig_v.dot(rz), vertices.dot(rz)
def set_parameters(self, V, F): # reset self.reset() # compute property given V and F self.N = igl.per_vertex_normals(V, F) self.L = igl.cotmatrix(V, F) VA = igl.massmatrix(V, F, 0) self.VA = VA.diagonal() # get face adjacency list VF, NI = igl.vertex_triangle_adjacency(F, V.shape[0]) adjFList = construct_adjacency_list(VF, NI) # arap self.K = igl.arap_rhs(V, F, d=3, energy=1) # they are all list since length can be different self.hEList = [None] * V.shape[0] self.WVecList = [None] * V.shape[0] self.dVList = [None] * V.shape[0] for i in range(0, V.shape[0]): adjF = adjFList[i] len_adjF = adjF.shape[0] self.hEList[i] = np.zeros((len_adjF * 3, 2), dtype=int) self.WVecList[i] = np.zeros(len_adjF * 3) self.dVList[i] = np.zeros((3, 3 * len_adjF)) for j in range(0, len_adjF): vIdx = adjF[j] v0 = F[vIdx, 0] v1 = F[vIdx, 1] v2 = F[vIdx, 2] # half edge indices # hE = np.array([[v0, v1], [v1, v2], [v2, v0]]) # self.hEList[i] = hE self.hEList[i][3 * j, 0] = v0 self.hEList[i][3 * j, 1] = v1 self.hEList[i][3 * j + 1, 0] = v1 self.hEList[i][3 * j + 1, 1] = v2 self.hEList[i][3 * j + 2, 0] = v2 self.hEList[i][3 * j + 2, 1] = v0 # weight vec self.WVecList[i][3 * j] = self.L[v0, v1] self.WVecList[i][3 * j + 1] = self.L[v1, v2] self.WVecList[i][3 * j + 2] = self.L[v2, v0] V_hE0 = V[self.hEList[i][:, 0], :] V_hE1 = V[self.hEList[i][:, 1], :] self.dVList[i] = np.transpose(V_hE1 - V_hE0) self.WVecList[i] = np.diag(self.WVecList[i]) # other var numV = V.shape[0] self.zAll = np.random.rand(3, numV) * 2.0 - 1.0 self.uAll = np.random.rand(3, numV) * 2.0 - 1.0 self.zAll = np.zeros((3, numV)) self.uAll = np.zeros((3, numV)) self.rhoAll = np.full(numV, self.param.rhoInit)
def normalize_area(vertices, faces, M=None): if M is None: M = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC) A = get_area(vertices, faces) v_w = M.dot(vertices) centroid = np.sum(v_w, axis=0) / A vertices -= centroid vertices /= np.sqrt(A / (np.pi * 4)) return vertices
def construct_matrices(self): """ Construct FEM matrices """ V = self.vertices F = self.faces # Compute gradient operator: #F*3 by #V G = igl.grad(V, F).tocoo() L = igl.cotmatrix(V, F).tocoo() N = igl.per_face_normals(V, F, np.array([0., 0., 0.])) A = igl.doublearea(V, F) A = A[:, np.newaxis] M = igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI).tocoo() M = M.data # Compute latitude and longitude directional vector fields NS = np.reshape(G.dot(self.lat), [self.nf, 3], order='F') EW = np.cross(NS, N) # Compute F2V matrix (weigh by area) # adjacency i = self.faces.ravel() j = np.arange(self.nf).repeat(3) one = np.ones(self.nf * 3) adj = sparse.csc_matrix((one, (i, j)), shape=(self.nv, self.nf)) tot_area = adj.dot(A) norm_area = A.ravel().repeat(3) / np.squeeze(tot_area[i]) F2V = sparse.csc_matrix((norm_area, (i, j)), shape=(self.nv, self.nf)) # Compute interpolation matrix if self.level > 0: intp = self.intp[self.nv_prev:] i = np.concatenate( (np.arange(self.nv), np.arange(self.nv_prev, self.nv))) j = np.concatenate((np.arange(self.nv_prev), intp[:, 0], intp[:, 1])) ratio = np.concatenate( (np.ones(self.nv_prev), 0.5 * np.ones(2 * intp.shape[0]))) intp = sparse.csc_matrix((ratio, (i, j)), shape=(self.nv, self.nv_prev)) else: intp = sparse.csc_matrix(np.eye(self.nv)) # Compute vertex mean matrix self.G = G # gradient matrix self.L = L # laplacian matrix self.N = N # normal vectors (per-triangle) self.NS = NS # north-south vectors (per-triangle) self.EW = EW # east-west vectors (per-triangle) self.F2V = F2V # map face quantities to vertices self.M = M # mass matrix (area of voronoi cell around node. for integration) self.Seq = self._rotseq(self.vertices) self.Intp = intp
def features_extractor(classes,DNA_size=50): """Extract the features from the samples in the dataset. The type of features is the ShapeDNA. :param classes: list list of the classes :param DNA_size: int size of the feature vector """ print("\nFeature extraction") for c in classes: print(c) for file in os.listdir(os.path.join('.', 'Dataset', c)): if file.endswith(".off"): print("\t", file, end=" ... ") # Load mesh v, f = igl.read_triangle_mesh(os.path.join('.', 'Dataset',c, file)) M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI) v = v / np.sqrt(M.sum()) # Compute Laplacian L = -igl.cotmatrix(v, f) M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI) # Compute EigenDecomposition try: evals, evecs = sp.sparse.linalg.eigsh(L, DNA_size+2, M, sigma=0.0, which='LM', maxiter=1e9, tol=1.e-15) except: evals, evecs = sp.sparse.linalg.eigsh(L + 1e-8 * sp.sparse.identity(v.shape[0]), DNA_size+2, M, sigma=0.0, which='LM', maxiter=1e9, tol=1.e-15) # Shape DNA descriptors = evals[2:] / evals[1] # Save descriptor np.save(os.path.join('.', 'Dataset', c, file[:-4] + "_DNA"), descriptors, allow_pickle=False) print("done.") print("Finished")
def compute_cotan_laplacian(df): """ compute mass matrix and cotan laplacian matrix :param df: pandas data frame :return L, M, in that order, CSR-sparse matrix """ points = df2points(df).T assert points.shape[-1] == 3 ch = ConvexHull(points) assert points.shape[0] == ch.points.shape[0] L = -csr_matrix(igl.cotmatrix(ch.points, ch.simplices)) # positive semidefiniteness # ISSUE: some areas are way too small such that igl ends up considering it exactly zero. # as a result, it's not even stored in the sparse matrix. coarsening needed M = csr_matrix(igl.massmatrix(ch.points, ch.simplices, igl.MASSMATRIX_TYPE_VORONOI)) return L, M
def IRF(orig_v, vertices, faces, max_degree=16): num_harm = max_degree**2 sigma = 0.001 theta = np.arccos(vertices[:, 2]) phi = np.arctan2(vertices[:, 1], vertices[:, 0]) orig_v = normalize_area(orig_v, faces) # weighted least squares with mass matrix to account for unequal mesh resolution. W = igl.massmatrix(orig_v, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC) weights = np.zeros((num_harm, 3)) i = 0 A = [] residual = np.copy(orig_v) for l in range(0, max_degree): Y = [] for m in range(-l, l + 1): y = sph_real(l, m, phi, theta) A.append(y) Y.append(y) smooth = 1 #np.exp(-sigma*l*(l+1)) Y = np.vstack(Y).T #w = np.linalg.solve(Y.T.dot(Y),Y.T.dot(residual)) w = np.linalg.solve(Y.T.dot(W.dot(Y)), Y.T.dot(W.dot(residual))) residual = residual - Y.dot(w) i1 = l**2 i2 = (l + 1)**2 weights[i1:i2] = smooth * w A = np.vstack(A).T #err = A.dot(weights) - orig_v #print("mean reconstruction error: " + str(np.mean(norm(err, axis=1)))) return weights, A
def __init__(self, mesh, OUTPUT_PATH): utils.check_package_is_installed('igl') import igl logger.info('GeodesicsSolver') self.mesh = mesh self.OUTPUT_PATH = OUTPUT_PATH self.use_forwards_euler = True v, f = mesh.to_vertices_and_faces() v = np.array(v) f = np.array(f) # compute necessary data self.cotans = igl.cotmatrix_entries(v, f) # compute_cotan_field(self.mesh) self.L = igl.cotmatrix(v, f) # assemble_laplacian_matrix(self.mesh, self.cotans) self.M = igl.massmatrix(v, f) # create_mass_matrix(mesh)
def mobius_center(orig_v, vertices, faces): M = igl.massmatrix(orig_v, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC) for i in range(10): # center of mass mu = np.sum(M.dot(vertices), axis=0) err = norm(mu) print("mobius error: " + str(norm(mu))) if err < 1e-10: break # c = -J^-1 * mu J = compute_jacobian(M, vertices) c = -np.linalg.inv(J).dot(mu) # compute inversion vertices = np.divide((vertices + c), norm(vertices + c, axis=1).reshape((-1, 1))**2) vertices = (1 - norm(c)**2) * vertices + c return vertices
def mass_matrix(self): if ('mass_matrix' not in self.cache): self.cache['mass_matrix'] = igl.massmatrix( self.vertices, self.faces, igl.MASSMATRIX_TYPE_VORONOI) return self.cache['mass_matrix']
def get_area(vertices, faces): M = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC) return np.sum(M.dot(np.ones(vertices.shape[0])), axis=0)
viewer = igl.viewer.Viewer() viewer.data.set_mesh(V, F) viewer.core.show_lines = False viewer.callback_key_down = key_down # One fixed point on belly b = igl.eigen.MatrixXi([[2556]]) bc = igl.eigen.MatrixXd([[1]]) # Construct Laplacian and mass matrix L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() Minv = igl.eigen.SparseMatrixd() igl.cotmatrix(V, F, L) igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI, M) igl.invert_diag(M, Minv) # Bi-Laplacian Q = L.transpose() * (Minv * L) # Zero linear term B = igl.eigen.MatrixXd.Zero(V.rows(), 1) # Lower and upper bound lx = igl.eigen.MatrixXd.Zero(V.rows(), 1) ux = igl.eigen.MatrixXd.Ones(V.rows(), 1) # Equality constraint constrain solution to sum to 1 Beq = igl.eigen.MatrixXd([[0.08]]) Aeq = M.diagonal().transpose().sparseView()
viewer = igl.viewer.Viewer() viewer.data.set_mesh(V, F) viewer.core.show_lines = False viewer.callback_key_down = key_down # One fixed point on belly b = igl.eigen.MatrixXi([[2556]]) bc = igl.eigen.MatrixXd([[1]]) # Construct Laplacian and mass matrix L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() Minv = igl.eigen.SparseMatrixd() igl.cotmatrix(V,F,L) igl.massmatrix(V,F,igl.MASSMATRIX_TYPE_VORONOI,M); igl.invert_diag(M,Minv) # Bi-Laplacian Q = L.transpose() * (Minv * L) # Zero linear term B = igl.eigen.MatrixXd.Zero(V.rows(),1) # Lower and upper bound lx = igl.eigen.MatrixXd.Zero(V.rows(),1) ux = igl.eigen.MatrixXd.Ones(V.rows(),1) # Equality constraint constrain solution to sum to 1 Beq = igl.eigen.MatrixXd([[0.08]]) Aeq = M.diagonal().sparseView().transpose()
c = 0 bbd = 1.0 twod = False if not igl.read_triangle_mesh("../tutorial/shared/beetle.off",V,F): print("failed to load mesh") twod = V.col(2).minCoeff() == V.col(2).maxCoeff() bbd = (V.colwiseMaxCoeff() - V.colwiseMinCoeff()).norm() L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() igl.cotmatrix(V,F,L) L = -L igl.massmatrix(V,F,igl.MASSMATRIX_TYPE_DEFAULT,M) k = 5 D = igl.eigen.MatrixXd() if not igl.eigs(L,M,k+1,igl.EIGS_TYPE_SM,U,D): print("Eigs failed.") U = (U-U.minCoeff())/(U.maxCoeff()-U.minCoeff()); viewer = igl.viewer.Viewer() def key_down(viewer,key,mod): global U, c if key == ord(' '): U = U.rightCols(k)
import polyscope import igl import numpy as np import polyscope as ps import scipy # https://www.cs.cmu.edu/~kmcrane/Projects/ModelRepository/spot.zip path = "/Users/cbabraham/data/mesh/spot/spot_triangulated.obj" # verts and faces V,F = igl.read_triangle_mesh(path) # cotan laplacian and mass matrix L = igl.cotmatrix(V,F) M = igl.massmatrix(V,F) # eigenvectors of the laplacian _, E = scipy.sparse.linalg.eigsh(L, 1000, M, which='BE') C = V.T.dot(E) # inner product of each eigenvector and the mesh R = np.einsum('km,nm->nk',C,E) # V: n x k verts (k = 3) # E: n x m eigenvectors, to use as bases # C: k x m basis weights # R: n x k synthesized vertices from weighted bases ps.init() original = ps.register_surface_mesh("mesh", V,F)
def makeLaplacianMatrixSolverIGLSoft(VPos, ITris, anchorsIdx, anchorWeights, makeSolver = True): VPosE = igl.eigen.MatrixXd(VPos) ITrisE = igl.eigen.MatrixXi(ITris) ''' #Doing this check slows things down by more than a factor of 2 (convert to numpy to make faster?) for f in range(ITrisE.rows()): v_list = ITrisE.row(f) v1 = VPosE.row(v_list[0]) v2 = VPosE.row(v_list[1]) v3 = VPosE.row(v_list[2]) if (v1-v2).norm() < 1e-10 and (v1-v3).norm() < 1e-10 and (v2-v3).norm() < 1e-10: print 'zero area triangle!',f ''' L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() M_inv = igl.eigen.SparseMatrixd() igl.cotmatrix(VPosE,ITrisE,L) igl.massmatrix(VPosE,ITrisE,igl.MASSMATRIX_TYPE_VORONOI,M) #np.set_printoptions(threshold='nan') #print 'what is M?',M.diagonal() igl.invert_diag(M,M_inv) #L = M_inv*L deltaCoords = (M_inv*L)*VPosE #TODO: What to do with decaying_anchor_weights? ''' anchor_dists = [] for i in range(VPosE.rows()): anchor_dists.append(min([ (VPosE.row(i)-VPosE.row(j)).norm() for j in anchorsIdx ])) max_anchor_dist = max(anchor_dists) # assume linear weighting for anchor weights -> we are 0 at the anchors, anchorWeights at max_anchor_dist decaying_anchor_weights = [] for anchor_dist in anchor_dists: decaying_anchor_weights.append(anchorWeights*(anchor_dist/max_anchor_dist)) ''' solver = None if makeSolver: Q = L*(M_inv*M_inv)*L #Now add in sparse constraints diagTerms = igl.eigen.SparseMatrixd(VPos.shape[0], VPos.shape[0]) # anchor points for a in anchorsIdx: diagTerms.insert(a, a, anchorWeights) # off points ''' for adx,decay_weight in enumerate(decaying_anchor_weights): if decay_weight == 0: diagTerms.insert(adx, adx, anchorWeights) else: diagTerms.insert(adx, adx, decay_weight) ''' Q = Q + diagTerms Q.makeCompressed() start_time = time.time() solver = igl.eigen.SimplicialLLTsparse(Q) #solver = igl.eigen.CholmodSupernodalLLT(Q) end_time = time.time() print 'factorization elapsed time:',end_time-start_time,'seconds' return (L, M_inv, solver, np.array(deltaCoords))
c = 0 bbd = 1.0 twod = False if not igl.read_triangle_mesh("../../tutorial/shared/beetle.off", V, F): print("failed to load mesh") twod = V.col(2).minCoeff() == V.col(2).maxCoeff() bbd = (V.colwiseMaxCoeff() - V.colwiseMinCoeff()).norm() L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() igl.cotmatrix(V, F, L) L = -L igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_DEFAULT, M) k = 5 D = igl.eigen.MatrixXd() if not igl.eigs(L, M, k + 1, igl.EIGS_TYPE_SM, U, D): print("Eigs failed.") U = (U - U.minCoeff()) / (U.maxCoeff() - U.minCoeff()) viewer = igl.viewer.Viewer() def key_down(viewer, key, mod): global U, c if key == ord(' '):