Skip to content

Commit 3487ed5

Browse files
AlejandroFernandezLucespyansys-ci-botpre-commit-ci[bot]
authored
feat: Refactor picker to allow custom callbacks (#360)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 8118675 commit 3487ed5

File tree

8 files changed

+497
-125
lines changed

8 files changed

+497
-125
lines changed

doc/changelog.d/360.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Feat: Refactor picker to allow custom callbacks

doc/source/user_guide/index.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,24 @@ class. After that, see these main use cases for customizing the plotter:
108108

109109
Some practical examples of how to use the ``PlotterInterface`` class are included in some PyAnsys libraries,
110110
such as `PyAnsys Geometry <https://github.com/ansys/pyansys-geometry/pull/959>`_.
111+
112+
113+
Customizing the picker and hover callbacks
114+
==========================================
115+
116+
The Visualization Interface Tool provides a base class, ``AbstractPicker``, for customizing the picker and hover
117+
callbacks of the plotter. This class provides a set of methods that can be overridden so that you can adapt the
118+
picker and hover functionalities to the specific need of your PyAnsys library.
119+
120+
The first thing you must do is to create a class that inherits from the ``AbstractPicker`` class. After that, see
121+
these main use cases for customizing the picker and hover callbacks:
122+
123+
* You may want to change the way that objects are picked in the plotter. To do this, you can override the
124+
``pick_select_object`` and ``pick_unselect_object`` methods. These methods are called when an object is
125+
selected or unselected, respectively.
126+
127+
* Similarly, you may want to change the way that objects are hovered over in the plotter. To do this, you can
128+
override the ``hover_select_object`` and ``hover_unselect_object`` methods. These methods are called when an
129+
object is hovered over or unhovered, respectively.
130+
131+
A practical example of how to use the ``AbstractPicker`` class are included in the examples section of the documentation.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
(?i)Ansys
2-
pytest
2+
pytest
3+
unhovered
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# Copyright (C) 2024 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
"""
24+
.. _ref_custom_picker:
25+
26+
====================
27+
Create custom picker
28+
====================
29+
30+
This example shows how to create a custom picker. In this case we will show how the default
31+
picker is implemented through the ``AbstractPicker`` class.
32+
"""
33+
34+
#####################################
35+
# Import the ``AbstractPicker`` class
36+
# ===================================
37+
38+
# Import the abstract picker class
39+
from ansys.tools.visualization_interface.backends.pyvista.picker import AbstractPicker
40+
41+
# Import custom object meshes
42+
from ansys.tools.visualization_interface.types.mesh_object_plot import MeshObjectPlot
43+
44+
# Import plotter and color enum
45+
from ansys.tools.visualization_interface import Plotter
46+
from ansys.tools.visualization_interface.utils.color import Color
47+
48+
49+
#####################################
50+
# Create a custom picker class
51+
# =================================
52+
53+
class CustomPicker(AbstractPicker):
54+
"""Custom picker class that extends the AbstractPicker.
55+
This custom picker changes the color of picked objects to red and adds a label with the object's name.
56+
It also adds a label when hovering over an object.
57+
58+
Parameters
59+
----------
60+
plotter_backend : Plotter
61+
The plotter backend to use.
62+
plot_picked_names : bool, optional
63+
Whether to plot the names of picked objects, by default True.
64+
"""
65+
def __init__(self, plotter_backend: "Plotter", plot_picked_names: bool = True) -> None:
66+
"""Initialize the ``Picker`` class."""
67+
# Picking variables
68+
self._plotter_backend = plotter_backend
69+
self._plot_picked_names = plot_picked_names
70+
71+
# Map that relates PyVista actors with the added actors by the picker
72+
self._picker_added_actors_map = {}
73+
74+
# Dictionary of picked objects in MeshObject format.
75+
self._picked_dict = {}
76+
77+
# Map that saves original colors of the plotted objects.
78+
self._origin_colors = {}
79+
80+
# Hovering variables
81+
self._added_hover_labels = []
82+
83+
def pick_select_object(self, custom_object: MeshObjectPlot, pt: "np.ndarray") -> None:
84+
"""Add actor to picked list and add label if required.
85+
86+
Parameters
87+
----------
88+
custom_object : MeshObjectPlot
89+
The object to be selected.
90+
pt : np.ndarray
91+
The point where the object was picked.
92+
"""
93+
added_actors = []
94+
95+
# Pick only custom objects
96+
if isinstance(custom_object, MeshObjectPlot):
97+
self._origin_colors[custom_object] = custom_object.actor.prop.color
98+
custom_object.actor.prop.color = Color.PICKED.value
99+
100+
# Get the name for the text label
101+
text = custom_object.name
102+
103+
# If picking names is enabled, add a label to the picked object
104+
if self._plot_picked_names:
105+
label_actor = self._plotter_backend.pv_interface.scene.add_point_labels(
106+
[pt],
107+
[text],
108+
always_visible=True,
109+
point_size=0,
110+
render_points_as_spheres=False,
111+
show_points=False,
112+
)
113+
# Add the label actor to the list of added actors
114+
added_actors.append(label_actor)
115+
116+
# Add the picked object to the picked dictionary if not already present, to keep track of it
117+
if custom_object.name not in self._picked_dict:
118+
self._picked_dict[custom_object.name] = custom_object
119+
# Add the picked object to the picked dictionary if not already present, to keep track of it
120+
self._picker_added_actors_map[custom_object.actor.name] = added_actors
121+
122+
def pick_unselect_object(self, custom_object: MeshObjectPlot) -> None:
123+
"""Remove actor from picked list and remove label if required.
124+
125+
Parameters
126+
----------
127+
custom_object : MeshObjectPlot
128+
The object to be unselected.
129+
"""
130+
# remove actor from picked list and from scene
131+
if custom_object.name in self._picked_dict:
132+
self._picked_dict.pop(custom_object.name)
133+
134+
# Restore original color if it was changed
135+
if isinstance(custom_object, MeshObjectPlot) and custom_object in self._origin_colors:
136+
custom_object.actor.prop.color = self._origin_colors[custom_object]
137+
138+
# Remove any added actors (like labels) associated with this picked object
139+
if custom_object.actor.name in self._picker_added_actors_map:
140+
self._plotter_backend._pl.scene.remove_actor(self._picker_added_actors_map[custom_object.actor.name])
141+
self._picker_added_actors_map.pop(custom_object.actor.name)
142+
143+
def hover_select_object(self, custom_object: MeshObjectPlot, actor: "Actor") -> None:
144+
"""Add label to hovered object if required.
145+
146+
Parameters
147+
----------
148+
custom_object : MeshObjectPlot
149+
The object to be hovered over.
150+
actor : vtkActor
151+
The actor corresponding to the hovered object.
152+
"""
153+
for label in self._added_hover_labels:
154+
self._plotter_backend._pl.scene.remove_actor(label)
155+
label_actor = self._plotter_backend._pl.scene.add_point_labels(
156+
[actor.GetCenter()],
157+
[custom_object.name],
158+
always_visible=True,
159+
point_size=0,
160+
render_points_as_spheres=False,
161+
show_points=False,
162+
)
163+
self._added_hover_labels.append(label_actor)
164+
165+
def hover_unselect_object(self):
166+
"""Remove all hover labels from the scene."""
167+
for label in self._added_hover_labels:
168+
self._plotter_backend._pl.scene.remove_actor(label)
169+
170+
@property
171+
def picked_dict(self) -> dict:
172+
"""Return the dictionary of picked objects.
173+
174+
Returns
175+
-------
176+
dict
177+
Dictionary of picked objects.
178+
"""
179+
return self._picked_dict
180+
181+
#######################################################
182+
# Initialize the plotter backend with the custom picker
183+
# =====================================================
184+
185+
from ansys.tools.visualization_interface.backends.pyvista import PyVistaBackend
186+
pl_backend = PyVistaBackend(allow_picking=True, custom_picker=CustomPicker)
187+
188+
189+
#################################################
190+
# Create a custom object with a name to be picked
191+
# ===============================================
192+
193+
import pyvista as pv
194+
195+
class CustomObject:
196+
def __init__(self):
197+
self.name = "CustomObject"
198+
self.mesh = pv.Cube(center=(1, 1, 0))
199+
200+
def get_mesh(self):
201+
return self.mesh
202+
203+
def name(self):
204+
return self.name
205+
206+
# Create a custom object
207+
custom_cube = CustomObject()
208+
custom_cube.name = "CustomCube"
209+
210+
#########################################
211+
# Create a ``MeshObjectPlot`` instance
212+
# =======================================
213+
214+
from ansys.tools.visualization_interface import MeshObjectPlot
215+
# Create an instance
216+
mesh_object_cube = MeshObjectPlot(custom_cube, custom_cube.get_mesh())
217+
218+
##################################################
219+
# Display the plotter and interact with the object
220+
# ================================================
221+
# .. code-block:: python
222+
#
223+
# pl = Plotter(backend=pl_backend)
224+
# pl.plot(mesh_object_cube)
225+
# pl.show()

0 commit comments

Comments
 (0)