Skip to content

Commit 284359e

Browse files
SMoraisAnsyspyansys-ci-botpre-commit-ci[bot]StefanThoenepluAtAnsys
authored
feat: add screenshot in pyvista related methods (#521)
## Description Adding `screenshot` option to `preview` methods. On top of it, the CI and test configuration has been updated to allow this kind of test to without issues. This will allow us to increase the code coverage !!! ## Issue linked None ## Checklist - [x] I have tested my changes locally. - [x] I have added necessary documentation or updated existing documentation. - [x] I have followed the coding style guidelines of this project. - [x] I have added appropriate tests (unit, integration, system). - [x] I have reviewed my changes before submitting this pull request. - [x] I have linked the issue or issues that are solved by the PR if any. - [x] I have assigned this PR to myself. - [x] I have made sure that the title of my PR follows [Conventional commits style](https://www.conventionalcommits.org/en/v1.0.0/#summary) (e.g. ``feat: add optical property``) - [ ] I have agreed with the Contributor License Agreement ([CLA](https://developer.ansys.com/form/cla-acceptance)). ## Previous PR content, kept for visibility ~To increase code coverage (and fix bugs, see #520) this PR starts to add test(s) on a method using pyvista rendering.~ ~Three options are available to do so:~ - ~leverage an external github action to use [headless display](https://github.com/pyvista/setup-headless-display-action);~ - ~use the python package `vtk-osmesa` from vtk, see [available python wheels]~(https://docs.vtk.org/en/latest/advanced/available_python_wheels.html); - ~patch the call to `show` so that we don't really trigger them.~ ~While I like the two first options as they would let us really test everything, they also come with a security risk as we don't maintain the solution associated (tbh, I see little risk in using them though). Therefore, I'm following the third option. Depending on what you think about this, we could make this PR evolve.~ --------- Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: sthoene <[email protected]> Co-authored-by: Pengyuan LU <[email protected]> Co-authored-by: Stefan Thöne <[email protected]>
1 parent 95bd1ab commit 284359e

File tree

7 files changed

+91
-11
lines changed

7 files changed

+91
-11
lines changed

.github/workflows/ci_cd.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ jobs:
8585
username: ${{ github.actor }}
8686
password: ${{ secrets.GITHUB_TOKEN }}
8787
- name: Start container
88+
shell: bash
8889
env:
8990
ANSYSLMD_LICENSE_FILE: 1055@${{ secrets.LICENSE_SERVER }}
9091
run: |
@@ -99,6 +100,7 @@ jobs:
99100
dependencies: "pandoc"
100101
needs-quarto: true
101102
- name: Stop container
103+
shell: bash
102104
run: |
103105
docker kill speos-rpc
104106
docker rm speos-rpc
@@ -112,18 +114,26 @@ jobs:
112114
steps:
113115
- uses: actions/checkout@v4
114116

117+
- name: Installing OS packages
118+
shell: bash
119+
run: |
120+
sudo apt-get update
121+
sudo apt install -y libgl1 libglx-mesa0 xvfb libgomp1
122+
115123
- name: Set up Python
116124
uses: actions/setup-python@v5
117125
with:
118126
python-version: ${{ env.MAIN_PYTHON_VERSION }}
119127

120128
- name: Create Python virtual environment
129+
shell: bash
121130
run: |
122131
python -m venv .venv
123132
source .venv/bin/activate
124133
python -c "import sys; print(sys.executable)"
125134
126135
- name: Install packages for testing
136+
shell: bash
127137
run: |
128138
. .venv/bin/activate
129139
python -m pip install --upgrade pip
@@ -140,13 +150,15 @@ jobs:
140150
- name: Start container
141151
env:
142152
ANSYSLMD_LICENSE_FILE: 1055@${{ secrets.LICENSE_SERVER }}
153+
shell: bash
143154
run: |
144155
docker run --detach --name speos-rpc -p 50098:50098 -e SPEOS_LOG_LEVEL=2 -e ANSYSLMD_LICENSE_FILE=${{ env.ANSYSLMD_LICENSE_FILE }} -v "${{ github.workspace }}/tests/assets:/app/assets" --entrypoint /app/SpeosRPC_Server.x ghcr.io/ansys/speos-rpc:dev
145156
146157
- name: Run pytest
158+
shell: bash
147159
run: |
148160
. .venv/bin/activate
149-
pytest -xs
161+
xvfb-run pytest -xs
150162
151163
- name: Upload Coverage Results
152164
if: always()
@@ -164,6 +176,7 @@ jobs:
164176
files: .cov/xml
165177

166178
- name: Stop container
179+
shell: bash
167180
run: |
168181
docker kill speos-rpc
169182
docker rm speos-rpc

doc/changelog.d/521.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add screenshot in pyvista related methods

src/ansys/speos/core/lxp.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
from __future__ import annotations
3030

3131
import os
32-
from typing import Union
32+
from pathlib import Path
33+
from typing import Optional, Union
3334

3435
import pyvista as pv
3536

@@ -431,7 +432,7 @@ def __add_ray_to_pv(plotter: pv.Plotter, ray: RayPath, max_ray_length: float):
431432
max_ray_length : float
432433
Length of the last ray.
433434
"""
434-
temp = ray.impacts
435+
temp = ray.impacts.copy()
435436
if not 7 <= ray.intersection_type[-1] <= 15:
436437
temp.append(
437438
[
@@ -452,6 +453,7 @@ def preview(
452453
max_ray_length: float = 50.0,
453454
ray_filter: bool = False,
454455
project: Project = None,
456+
screenshot: Optional[Union[str, Path]] = None,
455457
) -> LightPathFinder:
456458
"""Preview LPF file with pyvista.
457459
@@ -465,11 +467,19 @@ def preview(
465467
Boolean to decide if filtered rays or all rays should be shown.
466468
project : ansys.speos.core.project.Project
467469
Speos Project/Geometry to be added to pyvista visualisation.
470+
screenshot : str or Path or ``None``
471+
Path to save a screenshot of the plotter.
468472
469473
Returns
470474
-------
471475
ansys.speos.core.lxp.LightPathFinder
472476
LightPathFinder Instance.
477+
478+
Notes
479+
-----
480+
Please use the ``q``-key to close the plotter as some
481+
operating systems (namely Windows) will experience issues
482+
saving a screenshot if the exit button in the GUI is pressed.
473483
"""
474484
if ray_filter:
475485
if len(self._filtered_rays) > 0:
@@ -495,10 +505,10 @@ def preview(
495505
else:
496506
for i in range(nb_ray):
497507
self.__add_ray_to_pv(plotter, temp_rays[i], max_ray_length)
498-
if os.environ.get("DOCUMENTATION_BUILDING", "true") == "true":
499-
plotter.show(jupyter_backend="html")
508+
if os.environ.get("DOCUMENTATION_BUILDING", "false") == "true":
509+
plotter.show(screenshot=screenshot, jupyter_backend="html")
500510
else:
501-
plotter.show()
511+
plotter.show(screenshot=screenshot)
502512
return self
503513

504514

src/ansys/speos/core/project.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from __future__ import annotations
2525

2626
import os
27+
from pathlib import Path
2728
import re
2829
from typing import List, Mapping, Optional, Union
2930
import uuid
@@ -924,7 +925,11 @@ def _create_preview(self, viz_args=None) -> pv.Plotter:
924925
p.add_mesh(_preview_mesh, show_edges=True, **viz_args)
925926
return p
926927

927-
def preview(self, viz_args=None) -> None:
928+
def preview(
929+
self,
930+
viz_args=None,
931+
screenshot: Optional[Union[str, Path]] = None,
932+
) -> None:
928933
"""Preview cad bodies inside the project's scene.
929934
930935
Parameters
@@ -934,12 +939,19 @@ def preview(self, viz_args=None) -> None:
934939
e.g.
935940
- {'style': 'wireframe'},
936941
- {'style': 'surface', 'color':'white'},
937-
- {'opacity': 0.7, 'color':'white', 'show_edges': False},
942+
- {'opacity': 0.7, 'color':'white', 'show_edges': False}.
943+
944+
screenshot : str or Path or ``None``
945+
Path to save a screenshot of the plotter.
946+
938947
"""
939948
if viz_args is None:
940949
viz_args = {"opacity": 1}
950+
if screenshot is not None:
951+
screenshot = Path(screenshot)
952+
941953
p = self._create_preview(viz_args=viz_args)
942954
if os.environ.get("DOCUMENTATION_BUILDING", "true") == "true":
943-
p.show(jupyter_backend="html")
955+
p.show(screenshot=screenshot, jupyter_backend="html")
944956
else:
945-
p.show()
957+
p.show(screenshot=screenshot)

tests/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
from ansys.speos.core import LOG
3939
from ansys.speos.core.speos import Speos
4040

41+
try:
42+
import pyvista as pv
43+
44+
pv.OFF_SCREEN = True
45+
pv.global_theme.window_size = [500, 500]
46+
except ImportError:
47+
pass
48+
49+
IMAGE_RESULTS_DIR = Path(Path(__file__).parent, "image_results")
50+
4151

4252
@pytest.fixture(scope="session")
4353
def speos():

tests/core/test_lxp.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,17 @@
2525
from pathlib import Path
2626

2727
import ansys.speos.core.lxp as lxp
28+
from ansys.speos.core.project import Project
2829
from ansys.speos.core.speos import Speos
29-
from tests.conftest import test_path
30+
from tests.conftest import IMAGE_RESULTS_DIR, test_path
3031

3132

3233
def test_light_path_finder_direct(speos: Speos):
3334
"""Test for direct simulation lpf."""
3435
path = str(Path(test_path) / "basic_DirectSimu.lpf")
3536
lpf = lxp.LightPathFinder(speos=speos, path=path)
37+
screenshot = Path(IMAGE_RESULTS_DIR, "test_light_path_finder_preview.png")
38+
lpf.preview(screenshot=screenshot)
3639
expected_ray = {
3740
"nb_impacts": 4,
3841
"impacts": [
@@ -123,3 +126,32 @@ def test_light_path_finder_inverse(speos: Speos):
123126
lpf.filter_error_rays()
124127
assert len(lpf.filtered_rays) == 0
125128
assert lpf.rays[50].get() == expected_ray
129+
130+
131+
def test_lpf_preview_with_project(speos: Speos):
132+
"""Test for visualizing lpf data."""
133+
path = str(Path(test_path) / "basic_DirectSimu.lpf")
134+
screenshot = Path(IMAGE_RESULTS_DIR, "test_light_path_finder_direct.png")
135+
p = Project(
136+
speos=speos,
137+
path=str(
138+
Path(test_path) / "LG_50M_Colorimetric_short.sv5" / "LG_50M_Colorimetric_short.sv5"
139+
),
140+
)
141+
lpf = lxp.LightPathFinder(speos=speos, path=path)
142+
lpf.preview(project=p, screenshot=screenshot)
143+
assert screenshot.exists()
144+
assert screenshot.stat().st_size > 0
145+
146+
147+
def test_lpf_preview_without_project(speos: Speos):
148+
"""Test for visualizing lpf data."""
149+
path = str(Path(test_path) / "basic_DirectSimu.lpf")
150+
screenshot = Path(IMAGE_RESULTS_DIR, "test_lpf_preview_without_project.png")
151+
lpf1 = lxp.LightPathFinder(speos=speos, path=path)
152+
lpf1.filter_by_body_ids([3601101451])
153+
lpf1.filter_by_face_ids([3866239813], new=False)
154+
lpf1.remove_error_rays()
155+
lpf1.preview(ray_filter=True, screenshot=screenshot)
156+
assert screenshot.exists()
157+
assert screenshot.stat().st_size > 0

tests/image_results/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

0 commit comments

Comments
 (0)