Skip to content

Commit d07ad38

Browse files
feat: Add measurement widget (#727)
Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com>
1 parent 0eef81b commit d07ad38

File tree

8 files changed

+184
-40
lines changed

8 files changed

+184
-40
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: 3
20+
RESET_IMAGE_CACHE: 4
2121
IS_WORKFLOW_RUNNING: True
2222
ARTIFACTORY_VERSION: v241
2323

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

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@
3333
from ansys.geometry.core.math.frame import Frame
3434
from ansys.geometry.core.math.plane import Plane
3535
from ansys.geometry.core.plotting.plotting_types import EdgePlot, GeomObjectPlot
36-
from ansys.geometry.core.plotting.widgets import (
37-
CameraPanDirection,
38-
DisplacementArrow,
39-
PlotterWidget,
40-
Ruler,
41-
ViewButton,
42-
ViewDirection,
43-
)
4436
from ansys.geometry.core.sketch.sketch import Sketch
4537

4638
DEFAULT_COLOR = "#D6F7D1"
@@ -102,16 +94,7 @@ def __init__(
10294

10395
# geometry objects to actors mapping
10496
self._geom_object_actors_map = {}
105-
106-
# Create Plotter widgets
107-
if enable_widgets:
108-
self._widgets: List[PlotterWidget] = []
109-
self._widgets.append(Ruler(self._scene))
110-
[
111-
self._widgets.append(DisplacementArrow(self._scene, direction=dir))
112-
for dir in CameraPanDirection
113-
]
114-
[self._widgets.append(ViewButton(self._scene, direction=dir)) for dir in ViewDirection]
97+
self._enable_widgets = enable_widgets
11598

11699
@property
117100
def scene(self) -> PyVistaPlotter:
@@ -245,7 +228,8 @@ def plot_sketch(
245228
if show_frame:
246229
self.plot_frame(sketch._plane)
247230

248-
self.add_sketch_polydata(sketch.sketch_polydata(), **plotting_options)
231+
self.add_sketch_polydata(sketch.sketch_polydata_faces(), opacity=0.7, **plotting_options)
232+
self.add_sketch_polydata(sketch.sketch_polydata_edges(), **plotting_options)
249233

250234
def add_body_edges(self, body_plot: GeomObjectPlot, **plotting_options: Optional[dict]) -> None:
251235
"""
@@ -498,9 +482,6 @@ def show(
498482
# Enabling anti-aliasing by default on scene
499483
self.scene.enable_anti_aliasing("ssaa")
500484

501-
# Update all buttons/widgets
502-
[widget.update() for widget in self._widgets]
503-
504485
self.scene.show(jupyter_backend=jupyter_backend, **kwargs)
505486

506487
def __set_add_mesh_defaults(self, plotting_options: Optional[Dict]) -> None:

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

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@
3434
)
3535
from ansys.geometry.core.plotting.plotting_types import EdgePlot, GeomObjectPlot
3636
from ansys.geometry.core.plotting.trame_gui import _HAS_TRAME, TrameVisualizer
37+
from ansys.geometry.core.plotting.widgets import (
38+
CameraPanDirection,
39+
DisplacementArrow,
40+
MeasureWidget,
41+
PlotterWidget,
42+
Ruler,
43+
ViewButton,
44+
ViewDirection,
45+
)
3746

3847

3948
class PlotterHelper:
@@ -68,6 +77,7 @@ def __init__(
6877
self._picked_list = set()
6978
self._picker_added_actors_map = {}
7079
self._edge_actors_map = {}
80+
self._widgets = []
7181

7282
if self._use_trame and _HAS_TRAME:
7383
# avoids GUI window popping up
@@ -83,10 +93,23 @@ def __init__(
8393
else:
8494
self._pl = Plotter()
8595

86-
if self._allow_picking:
87-
self._pl.scene.enable_mesh_picking(
88-
callback=self.picker_callback, use_actor=True, show=False, show_message=False
89-
)
96+
self._enable_widgets = self._pl._enable_widgets
97+
98+
def enable_widgets(self):
99+
"""Enable the widgets for the plotter."""
100+
# Create Plotter widgets
101+
if self._enable_widgets:
102+
self._widgets: List[PlotterWidget] = []
103+
self._widgets.append(Ruler(self._pl._scene))
104+
[
105+
self._widgets.append(DisplacementArrow(self._pl._scene, direction=dir))
106+
for dir in CameraPanDirection
107+
]
108+
[
109+
self._widgets.append(ViewButton(self._pl._scene, direction=dir))
110+
for dir in ViewDirection
111+
]
112+
self._widgets.append(MeasureWidget(self))
90113

91114
def select_object(self, geom_object: Union[GeomObjectPlot, EdgePlot], pt: np.ndarray) -> None:
92115
"""
@@ -204,6 +227,16 @@ def compute_edge_object_map(self) -> Dict[pv.Actor, EdgePlot]:
204227
for edge in object.edges:
205228
self._edge_actors_map[edge.actor] = edge
206229

230+
def enable_picking(self):
231+
"""Enable picking capabilities in the plotter."""
232+
self._pl.scene.enable_mesh_picking(
233+
callback=self.picker_callback, use_actor=True, show=False, show_message=False
234+
)
235+
236+
def disable_picking(self):
237+
"""Disable picking capabilities in the plotter."""
238+
self._pl.scene.disable_picking()
239+
207240
def plot(
208241
self,
209242
object: Any,
@@ -262,6 +295,15 @@ def plot(
262295
vector=view_2d["vector"],
263296
viewup=view_2d["viewup"],
264297
)
298+
299+
# Enable widgets and picking capabilities
300+
self.enable_widgets()
301+
if self._allow_picking:
302+
self.enable_picking()
303+
304+
# Update all buttons/widgets
305+
[widget.update() for widget in self._widgets]
306+
265307
self.show_plotter(screenshot)
266308

267309
picked_objects_list = []

src/ansys/geometry/core/plotting/widgets/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
CameraPanDirection,
2525
DisplacementArrow,
2626
)
27+
from ansys.geometry.core.plotting.widgets.measure import MeasureWidget
2728
from ansys.geometry.core.plotting.widgets.ruler import Ruler
2829
from ansys.geometry.core.plotting.widgets.view_button import ViewButton, ViewDirection
2930
from ansys.geometry.core.plotting.widgets.widget import PlotterWidget
Loading
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright (C) 2023 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+
"""Provides the ruler widget for the PyAnsys Geometry plotter."""
23+
24+
import os
25+
26+
from vtk import vtkActor, vtkButtonWidget, vtkPNGReader
27+
28+
from ansys.geometry.core.plotting.widgets.widget import PlotterWidget
29+
30+
31+
class MeasureWidget(PlotterWidget):
32+
"""
33+
Provides the ruler widget for the PyAnsys Geometry ``Plotter`` class.
34+
35+
Parameters
36+
----------
37+
plotter : ~pyvista.Plotter
38+
Provides the plotter to add the ruler widget to.
39+
"""
40+
41+
def __init__(self, plotter_helper: "PlotterHelper") -> None:
42+
"""Initialize the ``Ruler`` class."""
43+
# Call PlotterWidget ctor
44+
super().__init__(plotter_helper._pl.scene)
45+
46+
# Initialize variables
47+
self._actor: vtkActor = None
48+
self.plotter_helper = plotter_helper
49+
self._button: vtkButtonWidget = self.plotter_helper._pl.scene.add_checkbox_button_widget(
50+
self.callback, position=(10, 60), size=30, border_size=3
51+
)
52+
53+
def callback(self, state: bool) -> None:
54+
"""
55+
Remove or add the measurement widget actor upon click.
56+
57+
Parameters
58+
----------
59+
state : bool
60+
State of the button, which is inherited from PyVista. The value is ``True``
61+
if the button is active.
62+
"""
63+
# This implementation uses direct calls to VTK due to limitations
64+
# in PyVista. If there are improvements in the compatibility between
65+
# the PyVista picker and the measurement widget, this should be reviewed.
66+
if not state:
67+
self._widget.Off()
68+
self.plotter_helper._pl.scene.clear_measure_widgets()
69+
if self.plotter_helper._allow_picking:
70+
self.plotter_helper.enable_picking()
71+
else:
72+
if self.plotter_helper._allow_picking:
73+
self.plotter_helper.disable_picking()
74+
self._widget = self.plotter_helper._pl.scene.add_measurement_widget()
75+
76+
def update(self) -> None:
77+
"""Define the measurement widget button params."""
78+
show_ruler_vr = self._button.GetRepresentation()
79+
show_ruler_icon_file = os.path.join(os.path.dirname(__file__), "_images", "measurement.png")
80+
show_ruler_r = vtkPNGReader()
81+
show_ruler_r.SetFileName(show_ruler_icon_file)
82+
show_ruler_r.Update()
83+
image = show_ruler_r.GetOutput()
84+
show_ruler_vr.SetButtonTexture(0, image)
85+
show_ruler_vr.SetButtonTexture(1, image)

src/ansys/geometry/core/sketch/sketch.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -910,18 +910,36 @@ def sketch_polydata(self) -> List["PolyData"]:
910910
List of the polydata configuration for all edges and faces in the sketch.
911911
"""
912912
sketches_polydata = []
913-
sketches_polydata.extend(
914-
[
915-
edge.visualization_polydata.transform(self._plane.transformation_matrix)
916-
for edge in self.edges
917-
]
918-
)
913+
sketches_polydata.extend(self.sketch_polydata_edges())
914+
sketches_polydata.extend(self.sketch_polydata_faces())
915+
return sketches_polydata
919916

920-
sketches_polydata.extend(
921-
[
922-
face.visualization_polydata.transform(self._plane.transformation_matrix)
923-
for face in self.faces
924-
]
925-
)
917+
def sketch_polydata_faces(self) -> List["PolyData"]:
918+
"""
919+
Get polydata configuration for all faces of the sketch to the scene.
926920
927-
return sketches_polydata
921+
Returns
922+
-------
923+
List[~pyvista.PolyData]
924+
List of the polydata configuration for faces in the sketch.
925+
"""
926+
sketches_polydata_faces = [
927+
face.visualization_polydata.transform(self._plane.transformation_matrix)
928+
for face in self.faces
929+
]
930+
return sketches_polydata_faces
931+
932+
def sketch_polydata_edges(self) -> List["PolyData"]:
933+
"""
934+
Get polydata configuration for all edges of the sketch to the scene.
935+
936+
Returns
937+
-------
938+
List[~pyvista.PolyData]
939+
List of the polydata configuration for edges in the sketch.
940+
"""
941+
sketches_polydata_edges = [
942+
edge.visualization_polydata.transform(self._plane.transformation_matrix)
943+
for edge in self.edges
944+
]
945+
return sketches_polydata_edges

tests/test_sketch.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,3 +753,20 @@ def test_arc_from_three_points():
753753
for point in pd.points:
754754
if point[1] > 0:
755755
assert point[0] < 0 or np.isclose(point[0], 0)
756+
757+
758+
def test_polydata_methods():
759+
sketch = Sketch()
760+
sketch.polygon(Point2D([10, 10], UNITS.m), Quantity(10, UNITS.m), sides=5, tag="Polygon1")
761+
sketch.arc(
762+
Point2D([10, 10], UNITS.m),
763+
Point2D([10, -10], UNITS.m),
764+
Point2D([10, 0], UNITS.m),
765+
tag="Arc1",
766+
)
767+
pd = sketch.sketch_polydata()
768+
pd_faces = sketch.sketch_polydata_faces()
769+
pd_edges = sketch.sketch_polydata_edges()
770+
assert len(pd) == 2
771+
assert len(pd_edges) == 1
772+
assert len(pd_faces) == 1

0 commit comments

Comments
 (0)