示例#1
0
文件: core.py 项目: bjlittle/geovista
def add_texture_coords(
    mesh: pv.PolyData,
    meridian: Optional[float] = None,
    antimeridian: Optional[bool] = False,
) -> pv.PolyData:
    """
    TODO

    Parameters
    ----------
    mesh
    meridian
    antimeridian
    inplace

    Returns
    -------

    Notes
    -----
    .. versionadded:: 0.1.0

    """
    if meridian is None:
        meridian = DEFAULT_MERIDIAN

    if antimeridian:
        meridian += 180

    meridian = wrap(meridian)[0]

    if GV_REMESH_POINT_IDS not in mesh.point_data:
        mesh = cut_along_meridian(mesh, meridian=meridian)
    else:
        mesh = mesh.copy(deep=True)

    # convert from cartesian xyz to spherical lat/lons
    ll = to_xy0(mesh, closed_interval=True)
    lons, lats = ll[:, 0], ll[:, 1]
    # convert to normalised UV space
    u = (lons + 180) / 360
    v = (lats + 90) / 180
    t = np.vstack([u, v]).T
    mesh.active_t_coords = t
    logger.debug(
        "u.min()=%s, u.max()=%s, v.min()=%s, v.max()=%s",
        u.min(),
        u.max(),
        v.min(),
        v.max(),
    )

    return mesh
示例#2
0
def plot_mesh_pyvista(
    plotter: pv.Plotter,
    polydata: pv.PolyData,
    # vertices: np.ndarray,
    # triangles: np.ndarray,
    rotations: List[Tuple[int, int, int]] = [(0, 0, 0)],
    vertexcolors: List[int] = [],
    vertexscalar: str = '',
    cmap: str = 'YlGnBu',
    title: str = '',
    scalar_bar_idx: int = 0,
    **mesh_kwargs,
):
    shape = plotter.shape
    if len(shape) == 1:
        assert shape[0] > 0
        assert shape[0] == len(rotations)
        subp_idx = [(x, ) for x in range(shape[0])]
    else:
        assert shape[0] > 0 and shape[1] > 0
        assert shape[0] * shape[1] == len(rotations)
        subp_idx = product(range(shape[0]), range(shape[1]))

    if vertexscalar and vertexcolors is not None:
        polydata[vertexscalar] = vertexcolors

    cmap = plt.cm.get_cmap(cmap)

    mesh_kwargs = {
        'cmap': cmap,
        'flip_scalars': True,
        'show_scalar_bar': False,
        **mesh_kwargs,
    }
    if vertexscalar and vertexcolors is not None:
        mesh_kwargs['scalars'] = vertexscalar

    for i, (subp, rots) in enumerate(zip(subp_idx, rotations)):
        x, y, z = rots
        plotter.subplot(*subp)
        poly_copy = polydata.copy()
        poly_copy.rotate_x(x)
        poly_copy.rotate_y(y)
        poly_copy.rotate_z(z)
        plotter.add_mesh(
            poly_copy,
            **mesh_kwargs,
        )
        if i == 0:
            plotter.add_title(title, font_size=5)
        if i == scalar_bar_idx:
            plotter.add_scalar_bar(label_font_size=10, position_x=0.85)
示例#3
0
文件: core.py 项目: bjlittle/geovista
def cut_along_meridian(
    mesh: pv.PolyData,
    meridian: Optional[float] = None,
    antimeridian: Optional[bool] = False,
) -> pv.PolyData:
    """
    TODO

    Parameters
    ----------
    mesh
    meridian
    antimeridian

    Returns
    -------

    Notes
    -----
    .. versionadded:: 0.1.0

    """
    if not isinstance(mesh, pv.PolyData):
        emsg = f"Require a 'pyvista.PolyData' mesh, got '{mesh.__class__.__name__}'."
        raise TypeError(emsg)

    if meridian is None:
        meridian = DEFAULT_MERIDIAN

    if antimeridian:
        meridian += 180

    meridian = wrap(meridian)[0]
    logger.debug(
        "meridian=%s, antimeridian=%s",
        meridian,
        antimeridian,
    )

    slicer = MeridianSlice(mesh, meridian)
    mesh_whole = slicer.extract(split_cells=False)
    mesh_split = slicer.extract(split_cells=True)
    info = mesh.active_scalars_info
    result: pv.PolyData = mesh.copy(deep=True)

    meshes = []
    remeshed_ids = np.array([])

    if mesh_whole.n_cells:
        ll = to_xy0(mesh_whole)
        meridian_mask = np.isclose(ll[:, 0], meridian)
        join_points = np.empty(mesh_whole.n_points, dtype=int)
        join_points.fill(REMESH_JOIN)
        mesh_whole[GV_REMESH_POINT_IDS] = join_points
        mesh_whole[GV_REMESH_POINT_IDS][meridian_mask] = REMESH_SEAM
        meshes.append(mesh_whole)
        remeshed_ids = mesh_whole[GV_CELL_IDS]
        result[GV_REMESH_POINT_IDS] = result[GV_POINT_IDS].copy()

    if mesh_split.n_cells:
        remeshed, remeshed_west, remeshed_east = remesh(mesh_split, meridian)
        meshes.extend([remeshed_west, remeshed_east])
        remeshed_ids = np.unique(np.hstack([remeshed_ids, remeshed[GV_CELL_IDS]]))
        if GV_REMESH_POINT_IDS not in result.point_data:
            result.point_data[GV_REMESH_POINT_IDS] = result[GV_POINT_IDS].copy()

    if meshes:
        result.remove_cells(remeshed_ids, inplace=True)
        result.set_active_scalars(info.name, preference=info.association.name.lower())
        result = combine(result, *meshes)

    return result
示例#4
0
def remesh(
    mesh: pv.PolyData,
    meridian: float,
    boundary: Optional[bool] = False,
    check: Optional[bool] = False,
    warnings: Optional[bool] = False,
) -> Remesh:
    """
    TODO

    Notes
    -----
    .. versionadded :: 0.1.0

    """
    if not warnings:
        # https://public.kitware.com/pipermail/vtkusers/2004-February/022390.html
        vtkObject.GlobalWarningDisplayOff()

    if mesh.n_cells == 0:
        emsg = "Cannot remesh an empty mesh"
        raise ValueError(emsg)

    meridian = wrap(meridian)[0]
    radius = calculate_radius(mesh)
    logger.debug(
        "meridian=%s, radius=%s",
        meridian,
        radius,
    )

    poly0: pv.PolyData = mesh.copy(deep=True)

    if GV_CELL_IDS not in poly0.cell_data:
        poly0.cell_data[GV_CELL_IDS] = np.arange(poly0.n_cells)

    if GV_POINT_IDS not in poly0.point_data:
        poly0.point_data[GV_POINT_IDS] = np.arange(poly0.n_points)

    if not triangulated(poly0):
        start = datetime.now()
        poly0.triangulate(inplace=True)
        end = datetime.now()
        logger.debug(
            "mesh: triangulated [%s secs]",
            (end - start).total_seconds(),
        )

    poly1 = pv.Plane(
        center=(radius / 2, 0, 0),
        i_resolution=1,
        j_resolution=1,
        i_size=radius,
        j_size=radius * 2,
        direction=(0, 1, 0),
    )
    poly1.rotate_z(meridian, inplace=True)
    poly1.triangulate(inplace=True)

    # https://vtk.org/doc/nightly/html/classvtkIntersectionPolyDataFilter.html
    alg = _vtk.vtkIntersectionPolyDataFilter()
    alg.SetInputDataObject(0, poly0)
    alg.SetInputDataObject(1, poly1)
    # BoundaryPoints (points) mask array
    alg.SetComputeIntersectionPointArray(True)
    # BadTriangle and FreeEdge (cells) mask arrays
    alg.SetCheckMesh(check)
    alg.SetSplitFirstOutput(True)
    alg.SetSplitSecondOutput(False)
    start = datetime.now()
    alg.Update()
    end = datetime.now()
    logger.debug(
        "remeshed: lines=%s, points=%s [%s secs]",
        alg.GetNumberOfIntersectionLines(),
        alg.GetNumberOfIntersectionPoints(),
        (end - start).total_seconds(),
    )

    remeshed: pv.PolyData = _get_output(alg, oport=1)

    if not warnings:
        vtkObject.GlobalWarningDisplayOn()

    if remeshed.n_cells == 0:
        # no remeshing has been performed as the meridian does not intersect the mesh
        remeshed_west, remeshed_east = pv.PolyData(), pv.PolyData()
        logger.debug(
            "no remesh performed using meridian=%s",
            meridian,
        )
    else:
        # split the triangulated remesh into its two halves, west and east of the meridian
        centers = remeshed.cell_centers()
        lons = to_xy0(centers)[:, 0]
        delta = lons - meridian
        lower_mask = (delta < 0) & (delta > -180)
        upper_mask = delta > 180
        west_mask = lower_mask | upper_mask
        east_mask = ~west_mask
        logger.debug(
            "split: lower=%s, upper=%s, west=%s, east=%s, total=%s",
            lower_mask.sum(),
            upper_mask.sum(),
            west_mask.sum(),
            east_mask.sum(),
            remeshed.n_cells,
        )

        # the vtkIntersectionPolyDataFilter is configured to *always* generate the boundary mask point array
        # as we require it internally, regardless of whether the caller wants it or not afterwards
        boundary_mask = np.asarray(remeshed.point_data[VTK_BOUNDARY_MASK],
                                   dtype=bool)
        if not boundary:
            del remeshed.point_data[VTK_BOUNDARY_MASK]

        remeshed.point_data[GV_REMESH_POINT_IDS] = remeshed[GV_POINT_IDS].copy(
        )

        remeshed[GV_REMESH_POINT_IDS][boundary_mask] = REMESH_SEAM
        remeshed_west = cast_UnstructuredGrid_to_PolyData(
            remeshed.extract_cells(west_mask))
        join_mask = np.where(
            remeshed_west[GV_REMESH_POINT_IDS] != REMESH_SEAM)[0]
        remeshed_west[GV_REMESH_POINT_IDS][join_mask] = REMESH_JOIN

        remeshed[GV_REMESH_POINT_IDS][boundary_mask] = REMESH_SEAM_EAST
        remeshed_east = cast_UnstructuredGrid_to_PolyData(
            remeshed.extract_cells(east_mask))
        join_mask = np.where(
            remeshed_east[GV_REMESH_POINT_IDS] != REMESH_SEAM_EAST)[0]
        remeshed_east[GV_REMESH_POINT_IDS][join_mask] = REMESH_JOIN

        del remeshed.point_data[GV_REMESH_POINT_IDS]
        sanitize_data(remeshed, remeshed_west, remeshed_east)

    return remeshed, remeshed_west, remeshed_east