Skip to content

Commit 1b31be9

Browse files
feat: Refactor picker to allow custom callbacks
1 parent 76c201c commit 1b31be9

File tree

5 files changed

+454
-119
lines changed

5 files changed

+454
-119
lines changed

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: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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_picker:
25+
26+
===================
27+
Activate the 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+
def __init__(self, plotter_backend: "Plotter", plot_picked_names: bool = True) -> None:
55+
"""Initialize the ``Picker`` class."""
56+
# Picking variables
57+
self._plotter_backend = plotter_backend
58+
self._plot_picked_names = plot_picked_names
59+
60+
# Map that relates PyVista actors with the added actors by the picker
61+
self._picker_added_actors_map = {}
62+
63+
# Dictionary of picked objects in MeshObject format.
64+
self._picked_dict = {}
65+
66+
# Map that saves original colors of the plotted objects.
67+
self._origin_colors = {}
68+
69+
# Hovering variables
70+
self._added_hover_labels = []
71+
72+
def pick_select_object(self, custom_object: MeshObjectPlot, pt: "np.ndarray") -> None:
73+
"""Add actor to picked list and add label if required.
74+
75+
Parameters
76+
----------
77+
custom_object : MeshObjectPlot
78+
The object to be selected.
79+
pt : np.ndarray
80+
The point where the object was picked.
81+
"""
82+
added_actors = []
83+
84+
# Pick only custom objects
85+
if isinstance(custom_object, MeshObjectPlot):
86+
self._origin_colors[custom_object] = custom_object.actor.prop.color
87+
custom_object.actor.prop.color = Color.PICKED.value
88+
89+
# Get the name for the text label
90+
text = custom_object.name
91+
92+
# If picking names is enabled, add a label to the picked object
93+
if self._plot_picked_names:
94+
label_actor = self._plotter_backend.pv_interface.scene.add_point_labels(
95+
[pt],
96+
[text],
97+
always_visible=True,
98+
point_size=0,
99+
render_points_as_spheres=False,
100+
show_points=False,
101+
)
102+
# Add the label actor to the list of added actors
103+
added_actors.append(label_actor)
104+
105+
# Add the picked object to the picked dictionary if not already present, to keep track of it
106+
if custom_object.name not in self._picked_dict:
107+
self._picked_dict[custom_object.name] = custom_object
108+
# Add the picked object to the picked dictionary if not already present, to keep track of it
109+
self._picker_added_actors_map[custom_object.actor.name] = added_actors
110+
111+
def pick_unselect_object(self, custom_object: MeshObjectPlot) -> None:
112+
"""Remove actor from picked list and remove label if required.
113+
114+
Parameters
115+
----------
116+
custom_object : MeshObjectPlot
117+
The object to be unselected.
118+
"""
119+
# remove actor from picked list and from scene
120+
if custom_object.name in self._picked_dict:
121+
self._picked_dict.pop(custom_object.name)
122+
123+
# Restore original color if it was changed
124+
if isinstance(custom_object, MeshObjectPlot) and custom_object in self._origin_colors:
125+
custom_object.actor.prop.color = self._origin_colors[custom_object]
126+
127+
# Remove any added actors (like labels) associated with this picked object
128+
if custom_object.actor.name in self._picker_added_actors_map:
129+
self._plotter_backend._pl.scene.remove_actor(self._picker_added_actors_map[custom_object.actor.name])
130+
self._picker_added_actors_map.pop(custom_object.actor.name)
131+
132+
def hover_select_object(self, custom_object: MeshObjectPlot, actor: "Actor") -> None:
133+
"""Add label to hovered object if required.
134+
135+
Parameters
136+
----------
137+
custom_object : MeshObjectPlot
138+
The object to be hovered over.
139+
actor : vtkActor
140+
The actor corresponding to the hovered object.
141+
"""
142+
for label in self._added_hover_labels:
143+
self._plotter_backend._pl.scene.remove_actor(label)
144+
label_actor = self._plotter_backend._pl.scene.add_point_labels(
145+
[actor.GetCenter()],
146+
[custom_object.name],
147+
always_visible=True,
148+
point_size=0,
149+
render_points_as_spheres=False,
150+
show_points=False,
151+
)
152+
self._added_hover_labels.append(label_actor)
153+
154+
def hover_unselect_object(self):
155+
"""Remove all hover labels from the scene."""
156+
for label in self._added_hover_labels:
157+
self._plotter_backend._pl.scene.remove_actor(label)
158+
159+
@property
160+
def picked_dict(self) -> dict:
161+
"""Return the dictionary of picked objects.
162+
163+
Returns
164+
-------
165+
dict
166+
Dictionary of picked objects.
167+
"""
168+
return self._picked_dict
169+
170+
#######################################################
171+
# Initialize the plotter backend with the custom picker
172+
# =====================================================
173+
174+
from ansys.tools.visualization_interface.backends.pyvista import PyVistaBackend
175+
pl_backend = PyVistaBackend(allow_picking=True, custom_picker=CustomPicker)
176+
177+
# Create the plotter with the custom backend
178+
pl = Plotter(backend=pl_backend)
179+
180+
#################################################
181+
# Create a custom object with a name to be picked
182+
# ===============================================
183+
184+
import pyvista as pv
185+
186+
class CustomObject:
187+
def __init__(self):
188+
self.name = "CustomObject"
189+
self.mesh = pv.Cube(center=(1, 1, 0))
190+
191+
def get_mesh(self):
192+
return self.mesh
193+
194+
def name(self):
195+
return self.name
196+
197+
# Create a custom object
198+
custom_cube = CustomObject()
199+
custom_cube.name = "CustomCube"
200+
201+
#########################################
202+
# Create a ``MeshObjectPlot`` instance
203+
# =======================================
204+
205+
from ansys.tools.visualization_interface import MeshObjectPlot
206+
# Create an instance
207+
mesh_object_cube = MeshObjectPlot(custom_cube, custom_cube.get_mesh())
208+
209+
##############################
210+
# Plot the custom object
211+
# ============================
212+
213+
pl.plot(mesh_object_cube)
214+
pl.show()

0 commit comments

Comments
 (0)