Files
geMoldInsight/venv/lib/python3.11/site-packages/pyvista/plotting/picking.py
T
2026-02-12 23:27:48 +08:00

2062 lines
75 KiB
Python

"""Module managing picking events."""
from __future__ import annotations
from functools import partial
from functools import wraps
import warnings
import weakref
import numpy as np
import pyvista
from pyvista._deprecate_positional_args import _deprecate_positional_args
from pyvista.core.errors import PyVistaDeprecationWarning
from pyvista.core.utilities.misc import _NoNewAttrMixin
from pyvista.core.utilities.misc import abstract_class
from pyvista.core.utilities.misc import try_callback
from . import _vtk
from .composite_mapper import CompositePolyDataMapper
from .errors import PyVistaPickingError
from .mapper import _mapper_get_data_set_input
from .mapper import _mapper_has_data_set_input
from .opts import ElementType
from .opts import PickerType
PICKED_REPRESENTATION_NAMES = {
'point': '_picked_point',
'mesh': '_picked_mesh',
'through': '_picked_through_selection',
'visible': '_picked_visible_selection',
'element': '_picked_element',
'path': '_picked_path',
'horizon': '_picked_horizon',
'frustum': '_rectangle_selection_frustum',
}
def _launch_pick_event(interactor, _event):
"""Create a Pick event based on coordinate or left-click."""
click_x, click_y = interactor.GetEventPosition()
click_z = 0
picker = interactor.GetPicker()
renderer = interactor.GetInteractorStyle()._parent()._plotter.iren.get_poked_renderer()
picker.Pick(click_x, click_y, click_z, renderer)
def _poked_context_callback(plotter, *args, **kwargs):
"""Use _poked_context_callback in a poked renderer context."""
with plotter.iren.poked_subplot():
try_callback(*args, **kwargs)
class RectangleSelection(_NoNewAttrMixin):
"""Internal data structure for rectangle based selections.
Parameters
----------
frustum : :vtk:`vtkPlanes`
Frustum that defines the selection.
viewport : tuple[float, float, float, float]
The selected viewport coordinates, given as ``(x0, y0, x1, y1)``.
"""
def __init__(self, frustum, viewport):
self._frustum = frustum
self._viewport = viewport
@property
def frustum(self) -> _vtk.vtkPlanes: # numpydoc ignore=RT01
"""Get the selected frustum through the scene."""
return self._frustum
@property
def frustum_mesh(self) -> pyvista.PolyData: # numpydoc ignore=RT01
"""Get the frustum as a PyVista mesh."""
frustum_source = _vtk.vtkFrustumSource()
frustum_source.ShowLinesOff()
frustum_source.SetPlanes(self.frustum)
frustum_source.Update()
return pyvista.wrap(frustum_source.GetOutput())
@property
def viewport(self) -> tuple[float, float, float, float]: # numpydoc ignore=RT01
"""Get the selected viewport coordinates.
Coordinates are given as: ``(x0, y0, x1, y1)``
"""
return self._viewport
class PointPickingElementHandler(_NoNewAttrMixin):
"""Internal picking handler for element-based picking.
This handler is only valid for single point picking operations.
Parameters
----------
mode : ElementType, optional
The element type to pick.
callback : callable, optional
A callback function to be executed on picking events.
"""
def __init__(self, mode: ElementType = ElementType.CELL, callback=None):
self._picker_ = None
self.callback = callback
self.mode = ElementType.from_any(mode)
@property
def picker(self): # numpydoc ignore=RT01
"""Get or set the picker instance."""
return self._picker_() # type: ignore[misc]
@picker.setter
def picker(self, picker):
self._picker_ = weakref.ref(picker) # type: ignore[assignment]
def get_mesh(self):
"""Get the picked mesh.
Returns
-------
pyvista.DataSet
Picked mesh.
"""
ds = self.picker.GetDataSet()
if ds is not None:
return pyvista.wrap(ds)
return None
def get_cell(self, picked_point):
"""Get the picked cell of the picked mesh.
Parameters
----------
picked_point : sequence[float]
Coordinates of the picked point.
Returns
-------
pyvista.UnstructuredGrid
UnstructuredGrid containing the picked cell.
"""
mesh = self.get_mesh()
# cell_id = self.picker.GetCellId()
cell_id = mesh.find_containing_cell(picked_point) # more accurate
if cell_id < 0:
return None # TODO: this happens but shouldn't # pragma: no cover
cell = mesh.extract_cells(cell_id)
cell.cell_data['vtkOriginalCellIds'] = np.array([cell_id])
return cell
def get_face(self, picked_point):
"""Get the picked face of the picked cell.
Parameters
----------
picked_point : sequence[float]
Coordinates of the picked point.
Returns
-------
pyvista.UnstructuredGrid
UnstructuredGrid containing the picked face.
"""
cell = self.get_cell(picked_point).get_cell(0)
if cell.n_faces > 1:
for face in cell.faces:
contains = face.cast_to_unstructured_grid().find_containing_cell(picked_point)
if contains > -1:
break
if contains < 0:
# this shouldn't happen
msg = 'Trouble aligning point with face.'
raise RuntimeError(msg)
face = face.cast_to_unstructured_grid()
face.field_data['vtkOriginalFaceIds'] = np.array([len(cell.faces) - 1])
else:
face = cell.cast_to_unstructured_grid()
face.field_data['vtkOriginalFaceIds'] = np.array([0])
return face
def get_edge(self, picked_point):
"""Get the picked edge of the picked cell.
Parameters
----------
picked_point : sequence[float]
Coordinates of the picked point.
Returns
-------
pyvista.UnstructuredGrid
UnstructuredGrid containing the picked edge.
"""
cell = self.get_cell(picked_point).get_cell(0)
if cell.n_edges > 1:
ei = (
cell.cast_to_unstructured_grid()
.extract_all_edges()
.find_closest_cell(picked_point)
)
edge = cell.edges[ei].cast_to_unstructured_grid()
edge.field_data['vtkOriginalEdgeIds'] = np.array([ei])
else:
edge = cell.cast_to_unstructured_grid()
return edge
def get_point(self, picked_point):
"""Get the picked point of the picked mesh.
Parameters
----------
picked_point : sequence[float]
Coordinates of the picked point.
Returns
-------
pyvista.PolyData
Picked mesh containing the point.
"""
mesh = self.get_mesh()
pid = mesh.find_closest_point(picked_point)
picked = pyvista.PolyData(mesh.points[pid])
picked.point_data['vtkOriginalPointIds'] = np.array([pid])
return picked
def __call__(self, picked_point, picker):
"""Perform the pick."""
self.picker = picker
mesh = self.get_mesh()
if mesh is None:
return # No selected mesh (point not on surface of mesh)
if self.mode == ElementType.MESH:
picked = mesh
elif self.mode == ElementType.CELL:
picked = self.get_cell(picked_point)
if picked is None:
return # TODO: handle
elif self.mode == ElementType.FACE:
picked = self.get_face(picked_point)
elif self.mode == ElementType.EDGE:
picked = self.get_edge(picked_point)
elif self.mode == ElementType.POINT:
picked = self.get_point(picked_point)
if self.callback:
try_callback(self.callback, picked)
@abstract_class
class PickingInterface: # numpydoc ignore=PR01
"""An internal class to hold core picking related features."""
def __init__(self, *args, **kwargs):
"""Initialize the picking interface."""
super().__init__(*args, **kwargs)
self._picking_left_clicking_observer = None
self._picking_right_clicking_observer = None
self._picker_in_use = False
self._picked_point = None
def _clear_picking_representations(self):
"""Clear all picking representations."""
for name in PICKED_REPRESENTATION_NAMES.values():
self.remove_actor(name) # type: ignore[attr-defined]
@property
def picked_point(self): # numpydoc ignore=RT01
"""Return the picked point.
This returns the picked point after selecting a point.
Returns
-------
numpy.ndarray or None
Picked point if available.
"""
return self._picked_point
def get_pick_position(self):
"""Get the pick position or area.
Returns
-------
sequence
Picked position or area as ``(x0, y0, x1, y1)``.
"""
renderer = self.iren.get_poked_renderer() # type: ignore[attr-defined]
return renderer.get_pick_position()
def pick_click_position(self):
"""Get corresponding click location in the 3D plot.
Returns
-------
tuple
Three item tuple with the 3D picked position.
"""
if self.click_position is None: # type: ignore[attr-defined]
self.store_click_position() # type: ignore[attr-defined]
renderer = self.iren.get_poked_renderer() # type: ignore[attr-defined]
self.iren.picker.Pick(self.click_position[0], self.click_position[1], 0, renderer) # type: ignore[attr-defined]
return self.iren.picker.GetPickPosition() # type: ignore[attr-defined]
def pick_mouse_position(self):
"""Get corresponding mouse location in the 3D plot.
Returns
-------
tuple
Three item tuple with the 3D picked position.
"""
if self.mouse_position is None: # type: ignore[attr-defined]
self.store_mouse_position() # type: ignore[attr-defined]
renderer = self.iren.get_poked_renderer() # type: ignore[attr-defined]
self.iren.picker.Pick(self.mouse_position[0], self.mouse_position[1], 0, renderer) # type: ignore[attr-defined]
return self.iren.picker.GetPickPosition() # type: ignore[attr-defined]
def _init_click_picking_callback(self, *, left_clicking=False):
if left_clicking:
self._picking_left_clicking_observer = self.iren.add_observer( # type: ignore[attr-defined]
'LeftButtonPressEvent',
partial(try_callback, _launch_pick_event),
)
else:
self._picking_right_clicking_observer = self.iren.add_observer( # type: ignore[attr-defined]
'RightButtonPressEvent',
partial(try_callback, _launch_pick_event),
)
def disable_picking(self) -> None:
"""Disable any active picking and remove observers.
Examples
--------
Enable and then disable picking.
>>> import pyvista as pv
>>> mesh = pv.Sphere(center=(1, 0, 0))
>>> cube = pv.Cube()
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(mesh)
>>> _ = pl.add_mesh(cube)
>>> _ = pl.enable_mesh_picking()
>>> pl.disable_picking()
"""
# remove left and right clicking observer if available
if getattr(self, 'iren', None):
self.iren.remove_observer(self._picking_left_clicking_observer) # type: ignore[attr-defined]
self.iren.remove_observer(self._picking_right_clicking_observer) # type: ignore[attr-defined]
# Reset to default picker
self.iren.reset_picker() # type: ignore[attr-defined]
self._picking_left_clicking_observer = None
self._picking_right_clicking_observer = None
self._picker_in_use = False
def _validate_picker_not_in_use(self):
if self._picker_in_use:
msg = (
'Picking is already enabled, please disable previous picking '
'with `disable_picking()`.'
)
raise PyVistaPickingError(msg)
@_deprecate_positional_args(allowed=['callback'])
def enable_point_picking( # noqa: PLR0917
self,
callback=None,
tolerance=0.025,
left_clicking=False, # noqa: FBT002
picker=PickerType.POINT,
show_message=True, # noqa: FBT002
font_size=18,
color='pink',
point_size=10,
show_point=True, # noqa: FBT002
use_picker=False, # noqa: FBT002
pickable_window=False, # noqa: FBT002
clear_on_no_selection=True, # noqa: FBT002
**kwargs,
):
"""Enable picking at points under the cursor.
Enable picking a point at the mouse location in the render
view using the right mouse button. This point is saved to the
``.picked_point`` attribute on the plotter. Pass a callback
that takes that point as an argument. The picked
point can either be a point on the first intersecting mesh, or
a point in the 3D window.
The ``picker`` choice will help determine how the point picking
is performed.
Parameters
----------
callback : callable, optional
When input, calls this callable after a pick is made. The
picked point is input as the first parameter to this
callable.
tolerance : float, tolerance: 0.025
Specify tolerance for performing pick operation. Tolerance
is specified as fraction of rendering window
size. Rendering window size is measured across diagonal.
This is only valid for some choices of ``picker``.
left_clicking : bool, default: False
When ``True``, points can be picked by clicking the left mouse
button. Default is to use the right mouse button.
picker : str | PickerType, optional
Choice of VTK picker class type:
* ``'hardware'``: Uses :vtk:`vtkHardwarePicker` which is more
performant for large geometries (default).
* ``'cell'``: Uses :vtk:`vtkCellPicker`.
* ``'point'``: Uses :vtk:`vtkPointPicker` which will snap to
points on the surface of the mesh.
* ``'volume'``: Uses :vtk:`vtkVolumePicker`.
show_message : bool | str, default: True
Show the message about how to use the point picking
tool. If this is a string, that will be the message shown.
font_size : int, default: 18
Sets the size of the message.
color : ColorLike, default: "pink"
The color of the selected mesh when shown.
point_size : int, default: 10
Size of picked points if ``show_point`` is ``True``.
show_point : bool, default: True
Show the picked point after clicking.
use_picker : bool, default: False
When ``True``, the callback will also be passed the picker.
pickable_window : bool, default: False
When ``True`` and the chosen picker supports it, points in the
3D window are pickable.
clear_on_no_selection : bool, default: True
Clear the selections when no point is selected.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the picked point is interactively displayed.
Examples
--------
Enable point picking with a custom message.
>>> import pyvista as pv
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(pv.Sphere())
>>> _ = pl.add_mesh(pv.Cube(), pickable=False)
>>> pl.enable_point_picking(show_message='Pick a point')
See :ref:`point_picking_example` for a full example using this method.
"""
self._validate_picker_not_in_use()
if 'use_mesh' in kwargs:
warnings.warn(
'`use_mesh` is deprecated. See `use_picker` instead.',
PyVistaDeprecationWarning,
)
use_mesh = kwargs.pop('use_mesh')
else:
use_mesh = False
self_ = weakref.ref(self)
def _end_pick_event(picker, _event):
if (
not pickable_window
and hasattr(picker, 'GetDataSet')
and picker.GetDataSet() is None
):
# Clear the selection
self._picked_point = None
if clear_on_no_selection:
with self_().iren.poked_subplot(): # type: ignore[union-attr]
self_()._clear_picking_representations() # type: ignore[union-attr]
return
with self_().iren.poked_subplot(): # type: ignore[union-attr]
point = np.array(picker.GetPickPosition())
point /= self_().scale # type: ignore[union-attr] # HACK: handle scale
self_()._picked_point = point # type: ignore[union-attr]
if show_point:
_kwargs = kwargs.copy()
self_().add_mesh( # type: ignore[union-attr]
self_().picked_point, # type: ignore[union-attr]
color=color,
point_size=point_size,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['point']),
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callable(callback):
if use_picker:
_poked_context_callback(self_(), callback, self.picked_point, picker)
elif use_mesh: # Lower priority
_poked_context_callback(
self_(),
callback,
picker.GetDataSet(),
picker.GetPointId(),
)
else:
_poked_context_callback(self_(), callback, self.picked_point)
if picker is not None: # If None, that means use already set picker
self.iren.picker = picker # type: ignore[attr-defined]
if hasattr(self.iren.picker, 'SetTolerance'): # type: ignore[attr-defined]
self.iren.picker.SetTolerance(tolerance) # type: ignore[attr-defined]
self.iren.add_pick_observer(_end_pick_event) # type: ignore[attr-defined]
self._init_click_picking_callback(left_clicking=left_clicking)
self._picker_in_use = True
# Now add text about cell-selection
if show_message:
if show_message is True:
show_message = 'Left-click' if left_clicking else 'Right-click'
show_message += ' or press P to pick under the mouse'
self._picking_text = self.add_text( # type: ignore[attr-defined]
str(show_message),
font_size=font_size,
name='_point_picking_message',
)
@_deprecate_positional_args(allowed=['callback'])
def enable_rectangle_picking( # noqa: PLR0917
self,
callback=None,
show_message=True, # noqa: FBT002
font_size=18,
start=False, # noqa: FBT002
show_frustum=False, # noqa: FBT002
style='wireframe',
color='pink',
**kwargs,
):
"""Enable rectangle based picking at cells.
Press ``"r"`` to enable rectangle based selection. Press
``"r"`` again to turn it off.
Picking with the rectangle selection tool provides two values that
are passed as the ``RectangleSelection`` object in the callback:
1. ``RectangleSelection.viewport``: the viewport coordinates of the
selection rectangle.
2. ``RectangleSelection.frustum``: the full frustum made from
the selection rectangle into the scene.
Parameters
----------
callback : callable, optional
When input, calls this callable after a selection is made.
The ``RectangleSelection`` is the only passed argument
containing the viewport coordinates of the selection and the
projected frustum.
show_message : bool | str, default: True
Show the message about how to use the cell picking tool. If this
is a string, that will be the message shown.
font_size : int, default: 18
Sets the font size of the message.
start : bool, default: True
Automatically start the cell selection tool.
show_frustum : bool, default: False
Show the frustum in the scene.
style : str, default: "wireframe"
Visualization style of the selection frustum. One of the
following: ``style='surface'``, ``style='wireframe'``, or
``style='points'``.
color : ColorLike, default: "pink"
The color of the selected frustum when shown.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the selection frustum is interactively displayed.
Examples
--------
Add a mesh and a cube to a plot and enable cell picking.
>>> import pyvista as pv
>>> mesh = pv.Sphere(center=(1, 0, 0))
>>> cube = pv.Cube()
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(mesh)
>>> _ = pl.add_mesh(cube)
>>> _ = pl.enable_rectangle_picking()
"""
self._validate_picker_not_in_use()
self_ = weakref.ref(self)
def _end_pick_helper(picker, *_):
renderer = picker.GetRenderer() # TODO: double check this is poked renderer
x0 = int(renderer.GetPickX1())
x1 = int(renderer.GetPickX2())
y0 = int(renderer.GetPickY1())
y1 = int(renderer.GetPickY2())
selection = RectangleSelection(frustum=picker.GetFrustum(), viewport=(x0, y0, x1, y1))
if show_frustum:
with self_().iren.poked_subplot(): # type: ignore[union-attr]
_kwargs = kwargs.copy()
self_().add_mesh( # type: ignore[union-attr]
selection.frustum_mesh,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['frustum']),
style=style,
color=color,
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callback is not None:
_poked_context_callback(self_(), callback, selection)
self.enable_rubber_band_style() # type: ignore[attr-defined] # TODO: better handle?
self.iren.picker = 'rendered' # type: ignore[attr-defined]
self.iren.add_pick_observer(_end_pick_helper) # type: ignore[attr-defined]
self._picker_in_use = True
# Now add text about cell-selection
if show_message:
if show_message is True:
show_message = 'Press R to toggle selection tool'
self._picking_text = self.add_text( # type: ignore[attr-defined]
str(show_message),
font_size=font_size,
name='_rectangle_picking_message',
)
if start:
self.iren._style_class.StartSelect() # type: ignore[attr-defined]
@abstract_class
class PickingMethods(PickingInterface): # numpydoc ignore=PR01
"""Internal class to contain picking utilities."""
def __init__(self, *args, **kwargs):
"""Initialize the picking methods."""
super().__init__(*args, **kwargs)
self._picked_actor = None
self._picked_mesh = None
self._picked_cell = None
self._picking_text = None
self._picked_block_index = None
@property
def picked_actor(self): # numpydoc ignore=RT01
"""Return the picked mesh.
This returns the picked actor after selecting a mesh with
:func:`enable_surface_point_picking <pyvista.Plotter.enable_surface_point_picking>` or
:func:`enable_mesh_picking <pyvista.Plotter.enable_mesh_picking>`.
Returns
-------
pyvista.Actor or None
Picked actor if available.
"""
return self._picked_actor
@property
def picked_mesh(self): # numpydoc ignore=RT01
"""Return the picked mesh.
This returns the picked mesh after selecting a mesh with
:func:`enable_surface_point_picking <pyvista.Plotter.enable_surface_point_picking>` or
:func:`enable_mesh_picking <pyvista.Plotter.enable_mesh_picking>`.
Returns
-------
pyvista.DataSet or None
Picked mesh if available.
"""
return self._picked_mesh
@property
def picked_cell(self): # numpydoc ignore=RT01
"""Return the picked cell.
This returns the picked cell after selecting a cell.
Returns
-------
pyvista.Cell or None
Picked cell if available.
"""
return self._picked_cell
@property
def picked_cells(self): # numpydoc ignore=RT01
"""Return the picked cells.
This returns the picked cells after selecting cells.
Returns
-------
pyvista.Cell or None
Picked cell if available.
"""
return self._picked_cell
@property
def picked_block_index(self): # numpydoc ignore=RT01
"""Return the picked block index.
This returns the picked block index after selecting a point with
:func:`enable_point_picking <pyvista.Plotter.enable_point_picking>`.
Returns
-------
int or None
Picked block if available. If ``-1``, then a non-composite dataset
was selected.
"""
return self._picked_block_index
@wraps(PickingInterface.disable_picking)
def disable_picking(self) -> None: # type: ignore[override]
"""Disable picking."""
super().disable_picking()
# remove any picking text
if hasattr(self, 'renderers'):
for renderer in self.renderers:
renderer.remove_actor(self._picking_text, render=False)
self._picking_text = None
@_deprecate_positional_args(allowed=['callback'])
def enable_surface_point_picking( # noqa: PLR0917
self,
callback=None,
show_message=True, # noqa: FBT002
font_size=18,
color='pink',
show_point=True, # noqa: FBT002
point_size=10,
tolerance=0.025,
pickable_window=False, # noqa: FBT002
left_clicking=False, # noqa: FBT002
picker=PickerType.CELL,
use_picker=False, # noqa: FBT002
clear_on_no_selection=True, # noqa: FBT002
**kwargs,
):
"""Enable picking of a point on the surface of a mesh.
Parameters
----------
callback : callable, optional
When input, calls this callable after a selection is made. The
``mesh`` is input as the first parameter to this callable.
show_message : bool | str, default: True
Show the message about how to use the mesh picking tool. If this
is a string, that will be the message shown.
font_size : int, default: 18
Sets the font size of the message.
color : ColorLike, default: "pink"
The color of the selected mesh when shown.
show_point : bool, default: True
Show the selection interactively.
point_size : int, default: 10
Size of picked points if ``show_point`` is ``True``.
tolerance : float, default: 0.025
Specify tolerance for performing pick operation. Tolerance
is specified as fraction of rendering window
size. Rendering window size is measured across diagonal.
.. warning::
This is ignored with the ``'hardware'`` ``picker``.
pickable_window : bool, default: False
When ``True``, points in the 3D window are pickable.
left_clicking : bool, default: False
When ``True``, meshes can be picked by clicking the left
mousebutton.
.. note::
If enabled, left-clicking will **not** display the bounding box
around the picked mesh.
picker : str | PickerType, optional
Choice of VTK picker class type:
* ``'hardware'``: Uses :vtk:`vtkHardwarePicker` which is more
performant for large geometries (default).
* ``'cell'``: Uses :vtk:`vtkCellPicker`.
* ``'point'``: Uses :vtk:`vtkPointPicker` which will snap to
points on the surface of the mesh.
* ``'volume'``: Uses :vtk:`vtkVolumePicker`.
use_picker : bool, default: False
When ``True``, the callback will also be passed the picker.
clear_on_no_selection : bool, default: True
Clear the selections when no point is selected.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the picked path is interactively displayed.
Notes
-----
Picked point can be accessed from :attr:`picked_point
<pyvista.Plotter.picked_point>` attribute.
Examples
--------
Add a cube to a plot and enable cell picking.
>>> import pyvista as pv
>>> cube = pv.Cube()
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(cube)
>>> _ = pl.enable_surface_point_picking()
See :ref:`surface_point_picking_example` for a full example using this method.
"""
# only allow certain pickers to be used for surface picking
# the picker class needs to have `GetDataSet()`
picker = PickerType.from_any(picker)
valid_pickers = [
PickerType.POINT,
PickerType.CELL,
PickerType.HARDWARE,
PickerType.VOLUME,
]
if picker not in valid_pickers:
msg = f'Invalid picker choice for surface picking. Use one of: {valid_pickers}'
raise ValueError(msg)
self_ = weakref.ref(self)
def _end_pick_event(picked_point, picker):
if not pickable_window and picker.GetActor() is None:
self_()._picked_point = None # type: ignore[union-attr]
self_()._picked_actor = None # type: ignore[union-attr]
self_()._picked_mesh = None # type: ignore[union-attr]
if clear_on_no_selection:
with self_().iren.poked_subplot(): # type: ignore[union-attr]
self_()._clear_picking_representations() # type: ignore[union-attr]
return
self_()._picked_actor = picker.GetActor() # type: ignore[union-attr]
self_()._picked_mesh = picker.GetDataSet() # type: ignore[union-attr]
if show_point:
with self_().iren.poked_subplot(): # type: ignore[union-attr]
_kwargs = kwargs.copy()
self_().add_mesh( # type: ignore[union-attr]
picked_point,
color=color,
point_size=point_size,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['point']),
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callable(callback):
if use_picker:
_poked_context_callback(self_(), callback, picked_point, picker)
else:
_poked_context_callback(self_(), callback, picked_point)
self.enable_point_picking(
callback=_end_pick_event,
picker=picker,
show_point=False,
show_message=show_message,
left_clicking=left_clicking,
use_picker=True,
font_size=font_size,
tolerance=tolerance,
pickable_window=True, # let this callback handle pickable window
clear_on_no_selection=clear_on_no_selection,
)
@_deprecate_positional_args(allowed=['callback'])
def enable_mesh_picking( # noqa: PLR0917
self,
callback=None,
show=True, # noqa: FBT002
show_message=True, # noqa: FBT002
style='wireframe',
line_width=5,
color='pink',
font_size=18,
left_clicking=False, # noqa: FBT002
use_actor=False, # noqa: FBT002
picker=PickerType.CELL,
**kwargs,
):
"""Enable picking of a mesh.
Parameters
----------
callback : callable, optional
When input, calls this callable after a selection is made. The
``mesh`` is input as the first parameter to this callable.
show : bool, default: True
Show the selection interactively. Best when combined with
``left_clicking``.
show_message : bool | str, default: True
Show the message about how to use the mesh picking tool. If this
is a string, that will be the message shown.
style : str, default: "wireframe"
Visualization style of the selection. One of the following:
* ``'surface'``
* ``'wireframe'``
* ``'points'``
line_width : float, default: 5.0
Thickness of selected mesh edges.
color : ColorLike, default: "pink"
The color of the selected mesh when shown.
font_size : int, default: 18
Sets the font size of the message.
left_clicking : bool, default: False
When ``True``, meshes can be picked by clicking the left
mousebutton.
.. note::
If enabled, left-clicking will **not** display the bounding box
around the picked point.
use_actor : bool, default: False
If True, the callback will be passed the picked actor instead of
the mesh object.
picker : str | PickerType, optional
Choice of VTK picker class type:
* ``'hardware'``: Uses :vtk:`vtkHardwarePicker` which is more
performant for large geometries (default).
* ``'cell'``: Uses :vtk:`vtkCellPicker`.
* ``'point'``: Uses :vtk:`vtkPointPicker` which will snap to
points on the surface of the mesh.
* ``'volume'``: Uses :vtk:`vtkVolumePicker`.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the picked path is interactively displayed.
Returns
-------
:vtk:`vtkPropPicker`
Property picker.
Examples
--------
Add a sphere and a cube to a plot and enable mesh picking. Enable
``left_clicking`` to immediately start picking on the left click and
disable showing the box. You can still press the ``p`` key to select
meshes.
>>> import pyvista as pv
>>> mesh = pv.Sphere(center=(1, 0, 0))
>>> cube = pv.Cube()
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(mesh)
>>> _ = pl.add_mesh(cube)
>>> _ = pl.enable_mesh_picking()
See :ref:`mesh_picking_example` for a full example using this method.
"""
self_ = weakref.ref(self)
def end_pick_call_back(*args): # noqa: ARG001
if callback:
if use_actor:
_poked_context_callback(self_(), callback, self_()._picked_actor) # type: ignore[union-attr]
else:
_poked_context_callback(self_(), callback, self_()._picked_mesh) # type: ignore[union-attr]
if show:
# Select the renderer where the mesh is added.
active_renderer_index = self_().renderers._active_index # type: ignore[union-attr]
loc = self_().iren.get_event_subplot_loc() # type: ignore[union-attr]
self_().subplot(*loc) # type: ignore[union-attr]
# Use try in case selection is empty or invalid
try:
with self_().iren.poked_subplot(): # type: ignore[union-attr]
_kwargs = kwargs.copy()
self_().add_mesh( # type: ignore[union-attr]
self_()._picked_mesh, # type: ignore[union-attr]
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['mesh']),
style=style,
color=color,
line_width=line_width,
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
except Exception as e: # noqa: BLE001 # pragma: no cover
warnings.warn('Unable to show mesh when picking:\n\n%s', str(e)) # type: ignore[call-overload]
# Reset to the active renderer.
loc = self_().renderers.index_to_loc(active_renderer_index) # type: ignore[union-attr]
self_().subplot(*loc) # type: ignore[union-attr]
# render here prior to running the callback
self_().render() # type: ignore[union-attr]
# add on-screen message about point-selection
if show_message and show_message is True:
show_message = 'Left-click' if left_clicking else 'Right-click'
show_message += ' or press P to pick single dataset under the mouse pointer'
self.enable_surface_point_picking(
callback=end_pick_call_back,
picker=picker,
show_point=False,
show_message=show_message,
left_clicking=left_clicking,
use_picker=True,
font_size=font_size,
pickable_window=False,
)
@_deprecate_positional_args(allowed=['callback'])
def enable_rectangle_through_picking( # noqa: PLR0917
self,
callback=None,
show=True, # noqa: FBT002
style='wireframe',
line_width=5,
color='pink',
show_message=True, # noqa: FBT002
font_size=18,
start=False, # noqa: FBT002
show_frustum=False, # noqa: FBT002
**kwargs,
):
"""Enable rectangle based cell picking through the scene.
Parameters
----------
callback : callable, optional
When input, calls this callable after a selection is made.
The picked cells is the only passed argument.
show : bool, default: True
Show the selection interactively.
style : str, default: "wireframe"
Visualization style of the selection frustum. One of the
following: ``style='surface'``, ``style='wireframe'``, or
``style='points'``.
line_width : float, default: 5.0
Thickness of selected mesh edges.
color : ColorLike, default: "pink"
The color of the selected frustum when shown.
show_message : bool | str, default: True
Show the message about how to use the cell picking tool. If this
is a string, that will be the message shown.
font_size : int, default: 18
Sets the font size of the message.
start : bool, default: True
Automatically start the cell selection tool.
show_frustum : bool, default: False
Show the frustum in the scene.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the selection frustum is interactively displayed.
"""
self_ = weakref.ref(self)
def finalize(picked):
if picked is None:
# Indicates invalid pick
with self_().iren.poked_subplot(): # type: ignore[union-attr]
self_()._clear_picking_representations() # type: ignore[union-attr]
return
self._picked_cell = picked
if show:
# Use try in case selection is empty
with self_().iren.poked_subplot(): # type: ignore[union-attr]
_kwargs = kwargs.copy()
self_().add_mesh( # type: ignore[union-attr]
picked,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['through']),
style=style,
color=color,
line_width=line_width,
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callback is not None:
_poked_context_callback(self_(), callback, self_().picked_cells) # type: ignore[union-attr]
def through_pick_callback(selection):
picked = pyvista.MultiBlock()
renderer = self_().iren.get_poked_renderer() # type: ignore[union-attr]
for actor in renderer.actors.values():
if (
(mapper := actor.GetMapper())
and _mapper_has_data_set_input(mapper)
and actor.GetPickable()
):
input_mesh = pyvista.wrap(_mapper_get_data_set_input(actor.GetMapper()))
input_mesh.cell_data['orig_extract_id'] = np.arange(input_mesh.n_cells)
extract = _vtk.vtkExtractGeometry()
extract.SetInputData(input_mesh)
extract.SetImplicitFunction(selection.frustum)
extract.Update()
picked.append(pyvista.wrap(extract.GetOutput()))
if picked.n_blocks == 0 or picked.combine().n_cells < 1:
self_()._picked_cell = None # type: ignore[union-attr]
elif picked.n_blocks == 1:
self_()._picked_cell = picked[0] # type: ignore[union-attr]
else:
self_()._picked_cell = picked # type: ignore[union-attr]
finalize(self_()._picked_cell) # type: ignore[union-attr]
self.enable_rectangle_picking(
callback=through_pick_callback,
show_message=show_message,
font_size=font_size,
show_frustum=show_frustum,
start=start,
style=style,
color=color,
)
@_deprecate_positional_args(allowed=['callback'])
def enable_rectangle_visible_picking( # noqa: PLR0917
self,
callback=None,
show=True, # noqa: FBT002
style='wireframe',
line_width=5,
color='pink',
show_message=True, # noqa: FBT002
font_size=18,
start=False, # noqa: FBT002
show_frustum=False, # noqa: FBT002
**kwargs,
):
"""Enable rectangle based cell picking on visible surfaces.
Parameters
----------
callback : callable, optional
When input, calls this callable after a selection is made.
The picked cells is the only passed argument.
show : bool, default: True
Show the selection interactively.
style : str, default: "wireframe"
Visualization style of the selection frustum. One of the
following: ``style='surface'``, ``style='wireframe'``, or
``style='points'``.
line_width : float, default: 5.0
Thickness of selected mesh edges.
color : ColorLike, default: "pink"
The color of the selected frustum when shown.
show_message : bool | str, default: True
Show the message about how to use the cell picking tool. If this
is a string, that will be the message shown.
font_size : int, default: 18
Sets the font size of the message.
start : bool, default: True
Automatically start the cell selection tool.
show_frustum : bool, default: False
Show the frustum in the scene.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the selection frustum is interactively displayed.
"""
self_ = weakref.ref(self)
def finalize(picked):
if picked is None:
# Indicates invalid pick
with self_().iren.poked_subplot(): # type: ignore[union-attr]
self_()._clear_picking_representations() # type: ignore[union-attr]
return
if show:
# Use try in case selection is empty
with self_().iren.poked_subplot(): # type: ignore[union-attr]
_kwargs = kwargs.copy()
self_().add_mesh( # type: ignore[union-attr]
picked,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['visible']),
style=style,
color=color,
line_width=line_width,
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callback is not None:
_poked_context_callback(self_(), callback, picked)
def visible_pick_callback(selection):
picked = pyvista.MultiBlock()
renderer = self_().iren.get_poked_renderer() # type: ignore[union-attr]
x0, y0, x1, y1 = renderer.get_pick_position()
# x0, y0, x1, y1 = selection.viewport
if x0 >= 0: # initial pick position is (-1, -1, -1, -1)
selector = _vtk.vtkOpenGLHardwareSelector()
selector.SetFieldAssociation(_vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS)
selector.SetRenderer(renderer)
selector.SetArea(x0, y0, x1, y1)
selection = selector.Select()
for node in range(selection.GetNumberOfNodes()):
selection_node = selection.GetNode(node)
if selection_node is None: # pragma: no cover
# No selection
continue
cids = pyvista.convert_array(selection_node.GetSelectionList())
actor = selection_node.GetProperties().Get(_vtk.vtkSelectionNode.PROP())
# TODO: this is too hacky - find better way to avoid non-dataset actors
if not actor.GetMapper() or not hasattr(
actor.GetProperty(),
'GetRepresentation',
):
continue
# if not a surface
if actor.GetProperty().GetRepresentation() != 2: # pragma: no cover
warnings.warn(
'Display representations other than `surface` will result '
'in incorrect results.',
)
smesh = pyvista.wrap(_mapper_get_data_set_input(actor.GetMapper()))
smesh = smesh.copy()
smesh['original_cell_ids'] = np.arange(smesh.n_cells)
tri_smesh = smesh.extract_surface().triangulate()
cids_to_get = tri_smesh.extract_cells(cids)['original_cell_ids']
picked.append(smesh.extract_cells(cids_to_get))
# memory leak issues on vtk==9.0.20210612.dev0
# See: https://gitlab.kitware.com/vtk/vtk/-/issues/18239#note_973826
selection.UnRegister(selection)
if len(picked) == 0 or picked.combine().n_cells < 1:
self_()._picked_cell = None # type: ignore[union-attr]
elif len(picked) == 1:
self_()._picked_cell = picked[0] # type: ignore[union-attr]
else:
self_()._picked_cell = picked # type: ignore[union-attr]
finalize(self_()._picked_cell) # type: ignore[union-attr]
self.enable_rectangle_picking(
callback=visible_pick_callback,
show_message=show_message,
font_size=font_size,
start=start,
show_frustum=show_frustum,
style=style,
color=color,
)
@_deprecate_positional_args(allowed=['callback'])
def enable_cell_picking( # noqa: PLR0917
self,
callback=None,
through=True, # noqa: FBT002
show=True, # noqa: FBT002
show_message=True, # noqa: FBT002
style='wireframe',
line_width=5,
color='pink',
font_size=18,
start=False, # noqa: FBT002
show_frustum=False, # noqa: FBT002
**kwargs,
):
"""Enable picking of cells with a rectangle selection tool.
Press ``"r"`` to enable rectangle based selection. Press
``"r"`` again to turn it off. Selection will be saved to
``self.picked_cells``.
All meshes in the scene are available for picking by default.
If you would like to only pick a single mesh in the scene,
use the ``pickable=False`` argument when adding the other
meshes to the scene.
When multiple meshes are being picked, the picked cells
in ``self.picked_cells`` will be a :class:`MultiBlock`
dataset for each mesh's selection.
Uses last input mesh for input by default.
.. warning::
Visible cell picking (``through=False``) will only work if
the mesh is displayed with a ``'surface'`` representation
style (the default).
Parameters
----------
callback : callable, optional
When input, calls this callable after a selection is made.
The picked_cells are input as the first parameter to this
callable.
through : bool, default: True
When ``True`` the picker will select all cells
through the mesh(es). When ``False``, the picker will select
only visible cells on the selected surface(s).
show : bool, default: True
Show the selection interactively.
show_message : bool | str, default: True
Show the message about how to use the cell picking tool. If this
is a string, that will be the message shown.
style : str, default: "wireframe"
Visualization style of the selection. One of the
following: ``style='surface'``, ``style='wireframe'``, or
``style='points'``.
line_width : float, default: 5.0
Thickness of selected mesh edges.
color : ColorLike, default: "pink"
The color of the selected mesh when shown.
font_size : int, default: 18
Sets the font size of the message.
start : bool, default: True
Automatically start the cell selection tool.
show_frustum : bool, default: False
Show the frustum in the scene.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the selection is interactively displayed.
Examples
--------
Add a mesh and a cube to a plot and enable cell picking.
>>> import pyvista as pv
>>> mesh = pv.Sphere(center=(1, 0, 0))
>>> cube = pv.Cube()
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(mesh)
>>> _ = pl.add_mesh(cube)
>>> _ = pl.enable_cell_picking()
"""
if through:
method = self.enable_rectangle_through_picking
else:
method = self.enable_rectangle_visible_picking
method(
callback=callback,
show=show,
show_message=show_message,
style=style,
line_width=line_width,
color=color,
font_size=font_size,
start=start,
show_frustum=show_frustum,
**kwargs,
)
@_deprecate_positional_args(allowed=['callback'])
def enable_element_picking( # noqa: PLR0917
self,
callback=None,
mode='cell',
show=True, # noqa: FBT002
show_message=True, # noqa: FBT002
font_size=18,
tolerance=0.025,
pickable_window=False, # noqa: FBT002
left_clicking=False, # noqa: FBT002
picker=PickerType.CELL,
**kwargs,
):
"""Select individual elements on a mesh.
Parameters
----------
callback : callable, optional
When input, calls this callable after a selection is made. The
``mesh`` is input as the first parameter to this callable.
mode : str | ElementType, default: "cell"
The picking mode. Either ``"mesh"``, ``"cell"``, ``"face"``,
``"edge"``, or ``"point"``.
show : bool, default: True
Show the selection interactively.
show_message : bool | str, default: True
Show the message about how to use the mesh picking tool. If this
is a string, that will be the message shown.
font_size : int, default: 18
Sets the font size of the message.
tolerance : float, default: 0.025
Specify tolerance for performing pick operation. Tolerance
is specified as fraction of rendering window
size. Rendering window size is measured across diagonal.
.. warning::
This is ignored with the ``'hardware'`` ``picker``.
pickable_window : bool, default: False
When ``True``, points in the 3D window are pickable.
left_clicking : bool, default: False
When ``True``, meshes can be picked by clicking the left
mousebutton.
.. note::
If enabled, left-clicking will **not** display the bounding box
around the picked mesh.
picker : str | PickerType, optional
Choice of VTK picker class type:
* ``'hardware'``: Uses :vtk:`vtkHardwarePicker` which is more
performant for large geometries (default).
* ``'cell'``: Uses :vtk:`vtkCellPicker`.
* ``'point'``: Uses :vtk:`vtkPointPicker` which will snap to
points on the surface of the mesh.
* ``'volume'``: Uses :vtk:`vtkVolumePicker`.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the picked path is interactively displayed.
See Also
--------
:ref:`element_picking_example`
"""
mode = ElementType.from_any(mode)
self_ = weakref.ref(self)
def _end_handler(picked):
if callback:
_poked_context_callback(self_(), callback, picked)
if mode == ElementType.CELL:
self._picked_cell = picked
if show:
if mode == ElementType.CELL:
kwargs.setdefault('color', 'pink')
elif mode == ElementType.EDGE:
kwargs.setdefault('color', 'magenta')
else:
kwargs.setdefault('color', 'pink')
if mode in [ElementType.CELL, ElementType.FACE]:
picked = picked.extract_all_edges()
with self.iren.poked_subplot(): # type: ignore[attr-defined]
_kwargs = kwargs.copy()
self.add_mesh( # type: ignore[attr-defined]
picked,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['element']),
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
point_size=_kwargs.pop('point_size', 5),
line_width=_kwargs.pop('line_width', 5),
**_kwargs,
)
handler = PointPickingElementHandler(mode=mode, callback=_end_handler)
self.enable_surface_point_picking(
callback=handler,
show_message=show_message,
font_size=font_size,
show_point=False,
tolerance=tolerance,
pickable_window=pickable_window,
left_clicking=left_clicking,
picker=picker,
use_picker=True,
**kwargs,
)
def enable_block_picking(self, callback=None, side='left'):
"""Enable composite block picking.
Use this picker to return the index of a DataSet when using composite
dataset like :class:`pyvista.MultiBlock` and pass it to a callback.
Parameters
----------
callback : callable, optional
When input, this picker calls this callable after a selection is
made. The composite index is passed to ``callback`` as the first
argument and the dataset as the second argument.
side : str, default: "left"
The mouse button to track (either ``'left'`` or ``'right'``).
Also accepts ``'r'`` or ``'l'``.
Notes
-----
The picked block index can be accessed from :attr:`picked_block_index
<pyvista.Plotter.picked_block_index>` attribute.
Examples
--------
Enable block picking with a multiblock dataset. Left clicking will turn
blocks blue while right picking will turn the block back to the default
color.
>>> import pyvista as pv
>>> multiblock = pv.MultiBlock([pv.Cube(), pv.Sphere(center=(0, 0, 1))])
>>> pl = pv.Plotter()
>>> actor, mapper = pl.add_composite(multiblock)
>>> def turn_blue(index, dataset):
... mapper.block_attr[index].color = 'blue'
>>> pl.enable_block_picking(callback=turn_blue, side='left')
>>> def clear_color(index, dataset):
... mapper.block_attr[index].color = None
>>> pl.enable_block_picking(callback=clear_color, side='right')
>>> pl.show()
"""
# use a weak reference to enable garbage collection
self_ = weakref.ref(self)
sel_index = _vtk.vtkSelectionNode.COMPOSITE_INDEX()
sel_prop = _vtk.vtkSelectionNode.PROP()
def get_picked_block(*args, **kwargs): # numpydoc ignore=PR01 # noqa: ARG001
"""Get the picked block and pass it to the user callback."""
x, y = self.mouse_position # type: ignore[attr-defined]
loc = self_().iren.get_event_subplot_loc() # type: ignore[union-attr]
index = self_().renderers.loc_to_index(loc) # type: ignore[union-attr]
renderer = self_().renderers[index] # type: ignore[union-attr]
selector = _vtk.vtkOpenGLHardwareSelector()
selector.SetRenderer(renderer)
selector.SetArea(x, y, x, y)
selection = selector.Select()
for ii in range(selection.GetNumberOfNodes()):
node = selection.GetNode(ii)
if node is None: # pragma: no cover
continue
node_prop = node.GetProperties()
self._picked_block_index = node_prop.Get(sel_index)
# Safely return the dataset as it's possible a non pyvista
# mapper was added
mapper = node_prop.Get(sel_prop).GetMapper()
if isinstance(mapper, CompositePolyDataMapper):
dataset = mapper.block_attr.get_block(self._picked_block_index)
else: # pragma: no cover
dataset = None
if callable(callback):
_poked_context_callback(self_(), callback, self._picked_block_index, dataset)
self.track_click_position(callback=get_picked_block, viewport=True, side=side) # type: ignore[attr-defined]
@abstract_class
class PickingHelper(PickingMethods):
"""Internal container class to contain picking helper methods."""
def __init__(self, *args, **kwargs):
"""Initialize the picking methods."""
super().__init__(*args, **kwargs)
self.picked_path = None
self.picked_geodesic = None
self.picked_horizon = None
self._last_picked_idx: int | None = None
@_deprecate_positional_args
def fly_to_mouse_position(self, focus=False): # noqa: FBT002
"""Focus on last stored mouse position."""
if self.mouse_position is None: # type: ignore[attr-defined]
self.store_mouse_position() # type: ignore[attr-defined]
click_point = self.pick_mouse_position()
if focus:
self.set_focus(click_point) # type: ignore[attr-defined]
else:
self.fly_to(click_point) # type: ignore[attr-defined]
def enable_fly_to_right_click(self, callback=None):
"""Set the camera to track right click positions.
A convenience method to track right click positions and fly to
the picked point in the scene. The callback will be passed the
point in 3D space.
Parameters
----------
callback : callable
Callback to call immediately after right clicking.
"""
self_ = weakref.ref(self)
def _the_callback(*_):
click_point = self.pick_mouse_position()
self.fly_to(click_point) # type: ignore[attr-defined]
if callable(callback):
_poked_context_callback(self_(), callback, click_point)
self.track_click_position(callback=_the_callback, side='right') # type: ignore[attr-defined]
@_deprecate_positional_args(allowed=['callback'])
def enable_path_picking( # noqa: PLR0917
self,
callback=None,
show_message=True, # noqa: FBT002
font_size=18,
color='pink',
point_size=10,
line_width=5,
show_path=True, # noqa: FBT002
tolerance=0.025,
**kwargs,
):
"""Enable picking at paths.
This is a convenience method for :func:`enable_point_picking
<pyvista.Plotter.enable_point_picking>` to keep track of the
picked points and create a line using those points.
The line is saved to the ``.picked_path`` attribute of this
plotter
Parameters
----------
callback : callable, optional
When given, calls this callable after a pick is made. The
entire picked path is passed as the only parameter to this
callable.
show_message : bool | str, default: True
Show the message about how to use the point picking
tool. If this is a string, that will be the message shown.
font_size : int, default: 18
Sets the size of the message.
color : ColorLike, default: "pink"
The color of the selected mesh when shown.
point_size : int, default: 10
Size of picked points if ``show_path`` is ``True``.
line_width : float, default: 5.0
Thickness of path representation if ``show_path`` is
``True``.
show_path : bool, default: True
Show the picked path interactively.
tolerance : float, default: 0.025
Specify tolerance for performing pick operation. Tolerance
is specified as fraction of rendering window
size. Rendering window size is measured across diagonal.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the picked path is interactively displayed.
"""
self_ = weakref.ref(self)
kwargs.setdefault('pickable', False)
def make_line_cells(n_points):
cells = np.arange(0, n_points, dtype=np.int_)
return np.insert(cells, 0, n_points)
the_points = []
def _the_callback(picked_point, picker):
if picker.GetDataSet() is None:
return
the_points.append(picked_point)
self.picked_path = pyvista.PolyData(np.array(the_points))
self.picked_path.lines = make_line_cells(len(the_points))
if show_path:
with self.iren.poked_subplot(): # type: ignore[attr-defined]
_kwargs = kwargs.copy()
self.add_mesh( # type: ignore[attr-defined]
self.picked_path,
color=color,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['path']),
line_width=line_width,
point_size=point_size,
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callable(callback):
_poked_context_callback(self_(), callback, self.picked_path)
def _clear_path_event_watcher():
del the_points[:]
with self.iren.poked_subplot(): # type: ignore[attr-defined]
self._clear_picking_representations()
self.add_key_event('c', _clear_path_event_watcher) # type: ignore[attr-defined]
if show_message is True:
show_message = 'Press P to pick under the mouse\nPress C to clear'
self.enable_surface_point_picking(
callback=_the_callback,
use_picker=True,
font_size=font_size,
show_message=show_message,
show_point=False,
tolerance=tolerance,
clear_on_no_selection=False,
)
@_deprecate_positional_args(allowed=['callback'])
def enable_geodesic_picking( # noqa: PLR0917
self,
callback=None,
show_message=True, # noqa: FBT002
font_size=18,
color='pink',
point_size=10,
line_width=5,
tolerance=0.025,
show_path=True, # noqa: FBT002
keep_order=True, # noqa: FBT002
**kwargs,
):
"""Enable picking at geodesic paths.
This is a convenience method for ``enable_point_picking`` to
keep track of the picked points and create a geodesic path
using those points.
The geodesic path is saved to the ``.picked_geodesic``
attribute of this plotter.
Parameters
----------
callback : callable, optional
When given, calls this callable after a pick is made. The
entire picked, geodesic path is passed as the only
parameter to this callable.
show_message : bool | str, default: True
Show the message about how to use the point picking
tool. If this is a string, that will be the message shown.
font_size : int, default: 18
Sets the size of the message.
color : ColorLike, default: "pink"
The color of the selected mesh when shown.
point_size : int, default: 10
Size of picked points if ``show_path`` is ``True``.
line_width : float, default: 5.0
Thickness of path representation if ``show_path`` is
``True``.
tolerance : float, default: 0.025
Specify tolerance for performing pick operation. Tolerance
is specified as fraction of rendering window
size. Rendering window size is measured across diagonal.
show_path : bool, default: True
Show the picked path interactively.
keep_order : bool, default: True
If ``True``, the created geodesic path is a single ordered
and cleaned line from the first point to the last.
.. note::
In older versions there were apparent discontinuities
in the resulting path due to the behavior of the
underlying VTK filter which corresponds to
``keep_order=False``.
.. versionadded:: 0.32.0
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the picked path is interactively displayed.
"""
self_ = weakref.ref(self)
kwargs.setdefault('pickable', False)
self.picked_geodesic = pyvista.PolyData()
def _the_callback(picked_point, picker):
if picker.GetDataSet() is None:
return
mesh = pyvista.wrap(picker.GetDataSet())
idx = mesh.find_closest_point(picked_point)
point = mesh.points[idx]
if self._last_picked_idx is None:
self.picked_geodesic = pyvista.PolyData(point)
self.picked_geodesic['vtkOriginalPointIds'] = [idx]
else:
surface = mesh.extract_surface().triangulate()
locator = _vtk.vtkPointLocator()
locator.SetDataSet(surface)
locator.BuildLocator()
start_idx = locator.FindClosestPoint(mesh.points[self._last_picked_idx])
end_idx = locator.FindClosestPoint(point)
self.picked_geodesic += surface.geodesic(start_idx, end_idx, keep_order=keep_order)
if keep_order:
# it makes sense to remove adjacent duplicate points
self.picked_geodesic.clean(
inplace=True,
lines_to_points=False,
polys_to_lines=False,
strips_to_polys=False,
)
self._last_picked_idx = idx
if show_path:
with self.iren.poked_subplot(): # type: ignore[attr-defined]
_kwargs = kwargs.copy()
self.add_mesh( # type: ignore[attr-defined]
self.picked_geodesic,
color=color,
name=_kwargs.pop('name', PICKED_REPRESENTATION_NAMES['path']),
line_width=line_width,
point_size=point_size,
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callable(callback):
_poked_context_callback(self_(), callback, self.picked_geodesic)
def _clear_g_path_event_watcher():
self.picked_geodesic = pyvista.PolyData()
with self.iren.poked_subplot(): # type: ignore[attr-defined]
self._clear_picking_representations()
self._last_picked_idx = None
self.add_key_event('c', _clear_g_path_event_watcher) # type: ignore[attr-defined]
if show_message is True:
show_message = 'Press P to pick under the mouse\nPress C to clear'
self.enable_surface_point_picking(
callback=_the_callback,
use_picker=True,
font_size=font_size,
show_message=show_message,
tolerance=tolerance,
show_point=False,
clear_on_no_selection=False,
)
@_deprecate_positional_args(allowed=['callback'])
def enable_horizon_picking( # noqa: PLR0917
self,
callback=None,
normal=(0.0, 0.0, 1.0),
width=None,
show_message=True, # noqa: FBT002
font_size=18,
color='pink',
point_size=10,
line_width=5,
show_path=True, # noqa: FBT002
opacity=0.75,
show_horizon=True, # noqa: FBT002
**kwargs,
):
"""Enable horizon picking.
Helper for the ``enable_path_picking`` method to also show a
ribbon surface along the picked path. Ribbon is saved under
``.picked_horizon``.
Parameters
----------
callback : callable, optional
When given, calls this callable after a pick is made. The
entire picked path is passed as the only parameter to this
callable.
normal : sequence[float], default: (0.0, 0.0, 1.0)
The normal to the horizon surface's projection plane.
width : float, optional
The width of the horizon surface. Default behaviour will
dynamically change the surface width depending on its
length.
show_message : bool | str, default: True
Show the message about how to use the horizon picking
tool. If this is a string, that will be the message shown.
font_size : int, default: 18
Sets the font size of the message.
color : ColorLike, default: "pink"
The color of the horizon surface if shown.
point_size : int, default: 10
Size of picked points if ``show_horizon`` is ``True``.
line_width : float, default: 5.0
Thickness of path representation if ``show_horizon`` is
``True``.
show_path : bool, default: True
Show the picked path that the horizon is built from
interactively.
opacity : float, default: 0.75
The opacity of the horizon surface if shown.
show_horizon : bool, default: True
Show the picked horizon surface interactively.
**kwargs : dict, optional
All remaining keyword arguments are used to control how
the picked path is interactively displayed.
"""
self_ = weakref.ref(self)
def _clear_horizon_event_watcher():
self.picked_horizon = pyvista.PolyData()
with self.iren.poked_subplot(): # type: ignore[attr-defined]
self._clear_picking_representations()
self.add_key_event('c', _clear_horizon_event_watcher) # type: ignore[attr-defined]
def _the_callback(path):
if path.n_points < 2:
_clear_horizon_event_watcher()
return
self.picked_horizon = path.ribbon(normal=normal, width=width)
if show_horizon:
with self.iren.poked_subplot(): # type: ignore[attr-defined]
_kwargs = kwargs.copy()
self.add_mesh( # type: ignore[attr-defined]
self.picked_horizon,
name=_kwargs.get('name', PICKED_REPRESENTATION_NAMES['horizon']),
color=color,
opacity=opacity,
pickable=_kwargs.pop('pickable', False),
reset_camera=_kwargs.pop('reset_camera', False),
**_kwargs,
)
if callable(callback):
_poked_context_callback(self_(), callback, path)
self.enable_path_picking(
callback=_the_callback,
show_message=show_message,
font_size=font_size,
color=color,
point_size=point_size,
line_width=line_width,
show_path=show_path,
**kwargs,
)