Skip to content

Commit d55c8e4

Browse files
germa89akaszynski
andauthored
Adding pickable nodes (#1123)
* First implementation of the API * Improving implementation * added unit tests * Updated API. Adding KP support. * Increasing coverage by covering the errors. * Fixing failure test * Fixing code static analyser issues. * Fixing code static analyser issues. * Fixing code static analyser issues. * fix get_ansys_bin for linux * add in trivial ksel test * Apply suggestions from code review Co-authored-by: Alex Kaszynski <[email protected]> * Fixing unit tests * Fixing unit tests * Adding ``plot_function`` for generalize more the ``allow_pickable_points``. Now non-selection will return an empty list. * Fixing unit tests * Adding "current selection" to the window text * Adding highlighting of selected points * Fixing code static analyser * Removing unnecessary prints because now it is more visual. * Allowing unselecting. Adding more info to text. Make title disappear. * Adding test for the mouse unselecting/selecting * fix test; fix point picking behavior * revert change to use mesh points * fix issue with latest picker Co-authored-by: Alex Kaszynski <[email protected]>
1 parent 8000ee6 commit d55c8e4

File tree

7 files changed

+600
-8
lines changed

7 files changed

+600
-8
lines changed

examples/00-mapdl-examples/lathe_cutter.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,12 @@
161161
mapdl.csys(1)
162162
mapdl.view(1, -1, 1, 1)
163163
mapdl.psymb("CS", 1)
164-
mapdl.vplot(color_areas=True, show_lines=True, cpos=[-1, 1, 1], smooth_shading=True)
164+
mapdl.vplot(
165+
color_areas=True,
166+
show_lines=True,
167+
cpos=[-1, 1, 1],
168+
smooth_shading=True,
169+
)
165170

166171
###############################################################################
167172
#
@@ -385,11 +390,18 @@
385390

386391
###############################################################################
387392
# Generate a single horizontal slice along the XY plane.
393+
#
394+
# .. note::
395+
# We're using ``eye_dome_lighting`` here to enhance the plots of our slices.
396+
# Read more about it at `Eye Dome Lighting
397+
# <https://docs.pyvista.org/examples/02-plot/edl.html>`_
398+
388399
single_slice = grid.slice(normal=[0, 0, 1], origin=[0, 0, 0])
389400
single_slice.plot(
390401
scalars="p1",
391402
background="white",
392403
lighting=False,
404+
eye_dome_lighting=True,
393405
show_edges=False,
394406
cmap="jet",
395407
n_colors=9,
@@ -403,6 +415,7 @@
403415
scalars="p1",
404416
background="white",
405417
lighting=False,
418+
eye_dome_lighting=True,
406419
show_edges=False,
407420
cmap="jet",
408421
n_colors=9,
@@ -416,13 +429,14 @@
416429
slices.plot(
417430
scalars="p1",
418431
background="white",
419-
lighting=False,
420432
show_edges=False,
433+
lighting=False,
434+
eye_dome_lighting=True,
421435
cmap="jet",
422436
n_colors=9,
423437
scalar_bar_args=sbar_kwargs,
424438
)
425439

426440
###############################################################################
427-
# Exit MAPDL.
441+
# Finally, exit MAPDL.
428442
mapdl.exit()

src/ansys/mapdl/core/mapdl.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@
3232
from ansys.mapdl.core.inline_functions import Query
3333
from ansys.mapdl.core.misc import (
3434
Information,
35+
allow_pickable_points,
3536
last_created,
3637
load_file,
3738
random_string,
3839
run_as_prep7,
3940
supress_logging,
41+
wrap_point_SEL,
4042
)
4143
from ansys.mapdl.core.plotting import general_plotter
4244
from ansys.mapdl.core.post import PostProcessing
@@ -3276,3 +3278,207 @@ def dim(
32763278
return super().dim(
32773279
par, type_, imax, jmax, kmax, var1, var2, var3, csysid, **kwargs
32783280
)
3281+
3282+
def _get_selected_(self, entity): # pragma: no cover
3283+
"""Get list of selected entities."""
3284+
allowed_values = ["NODE", "ELEM", "KP", "LINE", "AREA", "VOLU"]
3285+
if entity.upper() not in allowed_values:
3286+
raise ValueError(
3287+
f"The value '{entity}' is not allowed."
3288+
f"Only {allowed_values} are allowed"
3289+
)
3290+
3291+
entity = entity.upper()
3292+
3293+
if entity == "NODE":
3294+
return self.mesh.nnum.copy()
3295+
elif entity == "ELEM":
3296+
return self.mesh.enum.copy()
3297+
elif entity == "KP":
3298+
return self.geometry.knum
3299+
elif entity == "LINE":
3300+
return self.geometry.lnum
3301+
elif entity == "AREA":
3302+
return self.geometry.anum
3303+
elif entity == "VOLU":
3304+
return self.geometry.vnum
3305+
3306+
def _pick_points(self, entity, pl, type_, previous_picked_points, **kwargs):
3307+
"""Show a plot and get the selected points."""
3308+
_debug = kwargs.pop("_debug", False) # for testing purposes
3309+
previous_picked_points = set(previous_picked_points)
3310+
3311+
q = self.queries
3312+
picked_points = []
3313+
picked_ids = []
3314+
3315+
selector = getattr(q, entity.lower())
3316+
3317+
# adding selection inversor
3318+
pl._inver_mouse_click_selection = False
3319+
3320+
selection_text = {
3321+
"S": "New selection",
3322+
"A": "Adding to selection",
3323+
"R": "Reselecting from the selection",
3324+
"U": "Unselecting",
3325+
}
3326+
3327+
def gen_text(picked_points=None):
3328+
"""Generate helpful text for the render window."""
3329+
sel_ = "Unselecting" if pl._inver_mouse_click_selection else "Selecting"
3330+
type_text = selection_text[type_]
3331+
text = (
3332+
f"Please use the left mouse button to pick the {entity}s.\n"
3333+
f"Press the key 'u' to change between mouse selecting and unselecting.\n"
3334+
f"Type: {type_} - {type_text}\n"
3335+
f"Mouse selection: {sel_}\n"
3336+
)
3337+
3338+
picked_points_str = ""
3339+
if picked_points:
3340+
# reverse picked point order, exclude the brackets, and limit
3341+
# to 40 characters
3342+
picked_points_str = str(picked_points[::-1])[1:-1]
3343+
if len(picked_points_str) > 40:
3344+
picked_points_str = picked_points_str[:40]
3345+
idx = picked_points_str.rfind(",") + 2
3346+
picked_points_str = picked_points_str[:idx] + "..."
3347+
3348+
return text + f"Current {entity} selection: {picked_points_str}"
3349+
3350+
def callback_(mesh, id_):
3351+
point = mesh.points[id_]
3352+
node_id = selector(
3353+
point[0], point[1], point[2]
3354+
) # This will only return one node. Fine for now.
3355+
3356+
if not pl._inver_mouse_click_selection:
3357+
# Updating MAPDL points mapping
3358+
if node_id not in picked_points:
3359+
picked_points.append(node_id)
3360+
# Updating pyvista points mapping
3361+
if id_ not in picked_ids:
3362+
picked_ids.append(id_)
3363+
else:
3364+
# Updating MAPDL points mapping
3365+
if node_id in picked_points:
3366+
picked_points.remove(node_id)
3367+
# Updating pyvista points mapping
3368+
if id_ in picked_ids:
3369+
picked_ids.remove(id_)
3370+
3371+
# remov etitle and update text
3372+
pl.remove_actor("title")
3373+
pl._picking_text = pl.add_text(
3374+
gen_text(picked_points),
3375+
font_size=10,
3376+
name="_point_picking_message",
3377+
)
3378+
if picked_ids:
3379+
pl.add_mesh(
3380+
mesh.points[picked_ids],
3381+
color="red",
3382+
point_size=10,
3383+
name="_picked_points",
3384+
pickable=False,
3385+
reset_camera=False,
3386+
)
3387+
else:
3388+
pl.remove_actor("_picked_points")
3389+
3390+
pl.enable_point_picking(
3391+
callback=callback_,
3392+
use_mesh=True,
3393+
show_message=gen_text(),
3394+
show_point=True,
3395+
left_clicking=True,
3396+
font_size=10,
3397+
tolerance=kwargs.get("tolerance", 0.025),
3398+
)
3399+
3400+
def callback_u():
3401+
# inverting bool
3402+
pl._inver_mouse_click_selection = not pl._inver_mouse_click_selection
3403+
pl._picking_text = pl.add_text(
3404+
gen_text(picked_points),
3405+
font_size=10,
3406+
name="_point_picking_message",
3407+
)
3408+
3409+
pl.add_key_event("u", callback_u)
3410+
3411+
if not _debug: # pragma: no cover
3412+
pl.show()
3413+
else:
3414+
_debug(pl)
3415+
3416+
picked_points = set(
3417+
picked_points
3418+
) # removing duplicates (although there should be none)
3419+
3420+
if type_ == "S":
3421+
pass
3422+
elif type_ == "R":
3423+
picked_points = previous_picked_points.intersection(picked_points)
3424+
elif type_ == "A":
3425+
picked_points = previous_picked_points.union(picked_points)
3426+
elif type_ == "U":
3427+
picked_points = previous_picked_points.difference(picked_points)
3428+
3429+
return list(picked_points)
3430+
3431+
def _perform_entity_list_selection(
3432+
self, entity, selection_function, type_, item, comp, vmin, kabs
3433+
):
3434+
"""Select entities using CM, and the supplied selection function."""
3435+
self.cm(f"__temp_{entity}s__", f"{entity}") # Saving previous selection
3436+
3437+
# Getting new selection
3438+
for id_, each_ in enumerate(vmin):
3439+
selection_function(
3440+
self, "S" if id_ == 0 else "A", item, comp, each_, "", "", kabs
3441+
)
3442+
3443+
self.cm(f"__temp_{entity}s_1__", f"{entity}")
3444+
3445+
self.cmsel("S", f"__temp_{entity}s__")
3446+
self.cmsel(type_, f"__temp_{entity}s_1__")
3447+
3448+
# Cleaning
3449+
self.cmdele(f"__temp_{entity}s__")
3450+
self.cmdele(f"__temp_{entity}s_1__")
3451+
3452+
@wraps(Commands.nsel)
3453+
def nsel(self, *args, **kwargs):
3454+
"""Wraps previons NSEL to allow to use a list/tuple/array for vmin.
3455+
3456+
It will raise an error in case vmax or vinc are used too.
3457+
"""
3458+
sel_func = (
3459+
super().nsel
3460+
) # using super() inside the wrapped function confuses the references
3461+
3462+
@allow_pickable_points()
3463+
@wrap_point_SEL(entity="node")
3464+
def wrapped(self, *args, **kwargs):
3465+
return sel_func(*args, **kwargs)
3466+
3467+
return wrapped(self, *args, **kwargs)
3468+
3469+
@wraps(Commands.ksel)
3470+
def ksel(self, *args, **kwargs):
3471+
"""Wraps superclassed KSEL to allow to use a list/tuple/array for vmin.
3472+
3473+
It will raise an error in case vmax or vinc are used too.
3474+
"""
3475+
sel_func = (
3476+
super().ksel
3477+
) # using super() inside the wrapped function confuses the references
3478+
3479+
@allow_pickable_points(entity="kp", plot_function="kplot")
3480+
@wrap_point_SEL(entity="kp")
3481+
def wrapped(self, *args, **kwargs):
3482+
return sel_func(*args, **kwargs)
3483+
3484+
return wrapped(self, *args, **kwargs)

0 commit comments

Comments
 (0)