def _from_arrays(self, vertices, faces, deep=True, verts=False): """Set polygons and points from numpy arrays. Parameters ---------- vertices : np.ndarray of dtype=np.float32 or np.float64 Vertex array. 3D points. faces : np.ndarray of dtype=np.int64 Face index array. Faces can contain any number of points. verts : bool, optional Faces array is a vertex array. Examples -------- >>> import numpy as np >>> import pyvista >>> vertices = np.array([[0, 0, 0], ... [1, 0, 0], ... [1, 1, 0], ... [0, 1, 0], ... [0.5, 0.5, 1]]) >>> faces = np.hstack([[4, 0, 1, 2, 3], ... [3, 0, 1, 4], ... [3, 1, 2, 4]]) # one square and two triangles >>> surf = pyvista.PolyData(vertices, faces) """ self.SetPoints(pyvista.vtk_points(vertices, deep=deep)) if verts: self.SetVerts(CellArray(faces)) else: self.SetPolys(CellArray(faces))
def _from_arrays(self, offset, cells, cell_type, points, deep=True): """Create VTK unstructured grid from numpy arrays. Parameters ---------- offset : np.ndarray dtype=np.int64 Array indicating the start location of each cell in the cells array. Set to ``None`` when using VTK 9+. cells : np.ndarray dtype=np.int64 Array of cells. Each cell contains the number of points in the cell and the node numbers of the cell. cell_type : np.uint8 Cell types of each cell. Each cell type numbers can be found from vtk documentation. See example below. points : np.ndarray Numpy array containing point locations. Examples -------- >>> import numpy >>> import vtk >>> import pyvista >>> offset = np.array([0, 9]) >>> cells = np.array([8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15]) >>> cell_type = np.array([vtk.VTK_HEXAHEDRON, vtk.VTK_HEXAHEDRON], np.int8) >>> cell1 = np.array([[0, 0, 0], ... [1, 0, 0], ... [1, 1, 0], ... [0, 1, 0], ... [0, 0, 1], ... [1, 0, 1], ... [1, 1, 1], ... [0, 1, 1]]) >>> cell2 = np.array([[0, 0, 2], ... [1, 0, 2], ... [1, 1, 2], ... [0, 1, 2], ... [0, 0, 3], ... [1, 0, 3], ... [1, 1, 3], ... [0, 1, 3]]) >>> points = np.vstack((cell1, cell2)) >>> grid = pyvista.UnstructuredGrid(offset, cells, cell_type, points) """ # Convert to vtk arrays vtkcells = CellArray(cells, cell_type.size, deep) if cell_type.dtype != np.uint8: cell_type = cell_type.astype(np.uint8) cell_type = numpy_to_vtk(cell_type, deep=deep) # Convert points to vtkPoints object points = pyvista.vtk_points(points, deep=deep) self.SetPoints(points) # vtk9 does not require an offset array if VTK9: if offset is not None: warnings.warn('VTK 9 no longer accepts an offset array', stacklevel=3) self.SetCells(cell_type, vtkcells) else: self.SetCells(cell_type, numpy_to_idarr(offset), vtkcells)
def faces(self, faces): """Set the face cells.""" self.SetPolys(CellArray(faces))
def verts(self, verts): """Set the vertex cells.""" self.SetVerts(CellArray(verts))
def lines(self, lines): """Set the lines of the polydata.""" self.SetLines(CellArray(lines))
class PolyData(_vtk.vtkPolyData, PointSet, PolyDataFilters): """Extend the functionality of a vtk.vtkPolyData object. Can be initialized in several ways: - Create an empty mesh - Initialize from a vtk.vtkPolyData - Using vertices - Using vertices and faces - From a file Parameters ---------- var_inp : vtk.vtkPolyData, str, sequence, optional Flexible input type. Can be a ``vtk.vtkPolyData``, in which case this PolyData object will be copied if ``deep=True`` and will be a shallow copy if ``deep=False``. Also accepts a path, which may be local path as in ``'my_mesh.stl'`` or global path like ``'/tmp/my_mesh.ply'`` or ``'C:/Users/user/my_mesh.ply'``. Otherwise, this must be a points array or list containing one or more points. Each point must have 3 dimensions. faces : sequence, optional Face connectivity array. Faces must contain padding indicating the number of points in the face. For example, the two faces ``[10, 11, 12]`` and ``[20, 21, 22, 23]`` will be represented as ``[3, 10, 11, 12, 4, 20, 21, 22, 23]``. This lets you have an arbitrary number of points per face. When not including the face connectivity array, each point will be assigned to a single vertex. This is used for point clouds that have no connectivity. n_faces : int, optional Number of faces in the ``faces`` connectivity array. While optional, setting this speeds up the creation of the ``PolyData``. lines : sequence, optional The line connectivity array. Like ``faces``, this array requires padding indicating the number of points in a line segment. For example, the two line segments ``[0, 1]`` and ``[1, 2, 3, 4]`` will be represented as ``[2, 0, 1, 4, 1, 2, 3, 4]``. n_lines : int, optional Number of lines in the ``lines`` connectivity array. While optional, setting this speeds up the creation of the ``PolyData``. deep : bool, optional Whether to copy the inputs, or to create a mesh from them without copying them. Setting ``deep=True`` ensures that the original arrays can be modified outside the mesh without affecting the mesh. Default is ``False``. Examples -------- >>> import vtk >>> import numpy as np >>> from pyvista import examples >>> import pyvista Create an empty mesh >>> mesh = pyvista.PolyData() Initialize from a ``vtk.vtkPolyData`` object >>> vtkobj = vtk.vtkPolyData() >>> mesh = pyvista.PolyData(vtkobj) Initialize from just vertices >>> vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 0.5, 0], [0, 0.5, 0]]) >>> mesh = pyvista.PolyData(vertices) Initialize from vertices and faces >>> faces = np.hstack([[3, 0, 1, 2], [3, 0, 3, 2]]) >>> mesh = pyvista.PolyData(vertices, faces) Initialize from vertices and lines >>> lines = np.hstack([[2, 0, 1], [2, 1, 2]]) >>> mesh = pyvista.PolyData(vertices, lines=lines) Initialize from a filename >>> mesh = pyvista.PolyData(examples.antfile) """ _READERS = { '.ply': _vtk.vtkPLYReader, '.stl': _vtk.vtkSTLReader, '.vtk': _vtk.vtkPolyDataReader, '.vtp': _vtk.vtkXMLPolyDataReader, '.obj': _vtk.vtkOBJReader } _WRITERS = { '.ply': _vtk.vtkPLYWriter, '.vtp': _vtk.vtkXMLPolyDataWriter, '.stl': _vtk.vtkSTLWriter, '.vtk': _vtk.vtkPolyDataWriter } def __init__(self, var_inp=None, faces=None, n_faces=None, lines=None, n_lines=None, deep=False) -> None: """Initialize the polydata.""" local_parms = locals() super().__init__() # allow empty input if var_inp is None: return # filename opt_kwarg = ['faces', 'n_faces', 'lines', 'n_lines'] if isinstance(var_inp, (str, pathlib.Path)): for kwarg in opt_kwarg: if local_parms[kwarg]: raise ValueError( 'No other arguments should be set when first ' 'parameter is a string') self._from_file(var_inp) # is filename # When loading files with just point arrays, create and # set the polydata vertices if self.n_points > 0 and self.n_cells == 0: verts = self._make_vertex_cells(self.n_points) self.verts = CellArray(verts, self.n_points, deep) return # PolyData-like if isinstance(var_inp, _vtk.vtkPolyData): for kwarg in opt_kwarg: if local_parms[kwarg]: raise ValueError( 'No other arguments should be set when first ' 'parameter is a PolyData') if deep: self.deep_copy(var_inp) else: self.shallow_copy(var_inp) return # First parameter is points if isinstance(var_inp, (np.ndarray, list)): self.SetPoints(pyvista.vtk_points(var_inp, deep=deep)) else: msg = f""" Invalid Input type: Expected first argument to be either a: - vtk.PolyData - pyvista.PolyData - numeric numpy.ndarray (1 or 2 dimensions) - List (flat or nested with 3 points per vertex) Instead got: {type(var_inp)}""" raise TypeError(dedent(msg.strip('\n'))) # At this point, points have been setup, add faces and/or lines if faces is None and lines is None: # one cell per point (point cloud case) verts = self._make_vertex_cells(self.n_points) self.verts = CellArray(verts, self.n_points, deep) elif faces is not None: # here we use CellArray since we must specify deep and n_faces self.faces = CellArray(faces, n_faces, deep) # can always set lines if lines is not None: # here we use CellArray since we must specify deep and n_lines self.lines = CellArray(lines, n_lines, deep) def __repr__(self): """Return the standard representation.""" return DataSet.__repr__(self) def __str__(self): """Return the standard str representation.""" return DataSet.__str__(self) @staticmethod def _make_vertex_cells(npoints): cells = np.empty((npoints, 2), dtype=pyvista.ID_TYPE) cells[:, 0] = 1 cells[:, 1] = np.arange(npoints, dtype=pyvista.ID_TYPE) return cells @property def verts(self): """Get the vertex cells.""" return _vtk.vtk_to_numpy(self.GetVerts().GetData()) @verts.setter def verts(self, verts): """Set the vertex cells.""" if isinstance(verts, CellArray): self.SetVerts(verts) else: self.SetVerts(CellArray(verts)) @property def lines(self): """Return a pointer to the lines as a numpy object.""" return _vtk.vtk_to_numpy(self.GetLines().GetData()).ravel() @lines.setter def lines(self, lines): """Set the lines of the polydata.""" if isinstance(lines, CellArray): self.SetLines(lines) else: self.SetLines(CellArray(lines)) @property def faces(self): """Return a pointer to the points as a numpy object.""" return _vtk.vtk_to_numpy(self.GetPolys().GetData()) @faces.setter def faces(self, faces): """Set the face cells.""" if isinstance(faces, CellArray): self.SetPolys(faces) else: self.SetPolys(CellArray(faces)) def is_all_triangles(self): """Return True if all the faces of the polydata are triangles.""" # Need to make sure there are only face cells and no lines/verts if not len(self.faces) or len(self.lines) > 0 or len(self.verts) > 0: return False # All we have are faces, check if all faces are indeed triangles return self.faces.size % 4 == 0 and (self.faces.reshape(-1, 4)[:, 0] == 3).all() def __sub__(self, cutting_mesh): """Subtract two meshes.""" return self.boolean_cut(cutting_mesh) @property def n_faces(self): """Return the number of cells. Alias for ``n_cells``. """ return self.n_cells @property def number_of_faces(self): # pragma: no cover """Return the number of cells.""" raise DeprecationError('``number_of_faces`` has been depreciated. ' 'Please use ``n_faces``') def save(self, filename, binary=True): """Write a surface mesh to disk. Written file may be an ASCII or binary ply, stl, or vtk mesh file. If ply or stl format is chosen, the face normals are computed in place to ensure the mesh is properly saved. Parameters ---------- filename : str Filename of mesh to be written. File type is inferred from the extension of the filename unless overridden with ftype. Can be one of the following types (.ply, .stl, .vtk) binary : bool, optional Writes the file as binary when True and ASCII when False. Notes ----- Binary files write much faster than ASCII and have a smaller file size. """ filename = os.path.abspath(os.path.expanduser(str(filename))) ftype = get_ext(filename) # Recompute normals prior to save. Corrects a bug were some # triangular meshes are not saved correctly if ftype in ['stl', 'ply']: self.compute_normals(inplace=True) super().save(filename, binary) @property def area(self): """Return the mesh surface area. Returns ------- area : float Total area of the mesh. """ areas = self.compute_cell_sizes( length=False, area=True, volume=False, )["Area"] return np.sum(areas) @property def volume(self): """Return the mesh volume. This will throw a VTK error/warning if not a closed surface Returns ------- volume : float Total volume of the mesh. """ mprop = _vtk.vtkMassProperties() mprop.SetInputData(self.triangulate()) return mprop.GetVolume() @property def point_normals(self): """Return the point normals.""" mesh = self.compute_normals(cell_normals=False, inplace=False) return mesh.point_arrays['Normals'] @property def cell_normals(self): """Return the cell normals.""" mesh = self.compute_normals(point_normals=False, inplace=False) return mesh.cell_arrays['Normals'] @property def face_normals(self): """Return the cell normals.""" return self.cell_normals @property def obbTree(self): """Return the obbTree of the polydata. An obbTree is an object to generate oriented bounding box (OBB) trees. An oriented bounding box is a bounding box that does not necessarily line up along coordinate axes. The OBB tree is a hierarchical tree structure of such boxes, where deeper levels of OBB confine smaller regions of space. """ if not hasattr(self, '_obbTree'): self._obbTree = _vtk.vtkOBBTree() self._obbTree.SetDataSet(self) self._obbTree.BuildLocator() return self._obbTree @property def n_open_edges(self): """Return the number of open edges on this mesh.""" alg = _vtk.vtkFeatureEdges() alg.FeatureEdgesOff() alg.BoundaryEdgesOn() alg.NonManifoldEdgesOn() alg.SetInputDataObject(self) alg.Update() return alg.GetOutput().GetNumberOfCells() def __del__(self): """Delete the object.""" if hasattr(self, '_obbTree'): del self._obbTree
def faces(self, faces): """Set the face cells.""" if isinstance(faces, CellArray): self.SetPolys(faces) else: self.SetPolys(CellArray(faces))
def lines(self, lines): """Set the lines of the polydata.""" if isinstance(lines, CellArray): self.SetLines(lines) else: self.SetLines(CellArray(lines))
def verts(self, verts): """Set the vertex cells.""" if isinstance(verts, CellArray): self.SetVerts(verts) else: self.SetVerts(CellArray(verts))
def __init__(self, var_inp=None, faces=None, n_faces=None, lines=None, n_lines=None, deep=False) -> None: """Initialize the polydata.""" local_parms = locals() super().__init__() # allow empty input if var_inp is None: return # filename opt_kwarg = ['faces', 'n_faces', 'lines', 'n_lines'] if isinstance(var_inp, (str, pathlib.Path)): for kwarg in opt_kwarg: if local_parms[kwarg]: raise ValueError( 'No other arguments should be set when first ' 'parameter is a string') self._from_file(var_inp) # is filename # When loading files with just point arrays, create and # set the polydata vertices if self.n_points > 0 and self.n_cells == 0: verts = self._make_vertex_cells(self.n_points) self.verts = CellArray(verts, self.n_points, deep) return # PolyData-like if isinstance(var_inp, _vtk.vtkPolyData): for kwarg in opt_kwarg: if local_parms[kwarg]: raise ValueError( 'No other arguments should be set when first ' 'parameter is a PolyData') if deep: self.deep_copy(var_inp) else: self.shallow_copy(var_inp) return # First parameter is points if isinstance(var_inp, (np.ndarray, list)): self.SetPoints(pyvista.vtk_points(var_inp, deep=deep)) else: msg = f""" Invalid Input type: Expected first argument to be either a: - vtk.PolyData - pyvista.PolyData - numeric numpy.ndarray (1 or 2 dimensions) - List (flat or nested with 3 points per vertex) Instead got: {type(var_inp)}""" raise TypeError(dedent(msg.strip('\n'))) # At this point, points have been setup, add faces and/or lines if faces is None and lines is None: # one cell per point (point cloud case) verts = self._make_vertex_cells(self.n_points) self.verts = CellArray(verts, self.n_points, deep) elif faces is not None: # here we use CellArray since we must specify deep and n_faces self.faces = CellArray(faces, n_faces, deep) # can always set lines if lines is not None: # here we use CellArray since we must specify deep and n_lines self.lines = CellArray(lines, n_lines, deep)