diff --git a/examples/02-plot/point-picking.py b/examples/02-plot/point-picking.py new file mode 100644 index 0000000000..deb1f60c11 --- /dev/null +++ b/examples/02-plot/point-picking.py @@ -0,0 +1,54 @@ +""" +.. _point_picking_example: + +Picking points on a mesh +~~~~~~~~~~~~~~~~~~~~~~~~ +This example demonstrates how to pick points on meshes using +:func:`enable_point_picking() `. + +""" + +# sphinx_gallery_thumbnail_number = 2 +import pyvista as pv + +############################################################################### +# Pick points on a sphere +# +++++++++++++++++++++++ +# +sphere = pv.Sphere() + +p = pv.Plotter() +p.add_mesh(sphere, pickable=True) +p.enable_point_picking() +p.show() + +############################################################################### +# Ignore the 3D window +# ++++++++++++++++++++ +# +# In the above example, both points on the mesh and points in the 3d window can be +# selected. It is possible instead pick only points on the mesh. +sphere = pv.Sphere() + +p = pv.Plotter() +p.add_mesh(sphere, pickable=True) +p.enable_point_picking(pickable_window=False) # Make the 3D window unpickable +p.show() + +############################################################################### +# Modify which actors are pickable +# ++++++++++++++++++++++++++++++++ +# +# After enabling point picking, we can modify which actors are pickable. +sphere = pv.Sphere() +cube = pv.Cube() +cube.translate([10, 10, 0]) + +p = pv.Plotter() +sphere_actor = p.add_mesh(sphere, pickable=True) # initially pickable +cube_actor = p.add_mesh(cube, pickable=False) # initially unpickable +p.enable_point_picking(pickable_window=False) + +p.pickable_actors = [sphere_actor, cube_actor] # now both are pickable +p.view_xy() +p.show() diff --git a/pyvista/plotting/picking.py b/pyvista/plotting/picking.py index 54d909910d..8cec1b1f20 100644 --- a/pyvista/plotting/picking.py +++ b/pyvista/plotting/picking.py @@ -230,7 +230,7 @@ def visible_pick_call_back(picker, event_id): def enable_point_picking(self, callback=None, show_message=True, font_size=18, color='pink', point_size=10, use_mesh=False, show_point=True, tolerance=0.025, - **kwargs): + pickable_window=False, **kwargs): """Enable picking at points. Enable picking a point at the mouse location in the render @@ -276,16 +276,36 @@ def enable_point_picking(self, callback=None, show_message=True, is specified as fraction of rendering window size. Rendering window size is measured across diagonal. + pickable_window : bool, optional + When True, points in the 3D window are pickable. Default to ``True``. + **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="Press P to pick") + + See :ref:`point_picking_example` for a full example using this method. + """ def _end_pick_event(picker, event): + + picked_point_id = picker.GetPointId() + if (not pickable_window) and (picked_point_id < 0): + return None + self.picked_point = np.array(picker.GetPickPosition()) self.picked_mesh = picker.GetDataSet() - self.picked_point_id = picker.GetPointId() + self.picked_point_id = picked_point_id if show_point: self.add_mesh(self.picked_point, color=color, point_size=point_size, name='_picked_point', diff --git a/pyvista/plotting/plotting.py b/pyvista/plotting/plotting.py index 34ebe03d4b..3ac00babb1 100644 --- a/pyvista/plotting/plotting.py +++ b/pyvista/plotting/plotting.py @@ -1277,6 +1277,67 @@ def untrack_click_position(self): """Stop tracking the click position.""" self.iren.untrack_click_position() + @property + def pickable_actors(self): + """Return or set the pickable actors. + + When setting, this will be the list of actors to make + pickable. All actors not in the list will be made unpickable. + If ``actors`` is ``None``, all actors will be made unpickable. + + Returns + ------- + list of vtk.vtkActors + + Examples + -------- + Add two actors to a :class:`pyvista.Plotter`, make one + pickable, and then list the pickable actors. + + >>> import pyvista as pv + >>> pl = pv.Plotter() + >>> sphere_actor = pl.add_mesh(pv.Sphere()) + >>> cube_actor = pl.add_mesh(pv.Cube(), pickable=False, style='wireframe') + >>> len(pl.pickable_actors) + 1 + + Set the pickable actors to both actors. + + >>> pl.pickable_actors = [sphere_actor, cube_actor] + >>> len(pl.pickable_actors) + 2 + + Set the pickable actors to ``None``. + + >>> pl.pickable_actors = None + >>> len(pl.pickable_actors) + 0 + + """ + pickable = [] + for renderer in self.renderers: + for actor in renderer.actors.values(): + if actor.GetPickable(): + pickable.append(actor) + return pickable + + @pickable_actors.setter + def pickable_actors(self, actors=None): + """Set the pickable actors.""" + actors = [] if actors is None else actors + if isinstance(actors, _vtk.vtkActor): + actors = [actors] + + if not all([isinstance(actor, _vtk.vtkActor) for actor in actors]): + raise TypeError( + f'Expected a vtkActor instance or a list of vtkActors, got ' + f'{[type(actor) for actor in actors]} instead.' + ) + + for renderer in self.renderers: + for actor in renderer.actors.values(): + actor.SetPickable(actor in actors) + def _prep_for_close(self): """Make sure a screenshot is acquired before closing. diff --git a/tests/test_picking.py b/tests/test_picking.py index 7dc5d9e6aa..b75120a11e 100644 --- a/tests/test_picking.py +++ b/tests/test_picking.py @@ -117,6 +117,42 @@ def test_point_picking(): picker.Pick(50, 50, 0, renderer) plotter.close() +@skip_no_vtk9 +@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") +def test_point_picking_window_not_pickable(): + + plotter = pyvista.Plotter( + window_size=(100, 100), + ) + + # bottom left corner, pickable + sphere = pyvista.Sphere() + sphere.translate([-100, -100, 0]) + plotter.add_mesh(sphere, pickable=True) + + # top right corner, not pickable + unpickable_sphere = pyvista.Sphere() + unpickable_sphere.translate([100, 100, 0]) + plotter.add_mesh(unpickable_sphere, pickable=False) + + plotter.view_xy() + plotter.enable_point_picking( + pickable_window=False, + tolerance=0.2, + ) + + # simulate the pick + renderer = plotter.renderer + picker = plotter.iren.get_picker() + + successful_pick = picker.Pick(0, 0, 0, renderer) + assert successful_pick + + successful_pick = picker.Pick(100, 100, 0, renderer) + assert not successful_pick + + plotter.close() + @skip_no_vtk9 @pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") diff --git a/tests/test_plotter.py b/tests/test_plotter.py index 1ab21ddafd..9050aa2414 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -39,6 +39,35 @@ def test_disable_hidden_line_removal(): assert not plotter.renderers[1].GetUseHiddenLineRemoval() +def test_pickable_actors(): + + plotter = pyvista.Plotter() + sphere = plotter.add_mesh(pyvista.Sphere(), pickable=True) + cube = plotter.add_mesh(pyvista.Cube(), pickable=False) + + pickable = plotter.pickable_actors + assert sphere in pickable + assert cube not in pickable + + plotter.pickable_actors = cube + pickable = plotter.pickable_actors + assert sphere not in pickable + assert cube in pickable + + plotter.pickable_actors = [sphere, cube] + pickable = plotter.pickable_actors + assert sphere in pickable + assert cube in pickable + + plotter.pickable_actors = None + pickable = plotter.pickable_actors + assert sphere not in pickable + assert cube not in pickable + + with pytest.raises(TypeError, match="Expected a vtkActor instance or "): + plotter.pickable_actors = [0, 10] + + def test_prepare_smooth_shading_texture(globe): """Test edge cases for smooth shading""" mesh, scalars = _plotting.prepare_smooth_shading(