def mesh_and_extract_largest_connected_surface(volume, level_set): vertices, faces = marching_cubes(volume, level_set) mesh = Trimesh() mesh.vertices = vertices mesh.faces = faces meshes = mesh.split(only_watertight=False) sorted_meshes = sorted(meshes, key=lambda x: len(x.vertices)) return sorted_meshes[-1]
def voxelize(mesh: Trimesh, dims:int = 14) -> ndarray: workdir = mkdtemp() TMP_FILE_NAME = workdir + '/voxelization' TMP_STL_FILE_NAME = TMP_FILE_NAME + '.stl' TMP_BINVOX_FILE_NAME = TMP_FILE_NAME + '.binvox' mesh.export(TMP_STL_FILE_NAME, 'stl') cmd_std = sh(dirname(realpath(__file__)) + "/binvox", '-d' , str(dims) , TMP_STL_FILE_NAME) vox_matrix = read_as_3d_array(open(TMP_BINVOX_FILE_NAME, 'rb'), fix_coords=True) return vox_matrix
def __init__(self, faces=None, vertices=None, **kwargs): """ SuperClass for Revitinterface option """ RevitInterface.__init__(self, **kwargs) Trimesh.__init__(self, vertices=vertices, faces=faces) self._reps = {} self._facet_centroids = None self._convex_comps = None self._current_rep = 'mesh' self._axis = []
def __init__(self, faces=None, vertices=None, **kwargs): """ Arbitrary thing assumed to be a line-based geometry """ CadInterface.__init__(self, **kwargs) Trimesh.__init__(self, vertices=vertices, faces=faces) self._reps = {} self._facet_centroids = None self._current_rep = 'mesh' self._convex_comps = None # axis is two facet_box indices self._axis = []
def average_properties(vertices: np.ndarray, faces: np.ndarray, properties: np.ndarray, norm: bool = False) -> Union[float, np.ndarray]: """ Average property across an isosurface. Args: vertices: A (n, 3) float array of the vertices in the isosurface. faces: A (m, 3) int array of the faces of the isosurface. properties: A (m, ...) array of the face properties as scalars or vectors. norm: Whether to average the norm of the properties (vector properties only). Returns: The averaged property. """ from trimesh import Trimesh mesh = Trimesh(vertices=vertices, faces=faces) if norm and properties.ndim > 1: properties = np.linalg.norm(properties, axis=1) face_areas = mesh.area_faces # face_areas has to have same number of dimensions as property face_areas = face_areas.reshape(face_areas.shape + (1, ) * (properties.ndim - 1)) return np.sum(properties * face_areas, axis=0) / mesh.area
def ptcld_as_isosurf(pts, out_obj, res=128, center=False): """Visualizes point cloud as isosurface of its TDF. Args: pts (array_like): Cartesian coordinates in object space, of shape N-by-3. out_obj (str): The output path of the surface .obj. res (int, optional): Resolution of the TDF. center (bool, optional): Whether to center these points around object space origin. Writes - The .obj file of the isosurface. """ from skimage.measure import marching_cubes_lewiner from trimesh import Trimesh from trimesh.io.export import export_mesh from ..geometry.ptcld import ptcld2tdf # Point cloud to TDF tdf = ptcld2tdf(pts, res=res, center=center) # Isosurface of TDF vs, fs, ns, _ = marching_cubes_lewiner(tdf, 0.999 / res, spacing=(1 / res, 1 / res, 1 / res)) mesh = Trimesh(vertices=vs, faces=fs, normals=ns) export_mesh(mesh, out_obj)
def __getitem__(self, idx): """ Returns an item of the dataset. Args: idx (int): ID of datasets point """ data_path = self.data[idx] np_data = np.load(data_path, allow_pickle=True) to_ret = { 'can_vertices': np_data['can_vertices'].astype(np.float32), } # sample points to_ret.update( self.sample_points(Trimesh(to_ret['can_vertices'], self.faces), self.n_points_can, prefix='can_')) # proxy skinning weights to_ret['can_points_sk_weights'] = self.compute_sk_weights( to_ret['can_vertices'], to_ret['can_points'], np_data['gender'].item()) return to_ret
def add_data_files(self, np_data, to_ret): # sample training points to_ret.update( self.sample_points(Trimesh(to_ret['posed_vertices'], self.faces), self.n_points_posed, compute_occupancy=True)) to_ret.update( self.sample_points(Trimesh(to_ret['can_vertices'], self.faces), self.n_points_can, prefix='can_', compute_occupancy=True)) to_ret['can_points_sk_weights'] = self.compute_sk_weights( to_ret['can_vertices'], to_ret['can_points'], np_data['gender'].item())
def ptcld_as_isosurf(pts, out_obj, res=128, center=False): """ Visualize point cloud as isosurface of its TDF Args: pts: Cartesian coordinates in object space n-by-3 array_like of floats out_obj: The output path of the surface .obj String res: Resolution of the TDF Integer Optional; defaults to 128 center: Whether to center these points around object space origin Boolean Optional; defaults to False """ from skimage.measure import marching_cubes_lewiner from trimesh import Trimesh from trimesh.io.export import export_mesh from xiuminglib import geometry as xgeo # Point cloud to TDF tdf = xgeo.ptcld2tdf(pts, res=res, center=center) # Isosurface of TDF vs, fs, ns, _ = marching_cubes_lewiner(tdf, 0.999 / res, spacing=(1 / res, 1 / res, 1 / res)) mesh = Trimesh(vertices=vs, faces=fs, normals=ns) export_mesh(mesh, out_obj)
def main(args): if args.epoch is None: epochs = Path(f"lightning_logs/version_{args.version}/checkpoints/" ).glob('*.ckpt') epochs = list( map(lambda x: int(re.match(r"epoch=([0-9]+)", x.stem).group(1)), epochs)) print(epochs) epoch = max(epochs) else: epoch = args.epoch model = SALD.load_from_checkpoint( f"lightning_logs/version_{args.version}/checkpoints/epoch={epoch}.ckpt" ) if args.object is not None: objId = int(args.object) else: objId = np.random.randint(0, model.embedding.num_embeddings) grid = model.voxelize(objId, args.res).detach().numpy() verts, faces, normals, values = measure.marching_cubes(grid, 0) mesh = Trimesh(vertices=verts, faces=faces) scene = Scene([mesh]) viewer = SceneViewer(scene)
def get_fermi_slice( self, plane_normal: Tuple[int, int, int], distance: float = 0 ) -> FermiSlice: """ Get a slice through the Fermi surface, defined by the intersection of a plane with the fermi surface. Args: plane_normal: The plane normal in fractional indices. E.g., ``(1, 0, 0)``. distance: The distance from the center of the Brillouin zone (the Gamma point). Returns: The Fermi slice. """ cart_normal = np.dot(plane_normal, self.reciprocal_space.reciprocal_lattice) cart_origin = cart_normal * distance slices = {} for spin, spin_isosurfaces in self.isosurfaces.items(): spin_slices = [] for verts, faces in spin_isosurfaces: mesh = Trimesh(vertices=verts, faces=faces) lines = mesh_multiplane(mesh, cart_origin, cart_normal, [0])[0][0] spin_slices.append(lines) slices[spin] = spin_slices reciprocal_slice = self.reciprocal_space.get_reciprocal_slice( plane_normal, distance ) return FermiSlice(slices, reciprocal_slice, self.structure)
def calculate_scaling(local_neighborhood, local_positions, corresponding_positions, iteration, max_iterations): # scale oriented bounding box to be same size # use trimesh for min OBBs local_trimesh = Trimesh([local_positions[v] for v in local_neighborhood]) local_extents = local_trimesh.bounding_box_oriented.primitive.extents.copy( ) local_extents.sort() corresponding_trimesh = Trimesh([corresponding_positions.values()]) corresponding_extents = corresponding_trimesh.bounding_box_oriented.primitive.extents.copy( ) corresponding_extents.sort() scaling_factor = iteration / max_iterations scaling = (local_extents + (corresponding_extents - local_extents) * scaling_factor) / local_extents return np.matrix(np.diag(np.append( scaling, 1))) # matlib's diag() still makes arrays
def _to_unit_cube(mesh: trimesh.Trimesh): bounds = mesh.extents if bounds.min() == 0.0: return # translate to origin translation = (mesh.bounds[0] + mesh.bounds[1]) * 0.5 translation = trimesh.transformations.translation_matrix(direction=-translation) mesh.apply_transform(translation) # scale to unit cube scale = 1.0/bounds.max() scale_trafo = trimesh.transformations.scale_matrix(factor=scale) mesh.apply_transform(scale_trafo) return mesh
def _as_box2(self): b = Trimesh(vertices=self.vertices).convex_hull.bounding_box_oriented bx = self.__class__(vertices=b.vertices, faces=b.faces, **self.base_args) bx._reps[self._current_rep] = self bx._current_rep = 'box' return bx
def repair_mesh(mesh: Trimesh) -> Trimesh: """ Repair the given mesh using pymeshfix. """ mf = pymeshfix.MeshFix(mesh.vertices, mesh.faces) mf.repair() return Trimesh(mf.v, mf.f)
def smooth_laplacian(vertices, faces, **kwargs): """Smooth vertices and faces using a Laplacian filter""" from trimesh.smoothing import filter_humphrey from trimesh import Trimesh kwargs.setdefault("iterations", 2) mesh = Trimesh(vertices, faces) filter_humphrey(mesh, **kwargs) return mesh.vertices, mesh.faces
def connected_subsurfaces( vertices: np.ndarray, faces: np.ndarray) -> List[Tuple[np.ndarray, np.ndarray]]: """ Find connected sub-surfaces (those that share edges). Args: vertices: A (n, 3) float array of the vertices in the isosurface. faces: A (m, 3) int array of the faces of the isosurface. Returns: A list of of (vertices, faces) for each sub-surface. """ from trimesh import Trimesh mesh = Trimesh(vertices=vertices, faces=faces) connected_meshes = mesh.split(only_watertight=False) return [(m.vertices, m.faces) for m in connected_meshes]
def _get_points_near_surface(mesh: trimesh.Trimesh, num_query_pts_close, patch_radius, rng): samples, face_id = mesh.sample(num_query_pts_close, return_index=True) offset_factor = (rng.random(size=(num_query_pts_close,)) - 0.5) * 2.0 * patch_radius sample_normals = mesh.face_normals[face_id] sample_normals_len = np.sqrt(np.linalg.norm(sample_normals, axis=1)) sample_normals_len_broadcast = np.broadcast_to(np.expand_dims(sample_normals_len, axis=1), sample_normals.shape) sample_normals_normalized = sample_normals / sample_normals_len_broadcast offset_factor_broadcast = np.broadcast_to(np.expand_dims(offset_factor, axis=1), sample_normals.shape) noisy_samples = samples + offset_factor_broadcast * sample_normals_normalized return noisy_samples
def export_mesh_as_threejs(vertices, triangles): vertices = vertices.astype('float') mesh = Trimesh(vertices = vertices, faces = triangles) mesh.fix_normals() #add column with zeros s = mesh.faces.shape faces = np.zeros(shape= (s[0], s[1]+1) ) faces[:,1:4] = mesh.faces data = { "metadata": { "formatVersion" : 3 }, "vertices": list(mesh.vertices.reshape(-1)), "normals": list(mesh.vertex_normals.reshape(-1)), "faces": list(faces.reshape(-1)) } return data
def __init__(self, vertices=None, mesh=None, debug=False, **kwargs): if mesh is None and vertices is not None: mesh = Trimesh(vertices=vertices).convex_hull.bounding_box_oriented Trimesh.__init__(self, vertices=mesh.vertices, faces=mesh.faces) vert_face = self.vertices[self.faces[self.facets]] # facet centroids np.shape([6, 3]) self._facet_centers = vert_face.reshape(self.facets.shape[0], -1, 3).mean(axis=1) fct_norm = self.facets_normal mn1, mn2 = np.where( np.isclose(spatial.distance.cdist(fct_norm, -fct_norm), 0.)) # pairs of axis points of obb : [ 3, 2] self._pairs = np.unique([sorted(v) for v in zip(mn1, mn2)], axis=0) lines = [] for n1, n2 in zip(self.axes_vertices[:, 0, :], self.axes_vertices[:, 1, :]): lines.append(lib.geo.Line(lib.geo.Point(n1), lib.geo.Point(n2))) self._axis_skel = AxisSkeleton(lines)
def main(): parser = create_parser() args = parser.parse_args() print 'fname:', args.fname g = fio.read_geometry(args.fname) m = Trimesh(g[0], g[1]) if args.outname is None: # (name, ext) = os.path.splitext(args.fname) outname = '%s.stl' % args.fname else: outname = args.outname print 'outname:', outname export_mesh(m, outname)
def isosurface(self, isovalue=0.0): from trimesh import Trimesh from chmpy.mc import marching_cubes vol = self.data.reshape((self.nx, self.ny, self.nz)) seps = ( np.linalg.norm(self.x_basis), np.linalg.norm(self.y_basis), np.linalg.norm(self.z_basis), ) verts, faces, normals, _ = marching_cubes(vol, level=isovalue, spacing=seps) return Trimesh(vertices=verts, faces=faces, normals=normals)
def isosurface_dimensionality( fractional_vertices: np.ndarray, faces: np.ndarray) -> Tuple[str, Tuple[int, int, int]]: """ Calculate isosurface properties a single isosurface (fully connected). The vertices must cover a 3x3x3 supercell and must not have been trimmed to fit inside the reciprocal lattice. Note: This function expects the vertices to only belong to a single connected mesh, and the vertices should cover a 3x3x3 supercell, where the coordinates from -0.5 to 0.5 indicate the center image. Args: fractional_vertices: A (n, 3) float array of the vertices in the isosurface in fractional coordinates. faces: A (m, 3) int array of the faces of the isosurface. Returns: The dimensionality and (n, 3) int array orientation of the isosurface. """ from trimesh import Trimesh if len(connected_subsurfaces(fractional_vertices, faces)) != 1: raise ValueError("isosurface contains multiple subsurfaces") images = connected_images(fractional_vertices) rank = np.linalg.matrix_rank(images) orientation = None if rank == 1: orientation = line_orientation(images) dimensionality = "2D" elif rank == 2: orientation = plane_orientation(images) # use euler number to decide if mesh is a plane or multiple tubes euler_number = Trimesh(vertices=fractional_vertices, faces=faces).euler_number if euler_number == 1: dimensionality = "1D" else: dimensionality = "quasi-2D" elif rank == 0: dimensionality = "3D" else: dimensionality = "quasi-3D" return dimensionality, orientation
def __init__(self, files): if isinstance(files, tuple): white = read_geometry(files[0]) pial = read_geometry(files[1]) vertices = (pial[0] - white[0]) / 2 faces = pial[1] else: mid = read_geometry(files) vertices = mid[0] faces = mid[1] self.vertices = vertices self.faces = faces self.mesh = Trimesh(vertices=self.vertices, faces=self.faces)
def isosurface_area(vertices: np.ndarray, faces: np.ndarray) -> float: """ Calculate the area of an isosurface. Args: vertices: A (n, 3) float array of the vertices in the isosurface. faces: A (m, 3) int array of the faces of the isosurface. Returns: The area of the isosurface, in Å^-2. """ from trimesh import Trimesh mesh = Trimesh(vertices=vertices, faces=faces) return mesh.area
def create_trimesh_from_obj(obj): vertices, faces = get_vertices_and_faces(obj) tmesh = Trimesh(vertices=vertices, faces=faces) if tmesh.is_empty: raise ValueError("Mesh is empty!") if not tmesh.is_watertight: raise ValueError("Mesh is not watertight (has holes)!") if not tmesh.is_winding_consistent: raise ValueError("Mesh is not winding consistent!") if tmesh.body_count > 1: raise ValueError("Mesh consists of more than one connected component (bodies)!") return tmesh
def gen_intersecting_mesh(base_mesh, bodies): """Generates mesh of tows that are intersecting with ray offset Parameters ---------- base_mesh : Trimesh Mesh of exisiting tows already laid down bodies: set(int) Indexes of bodies intersecting with the new tow Returns ------- Trimesh Subset of base_mesh, containing only the tows from bodies """ # Create copy of mesh so to not change any face data mesh_copy = base_mesh # Body count should be equivalent to the number of tows - make sure not to merge vertices body_count = mesh_copy.body_count # Split mesh modies into individual tow meshes mesh_bodies = mesh_copy.split(only_watertight=False) if (len(bodies) is 0): return Trimesh() # Based on interesting bodies, create new mesh with only those bodies intersecting = Trimesh() for i in bodies: if intersecting.is_empty: intersecting = mesh_bodies[i - 1] else: intersecting = intersecting.__add__(mesh_bodies[i - 1]) return intersecting
def make_mesh(norms: ndarray, vertices: ndarray) -> Trimesh: # Convert to Mesh coordinates faces = [] verts = [] for idx in range(0, len(norms)): norm_x, norm_y, norm_z = norms[idx] x1, y1, z1, x2, y2, z2, x3, y3, z3 = vertices[idx] faces += [[norm_x, norm_y, norm_z]] verts += [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]] # Convert to Mesh return Trimesh(vertices=array(verts), faces=arange(int(len(faces) * 3)).reshape((-1, 3)), face_normals=array(faces))
def cut_by_plane(self, projection=SAGITTAL, ras=ORIGIN): """ :param projection: :param ras: :return: Y_array, X_array """ mesh = Trimesh(self.vertices, self.triangles) contours = intersections.mesh_plane( mesh, PLANE_NORMALS[projection], self._get_plane_origin(ras)) x_array = [0] * len(contours) y_array = [0] * len(contours) for s in range(len(contours)): x_array[s] = contours[s][:, X_Y_INDEX[projection][0]] y_array[s] = contours[s][:, X_Y_INDEX[projection][1]] return x_array, y_array
def vtk2trimesh(mesh): """ Convert ``vtkplotter.Mesh`` to ``Trimesh.Mesh`` object. """ if isSequence(mesh): tms = [] for a in mesh: tms.append(vtk2trimesh(a)) return tms from trimesh import Trimesh lut = mesh.mapper().GetLookupTable() tris = mesh.faces() carr = mesh.getCellArray('CellColors') ccols = None if carr is not None and len(carr) == len(tris): ccols = [] for i in range(len(tris)): r, g, b, a = lut.GetTableValue(carr[i]) ccols.append((r * 255, g * 255, b * 255, a * 255)) ccols = np.array(ccols, dtype=np.int16) points = mesh.points() varr = mesh.getPointArray('VertexColors') vcols = None if varr is not None and len(varr) == len(points): vcols = [] for i in range(len(points)): r, g, b, a = lut.GetTableValue(varr[i]) vcols.append((r * 255, g * 255, b * 255, a * 255)) vcols = np.array(vcols, dtype=np.int16) if len(tris) == 0: tris = None return Trimesh(vertices=points, faces=tris, face_colors=ccols, vertex_colors=vcols)
def vtk2trimesh(actor): """ Convert vtk ``Actor`` to ``Trimesh`` object. """ if isSequence(actor): tms = [] for a in actor: tms.append(vtk2trimesh(a)) return tms from trimesh import Trimesh lut = actor.mapper().GetLookupTable() tris = actor.faces() carr = actor.scalars('CellColors', datatype='cell') ccols = None if carr is not None and len(carr) == len(tris): ccols = [] for i in range(len(tris)): r, g, b, a = lut.GetTableValue(carr[i]) ccols.append((r * 255, g * 255, b * 255, a * 255)) ccols = np.array(ccols, dtype=np.int16) points = actor.coordinates() varr = actor.scalars('VertexColors', datatype='point') vcols = None if varr is not None and len(varr) == len(points): vcols = [] for i in range(len(points)): r, g, b, a = lut.GetTableValue(varr[i]) vcols.append((r * 255, g * 255, b * 255, a * 255)) vcols = np.array(vcols, dtype=np.int16) if len(tris) == 0: tris = None return Trimesh(vertices=points, faces=tris, face_colors=ccols, vertex_colors=vcols)
def box_from_bounds(mn, mx): """ in a right-hand system : 3-------7 /| /| 2-------6 | | 1-----|-5 |/ |/ 0-------4 OBB returns points thusly: 0 (self.max[0], self.max[1], self.min[2])), 1 (self.min[0], self.max[1], self.min[2])), 2 (self.min[0], self.max[1], self.max[2])), 3 (self.max), 4 (self.min), 5 self.max[0], self.min[1], self.min[2]), 6 self.max[0], self.min[1], self.max[2]), 7 self.min[0], self.min[1], self.max[2]) """ vs = [ mn, [mn[0], mx[1], mn[2]], [mn[0], mn[1], mx[2]], [mn[0], mx[1], mx[2]], [mx[0], mn[1], mn[2]], [mx[0], mx[1], mn[2]], [mx[0], mn[1], mx[2]], mx ] fcs = np.asarray([ [0, 1, 2], [1, 2, 3], [0, 1, 4], [1, 4, 5], [0, 2, 4], [2, 4, 6], [2, 3, 6], [2, 6, 7], [4, 5, 6], [5, 6, 7], [3, 5, 7], [1, 3, 5], ]) return Trimesh(vertices=vs, faces=fcs, process=True).bounding_box