def pyvista_polydata_to_polymesh(obj): """Import a mesh from ``pyvista`` or ``vtk``. Copies over the active scalars and only the active scalars. Parameters ---------- obj : pyvista compatible object Any object compatible with pyvista. Includes most ``vtk`` objects. Returns ------- PolyMesh ``ipygany.PolyMesh`` object. """ # attempt to wrap non-pyvista objects if not pv.is_pyvista_dataset(obj): # pragma: no cover mesh = pv.wrap(obj) if not pv.is_pyvista_dataset(mesh): raise TypeError(f'Object type ({type(mesh)}) cannot be converted to ' 'a pyvista dataset') else: mesh = obj # PolyMesh requires vertices and triangles, so we need to # convert the mesh to an all triangle polydata if not isinstance(obj, pv.PolyData): # unlikely case that mesh does not have extract_surface if not hasattr(mesh, 'extract_surface'): # pragma: no cover mesh = mesh.cast_to_unstructured_grid() surf = mesh.extract_surface() else: surf = mesh # convert to an all-triangular surface if surf.is_all_triangles(): trimesh = surf else: trimesh = surf.triangulate() # finally, pass the triangle vertices to PolyMesh triangle_indices = trimesh.faces.reshape(-1, 4)[:, 1:] if not triangle_indices.size: warnings.warn('Unable to convert mesh to triangular PolyMesh') # only copy active scalars data = [] if trimesh.active_scalars is not None: arr = array('f', trimesh.active_scalars) components = [ipygany.Component('X1', arr)] data = [ipygany.Data(trimesh.active_scalars_name, components)] # for speed, only convert the active scalars later return PolyMesh( vertices=trimesh.points, triangle_indices=triangle_indices, data=data )
def extract_surface_mesh(obj): """Extract a surface mesh from a pyvista or vtk dataset. Parameters ---------- obj : pyvista compatible object Any object compatible with pyvista. Includes most ``vtk`` objects. Returns ------- pyvista.PolyData Surface mesh """ # attempt to wrap non-pyvista objects if not pv.is_pyvista_dataset(obj): # pragma: no cover mesh = pv.wrap(obj) if not pv.is_pyvista_dataset(mesh): raise TypeError( f'Object type ({type(mesh)}) cannot be converted to ' 'a pyvista dataset') else: mesh = obj if not isinstance(obj, pv.PolyData): # unlikely case that mesh does not have extract_surface if not hasattr(mesh, 'extract_surface'): # pragma: no cover mesh = mesh.cast_to_unstructured_grid() return mesh.extract_surface() return mesh
def test_multi_block_copy(ant, sphere, uniform, airplane, globe): multi = multi_from_datasets(ant, sphere, uniform, airplane, globe) # Now check everything multi_copy = multi.copy() assert multi.n_blocks == 5 == multi_copy.n_blocks assert id(multi[0]) != id(multi_copy[0]) assert id(multi[-1]) != id(multi_copy[-1]) for i in range(multi_copy.n_blocks): assert pyvista.is_pyvista_dataset(multi_copy.GetBlock(i)) # Now check shallow multi_copy = multi.copy(deep=False) assert multi.n_blocks == 5 == multi_copy.n_blocks assert id(multi[0]) == id(multi_copy[0]) assert id(multi[-1]) == id(multi_copy[-1]) for i in range(multi_copy.n_blocks): assert pyvista.is_pyvista_dataset(multi_copy.GetBlock(i))
def add_mesh(self, mesh, color=None, scalars=None, clim=None, opacity=1.0, n_colors=256, cmap='Viridis (matplotlib)', **kwargs): """Add mesh to the scene.""" if not pv.is_pyvista_dataset(mesh): mesh = pv.wrap(mesh) mesh = mesh.copy() if scalars is None and color is None: scalars = mesh.active_scalars_name if scalars is not None: array = mesh[scalars].copy() mesh.clear_arrays() mesh[scalars] = array mesh.active_scalars_name = scalars elif color is not None: mesh.clear_arrays() mesh = to_geometry(mesh) self._geometries.append(mesh) self._geometry_colors.append(pv.parse_color(color)) self._geometry_opacities.append(opacity) self._cmap = cmap return
def add_textures(output, textures, elname): """Add textures to a pyvista data object""" if not is_pyvista_dataset(output): output = pyvista.wrap(output) for i, tex in enumerate(textures): # Now map the coordinates for the texture tmp = output.texture_map_to_plane(origin=tex.origin, point_u=tex.origin + tex.axis_u, point_v=tex.origin + tex.axis_v) # Grab the texture coordinates tcoord = tmp.GetPointData().GetTCoords() name = tex.name if name is None or name == '': name = '{}-texture-{}'.format(elname, i) tcoord.SetName(name) # Add these coordinates to the PointData of the output # NOTE: Let pyvista handle setting the TCoords because of how VTK cleans # up old TCoords output.GetPointData().AddArray(tcoord) # Add the vtkTexture to the output img = np.array(Image.open(tex.image)) tex.image.seek(0) # Reset the image bytes in case it is accessed again if img.shape[2] > 3: img = img[:, :, 0:3] vtexture = pyvista.numpy_to_texture(img) output.textures[name] = vtexture output._activate_texture(name) return output
def add_points(self, points, color=None): """Add XYZ points to the scene.""" if pv.is_pyvista_dataset(points): point_array = points.points else: point_array = points self._point_set_colors.append(pv.parse_color(color)) self._point_sets.append(point_array)
def test_multi_block_copy(): multi = pyvista.MultiBlock() # Add examples multi.append(ex.load_ant()) multi.append(ex.load_sphere()) multi.append(ex.load_uniform()) multi.append(ex.load_airplane()) multi.append(ex.load_globe()) # Now check everything newobj = multi.copy() assert multi.n_blocks == 5 == newobj.n_blocks assert id(multi[0]) != id(newobj[0]) assert id(multi[-1]) != id(newobj[-1]) for i in range(newobj.n_blocks): assert pyvista.is_pyvista_dataset(newobj.GetBlock(i)) # Now check shallow newobj = multi.copy(deep=False) assert multi.n_blocks == 5 == newobj.n_blocks assert id(multi[0]) == id(newobj[0]) assert id(multi[-1]) == id(newobj[-1]) for i in range(newobj.n_blocks): assert pyvista.is_pyvista_dataset(newobj.GetBlock(i)) return
def add_points(self, points, color=None, point_size=3.0): """Add points to plotter. Parameters ---------- points : np.ndarray or pyvista.DataSet n x 3 numpy array of points or pyvista dataset with points. color : string or 3 item list, optional. Color of points (if visible). Either a string, rgb list, or hex color string. For example: ``color='white'`` ``color='w'`` ``color=[1, 1, 1]`` ``color='#FFFFFF'`` point_size : float, optional Point size of any nodes in the dataset plotted. Also applicable when style='points'. Default ``3.0`` Examples -------- Add 10 random points to the plotter >>> add_points(np.random.random((10, 3)), 'r', 10) # doctest:+SKIP """ if pv.is_pyvista_dataset(points): point_array = points.points else: point_array = points # style : str, optional # How to represent the point set. One of ``'hidden'``, # ``'points'``, or ``'spheres'``. # if style not in ['hidden', 'points', 'spheres']: # raise ValueError("``style`` must be either 'hidden', 'points', or" # "'spheres'") if not isinstance(point_size, (int, float)): raise TypeError('``point_size`` parameter must be a float') self._point_set_sizes.append(point_size) self._point_set_colors.append(pv.parse_color(color)) self._point_sets.append(point_array)
def segment_poly_cells(mesh): """Segment lines from a mesh into line segments.""" if not pv.is_pyvista_dataset(mesh): # pragma: no cover mesh = pv.wrap(mesh) polylines = [] i, offset = 0, 0 cc = mesh.lines # fetch up front while i < mesh.n_cells: nn = cc[offset] polylines.append(cc[offset + 1:offset + 1 + nn]) offset += nn + 1 i += 1 lines = [] for poly in polylines: lines.append(np.column_stack((poly[:-1], poly[1:]))) return np.vstack(lines)
def add_points(self, points, color=None): """Add points to plotting object. Parameters ---------- points : np.ndarray or pyvista.Common n x 3 numpy array of points or pyvista dataset with points. color : string or 3 item list, optional. Color of points (if visible). Either a string, rgb list, or hex color string. For example: color='white' color='w' color=[1, 1, 1] color='#FFFFFF' """ if pv.is_pyvista_dataset(points): point_array = points.points else: point_array = points self._point_set_colors.append(pv.parse_color(color)) self._point_sets.append(point_array)
def voxelize(mesh, density=None, check_surface=True): """Voxelize mesh to UnstructuredGrid. Parameters ---------- density : float The uniform size of the voxels. Defaults to 1/100th of the mesh length. check_surface : bool Specify whether to check the surface for closure. If on, then the algorithm first checks to see if the surface is closed and manifold. If the surface is not closed and manifold, a runtime error is raised. """ if not pyvista.is_pyvista_dataset(mesh): mesh = pyvista.wrap(mesh) if density is None: density = mesh.length / 100 x_min, x_max, y_min, y_max, z_min, z_max = mesh.bounds x = np.arange(x_min, x_max, density) y = np.arange(y_min, y_max, density) z = np.arange(z_min, z_max, density) x, y, z = np.meshgrid(x, y, z) # Create unstructured grid from the structured grid grid = pyvista.StructuredGrid(x, y, z) ugrid = pyvista.UnstructuredGrid(grid) # get part of the mesh within the mesh's bounding surface. selection = ugrid.select_enclosed_points(mesh.extract_surface(), tolerance=0.0, check_surface=check_surface) mask = selection.point_arrays['SelectedPoints'].view(np.bool) # extract cells from point indices return ugrid.extract_points(mask)
def add_mesh(self, mesh, color=None, scalars=None, opacity=1.0, smooth_shading=False): """Add a PyVista/VTK mesh or dataset. Adds any PyVista/VTK mesh that itkwidgets can wrap to the scene. Parameters ---------- mesh : pyvista.DataSet or pyvista.MultiBlock Any PyVista or VTK mesh is supported. Also, any dataset that :func:`pyvista.wrap` can handle including NumPy arrays of XYZ points. color : string or 3 item list, optional, defaults to white Use to make the entire mesh have a single solid color. Either a string, RGB list, or hex color string. For example: ``color='white'``, ``color='w'``, ``color=[1, 1, 1]``, or ``color='#FFFFFF'``. Color will be overridden if scalars are specified. scalars : str or numpy.ndarray, optional Scalars used to "color" the mesh. Accepts a string name of an array that is present on the mesh or an array equal to the number of cells or the number of points in the mesh. Array should be sized as a single vector. If both ``color`` and ``scalars`` are ``None``, then the active scalars are used. opacity : float, optional Opacity of the mesh. If a single float value is given, it will be the global opacity of the mesh and uniformly applied everywhere - should be between 0 and 1. Default 1.0 smooth_shading : bool, optional Smooth mesh surface mesh by taking into account surface normals. Surface will appear smoother while sharp edges will still look sharp. Default False. """ if not pv.is_pyvista_dataset(mesh): mesh = pv.wrap(mesh) # smooth shading requires point normals to be freshly computed if smooth_shading: # extract surface if mesh is exterior if not isinstance(mesh, pv.PolyData): grid = mesh mesh = grid.extract_surface() ind = mesh.point_arrays['vtkOriginalPointIds'] # remap scalars if isinstance(scalars, np.ndarray): scalars = scalars[ind] mesh.compute_normals(cell_normals=False, inplace=True) elif 'Normals' in mesh.point_arrays: # if 'normals' in mesh.point_arrays: mesh.point_arrays.pop('Normals') # make the scalars active if isinstance(scalars, str): if scalars in mesh.point_arrays or scalars in mesh.cell_arrays: array = mesh[scalars].copy() else: raise ValueError(f'Scalars ({scalars}) not in mesh') mesh[scalars] = array mesh.active_scalars_name = scalars elif isinstance(scalars, np.ndarray): array = scalars scalar_name = '_scalars' mesh[scalar_name] = array mesh.active_scalars_name = scalar_name elif color is not None: mesh.active_scalars_name = None # itkwidgets does not support VTK_ID_TYPE if 'vtkOriginalPointIds' in mesh.point_arrays: mesh.point_arrays.pop('vtkOriginalPointIds') if 'vtkOriginalCellIds' in mesh.cell_arrays: mesh.cell_arrays.pop('vtkOriginalCellIds') from itkwidgets._transform_types import to_geometry mesh = to_geometry(mesh) self._geometries.append(mesh) self._geometry_colors.append(pv.parse_color(color)) self._geometry_opacities.append(opacity)
def generate_on_mesh(f_cls, mesh, points="centroids", direction="all", name="field", **kwargs): """Generate a field on a given meshio, ogs5py or pyvista mesh. Parameters ---------- f_cls : :any:`Field` The field class in use. mesh : meshio.Mesh or ogs5py.MSH or PyVista mesh The given meshio, ogs5py, or PyVista mesh points : :class:`str`, optional The points to evaluate the field at. Either the "centroids" of the mesh cells (calculated as mean of the cell vertices) or the "points" of the given mesh. Default: "centroids" direction : :class:`str` or :class:`list`, optional Here you can state which direction should be choosen for lower dimension. For example, if you got a 2D mesh in xz direction, you have to pass "xz". By default, all directions are used. One can also pass a list of indices. Default: "all" name : :class:`str` or :class:`list` of :class:`str`, optional Name(s) to store the field(s) in the given mesh as point_data or cell_data. If to few names are given, digits will be appended. Default: "field" **kwargs Keyword arguments forwareded to `Field.__call__`. Notes ----- This will store the field in the given mesh under the given name, if a meshio or PyVista mesh was given. See: https://github.com/nschloe/meshio See: https://github.com/GeoStat-Framework/ogs5py See: https://github.com/pyvista/pyvista """ has_pyvista = False has_ogs5py = False try: import pyvista as pv has_pyvista = True except ImportError: pass try: import ogs5py as ogs has_ogs5py = True except ImportError: pass if isinstance(direction, str) and direction == "all": select = list(range(f_cls.dim)) elif isinstance(direction, str): select = _get_select(direction)[:f_cls.dim] else: select = direction[:f_cls.dim] if len(select) < f_cls.dim: raise ValueError( f"Field.mesh: need at least {f_cls.dim} direction(s), " f"got '{direction}'") # convert pyvista mesh if has_pyvista and pv.is_pyvista_dataset(mesh): if points == "centroids": pnts = mesh.cell_centers().points.T[select] else: pnts = mesh.points.T[select] out = f_cls.unstructured(pos=pnts, **kwargs) # Deal with the output fields = [out] if isinstance(out, np.ndarray) else out if f_cls.value_type == "vector": fields = [f.T for f in fields] for f_name, field in zip(_names(name, len(fields)), fields): mesh[f_name] = field # convert ogs5py mesh elif has_ogs5py and isinstance(mesh, ogs.MSH): if points == "centroids": pnts = mesh.centroids_flat.T[select] else: pnts = mesh.NODES.T[select] out = f_cls.unstructured(pos=pnts, **kwargs) # convert meshio mesh elif isinstance(mesh, meshio.Mesh): if points == "centroids": # define unique order of cells offset = [] length = [] mesh_dim = mesh.points.shape[1] if mesh_dim < f_cls.dim: raise ValueError("Field.mesh: mesh dimension too low!") pnts = np.empty((0, mesh_dim), dtype=np.double) for cell in mesh.cells: cell_points = cell[1] if MESHIO_VERSION < [5, 1] else cell.data pnt = np.mean(mesh.points[cell_points], axis=1) offset.append(pnts.shape[0]) length.append(pnt.shape[0]) pnts = np.vstack((pnts, pnt)) # generate pos for __call__ pnts = pnts.T[select] out = f_cls.unstructured(pos=pnts, **kwargs) fields = [out] if isinstance(out, np.ndarray) else out if f_cls.value_type == "vector": fields = [f.T for f in fields] f_lists = [] for field in fields: f_list = [] for off, leng in zip(offset, length): f_list.append(field[off:off + leng]) f_lists.append(f_list) for f_name, f_list in zip(_names(name, len(f_lists)), f_lists): mesh.cell_data[f_name] = f_list else: out = f_cls.unstructured(pos=mesh.points.T[select], **kwargs) fields = [out] if isinstance(out, np.ndarray) else out if f_cls.value_type == "vector": fields = [f.T for f in fields] for f_name, field in zip(_names(name, len(fields)), fields): mesh.point_data[f_name] = field else: raise ValueError("Field.mesh: Unknown mesh format!") return out
def voxelize(mesh, density=None, check_surface=True): """Voxelize mesh to UnstructuredGrid. Parameters ---------- density : float or list The uniform size of the voxels when single float passed. A list of densities along x,y,z directions. Defaults to 1/100th of the mesh length. check_surface : bool Specify whether to check the surface for closure. If on, then the algorithm first checks to see if the surface is closed and manifold. If the surface is not closed and manifold, a runtime error is raised. Returns ------- vox : pyvista.core.pointset.UnstructuredGrid voxelized unstructured grid for original mesh Examples -------- This example creates an equal density voxelized mesh. >>> import pyvista as pv >>> import pyvista.examples as ex >>> mesh = pv.PolyData(ex.load_uniform().points) >>> vox = pv.voxelize(mesh, density=0.5) This example creates a voxelized mesh using unequal density dimensions >>> import pyvista as pv >>> import pyvista.examples as ex >>> mesh = pv.PolyData(ex.load_uniform().points) >>> vox = pv.voxelize(mesh, density=[0.5, 0.9, 1.4]) """ if not pyvista.is_pyvista_dataset(mesh): mesh = pyvista.wrap(mesh) if density is None: density = mesh.length / 100 if isinstance(density, (int, float)): density_x, density_y, density_z = [density] * 3 if isinstance(density, (list, set, tuple)): density_x, density_y, density_z = density x_min, x_max, y_min, y_max, z_min, z_max = mesh.bounds x = np.arange(x_min, x_max, density_x) y = np.arange(y_min, y_max, density_y) z = np.arange(z_min, z_max, density_z) x, y, z = np.meshgrid(x, y, z) # Create unstructured grid from the structured grid grid = pyvista.StructuredGrid(x, y, z) ugrid = pyvista.UnstructuredGrid(grid) # get part of the mesh within the mesh's bounding surface. selection = ugrid.select_enclosed_points(mesh.extract_surface(), tolerance=0.0, check_surface=check_surface) mask = selection.point_arrays['SelectedPoints'].view(np.bool_) # extract cells from point indices vox = ugrid.extract_points(mask) return vox