Skip to content

Commit 419b0cc

Browse files
feat: Add real edges to the plotter (#670)
Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com>
1 parent 2a54329 commit 419b0cc

File tree

3 files changed

+331
-63
lines changed

3 files changed

+331
-63
lines changed

.github/workflows/ci_cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ env:
1717
ANSRV_GEO_PORT: 700
1818
ANSRV_GEO_LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }}
1919
GEO_CONT_NAME: ans_geo
20-
RESET_IMAGE_CACHE: 1
20+
RESET_IMAGE_CACHE: 2
2121
IS_WORKFLOW_RUNNING: True
2222

2323
concurrency:

src/ansys/geometry/core/plotting/plotter.py

Lines changed: 133 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
"""Provides for plotting various PyAnsys Geometry objects."""
2-
from typing import Any
3-
4-
from beartype.typing import Dict, List, Optional
2+
from beartype.typing import Any, Dict, List, Optional, Union
53
import numpy as np
64
import pyvista as pv
75
from pyvista.plotting.tools import create_axes_marker
86

97
from ansys.geometry.core.designer import Body, Component, Design, MasterBody
108
from ansys.geometry.core.logger import LOG as logger
119
from ansys.geometry.core.math import Frame, Plane
10+
from ansys.geometry.core.plotting.plotting_types import EdgePlot, GeomObjectPlot
1211
from ansys.geometry.core.plotting.trame_gui import _HAS_TRAME, TrameVisualizer
1312
from ansys.geometry.core.plotting.widgets import (
1413
CameraPanDirection,
@@ -26,6 +25,12 @@
2625
PICKED_COLOR = "#BB6EEE"
2726
"""Color to use for the actors that are currently picked."""
2827

28+
EDGE_COLOR = "#000000"
29+
"""Default color to use for the edges."""
30+
31+
PICKED_EDGE_COLOR = "#9C9C9C"
32+
"""Color to use for the edges that are currently picked."""
33+
2934

3035
class Plotter:
3136
"""
@@ -71,6 +76,9 @@ def __init__(
7176
# Save the desired number of points
7277
self._num_points = num_points
7378

79+
# geometry objects to actors mapping
80+
self._geom_object_actors_map = {}
81+
7482
# Create Plotter widgets
7583
if enable_widgets:
7684
self._widgets: List[PlotterWidget] = []
@@ -215,9 +223,35 @@ def plot_sketch(
215223

216224
self.add_sketch_polydata(sketch.sketch_polydata(), **plotting_options)
217225

226+
def add_body_edges(self, body_plot: GeomObjectPlot, **plotting_options: Optional[dict]) -> None:
227+
"""
228+
Add the outer edges of a body to the plot.
229+
230+
This method has the side effect of adding the edges to the GeomObject that
231+
you pass through the parameters.
232+
233+
Parameters
234+
----------
235+
body : GeomObjectPlot
236+
Body of which to add the edges.
237+
**plotting_options : dict, default: None
238+
Keyword arguments. For allowable keyword arguments, see the
239+
:func:`pyvista.Plotter.add_mesh` method.
240+
"""
241+
edge_plot_list = []
242+
for edge in body_plot.object.edges:
243+
line = pv.Line(edge.start_point, edge.end_point)
244+
edge_actor = self.scene.add_mesh(
245+
line, line_width=10, color=EDGE_COLOR, **plotting_options
246+
)
247+
edge_actor.SetVisibility(False)
248+
edge_plot = EdgePlot(edge_actor, edge, body_plot)
249+
edge_plot_list.append(edge_plot)
250+
body_plot.edges = edge_plot_list
251+
218252
def add_body(
219253
self, body: Body, merge: Optional[bool] = False, **plotting_options: Optional[Dict]
220-
) -> str:
254+
) -> None:
221255
"""
222256
Add a body to the scene.
223257
@@ -232,11 +266,6 @@ def add_body(
232266
**plotting_options : dict, default: None
233267
Keyword arguments. For allowable keyword arguments,
234268
see the :func:`pyvista.Plotter.add_mesh` method.
235-
236-
Returns
237-
-------
238-
str
239-
Name of the added PyVista actor.
240269
"""
241270
# Use the default PyAnsys Geometry add_mesh arguments
242271
self.__set_add_mesh_defaults(plotting_options)
@@ -246,7 +275,9 @@ def add_body(
246275
else:
247276
actor = self.scene.add_mesh(dataset, **plotting_options)
248277

249-
return actor.name
278+
body_plot = GeomObjectPlot(actor=actor, object=body, add_body_edges=True)
279+
self.add_body_edges(body_plot)
280+
self._geom_object_actors_map[actor] = body_plot
250281

251282
def add_component(
252283
self,
@@ -303,15 +334,15 @@ def add_sketch_polydata(self, polydata_entries: List[pv.PolyData], **plotting_op
303334
"""
304335
# Use the default PyAnsys Geometry add_mesh arguments
305336
for polydata in polydata_entries:
306-
self.scene.add_mesh(polydata, **plotting_options)
337+
self.scene.add_mesh(polydata, color=EDGE_COLOR, **plotting_options)
307338

308339
def add(
309340
self,
310341
object: Any,
311342
merge_bodies: bool = False,
312343
merge_components: bool = False,
313344
**plotting_options,
314-
) -> Dict[str, str]:
345+
) -> Dict[pv.Actor, GeomObjectPlot]:
315346
"""
316347
Add any type of object to the scene.
317348
@@ -336,11 +367,11 @@ def add(
336367
337368
Returns
338369
-------
339-
Mapping[str, str]
370+
Dict[pv.Actor, GeomObjectPlot]
340371
Mapping between the pv.Actor and the PyAnsys Geometry object.
341372
"""
342373
logger.debug(f"Adding object type {type(object)} to the PyVista plotter")
343-
actor_name = None
374+
344375
if isinstance(object, List) and isinstance(object[0], pv.PolyData):
345376
self.add_sketch_polydata(object, **plotting_options)
346377
elif isinstance(object, pv.PolyData):
@@ -350,22 +381,20 @@ def add(
350381
elif isinstance(object, Sketch):
351382
self.plot_sketch(object, **plotting_options)
352383
elif isinstance(object, Body) or isinstance(object, MasterBody):
353-
actor_name = self.add_body(object, merge_bodies, **plotting_options)
384+
self.add_body(object, merge_bodies, **plotting_options)
354385
elif isinstance(object, Design) or isinstance(object, Component):
355-
actor_name = self.add_component(
356-
object, merge_components, merge_bodies, **plotting_options
357-
)
386+
self.add_component(object, merge_components, merge_bodies, **plotting_options)
358387
else:
359388
logger.warning(f"Object type {type(object)} can not be plotted.")
360-
return {actor_name: object.name} if actor_name else {}
389+
return self._geom_object_actors_map
361390

362391
def add_list(
363392
self,
364393
plotting_list: List[Any],
365394
merge_bodies: bool = False,
366395
merge_components: bool = False,
367396
**plotting_options,
368-
) -> Dict[str, str]:
397+
) -> Dict[pv.Actor, GeomObjectPlot]:
369398
"""
370399
Add a list of any type of object to the scene.
371400
@@ -390,17 +419,12 @@ def add_list(
390419
391420
Returns
392421
-------
393-
Mapping[str, str]
394-
Dictionary with the mapping between pv.Actor and PyAnsys Geometry objects.
422+
Dict[pv.Actor, GeomObjectPlot]
423+
Mapping between the pv.Actor and the PyAnsys Geometry objects.
395424
"""
396-
actors_objects_mapping = {}
397425
for object in plotting_list:
398-
actor_object_mapping = self.add(
399-
object, merge_bodies, merge_components, **plotting_options
400-
)
401-
if actor_object_mapping:
402-
actors_objects_mapping.update(actor_object_mapping)
403-
return actors_objects_mapping
426+
_ = self.add(object, merge_bodies, merge_components, **plotting_options)
427+
return self._geom_object_actors_map
404428

405429
def show(
406430
self,
@@ -490,10 +514,11 @@ def __init__(
490514
self._use_trame = use_trame
491515
self._allow_picking = allow_picking
492516
self._pv_off_screen_original = bool(pv.OFF_SCREEN)
493-
self._actor_object_mapping = {}
517+
self._geom_object_actors_map = {}
494518
self._pl = None
495519
self._picked_list = set()
496520
self._picker_added_actors_map = {}
521+
self._edge_actors_map = {}
497522

498523
if self._use_trame and _HAS_TRAME:
499524
# avoids GUI window popping up
@@ -511,10 +536,10 @@ def __init__(
511536

512537
if self._allow_picking:
513538
self._pl.scene.enable_mesh_picking(
514-
callback=self.picker_callback, use_actor=True, show=False
539+
callback=self.picker_callback, use_actor=True, show=False, show_message=False
515540
)
516541

517-
def select_object(self, actor: pv.Actor, body_name: str, pt: "np.Array") -> None:
542+
def select_object(self, geom_object: Union[GeomObjectPlot, EdgePlot], pt: "np.Array") -> None:
518543
"""
519544
Select an object in the plotter.
520545
@@ -523,17 +548,25 @@ def select_object(self, actor: pv.Actor, body_name: str, pt: "np.Array") -> None
523548
524549
Parameters
525550
----------
526-
actor : pv.Actor
527-
Actor on which to perform the operations.
528-
body_name : str
529-
Name of the Body to highlight.
551+
geom_object : Union[GeomObjectPlot, EdgePlot]
552+
Geometry object to select.
530553
pt : np.Array
531554
Set of points to determine the label position.
532555
"""
533556
added_actors = []
534-
actor.prop.show_edges = True
535-
actor.prop.color = PICKED_COLOR
536-
text = body_name
557+
558+
# Add edges if selecting an object
559+
if isinstance(geom_object, GeomObjectPlot):
560+
geom_object.actor.prop.color = PICKED_COLOR
561+
children_list = geom_object.edges
562+
for edge in children_list:
563+
edge.actor.SetVisibility(True)
564+
edge.actor.prop.color = EDGE_COLOR
565+
elif isinstance(geom_object, EdgePlot):
566+
geom_object.actor.prop.color = PICKED_EDGE_COLOR
567+
568+
text = geom_object.name
569+
537570
label_actor = self._pl.scene.add_point_labels(
538571
[pt],
539572
[text],
@@ -542,13 +575,12 @@ def select_object(self, actor: pv.Actor, body_name: str, pt: "np.Array") -> None
542575
render_points_as_spheres=False,
543576
show_points=False,
544577
)
545-
if body_name not in self._picked_list:
546-
self._picked_list.add(body_name)
578+
if geom_object.name not in self._picked_list:
579+
self._picked_list.add(geom_object.name)
547580
added_actors.append(label_actor)
581+
self._picker_added_actors_map[geom_object.actor.name] = added_actors
548582

549-
self._picker_added_actors_map[actor.name] = added_actors
550-
551-
def unselect_object(self, actor: pv.Actor, body_name: str) -> None:
583+
def unselect_object(self, geom_object: Union[GeomObjectPlot, EdgePlot]) -> None:
552584
"""
553585
Unselect an object in the plotter.
554586
@@ -557,17 +589,30 @@ def unselect_object(self, actor: pv.Actor, body_name: str) -> None:
557589
558590
Parameters
559591
----------
560-
actor : pv.Actor
561-
Actor that is currently highlighted
562-
body_name : str
563-
Body name to remove
592+
geom_object : Union[GeomObjectPlot, EdgePlot]
593+
Object to unselect.
564594
"""
565-
actor.prop.show_edges = False
566-
actor.prop.color = DEFAULT_COLOR
567-
self._picked_list.remove(body_name)
568-
if actor.name in self._picker_added_actors_map:
569-
self._pl.scene.remove_actor(self._picker_added_actors_map[actor.name])
570-
self._picker_added_actors_map.pop(actor.name)
595+
# remove actor from picked list and from scene
596+
object_name = geom_object.name
597+
if object_name in self._picked_list:
598+
self._picked_list.remove(object_name)
599+
600+
if isinstance(geom_object, GeomObjectPlot):
601+
geom_object.actor.prop.color = DEFAULT_COLOR
602+
elif isinstance(geom_object, EdgePlot):
603+
geom_object.actor.prop.color = EDGE_COLOR
604+
605+
if geom_object.actor.name in self._picker_added_actors_map:
606+
self._pl.scene.remove_actor(self._picker_added_actors_map[geom_object.actor.name])
607+
608+
# remove actor and its children(edges) from the scene
609+
if isinstance(geom_object, GeomObjectPlot):
610+
for edge in geom_object.edges:
611+
# hide edges in the scene
612+
edge.actor.SetVisibility(False)
613+
# recursion
614+
self.unselect_object(edge)
615+
self._picker_added_actors_map.pop(geom_object.actor.name)
571616

572617
def picker_callback(self, actor: "pv.Actor") -> None:
573618
"""
@@ -579,13 +624,36 @@ def picker_callback(self, actor: "pv.Actor") -> None:
579624
Actor that we are picking.
580625
"""
581626
pt = self._pl.scene.picked_point
582-
self._actor_object_mapping.keys
583-
if actor.name in self._actor_object_mapping:
584-
body_name = self._actor_object_mapping[actor.name]
585-
if body_name not in self._picked_list:
586-
self.select_object(actor, body_name, pt)
627+
628+
# if object is a body/component
629+
if actor in self._geom_object_actors_map:
630+
body_plot = self._geom_object_actors_map[actor]
631+
if body_plot.object.name not in self._picked_list:
632+
self.select_object(body_plot, pt)
633+
else:
634+
self.unselect_object(body_plot)
635+
636+
# if object is an edge
637+
elif actor in self._edge_actors_map and actor.GetVisibility():
638+
edge = self._edge_actors_map[actor]
639+
if edge.name not in self._picked_list:
640+
self.select_object(edge, pt)
587641
else:
588-
self.unselect_object(actor, body_name)
642+
self.unselect_object(edge)
643+
actor.prop.color = EDGE_COLOR
644+
645+
def compute_edge_object_map(self) -> Dict[pv.Actor, EdgePlot]:
646+
"""
647+
Compute the mapping between plotter actors and EdgePlot objects.
648+
649+
Returns
650+
-------
651+
Dict[pv.Actor, EdgePlot]
652+
Mapping between plotter actors and EdgePlot objects.
653+
"""
654+
for object in self._geom_object_actors_map.values():
655+
for edge in object.edges:
656+
self._edge_actors_map[edge.actor] = edge
589657

590658
def plot(
591659
self,
@@ -629,14 +697,17 @@ def plot(
629697
"""
630698
if isinstance(object, List) and not isinstance(object[0], pv.PolyData):
631699
logger.debug("Plotting objects in list...")
632-
self._actor_object_mapping = self._pl.add_list(
700+
self._geom_object_actors_map = self._pl.add_list(
633701
object, merge_bodies, merge_component, **plotting_options
634702
)
635703
else:
636-
self._actor_object_mapping = self._pl.add(
704+
self._geom_object_actors_map = self._pl.add(
637705
object, merge_bodies, merge_component, **plotting_options
638706
)
639707

708+
self.compute_edge_object_map()
709+
# Compute mapping between the objects and its edges.
710+
640711
if view_2d is not None:
641712
self._pl.scene.view_vector(
642713
vector=view_2d["vector"],

0 commit comments

Comments
 (0)