From 7d78e7f8eae7304c66a33c23f8977ee5fe68c512 Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 29 Mar 2025 17:19:30 +0000 Subject: [PATCH 01/33] add preview irradiance sensor --- src/ansys/speos/core/project.py | 70 ++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index ae665de5f..74e7d9751 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -901,6 +901,7 @@ def _create_preview(self, viz_args=None) -> pv.Plotter: """ if viz_args is None: viz_args = {} + p = pv.Plotter() _preview_mesh = pv.PolyData() # Retrieve root part root_part_data = self.client[self.scene_link.get().part_guid].get() @@ -920,8 +921,75 @@ def _create_preview(self, viz_args=None) -> pv.Plotter: poly_data = self.__extract_part_mesh_info(part_data=root_part_data) if poly_data is not None: _preview_mesh = _preview_mesh.append_polydata(poly_data) - p = pv.Plotter() p.add_mesh(_preview_mesh, show_edges=True, **viz_args) + + # Add sensor at the root part + for feature in self._features: + if isinstance(feature, SensorIrradiance): + feature_pos_info = feature.get(key="axis_system") + feature_irradiance_pos = np.array(feature_pos_info[:3]) + feature_irradiance_x_dir = np.array(feature_pos_info[3:6]) + feature_irradiance_y_dir = np.array(feature_pos_info[6:9]) + feature_irradiance_z_dir = np.array(feature_pos_info[9:12]) + feature_x_start = feature.get(key="x_start") + feature_x_end = feature.get(key="x_end") + feature_y_start = feature.get(key="y_start") + feature_y_end = feature.get(key="y_end") + + # irradiance sensor + p1 = ( + feature_irradiance_pos + + feature_irradiance_x_dir * feature_x_end + + feature_irradiance_y_dir * feature_y_end + ) + p2 = ( + feature_irradiance_pos + + feature_irradiance_x_dir * feature_x_start + + feature_irradiance_y_dir * feature_y_start + ) + p3 = ( + feature_irradiance_pos + + feature_irradiance_x_dir * feature_x_end + + feature_irradiance_y_dir * feature_y_start + ) + rectangle = pv.Rectangle([p1, p2, p3]) + p.add_mesh( + rectangle, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + + # irradiance direction + x_arrow = pv.Arrow( + start=feature_irradiance_pos, + direction=feature_irradiance_x_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + y_arrow = pv.Arrow( + start=feature_irradiance_pos, + direction=feature_irradiance_y_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + integration_arrow = pv.Arrow( + start=feature_irradiance_pos, + direction=feature_irradiance_z_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + p.add_mesh(x_arrow, color="red") + p.add_mesh(y_arrow, color="green") + p.add_mesh(integration_arrow, color="blue") return p def preview(self, viz_args=None) -> None: From 38e1a405d4fd79428a254e436a536d9ec97c0863 Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 29 Mar 2025 17:20:08 +0000 Subject: [PATCH 02/33] add preview radiance sensor --- src/ansys/speos/core/project.py | 100 ++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 74e7d9751..1c2cbf66d 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -990,6 +990,106 @@ def _create_preview(self, viz_args=None) -> pv.Plotter: p.add_mesh(x_arrow, color="red") p.add_mesh(y_arrow, color="green") p.add_mesh(integration_arrow, color="blue") + if isinstance(feature, SensorRadiance): + feature_pos_info = feature.get(key="axis_system") + feature_radiance_pos = np.array(feature_pos_info[:3]) + feature_radiance_x_dir = np.array(feature_pos_info[3:6]) + feature_radiance_y_dir = np.array(feature_pos_info[6:9]) + feature_radiance_z_dir = np.array(feature_pos_info[9:12]) + feature_x_start = feature.get(key="x_start") + feature_x_end = feature.get(key="x_end") + feature_y_start = feature.get(key="y_start") + feature_y_end = feature.get(key="y_end") + feature_radiance_focal = feature.get(key="focal") + + # radiance sensor + p1 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_end + + feature_radiance_y_dir * feature_y_end + ) + p2 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_end + + feature_radiance_y_dir * feature_y_start + ) + p3 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_start + + feature_radiance_y_dir * feature_y_start + ) + p4 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_start + + feature_radiance_y_dir * feature_y_end + ) + rectangle = pv.Rectangle([p1, p2, p3]) + p.add_mesh( + rectangle, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p5 = feature_radiance_pos + feature_radiance_z_dir * feature_radiance_focal + faces = np.hstack([[3, 0, 1, 2]]) + radiance_f1 = pv.PolyData(np.array([p1, p2, p5]), faces) + radiance_f2 = pv.PolyData(np.array([p3, p4, p5]), faces) + radiance_f3 = pv.PolyData(np.array([p1, p4, p5]), faces) + radiance_f4 = pv.PolyData(np.array([p2, p3, p5]), faces) + p.add_mesh( + radiance_f1, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.add_mesh( + radiance_f2, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.add_mesh( + radiance_f3, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.add_mesh( + radiance_f4, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + + # radiance direction + x_arrow = pv.Arrow( + start=feature_radiance_y_dir, + direction=feature_radiance_x_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + y_arrow = pv.Arrow( + start=feature_radiance_y_dir, + direction=feature_radiance_y_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + p.add_mesh(x_arrow, color="red") + p.add_mesh(y_arrow, color="green") return p def preview(self, viz_args=None) -> None: From 0f60d136464aca64bbb6d7c22d89703354980cc9 Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 29 Mar 2025 17:20:40 +0000 Subject: [PATCH 03/33] add preview camera sensor --- src/ansys/speos/core/project.py | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 1c2cbf66d..5d859e2dc 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -1090,6 +1090,113 @@ def _create_preview(self, viz_args=None) -> pv.Plotter: ) p.add_mesh(x_arrow, color="red") p.add_mesh(y_arrow, color="green") + if isinstance(feature, SensorCamera): + feature_pos_info = feature.get(key="axis_system") + feature_camera_pos = np.array(feature_pos_info[:3]) + feature_camera_x_dir = np.array(feature_pos_info[3:6]) + feature_camera_y_dir = np.array(feature_pos_info[6:9]) + feature_camera_z_dir = np.array(feature_pos_info[9:12]) + feature_width = feature.get(key="width") + feature_height = feature.get(key="height") + feature_camera_focal = feature.get(key="focal_length") + feature_camera_image_dis = feature.get(key="imager_distance") + + # camera radiance sensor + p1 = ( + feature_camera_pos + + feature_camera_x_dir * feature_width / 2.0 + + feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + p2 = ( + feature_camera_pos + + feature_camera_x_dir * feature_width / 2.0 + - feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + p3 = ( + feature_camera_pos + - feature_camera_x_dir * feature_width / 2.0 + + feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + p4 = ( + feature_camera_pos + - feature_camera_x_dir * feature_width / 2.0 + - feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + rectangle = pv.Rectangle([p1, p2, p3]) + p.add_mesh( + rectangle, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p5 = feature_camera_pos + feature_camera_z_dir * ( + feature_camera_image_dis - feature_camera_focal + ) + faces = np.hstack([[3, 0, 1, 2]]) + radiance_f1 = pv.PolyData(np.array([p1, p2, p5]), faces) + radiance_f2 = pv.PolyData(np.array([p3, p4, p5]), faces) + radiance_f3 = pv.PolyData(np.array([p1, p3, p5]), faces) + radiance_f4 = pv.PolyData(np.array([p2, p4, p5]), faces) + p.add_mesh( + radiance_f1, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.add_mesh( + radiance_f2, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.add_mesh( + radiance_f3, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.add_mesh( + radiance_f4, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + + # camera object field + camera_object_field_radius = 100 + object_field_sphere = pv.Sphere( + radius=camera_object_field_radius, + center=feature_camera_pos, + direction=feature_camera_x_dir, + theta_resolution=30, + phi_resolution=30, + start_theta=0.0, + end_theta=60.0, + start_phi=45, + end_phi=135, + ) + p.add_mesh( + object_field_sphere, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) return p def preview(self, viz_args=None) -> None: From 3cca6ee0a771434ceb4830cc8c33363454a07e1a Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Sat, 29 Mar 2025 17:23:32 +0000 Subject: [PATCH 04/33] chore: adding changelog file 528.added.md [dependabot-skip] --- doc/changelog.d/528.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/528.added.md diff --git a/doc/changelog.d/528.added.md b/doc/changelog.d/528.added.md new file mode 100644 index 000000000..db2422329 --- /dev/null +++ b/doc/changelog.d/528.added.md @@ -0,0 +1 @@ +enhance the project preview \ No newline at end of file From 058e88a89cedbd7870183a17e94e93e68d49ea5d Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 1 Apr 2025 16:17:15 +0100 Subject: [PATCH 05/33] create pyvista visualization data at each sensor class --- src/ansys/speos/core/sensor.py | 317 +++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 727554372..852ff6976 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -28,6 +28,9 @@ from typing import List, Mapping, Optional, Union import uuid +import numpy as np +import pyvista as pv + from ansys.api.speos.sensor.v1 import camera_sensor_pb2, common_pb2 from ansys.speos.core.geo_ref import GeoRef from ansys.speos.core.kernel.scene import ProtoScene @@ -76,6 +79,8 @@ def __init__( self._project = project self._name = name self._unique_id = None + self._visual_data = self.VisualData() + self._visual_data_updated = False self.sensor_template_link = None """Link object for the sensor template in database.""" if metadata is None: @@ -98,6 +103,34 @@ def __init__( # (using _unique_id) and sensor_template_link self.reset() + class VisualData: + """Visualization data for the sensor. + + By default, there is empty visualization data. + + Parameters + ---------- + data : pyvista.PolyData + Surface data defining the visualization faces. + x_axis : pyvista.PolyData + x-axis arrow data. + y_axis : pyvista.PolyData + y-axis arrow data. + z_axis: pyvista.PolyData + z-axis arrow data. + + Notes + ----- + **Do not instantiate this class yourself**, use set_dimensions method available in sensor + classes. + """ + + def __init__(self) -> None: + self.data = pv.PolyData() + self.x_axis = pv.PolyData() + self.y_axis = pv.PolyData() + self.z_axis = pv.PolyData() + class WavelengthsRange: """Range of wavelengths. @@ -787,6 +820,8 @@ def commit(self) -> BaseSensor: ansys.speos.core.sensor.BaseSensor Sensor feature. """ + self._visual_data_updated = False + # The _unique_id will help to find the correct item in the scene.sensors: # the list of SensorInstance if self._unique_id is None: @@ -1596,6 +1631,123 @@ def __init__( # Default values properties self.set_axis_system() + @property + def visual_data(self) -> BaseSensor.VisualData: + """Property containing camera sensor visualization data. + + Returns + ------- + BaseSensor.VisualData + Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems. + + """ + if self._visual_data_updated: + return self._visual_data + else: + feature_pos_info = self.get(key="axis_system") + feature_camera_pos = np.array(feature_pos_info[:3]) + feature_camera_x_dir = np.array(feature_pos_info[3:6]) + feature_camera_y_dir = np.array(feature_pos_info[6:9]) + feature_camera_z_dir = np.array(feature_pos_info[9:12]) + feature_width = self.get(key="width") + feature_height = self.get(key="height") + feature_camera_focal = self.get(key="focal_length") + feature_camera_image_dis = self.get(key="imager_distance") + + # camera radiance sensor + p1 = ( + feature_camera_pos + + feature_camera_x_dir * feature_width / 2.0 + + feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + p2 = ( + feature_camera_pos + + feature_camera_x_dir * feature_width / 2.0 + - feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + p3 = ( + feature_camera_pos + - feature_camera_x_dir * feature_width / 2.0 + + feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + p4 = ( + feature_camera_pos + - feature_camera_x_dir * feature_width / 2.0 + - feature_camera_y_dir * feature_height / 2.0 + + feature_camera_z_dir * feature_camera_image_dis + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.Rectangle([p1, p2, p3]) + ) + + p5 = feature_camera_pos + feature_camera_z_dir * ( + feature_camera_image_dis - feature_camera_focal + ) + faces = np.hstack([[3, 0, 1, 2]]) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p1, p2, p5]), faces) + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p3, p4, p5]), faces) + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p1, p3, p5]), faces) + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p2, p4, p5]), faces) + ) + + # camera object field + camera_object_field_radius = 100 + self._visual_data.data = self._visual_data.data.append_polydata( + pv.Sphere( + radius=camera_object_field_radius, + center=feature_camera_pos, + direction=feature_camera_x_dir, + theta_resolution=30, + phi_resolution=30, + start_theta=0.0, + end_theta=60.0, + start_phi=45, + end_phi=135, + ) + ) + + # camera axis system + self._visual_data.x_axis = self._visual_data.x_axis.append_polydata( + pv.Arrow( + start=feature_camera_pos, + direction=feature_camera_x_dir, + scale=max(feature_width, feature_height) / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + self._visual_data.y_axis = self._visual_data.y_axis.append_polydata( + pv.Arrow( + start=feature_camera_pos, + direction=feature_camera_y_dir, + scale=max(feature_width, feature_height) / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + self._visual_data.z_axis = self._visual_data.z_axis.append_polydata( + pv.Arrow( + start=feature_camera_pos, + direction=feature_camera_z_dir, + scale=max(feature_width, feature_height) / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + + self._visual_data_updated = True + return self._visual_data + @property def photometric(self) -> Union[SensorCamera.Photometric, None]: """Property containing the instance of SensorCamera.Photometric used to build the sensor. @@ -1884,6 +2036,83 @@ def __init__( # Default values properties self.set_axis_system().set_ray_file_type_none().set_layer_type_none() + @property + def visual_data(self) -> BaseSensor.VisualData: + """Property containing irradiance sensor visualization data. + + Returns + ------- + BaseSensor.VisualData + Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems. + + """ + if self._visual_data_updated is True: + return self._visual_data + else: + feature_pos_info = self.get(key="axis_system") + feature_irradiance_pos = np.array(feature_pos_info[:3]) + feature_irradiance_x_dir = np.array(feature_pos_info[3:6]) + feature_irradiance_y_dir = np.array(feature_pos_info[6:9]) + feature_irradiance_z_dir = np.array(feature_pos_info[9:12]) + feature_x_start = self.get(key="x_start") + feature_x_end = self.get(key="x_end") + feature_y_start = self.get(key="y_start") + feature_y_end = self.get(key="y_end") + + # irradiance sensor + p1 = ( + feature_irradiance_pos + + feature_irradiance_x_dir * feature_x_end + + feature_irradiance_y_dir * feature_y_end + ) + p2 = ( + feature_irradiance_pos + + feature_irradiance_x_dir * feature_x_start + + feature_irradiance_y_dir * feature_y_start + ) + p3 = ( + feature_irradiance_pos + + feature_irradiance_x_dir * feature_x_end + + feature_irradiance_y_dir * feature_y_start + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.Rectangle([p1, p2, p3]) + ) + + # irradiance direction + self._visual_data.x_axis = self._visual_data.x_axis.append_polydata( + pv.Arrow( + start=feature_irradiance_pos, + direction=feature_irradiance_x_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + self._visual_data.y_axis = self._visual_data.y_axis.append_polydata( + pv.Arrow( + start=feature_irradiance_pos, + direction=feature_irradiance_y_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + self._visual_data.z_axis = self._visual_data.z_axis.append_polydata( + pv.Arrow( + start=feature_irradiance_pos, + direction=feature_irradiance_z_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + self._visual_data_updated = True + return self._visual_data + @property def dimensions(self) -> BaseSensor.Dimensions: """Property containing all options in regard to the Dimensions sensor properties. @@ -2486,6 +2715,94 @@ def __init__( # Default values properties self.set_axis_system().set_layer_type_none() + @property + def visual_data(self) -> BaseSensor.VisualData: + """Property containing radiance sensor visualization data. + + Returns + ------- + BaseSensor.VisualData + Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems. + + """ + if self._visual_data_updated: + return self._visual_data + else: + feature_pos_info = self.get(key="axis_system") + feature_radiance_pos = np.array(feature_pos_info[:3]) + feature_radiance_x_dir = np.array(feature_pos_info[3:6]) + feature_radiance_y_dir = np.array(feature_pos_info[6:9]) + feature_radiance_z_dir = np.array(feature_pos_info[9:12]) + feature_x_start = self.get(key="x_start") + feature_x_end = self.get(key="x_end") + feature_y_start = self.get(key="y_start") + feature_y_end = self.get(key="y_end") + feature_radiance_focal = self.get(key="focal") + + # radiance sensor + p1 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_end + + feature_radiance_y_dir * feature_y_end + ) + p2 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_end + + feature_radiance_y_dir * feature_y_start + ) + p3 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_start + + feature_radiance_y_dir * feature_y_start + ) + p4 = ( + feature_radiance_pos + + feature_radiance_x_dir * feature_x_start + + feature_radiance_y_dir * feature_y_end + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.Rectangle([p1, p2, p3]) + ) + + p5 = feature_radiance_pos + feature_radiance_z_dir * feature_radiance_focal + faces = np.hstack([[3, 0, 1, 2]]) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p1, p2, p5]), faces) + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p3, p4, p5]), faces) + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p1, p4, p5]), faces) + ) + self._visual_data.data = self._visual_data.data.append_polydata( + pv.PolyData(np.array([p2, p3, p5]), faces) + ) + + # radiance direction + self._visual_data.x_axis = self._visual_data.x_axis.append_polydata( + pv.Arrow( + start=feature_radiance_y_dir, + direction=feature_radiance_x_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + self._visual_data.y_axis = self._visual_data.y_axis.append_polydata( + pv.Arrow( + start=feature_radiance_y_dir, + direction=feature_radiance_y_dir, + scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) + / 4.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + ) + self._visual_data_updated = True + return self._visual_data + @property def dimensions(self) -> BaseSensor.Dimensions: """Property containing all options in regard to the Dimensions sensor properties. From b25fede088b5b19550fd55de3999bb270d99eab1 Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 1 Apr 2025 16:18:09 +0100 Subject: [PATCH 06/33] refactor the _create_preview() method where visualization data is retrieved from sensor class. --- src/ansys/speos/core/project.py | 257 ++------------------------------ 1 file changed, 10 insertions(+), 247 deletions(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 5d859e2dc..5bd8419e2 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -926,277 +926,40 @@ def _create_preview(self, viz_args=None) -> pv.Plotter: # Add sensor at the root part for feature in self._features: if isinstance(feature, SensorIrradiance): - feature_pos_info = feature.get(key="axis_system") - feature_irradiance_pos = np.array(feature_pos_info[:3]) - feature_irradiance_x_dir = np.array(feature_pos_info[3:6]) - feature_irradiance_y_dir = np.array(feature_pos_info[6:9]) - feature_irradiance_z_dir = np.array(feature_pos_info[9:12]) - feature_x_start = feature.get(key="x_start") - feature_x_end = feature.get(key="x_end") - feature_y_start = feature.get(key="y_start") - feature_y_end = feature.get(key="y_end") - - # irradiance sensor - p1 = ( - feature_irradiance_pos - + feature_irradiance_x_dir * feature_x_end - + feature_irradiance_y_dir * feature_y_end - ) - p2 = ( - feature_irradiance_pos - + feature_irradiance_x_dir * feature_x_start - + feature_irradiance_y_dir * feature_y_start - ) - p3 = ( - feature_irradiance_pos - + feature_irradiance_x_dir * feature_x_end - + feature_irradiance_y_dir * feature_y_start - ) - rectangle = pv.Rectangle([p1, p2, p3]) p.add_mesh( - rectangle, + feature.visual_data.data, show_edges=True, line_width=2, edge_color="red", color="orange", opacity=0.5, ) - - # irradiance direction - x_arrow = pv.Arrow( - start=feature_irradiance_pos, - direction=feature_irradiance_x_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) - y_arrow = pv.Arrow( - start=feature_irradiance_pos, - direction=feature_irradiance_y_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) - integration_arrow = pv.Arrow( - start=feature_irradiance_pos, - direction=feature_irradiance_z_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) - p.add_mesh(x_arrow, color="red") - p.add_mesh(y_arrow, color="green") - p.add_mesh(integration_arrow, color="blue") + p.add_mesh(feature.visual_data.x_axis, color="red") + p.add_mesh(feature.visual_data.y_axis, color="green") + p.add_mesh(feature.visual_data.z_axis, color="blue") if isinstance(feature, SensorRadiance): - feature_pos_info = feature.get(key="axis_system") - feature_radiance_pos = np.array(feature_pos_info[:3]) - feature_radiance_x_dir = np.array(feature_pos_info[3:6]) - feature_radiance_y_dir = np.array(feature_pos_info[6:9]) - feature_radiance_z_dir = np.array(feature_pos_info[9:12]) - feature_x_start = feature.get(key="x_start") - feature_x_end = feature.get(key="x_end") - feature_y_start = feature.get(key="y_start") - feature_y_end = feature.get(key="y_end") - feature_radiance_focal = feature.get(key="focal") - - # radiance sensor - p1 = ( - feature_radiance_pos - + feature_radiance_x_dir * feature_x_end - + feature_radiance_y_dir * feature_y_end - ) - p2 = ( - feature_radiance_pos - + feature_radiance_x_dir * feature_x_end - + feature_radiance_y_dir * feature_y_start - ) - p3 = ( - feature_radiance_pos - + feature_radiance_x_dir * feature_x_start - + feature_radiance_y_dir * feature_y_start - ) - p4 = ( - feature_radiance_pos - + feature_radiance_x_dir * feature_x_start - + feature_radiance_y_dir * feature_y_end - ) - rectangle = pv.Rectangle([p1, p2, p3]) - p.add_mesh( - rectangle, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p5 = feature_radiance_pos + feature_radiance_z_dir * feature_radiance_focal - faces = np.hstack([[3, 0, 1, 2]]) - radiance_f1 = pv.PolyData(np.array([p1, p2, p5]), faces) - radiance_f2 = pv.PolyData(np.array([p3, p4, p5]), faces) - radiance_f3 = pv.PolyData(np.array([p1, p4, p5]), faces) - radiance_f4 = pv.PolyData(np.array([p2, p3, p5]), faces) - p.add_mesh( - radiance_f1, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.add_mesh( - radiance_f2, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) p.add_mesh( - radiance_f3, + feature.visual_data.data, show_edges=True, line_width=2, edge_color="red", color="orange", opacity=0.5, ) - p.add_mesh( - radiance_f4, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - - # radiance direction - x_arrow = pv.Arrow( - start=feature_radiance_y_dir, - direction=feature_radiance_x_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) - y_arrow = pv.Arrow( - start=feature_radiance_y_dir, - direction=feature_radiance_y_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) - p.add_mesh(x_arrow, color="red") - p.add_mesh(y_arrow, color="green") + p.add_mesh(feature.visual_data.x_axis, color="red") + p.add_mesh(feature.visual_data.y_axis, color="green") if isinstance(feature, SensorCamera): - feature_pos_info = feature.get(key="axis_system") - feature_camera_pos = np.array(feature_pos_info[:3]) - feature_camera_x_dir = np.array(feature_pos_info[3:6]) - feature_camera_y_dir = np.array(feature_pos_info[6:9]) - feature_camera_z_dir = np.array(feature_pos_info[9:12]) - feature_width = feature.get(key="width") - feature_height = feature.get(key="height") - feature_camera_focal = feature.get(key="focal_length") - feature_camera_image_dis = feature.get(key="imager_distance") - - # camera radiance sensor - p1 = ( - feature_camera_pos - + feature_camera_x_dir * feature_width / 2.0 - + feature_camera_y_dir * feature_height / 2.0 - + feature_camera_z_dir * feature_camera_image_dis - ) - p2 = ( - feature_camera_pos - + feature_camera_x_dir * feature_width / 2.0 - - feature_camera_y_dir * feature_height / 2.0 - + feature_camera_z_dir * feature_camera_image_dis - ) - p3 = ( - feature_camera_pos - - feature_camera_x_dir * feature_width / 2.0 - + feature_camera_y_dir * feature_height / 2.0 - + feature_camera_z_dir * feature_camera_image_dis - ) - p4 = ( - feature_camera_pos - - feature_camera_x_dir * feature_width / 2.0 - - feature_camera_y_dir * feature_height / 2.0 - + feature_camera_z_dir * feature_camera_image_dis - ) - rectangle = pv.Rectangle([p1, p2, p3]) p.add_mesh( - rectangle, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p5 = feature_camera_pos + feature_camera_z_dir * ( - feature_camera_image_dis - feature_camera_focal - ) - faces = np.hstack([[3, 0, 1, 2]]) - radiance_f1 = pv.PolyData(np.array([p1, p2, p5]), faces) - radiance_f2 = pv.PolyData(np.array([p3, p4, p5]), faces) - radiance_f3 = pv.PolyData(np.array([p1, p3, p5]), faces) - radiance_f4 = pv.PolyData(np.array([p2, p4, p5]), faces) - p.add_mesh( - radiance_f1, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.add_mesh( - radiance_f2, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.add_mesh( - radiance_f3, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.add_mesh( - radiance_f4, + feature.visual_data.data, show_edges=True, line_width=2, edge_color="red", color="orange", opacity=0.5, ) + p.add_mesh(feature.visual_data.x_axis, color="red") + p.add_mesh(feature.visual_data.y_axis, color="green") - # camera object field - camera_object_field_radius = 100 - object_field_sphere = pv.Sphere( - radius=camera_object_field_radius, - center=feature_camera_pos, - direction=feature_camera_x_dir, - theta_resolution=30, - phi_resolution=30, - start_theta=0.0, - end_theta=60.0, - start_phi=45, - end_phi=135, - ) - p.add_mesh( - object_field_sphere, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) return p def preview(self, viz_args=None) -> None: From 3977e3ccb0fbc5800ea04440a62c7ba2efac4d9e Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 1 Apr 2025 16:51:58 +0100 Subject: [PATCH 07/33] update the plotter method to add sensor PolyData --- src/ansys/speos/core/project.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 9d8d45100..5d57a96d6 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -940,12 +940,13 @@ def _create_preview(self, viz_args=None) -> Plotter: poly_data = self.__extract_part_mesh_info(part_data=root_part_data) if poly_data is not None: _preview_mesh = _preview_mesh.append_polydata(poly_data) - p.add_mesh(_preview_mesh, show_edges=True, **viz_args) + viz_args["show_edges"] = True + p.plot(_preview_mesh, **viz_args) # Add sensor at the root part for feature in self._features: if isinstance(feature, SensorIrradiance): - p.add_mesh( + p.plot( feature.visual_data.data, show_edges=True, line_width=2, @@ -953,11 +954,11 @@ def _create_preview(self, viz_args=None) -> Plotter: color="orange", opacity=0.5, ) - p.add_mesh(feature.visual_data.x_axis, color="red") - p.add_mesh(feature.visual_data.y_axis, color="green") - p.add_mesh(feature.visual_data.z_axis, color="blue") + p.plot(feature.visual_data.x_axis, color="red") + p.plot(feature.visual_data.y_axis, color="green") + p.plot(feature.visual_data.z_axis, color="blue") if isinstance(feature, SensorRadiance): - p.add_mesh( + p.plot( feature.visual_data.data, show_edges=True, line_width=2, @@ -965,10 +966,10 @@ def _create_preview(self, viz_args=None) -> Plotter: color="orange", opacity=0.5, ) - p.add_mesh(feature.visual_data.x_axis, color="red") - p.add_mesh(feature.visual_data.y_axis, color="green") + p.plot(feature.visual_data.x_axis, color="red") + p.plot(feature.visual_data.y_axis, color="green") if isinstance(feature, SensorCamera): - p.add_mesh( + p.plot( feature.visual_data.data, show_edges=True, line_width=2, @@ -976,10 +977,8 @@ def _create_preview(self, viz_args=None) -> Plotter: color="orange", opacity=0.5, ) - p.add_mesh(feature.visual_data.x_axis, color="red") - p.add_mesh(feature.visual_data.y_axis, color="green") - viz_args["show_edges"] = True - p.plot(_preview_mesh, **viz_args) + p.plot(feature.visual_data.x_axis, color="red") + p.plot(feature.visual_data.y_axis, color="green") return p @graphics_required From 95a1bfd5a5dee8675fa72d7202542c5fd3059ef3 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 2 Apr 2025 18:09:47 +0100 Subject: [PATCH 08/33] add camera object field visualization sphere position --- src/ansys/speos/core/project.py | 3 ++- src/ansys/speos/core/sensor.py | 28 ++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 5d57a96d6..d4ce0e0f4 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -920,7 +920,7 @@ def _create_preview(self, viz_args=None) -> Plotter: if viz_args is None: viz_args = {} - p = Plotter() + _preview_mesh = pv.PolyData() # Retrieve root part root_part_data = self.client[self.scene_link.get().part_guid].get() @@ -940,6 +940,7 @@ def _create_preview(self, viz_args=None) -> Plotter: poly_data = self.__extract_part_mesh_info(part_data=root_part_data) if poly_data is not None: _preview_mesh = _preview_mesh.append_polydata(poly_data) + p = Plotter() viz_args["show_edges"] = True p.plot(_preview_mesh, **viz_args) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 852ff6976..f6b854305 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -1701,19 +1701,23 @@ def visual_data(self) -> BaseSensor.VisualData: ) # camera object field - camera_object_field_radius = 100 + camera_object_field_radius = 500 + camera_object_field_data = pv.Sphere( + radius=camera_object_field_radius, + center=feature_camera_pos, + direction=feature_camera_y_dir, + theta_resolution=30, + phi_resolution=30, + start_theta=0.0, + end_theta=60.0, + start_phi=90, + end_phi=135, + ) + camera_object_field_data = camera_object_field_data.rotate_vector( + vector=feature_camera_y_dir, angle=-30, point=feature_camera_pos + ) self._visual_data.data = self._visual_data.data.append_polydata( - pv.Sphere( - radius=camera_object_field_radius, - center=feature_camera_pos, - direction=feature_camera_x_dir, - theta_resolution=30, - phi_resolution=30, - start_theta=0.0, - end_theta=60.0, - start_phi=45, - end_phi=135, - ) + camera_object_field_data ) # camera axis system From 0bcf1255a0ada2d9f29b2dd65b1ac2f9eeb5496e Mon Sep 17 00:00:00 2001 From: plu Date: Thu, 3 Apr 2025 13:31:14 +0100 Subject: [PATCH 09/33] add comments about camera object field visualization --- src/ansys/speos/core/sensor.py | 37 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index f6b854305..87ceb4011 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -1701,24 +1701,25 @@ def visual_data(self) -> BaseSensor.VisualData: ) # camera object field - camera_object_field_radius = 500 - camera_object_field_data = pv.Sphere( - radius=camera_object_field_radius, - center=feature_camera_pos, - direction=feature_camera_y_dir, - theta_resolution=30, - phi_resolution=30, - start_theta=0.0, - end_theta=60.0, - start_phi=90, - end_phi=135, - ) - camera_object_field_data = camera_object_field_data.rotate_vector( - vector=feature_camera_y_dir, angle=-30, point=feature_camera_pos - ) - self._visual_data.data = self._visual_data.data.append_polydata( - camera_object_field_data - ) + # current gRPC service not available to get object field open angle + # camera_object_field_radius = 500 + # camera_object_field_data = pv.Sphere( + # radius=camera_object_field_radius, + # center=feature_camera_pos, + # direction=feature_camera_y_dir, + # theta_resolution=30, + # phi_resolution=30, + # start_theta=0.0, + # end_theta=60.0, + # start_phi=90, + # end_phi=135, + # ) + # camera_object_field_data = camera_object_field_data.rotate_vector( + # vector=feature_camera_y_dir, angle=-30, point=feature_camera_pos + # ) + # self._visual_data.data = self._visual_data.data.append_polydata( + # camera_object_field_data + # ) # camera axis system self._visual_data.x_axis = self._visual_data.x_axis.append_polydata( From 5a1cb31045a0f057237e691132ab77e6e781c08f Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 8 Apr 2025 22:26:23 +0100 Subject: [PATCH 10/33] refactor VisualData class into visualization_methods.py --- .../core/generic/visualization_methods.py | 169 ++++++++++++++++++ src/ansys/speos/core/sensor.py | 37 +--- 2 files changed, 175 insertions(+), 31 deletions(-) create mode 100644 src/ansys/speos/core/generic/visualization_methods.py diff --git a/src/ansys/speos/core/generic/visualization_methods.py b/src/ansys/speos/core/generic/visualization_methods.py new file mode 100644 index 000000000..44169d5b0 --- /dev/null +++ b/src/ansys/speos/core/generic/visualization_methods.py @@ -0,0 +1,169 @@ +# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Provides the ``VisualData`` class.""" + +from typing import TYPE_CHECKING + +from ansys.speos.core.generic.general_methods import graphics_required + +if TYPE_CHECKING: # pragma: no cover + import pyvista as pv + + +@graphics_required +class VisualData: + """Visualization data for the sensor. + + By default, there is empty visualization data. + + Notes + ----- + **Do not instantiate this class yourself**, use set_dimensions method available in sensor + classes. + """ + + def __init__(self) -> None: + self.data = pv.PolyData() + self.x_axis = pv.PolyData() + self.y_axis = pv.PolyData() + self.z_axis = pv.PolyData() + + @property + def data(self) -> pv.PolyData: + """ + Get visualization mesh data. + + Returns + ------- + pv.PolyData + mesh data. + + """ + return self.data + + @property + def x_axis(self) -> pv.PolyData: + """ + Get x-axis arrow data. + + Returns + ------- + pv.PolyData + x-axis arrow data. + + """ + return self.x_axis + + @property + def y_axis(self) -> pv.PolyData: + """ + Get y-axis arrow data. + + Returns + ------- + pv.PolyData + y-axis arrow data. + + """ + return self.y_axis + + @property + def z_axis(self) -> pv.PolyData: + """ + Get z-axis arrow data. + + Returns + ------- + pv.PolyData + z-axis arrow data. + + """ + return self.z_axis + + @data.setter + def data(self, value) -> None: + """ + Set visualization data. + + Parameters + ---------- + value: pv.PolyData + visualization data. + + Returns + ------- + None + + """ + self._data = value + + @x_axis.setter + def x_axis(self, value) -> None: + """ + Set x-axis arrow data. + + Parameters + ---------- + value: pv.PolyData + x-axis arrow data. + + Returns + ------- + None + + """ + self._x_axis = value + + @y_axis.setter + def y_axis(self, value) -> None: + """ + Set y-axis arrow data. + + Parameters + ---------- + value: pv.PolyData + y-axis arrow data. + + Returns + ------- + None + + """ + self._y_axis = value + + @z_axis.setter + def z_axis(self, value) -> None: + """ + Set z-axis arrow data. + + Parameters + ---------- + value: pv.PolyData + z-axis arrow data. + + Returns + ------- + None + + """ + self._z_axis = value diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 87ceb4011..507113c43 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -25,13 +25,16 @@ from __future__ import annotations from difflib import SequenceMatcher -from typing import List, Mapping, Optional, Union +from typing import TYPE_CHECKING, List, Mapping, Optional, Union import uuid import numpy as np -import pyvista as pv + +if TYPE_CHECKING: # pragma: no cover + import pyvista as pv from ansys.api.speos.sensor.v1 import camera_sensor_pb2, common_pb2 +from ansys.speos.core.generic.visualization_methods import VisualData from ansys.speos.core.geo_ref import GeoRef from ansys.speos.core.kernel.scene import ProtoScene from ansys.speos.core.kernel.sensor_template import ProtoSensorTemplate @@ -79,7 +82,7 @@ def __init__( self._project = project self._name = name self._unique_id = None - self._visual_data = self.VisualData() + self._visual_data = VisualData() self._visual_data_updated = False self.sensor_template_link = None """Link object for the sensor template in database.""" @@ -103,34 +106,6 @@ def __init__( # (using _unique_id) and sensor_template_link self.reset() - class VisualData: - """Visualization data for the sensor. - - By default, there is empty visualization data. - - Parameters - ---------- - data : pyvista.PolyData - Surface data defining the visualization faces. - x_axis : pyvista.PolyData - x-axis arrow data. - y_axis : pyvista.PolyData - y-axis arrow data. - z_axis: pyvista.PolyData - z-axis arrow data. - - Notes - ----- - **Do not instantiate this class yourself**, use set_dimensions method available in sensor - classes. - """ - - def __init__(self) -> None: - self.data = pv.PolyData() - self.x_axis = pv.PolyData() - self.y_axis = pv.PolyData() - self.z_axis = pv.PolyData() - class WavelengthsRange: """Range of wavelengths. From 0b090f54cfe1be81bf856867aa902c06df87047d Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 9 Apr 2025 20:37:50 +0100 Subject: [PATCH 11/33] refactor the _GRAPHICS_AVAILABLE --- src/ansys/speos/core/generic/general_methods.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ansys/speos/core/generic/general_methods.py b/src/ansys/speos/core/generic/general_methods.py index eb8676fef..d0e74879b 100644 --- a/src/ansys/speos/core/generic/general_methods.py +++ b/src/ansys/speos/core/generic/general_methods.py @@ -28,7 +28,7 @@ from functools import wraps import warnings -__GRAPHICS_AVAILABLE = None +_GRAPHICS_AVAILABLE = None GRAPHICS_ERROR = ( "Preview unsupported without 'ansys-tools-visualization_interface' installed. " "You can install this using `pip install ansys-speos-core[graphics]`." @@ -72,20 +72,20 @@ def wrapper(*args, **kwargs): def run_if_graphics_required(warning=False): """Check if graphics are available.""" - global __GRAPHICS_AVAILABLE - if __GRAPHICS_AVAILABLE is None: + global _GRAPHICS_AVAILABLE + if _GRAPHICS_AVAILABLE is None: try: import pyvista as pv # noqa: F401 from ansys.tools.visualization_interface import Plotter # noqa: F401 - __GRAPHICS_AVAILABLE = True + _GRAPHICS_AVAILABLE = True except ImportError: # pragma: no cover - __GRAPHICS_AVAILABLE = False + _GRAPHICS_AVAILABLE = False - if __GRAPHICS_AVAILABLE is False and warning is False: # pragma: no cover + if _GRAPHICS_AVAILABLE is False and warning is False: # pragma: no cover raise ImportError(GRAPHICS_ERROR) - elif __GRAPHICS_AVAILABLE is False: # pragma: no cover + elif _GRAPHICS_AVAILABLE is False: # pragma: no cover warnings.warn(GRAPHICS_ERROR) From d3077ffed3793a8df66cd6923b800ea0e1bf7978 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 9 Apr 2025 20:39:55 +0100 Subject: [PATCH 12/33] Add more methods in visualization_methods.py --- .../core/generic/visualization_methods.py | 248 +++++++++++++----- 1 file changed, 186 insertions(+), 62 deletions(-) diff --git a/src/ansys/speos/core/generic/visualization_methods.py b/src/ansys/speos/core/generic/visualization_methods.py index 44169d5b0..61d851d5b 100644 --- a/src/ansys/speos/core/generic/visualization_methods.py +++ b/src/ansys/speos/core/generic/visualization_methods.py @@ -22,148 +22,272 @@ """Provides the ``VisualData`` class.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List -from ansys.speos.core.generic.general_methods import graphics_required +from ansys.speos.core.generic.general_methods import ( + graphics_required, + magnitude_vector, + normalize_vector, +) if TYPE_CHECKING: # pragma: no cover import pyvista as pv @graphics_required -class VisualData: - """Visualization data for the sensor. +class VisualCoordinateSystem: + """Visualization data for the coordinate system.. By default, there is empty visualization data. Notes ----- - **Do not instantiate this class yourself**, use set_dimensions method available in sensor + **Do not instantiate this class yourself**. classes. """ - def __init__(self) -> None: - self.data = pv.PolyData() - self.x_axis = pv.PolyData() - self.y_axis = pv.PolyData() - self.z_axis = pv.PolyData() + def __init__(self): + import pyvista as pv + + self._origin = [0.0, 0.0, 0.0] + self._x_axis = pv.Arrow( + start=self._origin, + direction=[1.0, 0.0, 0.0], + scale=10.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + self._y_axis = pv.Arrow( + start=self._origin, + direction=[0.0, 1.0, 0.0], + scale=10.0, + tip_radius=0.05, + shaft_radius=0.01, + ) + self._z_axis = pv.Arrow( + start=self._origin, + direction=[0.0, 0.0, 1.0], + scale=10.0, + tip_radius=0.05, + shaft_radius=0.01, + ) @property - def data(self) -> pv.PolyData: - """ - Get visualization mesh data. + def origin(self) -> List[float]: + """Returns the origin of the coordinate system. Returns ------- - pv.PolyData - mesh data. + List[float] + The origin of the coordinate system. """ - return self.data + return self._origin - @property - def x_axis(self) -> pv.PolyData: - """ - Get x-axis arrow data. + @origin.setter + def origin(self, value: List[float]) -> None: + """Set the origin of the coordinate system. + + Parameters + ---------- + value: List[float] + The origin of the coordinate system. Returns ------- - pv.PolyData - x-axis arrow data. - + None """ - return self.x_axis + if len(value) != 3: + msg = "origin must be a list with three elements." + raise ValueError(msg) + self._origin = value @property - def y_axis(self) -> pv.PolyData: - """ - Get y-axis arrow data. + def x_axis(self) -> "pv.Arrow": + """Returns the x-axis of the coordinate system. Returns ------- - pv.PolyData - y-axis arrow data. + pv.Arrow + pyvista.Arrow the x-axis of the coordinate system. + + """ + return self._x_axis + @x_axis.setter + def x_axis(self, x_vector: List[float]) -> None: + """Set the x-axis of the coordinate system. + + Parameters + ---------- + x_vector: List[float] + The x-axis of the coordinate system. + + Returns + ------- + None """ - return self.y_axis + import pyvista as pv + + if len(x_vector) != 3: + msg = "x_axis must be a list with three elements." + raise ValueError(msg) + self._x_axis = pv.Arrow( + start=self._origin, + direction=normalize_vector(vector=x_vector), + scale=magnitude_vector(vector=x_vector), + tip_radius=0.05, + shaft_radius=0.01, + ) @property - def z_axis(self) -> pv.PolyData: + def y_axis(self) -> "pv.Arrow": """ - Get z-axis arrow data. + Returns the y-axis of the coordinate system. Returns ------- - pv.PolyData - z-axis arrow data. + pv.Arrow + pyvista.Arrow the y-axis of the coordinate system. """ - return self.z_axis + return self._y_axis - @data.setter - def data(self, value) -> None: - """ - Set visualization data. + @y_axis.setter + def y_axis(self, y_vector: List[float]) -> None: + """Set the y-axis of the coordinate system. Parameters ---------- - value: pv.PolyData - visualization data. + y_vector: List[float] + The y-axis of the coordinate system. Returns ------- None + """ + import pyvista as pv + + if len(y_vector) != 3: + msg = "y_axis must be a list with three elements." + raise ValueError(msg) + self._y_axis = pv.Arrow( + start=self._origin, + direction=normalize_vector(vector=y_vector), + scale=magnitude_vector(vector=y_vector), + tip_radius=0.05, + shaft_radius=0.01, + ) + @property + def z_axis(self) -> "pv.Arrow": """ - self._data = value + Returns the z-axis of the coordinate system. + + Returns + ------- + pv.Arrow + pyvista.Arrow the z-axis of the coordinate system. - @x_axis.setter - def x_axis(self, value) -> None: """ - Set x-axis arrow data. + return self._z_axis + + @z_axis.setter + def z_axis(self, z_vector: List[float]) -> None: + """Set the z-axis of the coordinate system. Parameters ---------- - value: pv.PolyData - x-axis arrow data. + z_vector: List[float] + The z-axis of the coordinate system. Returns ------- None + """ + import pyvista as pv + + if len(z_vector) != 3: + msg = "z_axis must be a list with three elements." + raise ValueError(msg) + self._z_axis = pv.Arrow( + start=self._origin, + direction=normalize_vector(vector=z_vector), + scale=magnitude_vector(vector=z_vector), + tip_radius=0.05, + shaft_radius=0.01, + ) + + +@graphics_required +class VisualData: + """Visualization data for the sensor. + + By default, there is empty visualization data. + + Notes + ----- + **Do not instantiate this class yourself** + classes. + """ + + def __init__(self, coordinate_system=True): + import pyvista as pv + self._data = pv.PolyData() + self.coordinates = VisualCoordinateSystem() if coordinate_system else None + self.updated = False + + @property + def data(self) -> "pv.PolyData": """ - self._x_axis = value + Returns the data of the sensor. - @y_axis.setter - def y_axis(self, value) -> None: + Returns + ------- + pv.PolyData + The data of the surface visualization. + + """ + return self._data + + def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: """ - Set y-axis arrow data. + Add surface data triangle to Visualization data. Parameters ---------- - value: pv.PolyData - y-axis arrow data. + triangle_vertices: List[List[float]] + The vertices of the triangle. Returns ------- None - """ - self._y_axis = value + import pyvista as pv - @z_axis.setter - def z_axis(self, value) -> None: + if len(triangle_vertices) != 3 or any(len(vertice) != 3 for vertice in triangle_vertices): + msg = "provided triangle vertices must have 3 elements" + raise ValueError(msg) + faces = [[3, 0, 1, 2]] + self._data = self._data.append_polydata(pv.PolyData(triangle_vertices, faces)) + + def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None: """ - Set z-axis arrow data. + Add surface data rectangle to Visualization data. Parameters ---------- - value: pv.PolyData - z-axis arrow data. + rectangle_vertices: List[List[float]] + The vertices of the rectangle. Returns ------- None - """ - self._z_axis = value + import pyvista as pv + + if len(rectangle_vertices) != 3 or any(len(vertice) != 3 for vertice in rectangle_vertices): + msg = "provided rectangle vertices must have 3 elements" + raise ValueError(msg) + self._data = self._data.append_polydata(pv.Rectangle(rectangle_vertices)) From 3d489ecada6f59c334c27bf096711ae66a271f92 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 9 Apr 2025 20:43:40 +0100 Subject: [PATCH 13/33] Add cache saving and add vector calculation methods --- .../speos/core/generic/general_methods.py | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/ansys/speos/core/generic/general_methods.py b/src/ansys/speos/core/generic/general_methods.py index d0e74879b..e428a0a38 100644 --- a/src/ansys/speos/core/generic/general_methods.py +++ b/src/ansys/speos/core/generic/general_methods.py @@ -25,7 +25,9 @@ this includes decorator and methods """ -from functools import wraps +from functools import lru_cache, wraps +import math +from typing import List import warnings _GRAPHICS_AVAILABLE = None @@ -70,6 +72,7 @@ def wrapper(*args, **kwargs): return decorator +@lru_cache def run_if_graphics_required(warning=False): """Check if graphics are available.""" global _GRAPHICS_AVAILABLE @@ -108,3 +111,58 @@ def wrapper(*args, **kwargs): return method(*args, **kwargs) return wrapper + + +def magnitude_vector(vector: List[float]) -> float: + """ + Compute the magnitude (length) of a 3D vector. + + Parameters + ---------- + vector: List + A 2D or 3D vector as a list [x, y, z]. + + Returns + ------- + float + The magnitude (length) of the vector. + """ + if len(vector) == 2: + # 2D vector magnitude + magnitude = math.sqrt(vector[0] ** 2 + vector[1] ** 2) + elif len(vector) == 3: + # 3D vector magnitude + magnitude = math.sqrt(vector[0] ** 2 + vector[1] ** 2 + vector[2] ** 2) + else: + raise ValueError("Input vector must be either 2D or 3D") + + return magnitude + + +def normalize_vector(vector: List[float]) -> List[float]: + """ + Normalize a 2D or 3D vector to have a length of 1. + + Parameters + ---------- + vector: List + A vector as a list [x, y] for 2D or [x, y, z] for 3D. + + Returns + ------- + List + The normalized vector. + """ + # Check if the vector has 2 or 3 components + magnitude = magnitude_vector(vector) + if magnitude == 0: + raise ValueError("Cannot normalize the zero vector") + + if len(vector) == 2: + normalized_v = [vector[0] / magnitude, vector[1] / magnitude] + elif len(vector) == 3: + normalized_v = [vector[0] / magnitude, vector[1] / magnitude, vector[2] / magnitude] + else: + raise ValueError("Input vector must be either 2D or 3D") + + return normalized_v From ef2673a741b7aa4aebbd79ad0e479179c7aea34f Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 9 Apr 2025 20:44:20 +0100 Subject: [PATCH 14/33] refactor the sensor classes to de-couple using pyvista --- src/ansys/speos/core/sensor.py | 168 ++++++++++----------------------- 1 file changed, 48 insertions(+), 120 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 507113c43..26392d249 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -25,15 +25,13 @@ from __future__ import annotations from difflib import SequenceMatcher -from typing import TYPE_CHECKING, List, Mapping, Optional, Union +from typing import List, Mapping, Optional, Union import uuid import numpy as np -if TYPE_CHECKING: # pragma: no cover - import pyvista as pv - from ansys.api.speos.sensor.v1 import camera_sensor_pb2, common_pb2 +import ansys.speos.core.generic.general_methods as general_methods from ansys.speos.core.generic.visualization_methods import VisualData from ansys.speos.core.geo_ref import GeoRef from ansys.speos.core.kernel.scene import ProtoScene @@ -82,8 +80,7 @@ def __init__( self._project = project self._name = name self._unique_id = None - self._visual_data = VisualData() - self._visual_data_updated = False + self._visual_data = VisualData() if general_methods._GRAPHICS_AVAILABLE else None self.sensor_template_link = None """Link object for the sensor template in database.""" if metadata is None: @@ -1607,7 +1604,8 @@ def __init__( self.set_axis_system() @property - def visual_data(self) -> BaseSensor.VisualData: + @general_methods.graphics_required + def visual_data(self) -> VisualData: """Property containing camera sensor visualization data. Returns @@ -1616,7 +1614,7 @@ def visual_data(self) -> BaseSensor.VisualData: Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems. """ - if self._visual_data_updated: + if self._visual_data.updated: return self._visual_data else: feature_pos_info = self.get(key="axis_system") @@ -1654,26 +1652,15 @@ def visual_data(self) -> BaseSensor.VisualData: - feature_camera_y_dir * feature_height / 2.0 + feature_camera_z_dir * feature_camera_image_dis ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.Rectangle([p1, p2, p3]) - ) + self._visual_data.add_data_rectangle([p1, p2, p3]) p5 = feature_camera_pos + feature_camera_z_dir * ( feature_camera_image_dis - feature_camera_focal ) - faces = np.hstack([[3, 0, 1, 2]]) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p1, p2, p5]), faces) - ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p3, p4, p5]), faces) - ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p1, p3, p5]), faces) - ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p2, p4, p5]), faces) - ) + self._visual_data.add_data_triangle([p1, p2, p5]) + self._visual_data.add_data_triangle([p3, p4, p5]) + self._visual_data.add_data_triangle([p1, p3, p5]) + self._visual_data.add_data_triangle([p2, p4, p5]) # camera object field # current gRPC service not available to get object field open angle @@ -1697,35 +1684,18 @@ def visual_data(self) -> BaseSensor.VisualData: # ) # camera axis system - self._visual_data.x_axis = self._visual_data.x_axis.append_polydata( - pv.Arrow( - start=feature_camera_pos, - direction=feature_camera_x_dir, - scale=max(feature_width, feature_height) / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.origin = feature_camera_pos + self._visual_data.coordinates.x_axis = ( + feature_camera_x_dir * max(feature_width, feature_height) / 4.0 ) - self._visual_data.y_axis = self._visual_data.y_axis.append_polydata( - pv.Arrow( - start=feature_camera_pos, - direction=feature_camera_y_dir, - scale=max(feature_width, feature_height) / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.y_axis = ( + feature_camera_y_dir * max(feature_width, feature_height) / 4.0 ) - self._visual_data.z_axis = self._visual_data.z_axis.append_polydata( - pv.Arrow( - start=feature_camera_pos, - direction=feature_camera_z_dir, - scale=max(feature_width, feature_height) / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.z_axis = ( + feature_camera_z_dir * max(feature_width, feature_height) / 4.0 ) - self._visual_data_updated = True + self._visual_data.updated = True return self._visual_data @property @@ -2017,7 +1987,8 @@ def __init__( self.set_axis_system().set_ray_file_type_none().set_layer_type_none() @property - def visual_data(self) -> BaseSensor.VisualData: + @general_methods.graphics_required + def visual_data(self) -> VisualData: """Property containing irradiance sensor visualization data. Returns @@ -2026,7 +1997,7 @@ def visual_data(self) -> BaseSensor.VisualData: Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems. """ - if self._visual_data_updated is True: + if self._visual_data.updated is True: return self._visual_data else: feature_pos_info = self.get(key="axis_system") @@ -2055,42 +2026,21 @@ def visual_data(self) -> BaseSensor.VisualData: + feature_irradiance_x_dir * feature_x_end + feature_irradiance_y_dir * feature_y_start ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.Rectangle([p1, p2, p3]) - ) + self._visual_data.add_data_rectangle([p1, p2, p3]) # irradiance direction - self._visual_data.x_axis = self._visual_data.x_axis.append_polydata( - pv.Arrow( - start=feature_irradiance_pos, - direction=feature_irradiance_x_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.origin = feature_irradiance_pos + self._visual_data.coordinates.x_axis = feature_irradiance_x_dir * max( + feature_y_end - feature_y_start, feature_x_end - feature_x_start ) - self._visual_data.y_axis = self._visual_data.y_axis.append_polydata( - pv.Arrow( - start=feature_irradiance_pos, - direction=feature_irradiance_y_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.y_axis = feature_irradiance_y_dir * max( + feature_y_end - feature_y_start, feature_x_end - feature_x_start ) - self._visual_data.z_axis = self._visual_data.z_axis.append_polydata( - pv.Arrow( - start=feature_irradiance_pos, - direction=feature_irradiance_z_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.z_axis = feature_irradiance_z_dir * max( + feature_y_end - feature_y_start, feature_x_end - feature_x_start ) - self._visual_data_updated = True + + self._visual_data.updated = True return self._visual_data @property @@ -2696,16 +2646,17 @@ def __init__( self.set_axis_system().set_layer_type_none() @property - def visual_data(self) -> BaseSensor.VisualData: + @general_methods.graphics_required + def visual_data(self) -> VisualData: """Property containing radiance sensor visualization data. Returns ------- - BaseSensor.VisualData + VisualData Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems. """ - if self._visual_data_updated: + if self._visual_data.updated: return self._visual_data else: feature_pos_info = self.get(key="axis_system") @@ -2740,47 +2691,24 @@ def visual_data(self) -> BaseSensor.VisualData: + feature_radiance_x_dir * feature_x_start + feature_radiance_y_dir * feature_y_end ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.Rectangle([p1, p2, p3]) - ) + self._visual_data.add_data_rectangle([p1, p2, p3]) p5 = feature_radiance_pos + feature_radiance_z_dir * feature_radiance_focal - faces = np.hstack([[3, 0, 1, 2]]) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p1, p2, p5]), faces) - ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p3, p4, p5]), faces) - ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p1, p4, p5]), faces) - ) - self._visual_data.data = self._visual_data.data.append_polydata( - pv.PolyData(np.array([p2, p3, p5]), faces) - ) + self._visual_data.add_data_triangle([p1, p2, p5]) + self._visual_data.add_data_triangle([p3, p4, p5]) + self._visual_data.add_data_triangle([p1, p4, p5]) + self._visual_data.add_data_triangle([p2, p3, p5]) # radiance direction - self._visual_data.x_axis = self._visual_data.x_axis.append_polydata( - pv.Arrow( - start=feature_radiance_y_dir, - direction=feature_radiance_x_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.origin = feature_radiance_pos + self._visual_data.coordinates.x_axis = feature_radiance_x_dir * max( + feature_y_end - feature_y_start, feature_x_end - feature_x_start ) - self._visual_data.y_axis = self._visual_data.y_axis.append_polydata( - pv.Arrow( - start=feature_radiance_y_dir, - direction=feature_radiance_y_dir, - scale=max(feature_y_end - feature_y_start, feature_x_end - feature_x_start) - / 4.0, - tip_radius=0.05, - shaft_radius=0.01, - ) + self._visual_data.coordinates.y_axis = feature_radiance_y_dir * max( + feature_y_end - feature_y_start, feature_x_end - feature_x_start ) - self._visual_data_updated = True + + self._visual_data.updated = True return self._visual_data @property From 89ec1806a5c56155874828d598d7e0472dca1d82 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 9 Apr 2025 20:44:41 +0100 Subject: [PATCH 15/33] some corrections --- src/ansys/speos/core/project.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index d4ce0e0f4..373832743 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -748,6 +748,7 @@ def _fill_features(self): op_feature._fill(mat_inst=mat_inst) for src_inst in scene_data.sources: + src_feat = None if src_inst.HasField("rayfile_properties"): src_feat = SourceRayFile( project=self, @@ -772,6 +773,7 @@ def _fill_features(self): self._features.append(src_feat) for ssr_inst in scene_data.sensors: + ssr_feat = None if ssr_inst.HasField("irradiance_properties"): ssr_feat = SensorIrradiance( project=self, @@ -796,6 +798,7 @@ def _fill_features(self): self._features.append(ssr_feat) for sim_inst in scene_data.simulations: + sim_feat = None simulation_template_link = self.client[sim_inst.simulation_guid].get() if simulation_template_link.HasField("direct_mc_simulation_template"): sim_feat = SimulationDirect( @@ -955,9 +958,9 @@ def _create_preview(self, viz_args=None) -> Plotter: color="orange", opacity=0.5, ) - p.plot(feature.visual_data.x_axis, color="red") - p.plot(feature.visual_data.y_axis, color="green") - p.plot(feature.visual_data.z_axis, color="blue") + p.plot(feature.visual_data.coordinates.x_axis, color="red") + p.plot(feature.visual_data.coordinates.y_axis, color="green") + p.plot(feature.visual_data.coordinates.z_axis, color="blue") if isinstance(feature, SensorRadiance): p.plot( feature.visual_data.data, @@ -967,8 +970,8 @@ def _create_preview(self, viz_args=None) -> Plotter: color="orange", opacity=0.5, ) - p.plot(feature.visual_data.x_axis, color="red") - p.plot(feature.visual_data.y_axis, color="green") + p.plot(feature.visual_data.coordinates.x_axis, color="red") + p.plot(feature.visual_data.coordinates.y_axis, color="green") if isinstance(feature, SensorCamera): p.plot( feature.visual_data.data, @@ -978,8 +981,8 @@ def _create_preview(self, viz_args=None) -> Plotter: color="orange", opacity=0.5, ) - p.plot(feature.visual_data.x_axis, color="red") - p.plot(feature.visual_data.y_axis, color="green") + p.plot(feature.visual_data.coordinates.x_axis, color="red") + p.plot(feature.visual_data.coordinates.y_axis, color="green") return p @graphics_required From 27743e837035a72685268e077fb7e3b0e0506618 Mon Sep 17 00:00:00 2001 From: plu Date: Thu, 10 Apr 2025 08:54:04 +0100 Subject: [PATCH 16/33] Add vector class --- .../speos/core/generic/general_methods.py | 131 +++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/src/ansys/speos/core/generic/general_methods.py b/src/ansys/speos/core/generic/general_methods.py index e428a0a38..8094ef781 100644 --- a/src/ansys/speos/core/generic/general_methods.py +++ b/src/ansys/speos/core/generic/general_methods.py @@ -27,7 +27,7 @@ from functools import lru_cache, wraps import math -from typing import List +from typing import List, Union import warnings _GRAPHICS_AVAILABLE = None @@ -166,3 +166,132 @@ def normalize_vector(vector: List[float]) -> List[float]: raise ValueError("Input vector must be either 2D or 3D") return normalized_v + + +class Vector: + """A simple 2D or 3D vector class supporting basic vector operations.""" + + def __init__(self, components: List[Union[int, float]]): + """ + Initialize a Vector. + + Parameters + ---------- + components : List[int or float] + The components of the vector (must be 2D or 3D). + + Raises + ------ + TypeError + If the input is not a list. + ValueError + If the input list is not of length 2 or 3. + """ + if not isinstance(components, list): + raise TypeError("Vector components must be provided as a list.") + if len(components) not in (2, 3): + raise ValueError("Only 2D or 3D vectors are supported.") + self.components = components + + def __add__(self, other: "Vector") -> "Vector": + """ + Add two vectors. + + Parameters + ---------- + other : Vector + The vector to add. + + Returns + ------- + Vector + The result of vector addition. + """ + if not isinstance(other, Vector): + return NotImplemented + if len(self.components) != len(other.components): + raise ValueError("Vectors must have the same dimensions.") + return Vector([a + b for a, b in zip(self.components, other.components)]) + + def __sub__(self, other: "Vector") -> "Vector": + """ + Subtract one vector from another. + + Parameters + ---------- + other : Vector + The vector to subtract. + + Returns + ------- + Vector + The result of vector subtraction. + """ + if not isinstance(other, Vector): + return NotImplemented + if len(self.components) != len(other.components): + raise ValueError("Vectors must have the same dimensions.") + return Vector([a - b for a, b in zip(self.components, other.components)]) + + def __mul__(self, other: Union[int, float, "Vector"]) -> Union["Vector", float]: + """ + Multiply the vector by a scalar or compute the dot product with another vector. + + Parameters + ---------- + other : int, float, or Vector + A scalar for scalar multiplication, or another vector for dot product. + + Returns + ------- + Vector or float + A new scaled vector or the result of the dot product. + """ + if isinstance(other, (int, float)): + return Vector([a * other for a in self.components]) + elif isinstance(other, Vector): + if len(self.components) != len(other.components): + raise ValueError("Vectors must have the same dimensions.") + return sum(a * b for a, b in zip(self.components, other.components)) + else: + return NotImplemented + + def __eq__(self, other: object) -> bool: + """ + Check if two vectors are equal. + + Parameters + ---------- + other : object + The vector to compare. + + Returns + ------- + bool + True if vectors have the same components, False otherwise. + """ + if not isinstance(other, Vector): + return NotImplemented + return self.components == other.components + + def __len__(self) -> int: + """ + Return the number of dimensions of the vector. + + Returns + ------- + int + The number of elements in the vector (2 or 3). + """ + return len(self.components) + + def __repr__(self) -> str: + """ + Return a string representation of the vector. + + Returns + ------- + str + The string representation. + """ + return f"Vector({self.components})" From 013021a1ce52fb140cbe06ceaa5a337ecef41b97 Mon Sep 17 00:00:00 2001 From: plu Date: Thu, 10 Apr 2025 17:12:55 +0100 Subject: [PATCH 17/33] add divide and get_item method for Vector class --- .../speos/core/generic/general_methods.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/ansys/speos/core/generic/general_methods.py b/src/ansys/speos/core/generic/general_methods.py index 8094ef781..5b97c541a 100644 --- a/src/ansys/speos/core/generic/general_methods.py +++ b/src/ansys/speos/core/generic/general_methods.py @@ -256,6 +256,33 @@ def __mul__(self, other: Union[int, float, "Vector"]) -> Union["Vector", float]: else: return NotImplemented + def __truediv__(self, other: Union[int, float]) -> "Vector": + """ + Divide the vector by a scalar. + + Parameters + ---------- + other : int or float + The scalar value to divide by. + + Returns + ------- + Vector + A new vector with each component divided by the scalar. + + Raises + ------ + TypeError + If the divisor is not an int or float. + ZeroDivisionError + If attempting to divide by zero. + """ + if not isinstance(other, (int, float)): + return NotImplemented + if other == 0: + raise ZeroDivisionError("Cannot divide by zero.") + return Vector([a / other for a in self.components]) + def __eq__(self, other: object) -> bool: """ Check if two vectors are equal. @@ -285,6 +312,23 @@ def __len__(self) -> int: """ return len(self.components) + def __getitem__(self, index: int) -> Union[int, float]: + """ + Get a vector element by index. + + Parameters + ---------- + index: int + The index of the element to get. + + Returns + ------- + Union[int, float] + The vector element. + + """ + return self.components[index] + def __repr__(self) -> str: """ Return a string representation of the vector. From 9f7c0073ee4111422b37da199b408d9a9c03c279 Mon Sep 17 00:00:00 2001 From: plu Date: Thu, 10 Apr 2025 17:13:56 +0100 Subject: [PATCH 18/33] refactor to using Vector rather than List --- .../core/generic/visualization_methods.py | 29 +++++----- src/ansys/speos/core/sensor.py | 53 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/ansys/speos/core/generic/visualization_methods.py b/src/ansys/speos/core/generic/visualization_methods.py index 61d851d5b..5afdf2d49 100644 --- a/src/ansys/speos/core/generic/visualization_methods.py +++ b/src/ansys/speos/core/generic/visualization_methods.py @@ -22,9 +22,10 @@ """Provides the ``VisualData`` class.""" -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Union from ansys.speos.core.generic.general_methods import ( + Vector, graphics_required, magnitude_vector, normalize_vector, @@ -49,7 +50,7 @@ class VisualCoordinateSystem: def __init__(self): import pyvista as pv - self._origin = [0.0, 0.0, 0.0] + self._origin = Vector([0.0, 0.0, 0.0]) self._x_axis = pv.Arrow( start=self._origin, direction=[1.0, 0.0, 0.0], @@ -73,7 +74,7 @@ def __init__(self): ) @property - def origin(self) -> List[float]: + def origin(self) -> Vector: """Returns the origin of the coordinate system. Returns @@ -85,12 +86,12 @@ def origin(self) -> List[float]: return self._origin @origin.setter - def origin(self, value: List[float]) -> None: + def origin(self, value: Union[List[float], Vector]) -> None: """Set the origin of the coordinate system. Parameters ---------- - value: List[float] + value: Union[List[float], Vector] The origin of the coordinate system. Returns @@ -100,7 +101,7 @@ def origin(self, value: List[float]) -> None: if len(value) != 3: msg = "origin must be a list with three elements." raise ValueError(msg) - self._origin = value + self._origin = value if isinstance(value, Vector) else Vector(value) @property def x_axis(self) -> "pv.Arrow": @@ -115,12 +116,12 @@ def x_axis(self) -> "pv.Arrow": return self._x_axis @x_axis.setter - def x_axis(self, x_vector: List[float]) -> None: + def x_axis(self, x_vector: Union[List[float], Vector]) -> None: """Set the x-axis of the coordinate system. Parameters ---------- - x_vector: List[float] + x_vector: Union[List[float], Vector] The x-axis of the coordinate system. Returns @@ -154,7 +155,7 @@ def y_axis(self) -> "pv.Arrow": return self._y_axis @y_axis.setter - def y_axis(self, y_vector: List[float]) -> None: + def y_axis(self, y_vector: Union[List[float], Vector]) -> None: """Set the y-axis of the coordinate system. Parameters @@ -193,7 +194,7 @@ def z_axis(self) -> "pv.Arrow": return self._z_axis @z_axis.setter - def z_axis(self, z_vector: List[float]) -> None: + def z_axis(self, z_vector: Union[List[float], Vector]) -> None: """Set the z-axis of the coordinate system. Parameters @@ -251,13 +252,13 @@ def data(self) -> "pv.PolyData": """ return self._data - def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: + def add_data_triangle(self, triangle_vertices: List[Vector]) -> None: """ Add surface data triangle to Visualization data. Parameters ---------- - triangle_vertices: List[List[float]] + triangle_vertices: List[Vector] The vertices of the triangle. Returns @@ -272,13 +273,13 @@ def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: faces = [[3, 0, 1, 2]] self._data = self._data.append_polydata(pv.PolyData(triangle_vertices, faces)) - def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None: + def add_data_rectangle(self, rectangle_vertices: List[Vector]) -> None: """ Add surface data rectangle to Visualization data. Parameters ---------- - rectangle_vertices: List[List[float]] + rectangle_vertices: List[Vector] The vertices of the rectangle. Returns diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 26392d249..907062fc0 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -28,10 +28,9 @@ from typing import List, Mapping, Optional, Union import uuid -import numpy as np - from ansys.api.speos.sensor.v1 import camera_sensor_pb2, common_pb2 import ansys.speos.core.generic.general_methods as general_methods +from ansys.speos.core.generic.general_methods import Vector from ansys.speos.core.generic.visualization_methods import VisualData from ansys.speos.core.geo_ref import GeoRef from ansys.speos.core.kernel.scene import ProtoScene @@ -1618,14 +1617,14 @@ def visual_data(self) -> VisualData: return self._visual_data else: feature_pos_info = self.get(key="axis_system") - feature_camera_pos = np.array(feature_pos_info[:3]) - feature_camera_x_dir = np.array(feature_pos_info[3:6]) - feature_camera_y_dir = np.array(feature_pos_info[6:9]) - feature_camera_z_dir = np.array(feature_pos_info[9:12]) - feature_width = self.get(key="width") - feature_height = self.get(key="height") - feature_camera_focal = self.get(key="focal_length") - feature_camera_image_dis = self.get(key="imager_distance") + feature_camera_pos = Vector(feature_pos_info[:3]) + feature_camera_x_dir = Vector(feature_pos_info[3:6]) + feature_camera_y_dir = Vector(feature_pos_info[6:9]) + feature_camera_z_dir = Vector(feature_pos_info[9:12]) + feature_width = float(self.get(key="width")) + feature_height = float(self.get(key="height")) + feature_camera_focal = float(self.get(key="focal_length")) + feature_camera_image_dis = float(self.get(key="imager_distance")) # camera radiance sensor p1 = ( @@ -2001,14 +2000,14 @@ def visual_data(self) -> VisualData: return self._visual_data else: feature_pos_info = self.get(key="axis_system") - feature_irradiance_pos = np.array(feature_pos_info[:3]) - feature_irradiance_x_dir = np.array(feature_pos_info[3:6]) - feature_irradiance_y_dir = np.array(feature_pos_info[6:9]) - feature_irradiance_z_dir = np.array(feature_pos_info[9:12]) - feature_x_start = self.get(key="x_start") - feature_x_end = self.get(key="x_end") - feature_y_start = self.get(key="y_start") - feature_y_end = self.get(key="y_end") + feature_irradiance_pos = Vector(feature_pos_info[:3]) + feature_irradiance_x_dir = Vector(feature_pos_info[3:6]) + feature_irradiance_y_dir = Vector(feature_pos_info[6:9]) + feature_irradiance_z_dir = Vector(feature_pos_info[9:12]) + feature_x_start = float(self.get(key="x_start")) + feature_x_end = float(self.get(key="x_end")) + feature_y_start = float(self.get(key="y_start")) + feature_y_end = float(self.get(key="y_end")) # irradiance sensor p1 = ( @@ -2660,15 +2659,15 @@ def visual_data(self) -> VisualData: return self._visual_data else: feature_pos_info = self.get(key="axis_system") - feature_radiance_pos = np.array(feature_pos_info[:3]) - feature_radiance_x_dir = np.array(feature_pos_info[3:6]) - feature_radiance_y_dir = np.array(feature_pos_info[6:9]) - feature_radiance_z_dir = np.array(feature_pos_info[9:12]) - feature_x_start = self.get(key="x_start") - feature_x_end = self.get(key="x_end") - feature_y_start = self.get(key="y_start") - feature_y_end = self.get(key="y_end") - feature_radiance_focal = self.get(key="focal") + feature_radiance_pos = Vector(feature_pos_info[:3]) + feature_radiance_x_dir = Vector(feature_pos_info[3:6]) + feature_radiance_y_dir = Vector(feature_pos_info[6:9]) + feature_radiance_z_dir = Vector(feature_pos_info[9:12]) + feature_x_start = float(self.get(key="x_start")) + feature_x_end = float(self.get(key="x_end")) + feature_y_start = float(self.get(key="y_start")) + feature_y_end = float(self.get(key="y_end")) + feature_radiance_focal = float(self.get(key="focal")) # radiance sensor p1 = ( From bd3a8ac397d476188727b0652b2ac629d13e3824 Mon Sep 17 00:00:00 2001 From: plu Date: Fri, 11 Apr 2025 18:49:47 +0100 Subject: [PATCH 19/33] remove Vector class and refactor using numpy --- .../speos/core/generic/general_methods.py | 220 ++---------------- 1 file changed, 19 insertions(+), 201 deletions(-) diff --git a/src/ansys/speos/core/generic/general_methods.py b/src/ansys/speos/core/generic/general_methods.py index 5b97c541a..bc420ad9c 100644 --- a/src/ansys/speos/core/generic/general_methods.py +++ b/src/ansys/speos/core/generic/general_methods.py @@ -26,10 +26,11 @@ """ from functools import lru_cache, wraps -import math from typing import List, Union import warnings +import numpy as np + _GRAPHICS_AVAILABLE = None GRAPHICS_ERROR = ( "Preview unsupported without 'ansys-tools-visualization_interface' installed. " @@ -113,229 +114,46 @@ def wrapper(*args, **kwargs): return wrapper -def magnitude_vector(vector: List[float]) -> float: +def magnitude_vector(vector: Union[List[float], np.array]) -> float: """ - Compute the magnitude (length) of a 3D vector. + Compute the magnitude (length) of a 2D or 3D vector using NumPy. Parameters ---------- - vector: List - A 2D or 3D vector as a list [x, y, z]. + vector: List[float] + A 2D or 3D vector as a list [x, y] or [x, y, z]. Returns ------- float The magnitude (length) of the vector. """ - if len(vector) == 2: - # 2D vector magnitude - magnitude = math.sqrt(vector[0] ** 2 + vector[1] ** 2) - elif len(vector) == 3: - # 3D vector magnitude - magnitude = math.sqrt(vector[0] ** 2 + vector[1] ** 2 + vector[2] ** 2) - else: + vector_np = np.array(vector, dtype=float) + if vector_np.size not in (2, 3): raise ValueError("Input vector must be either 2D or 3D") - - return magnitude + return np.linalg.norm(vector_np) -def normalize_vector(vector: List[float]) -> List[float]: +def normalize_vector(vector: Union[List[float], np.array]) -> List[float]: """ - Normalize a 2D or 3D vector to have a length of 1. + Normalize a 2D or 3D vector to have a length of 1 using NumPy. Parameters ---------- - vector: List + vector: List[float] A vector as a list [x, y] for 2D or [x, y, z] for 3D. Returns ------- - List + List[float] The normalized vector. """ - # Check if the vector has 2 or 3 components - magnitude = magnitude_vector(vector) + vector_np = np.array(vector, dtype=float) + if vector_np.size not in (2, 3): + raise ValueError("Input vector must be either 2D or 3D") + + magnitude = magnitude_vector(vector_np) if magnitude == 0: raise ValueError("Cannot normalize the zero vector") - if len(vector) == 2: - normalized_v = [vector[0] / magnitude, vector[1] / magnitude] - elif len(vector) == 3: - normalized_v = [vector[0] / magnitude, vector[1] / magnitude, vector[2] / magnitude] - else: - raise ValueError("Input vector must be either 2D or 3D") - - return normalized_v - - -class Vector: - """A simple 2D or 3D vector class supporting basic vector operations.""" - - def __init__(self, components: List[Union[int, float]]): - """ - Initialize a Vector. - - Parameters - ---------- - components : List[int or float] - The components of the vector (must be 2D or 3D). - - Raises - ------ - TypeError - If the input is not a list. - ValueError - If the input list is not of length 2 or 3. - """ - if not isinstance(components, list): - raise TypeError("Vector components must be provided as a list.") - if len(components) not in (2, 3): - raise ValueError("Only 2D or 3D vectors are supported.") - self.components = components - - def __add__(self, other: "Vector") -> "Vector": - """ - Add two vectors. - - Parameters - ---------- - other : Vector - The vector to add. - - Returns - ------- - Vector - The result of vector addition. - """ - if not isinstance(other, Vector): - return NotImplemented - if len(self.components) != len(other.components): - raise ValueError("Vectors must have the same dimensions.") - return Vector([a + b for a, b in zip(self.components, other.components)]) - - def __sub__(self, other: "Vector") -> "Vector": - """ - Subtract one vector from another. - - Parameters - ---------- - other : Vector - The vector to subtract. - - Returns - ------- - Vector - The result of vector subtraction. - """ - if not isinstance(other, Vector): - return NotImplemented - if len(self.components) != len(other.components): - raise ValueError("Vectors must have the same dimensions.") - return Vector([a - b for a, b in zip(self.components, other.components)]) - - def __mul__(self, other: Union[int, float, "Vector"]) -> Union["Vector", float]: - """ - Multiply the vector by a scalar or compute the dot product with another vector. - - Parameters - ---------- - other : int, float, or Vector - A scalar for scalar multiplication, or another vector for dot product. - - Returns - ------- - Vector or float - A new scaled vector or the result of the dot product. - """ - if isinstance(other, (int, float)): - return Vector([a * other for a in self.components]) - elif isinstance(other, Vector): - if len(self.components) != len(other.components): - raise ValueError("Vectors must have the same dimensions.") - return sum(a * b for a, b in zip(self.components, other.components)) - else: - return NotImplemented - - def __truediv__(self, other: Union[int, float]) -> "Vector": - """ - Divide the vector by a scalar. - - Parameters - ---------- - other : int or float - The scalar value to divide by. - - Returns - ------- - Vector - A new vector with each component divided by the scalar. - - Raises - ------ - TypeError - If the divisor is not an int or float. - ZeroDivisionError - If attempting to divide by zero. - """ - if not isinstance(other, (int, float)): - return NotImplemented - if other == 0: - raise ZeroDivisionError("Cannot divide by zero.") - return Vector([a / other for a in self.components]) - - def __eq__(self, other: object) -> bool: - """ - Check if two vectors are equal. - - Parameters - ---------- - other : object - The vector to compare. - - Returns - ------- - bool - True if vectors have the same components, False otherwise. - """ - if not isinstance(other, Vector): - return NotImplemented - return self.components == other.components - - def __len__(self) -> int: - """ - Return the number of dimensions of the vector. - - Returns - ------- - int - The number of elements in the vector (2 or 3). - """ - return len(self.components) - - def __getitem__(self, index: int) -> Union[int, float]: - """ - Get a vector element by index. - - Parameters - ---------- - index: int - The index of the element to get. - - Returns - ------- - Union[int, float] - The vector element. - - """ - return self.components[index] - - def __repr__(self) -> str: - """ - Return a string representation of the vector. - - Returns - ------- - str - The string representation. - """ - return f"Vector({self.components})" + return (vector_np / magnitude).tolist() From 54062b1e4d174566f2e8e77f9a20c6e7b11f86fa Mon Sep 17 00:00:00 2001 From: plu Date: Fri, 11 Apr 2025 18:51:01 +0100 Subject: [PATCH 20/33] refactor using as list[float] as input --- .../core/generic/visualization_methods.py | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/src/ansys/speos/core/generic/visualization_methods.py b/src/ansys/speos/core/generic/visualization_methods.py index 5afdf2d49..7a83e5ff4 100644 --- a/src/ansys/speos/core/generic/visualization_methods.py +++ b/src/ansys/speos/core/generic/visualization_methods.py @@ -22,10 +22,9 @@ """Provides the ``VisualData`` class.""" -from typing import TYPE_CHECKING, List, Union +from typing import TYPE_CHECKING, List from ansys.speos.core.generic.general_methods import ( - Vector, graphics_required, magnitude_vector, normalize_vector, @@ -36,7 +35,7 @@ @graphics_required -class VisualCoordinateSystem: +class _VisualCoordinateSystem: """Visualization data for the coordinate system.. By default, there is empty visualization data. @@ -44,13 +43,12 @@ class VisualCoordinateSystem: Notes ----- **Do not instantiate this class yourself**. - classes. """ def __init__(self): import pyvista as pv - self._origin = Vector([0.0, 0.0, 0.0]) + self._origin = [0.0, 0.0, 0.0] self._x_axis = pv.Arrow( start=self._origin, direction=[1.0, 0.0, 0.0], @@ -74,7 +72,7 @@ def __init__(self): ) @property - def origin(self) -> Vector: + def origin(self) -> List[float]: """Returns the origin of the coordinate system. Returns @@ -86,12 +84,12 @@ def origin(self) -> Vector: return self._origin @origin.setter - def origin(self, value: Union[List[float], Vector]) -> None: + def origin(self, value: List[float]) -> None: """Set the origin of the coordinate system. Parameters ---------- - value: Union[List[float], Vector] + value: List[float] The origin of the coordinate system. Returns @@ -99,9 +97,8 @@ def origin(self, value: Union[List[float], Vector]) -> None: None """ if len(value) != 3: - msg = "origin must be a list with three elements." - raise ValueError(msg) - self._origin = value if isinstance(value, Vector) else Vector(value) + raise ValueError("origin must be a list with three elements.") + self._origin = value @property def x_axis(self) -> "pv.Arrow": @@ -116,12 +113,12 @@ def x_axis(self) -> "pv.Arrow": return self._x_axis @x_axis.setter - def x_axis(self, x_vector: Union[List[float], Vector]) -> None: + def x_axis(self, x_vector: List[float]) -> None: """Set the x-axis of the coordinate system. Parameters ---------- - x_vector: Union[List[float], Vector] + x_vector: List[float] The x-axis of the coordinate system. Returns @@ -131,8 +128,7 @@ def x_axis(self, x_vector: Union[List[float], Vector]) -> None: import pyvista as pv if len(x_vector) != 3: - msg = "x_axis must be a list with three elements." - raise ValueError(msg) + raise ValueError("x_axis must be a list with three elements.") self._x_axis = pv.Arrow( start=self._origin, direction=normalize_vector(vector=x_vector), @@ -155,7 +151,7 @@ def y_axis(self) -> "pv.Arrow": return self._y_axis @y_axis.setter - def y_axis(self, y_vector: Union[List[float], Vector]) -> None: + def y_axis(self, y_vector: List[float]) -> None: """Set the y-axis of the coordinate system. Parameters @@ -170,8 +166,7 @@ def y_axis(self, y_vector: Union[List[float], Vector]) -> None: import pyvista as pv if len(y_vector) != 3: - msg = "y_axis must be a list with three elements." - raise ValueError(msg) + raise ValueError("y_axis must be a list with three elements.") self._y_axis = pv.Arrow( start=self._origin, direction=normalize_vector(vector=y_vector), @@ -194,7 +189,7 @@ def z_axis(self) -> "pv.Arrow": return self._z_axis @z_axis.setter - def z_axis(self, z_vector: Union[List[float], Vector]) -> None: + def z_axis(self, z_vector: List[float]) -> None: """Set the z-axis of the coordinate system. Parameters @@ -209,8 +204,7 @@ def z_axis(self, z_vector: Union[List[float], Vector]) -> None: import pyvista as pv if len(z_vector) != 3: - msg = "z_axis must be a list with three elements." - raise ValueError(msg) + raise ValueError("z_axis must be a list with three elements.") self._z_axis = pv.Arrow( start=self._origin, direction=normalize_vector(vector=z_vector), @@ -221,7 +215,7 @@ def z_axis(self, z_vector: Union[List[float], Vector]) -> None: @graphics_required -class VisualData: +class _VisualData: """Visualization data for the sensor. By default, there is empty visualization data. @@ -229,14 +223,13 @@ class VisualData: Notes ----- **Do not instantiate this class yourself** - classes. """ def __init__(self, coordinate_system=True): import pyvista as pv self._data = pv.PolyData() - self.coordinates = VisualCoordinateSystem() if coordinate_system else None + self.coordinates = _VisualCoordinateSystem() if coordinate_system else None self.updated = False @property @@ -252,7 +245,7 @@ def data(self) -> "pv.PolyData": """ return self._data - def add_data_triangle(self, triangle_vertices: List[Vector]) -> None: + def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: """ Add surface data triangle to Visualization data. @@ -268,12 +261,11 @@ def add_data_triangle(self, triangle_vertices: List[Vector]) -> None: import pyvista as pv if len(triangle_vertices) != 3 or any(len(vertice) != 3 for vertice in triangle_vertices): - msg = "provided triangle vertices must have 3 elements" - raise ValueError(msg) + raise ValueError("provided triangle vertices must have 3 elements") faces = [[3, 0, 1, 2]] self._data = self._data.append_polydata(pv.PolyData(triangle_vertices, faces)) - def add_data_rectangle(self, rectangle_vertices: List[Vector]) -> None: + def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None: """ Add surface data rectangle to Visualization data. @@ -289,6 +281,5 @@ def add_data_rectangle(self, rectangle_vertices: List[Vector]) -> None: import pyvista as pv if len(rectangle_vertices) != 3 or any(len(vertice) != 3 for vertice in rectangle_vertices): - msg = "provided rectangle vertices must have 3 elements" - raise ValueError(msg) + raise ValueError("provided rectangle vertices must have 3 elements") self._data = self._data.append_polydata(pv.Rectangle(rectangle_vertices)) From 6461729a72d7fcd43a15d7b24946ddde9f534daa Mon Sep 17 00:00:00 2001 From: plu Date: Fri, 11 Apr 2025 18:51:32 +0100 Subject: [PATCH 21/33] refactor --- src/ansys/speos/core/sensor.py | 39 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 907062fc0..589f588d5 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -28,10 +28,11 @@ from typing import List, Mapping, Optional, Union import uuid +import numpy as np + from ansys.api.speos.sensor.v1 import camera_sensor_pb2, common_pb2 import ansys.speos.core.generic.general_methods as general_methods -from ansys.speos.core.generic.general_methods import Vector -from ansys.speos.core.generic.visualization_methods import VisualData +from ansys.speos.core.generic.visualization_methods import _VisualData from ansys.speos.core.geo_ref import GeoRef from ansys.speos.core.kernel.scene import ProtoScene from ansys.speos.core.kernel.sensor_template import ProtoSensorTemplate @@ -79,7 +80,7 @@ def __init__( self._project = project self._name = name self._unique_id = None - self._visual_data = VisualData() if general_methods._GRAPHICS_AVAILABLE else None + self._visual_data = _VisualData() if general_methods._GRAPHICS_AVAILABLE else None self.sensor_template_link = None """Link object for the sensor template in database.""" if metadata is None: @@ -1604,7 +1605,7 @@ def __init__( @property @general_methods.graphics_required - def visual_data(self) -> VisualData: + def visual_data(self) -> _VisualData: """Property containing camera sensor visualization data. Returns @@ -1617,10 +1618,10 @@ def visual_data(self) -> VisualData: return self._visual_data else: feature_pos_info = self.get(key="axis_system") - feature_camera_pos = Vector(feature_pos_info[:3]) - feature_camera_x_dir = Vector(feature_pos_info[3:6]) - feature_camera_y_dir = Vector(feature_pos_info[6:9]) - feature_camera_z_dir = Vector(feature_pos_info[9:12]) + feature_camera_pos = np.array(feature_pos_info[:3]) + feature_camera_x_dir = np.array(feature_pos_info[3:6]) + feature_camera_y_dir = np.array(feature_pos_info[6:9]) + feature_camera_z_dir = np.array(feature_pos_info[9:12]) feature_width = float(self.get(key="width")) feature_height = float(self.get(key="height")) feature_camera_focal = float(self.get(key="focal_length")) @@ -1987,7 +1988,7 @@ def __init__( @property @general_methods.graphics_required - def visual_data(self) -> VisualData: + def visual_data(self) -> _VisualData: """Property containing irradiance sensor visualization data. Returns @@ -2000,10 +2001,10 @@ def visual_data(self) -> VisualData: return self._visual_data else: feature_pos_info = self.get(key="axis_system") - feature_irradiance_pos = Vector(feature_pos_info[:3]) - feature_irradiance_x_dir = Vector(feature_pos_info[3:6]) - feature_irradiance_y_dir = Vector(feature_pos_info[6:9]) - feature_irradiance_z_dir = Vector(feature_pos_info[9:12]) + feature_irradiance_pos = np.array(feature_pos_info[:3]) + feature_irradiance_x_dir = np.array(feature_pos_info[3:6]) + feature_irradiance_y_dir = np.array(feature_pos_info[6:9]) + feature_irradiance_z_dir = np.array(feature_pos_info[9:12]) feature_x_start = float(self.get(key="x_start")) feature_x_end = float(self.get(key="x_end")) feature_y_start = float(self.get(key="y_start")) @@ -2646,12 +2647,12 @@ def __init__( @property @general_methods.graphics_required - def visual_data(self) -> VisualData: + def visual_data(self) -> _VisualData: """Property containing radiance sensor visualization data. Returns ------- - VisualData + _VisualData Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems. """ @@ -2659,10 +2660,10 @@ def visual_data(self) -> VisualData: return self._visual_data else: feature_pos_info = self.get(key="axis_system") - feature_radiance_pos = Vector(feature_pos_info[:3]) - feature_radiance_x_dir = Vector(feature_pos_info[3:6]) - feature_radiance_y_dir = Vector(feature_pos_info[6:9]) - feature_radiance_z_dir = Vector(feature_pos_info[9:12]) + feature_radiance_pos = np.array(feature_pos_info[:3]) + feature_radiance_x_dir = np.array(feature_pos_info[3:6]) + feature_radiance_y_dir = np.array(feature_pos_info[6:9]) + feature_radiance_z_dir = np.array(feature_pos_info[9:12]) feature_x_start = float(self.get(key="x_start")) feature_x_end = float(self.get(key="x_end")) feature_y_start = float(self.get(key="y_start")) From fbf37ec013c4baa8cfa857e2582a0cc13a1f9ed7 Mon Sep 17 00:00:00 2001 From: plu Date: Sat, 12 Apr 2025 13:59:36 +0100 Subject: [PATCH 22/33] add unit test for preview sensor visual data --- tests/core/test_project.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/core/test_project.py b/tests/core/test_project.py index ec7f3eeae..ca04ebd7f 100644 --- a/tests/core/test_project.py +++ b/tests/core/test_project.py @@ -445,3 +445,26 @@ def test_find_geom(speos: Speos): # All faces of specific body all_faces = p.find(name="Solid Body in GUIDE.*/.*", name_regex=True, feature_type=Part) assert len(all_faces) == 11 + + +def test_preview_visual_data(speos: Speos): + """Test preview visualization data inside a project.""" + # preview irradiance sensor data + p1 = Project( + speos=speos, + path=str( + Path(test_path) / "LG_50M_Colorimetric_short.sv5" / "LG_50M_Colorimetric_short.sv5" + ), + ) + p1.preview() + + # preview irradiance sensor visual data and camera sensor visual data + p2 = Project( + speos=speos, + path=str(Path(test_path) / "Inverse_SeveralSensors.speos" / "Inverse_SeveralSensors.speos"), + ) + p2.preview() + + # preview radiance sensor visual data + p2.create_sensor(name="radiance_sensor", feature_type=SensorRadiance) + p2.preview() From 8ffed79b7830f31fd9f48a5de5c3adb4c827341e Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:17:01 +0000 Subject: [PATCH 23/33] chore: adding changelog file 528.added.md [dependabot-skip] --- doc/changelog.d/528.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.d/528.added.md b/doc/changelog.d/528.added.md index db2422329..0d29cfd7f 100644 --- a/doc/changelog.d/528.added.md +++ b/doc/changelog.d/528.added.md @@ -1 +1 @@ -enhance the project preview \ No newline at end of file +enhance the project preview: irrad, rad, camera sensor features \ No newline at end of file From ee0090917c0c823e71928c1ce2d8e38b61a478bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 08:49:51 +0000 Subject: [PATCH 24/33] ci: auto fixes from pre-commit.com hooks. for more information, see https://pre-commit.ci --- src/ansys/speos/core/generic/general_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/speos/core/generic/general_methods.py b/src/ansys/speos/core/generic/general_methods.py index 4d9e77eff..58a7e6585 100644 --- a/src/ansys/speos/core/generic/general_methods.py +++ b/src/ansys/speos/core/generic/general_methods.py @@ -164,6 +164,7 @@ def normalize_vector(vector: Union[List[float], np.array]) -> List[float]: return (vector_np / magnitude).tolist() + def error_no_install(install_path: Union[Path, str], version: Union[int, str]): """Raise error that installation was not found at a location. @@ -226,4 +227,3 @@ def retrieve_speos_install_dir( if not speos_exec.is_file(): error_no_install(speos_rpc_path, int(version)) return speos_rpc_path - From 8a9fad3d8a190c594217d55727cd2a3211e3712a Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 15 Apr 2025 10:01:51 +0100 Subject: [PATCH 25/33] refactor the attribute names --- .../core/generic/visualization_methods.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ansys/speos/core/generic/visualization_methods.py b/src/ansys/speos/core/generic/visualization_methods.py index 7a83e5ff4..54eb160e7 100644 --- a/src/ansys/speos/core/generic/visualization_methods.py +++ b/src/ansys/speos/core/generic/visualization_methods.py @@ -48,23 +48,23 @@ class _VisualCoordinateSystem: def __init__(self): import pyvista as pv - self._origin = [0.0, 0.0, 0.0] - self._x_axis = pv.Arrow( - start=self._origin, + self.__origin = [0.0, 0.0, 0.0] + self.__x_axis = pv.Arrow( + start=self.__origin, direction=[1.0, 0.0, 0.0], scale=10.0, tip_radius=0.05, shaft_radius=0.01, ) - self._y_axis = pv.Arrow( - start=self._origin, + self.__y_axis = pv.Arrow( + start=self.__origin, direction=[0.0, 1.0, 0.0], scale=10.0, tip_radius=0.05, shaft_radius=0.01, ) - self._z_axis = pv.Arrow( - start=self._origin, + self.__z_axis = pv.Arrow( + start=self.__origin, direction=[0.0, 0.0, 1.0], scale=10.0, tip_radius=0.05, @@ -81,7 +81,7 @@ def origin(self) -> List[float]: The origin of the coordinate system. """ - return self._origin + return self.__origin @origin.setter def origin(self, value: List[float]) -> None: @@ -98,7 +98,7 @@ def origin(self, value: List[float]) -> None: """ if len(value) != 3: raise ValueError("origin must be a list with three elements.") - self._origin = value + self.__origin = value @property def x_axis(self) -> "pv.Arrow": @@ -110,7 +110,7 @@ def x_axis(self) -> "pv.Arrow": pyvista.Arrow the x-axis of the coordinate system. """ - return self._x_axis + return self.__x_axis @x_axis.setter def x_axis(self, x_vector: List[float]) -> None: @@ -129,8 +129,8 @@ def x_axis(self, x_vector: List[float]) -> None: if len(x_vector) != 3: raise ValueError("x_axis must be a list with three elements.") - self._x_axis = pv.Arrow( - start=self._origin, + self.__x_axis = pv.Arrow( + start=self.__origin, direction=normalize_vector(vector=x_vector), scale=magnitude_vector(vector=x_vector), tip_radius=0.05, @@ -148,7 +148,7 @@ def y_axis(self) -> "pv.Arrow": pyvista.Arrow the y-axis of the coordinate system. """ - return self._y_axis + return self.__y_axis @y_axis.setter def y_axis(self, y_vector: List[float]) -> None: @@ -167,8 +167,8 @@ def y_axis(self, y_vector: List[float]) -> None: if len(y_vector) != 3: raise ValueError("y_axis must be a list with three elements.") - self._y_axis = pv.Arrow( - start=self._origin, + self.__y_axis = pv.Arrow( + start=self.__origin, direction=normalize_vector(vector=y_vector), scale=magnitude_vector(vector=y_vector), tip_radius=0.05, @@ -186,7 +186,7 @@ def z_axis(self) -> "pv.Arrow": pyvista.Arrow the z-axis of the coordinate system. """ - return self._z_axis + return self.__z_axis @z_axis.setter def z_axis(self, z_vector: List[float]) -> None: @@ -205,8 +205,8 @@ def z_axis(self, z_vector: List[float]) -> None: if len(z_vector) != 3: raise ValueError("z_axis must be a list with three elements.") - self._z_axis = pv.Arrow( - start=self._origin, + self.__z_axis = pv.Arrow( + start=self.__origin, direction=normalize_vector(vector=z_vector), scale=magnitude_vector(vector=z_vector), tip_radius=0.05, @@ -228,7 +228,7 @@ class _VisualData: def __init__(self, coordinate_system=True): import pyvista as pv - self._data = pv.PolyData() + self.__data = pv.PolyData() self.coordinates = _VisualCoordinateSystem() if coordinate_system else None self.updated = False @@ -243,7 +243,7 @@ def data(self) -> "pv.PolyData": The data of the surface visualization. """ - return self._data + return self.__data def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: """ @@ -263,7 +263,7 @@ def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: if len(triangle_vertices) != 3 or any(len(vertice) != 3 for vertice in triangle_vertices): raise ValueError("provided triangle vertices must have 3 elements") faces = [[3, 0, 1, 2]] - self._data = self._data.append_polydata(pv.PolyData(triangle_vertices, faces)) + self.__data = self.__data.append_polydata(pv.PolyData(triangle_vertices, faces)) def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None: """ @@ -282,4 +282,4 @@ def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None: if len(rectangle_vertices) != 3 or any(len(vertice) != 3 for vertice in rectangle_vertices): raise ValueError("provided rectangle vertices must have 3 elements") - self._data = self._data.append_polydata(pv.Rectangle(rectangle_vertices)) + self.__data = self.__data.append_polydata(pv.Rectangle(rectangle_vertices)) From 86c6e5ee4e705cc7704146176ae5ec61024fc3d1 Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 15 Apr 2025 10:18:46 +0100 Subject: [PATCH 26/33] some corrections --- .../speos/core/generic/visualization_methods.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ansys/speos/core/generic/visualization_methods.py b/src/ansys/speos/core/generic/visualization_methods.py index 54eb160e7..f84a741db 100644 --- a/src/ansys/speos/core/generic/visualization_methods.py +++ b/src/ansys/speos/core/generic/visualization_methods.py @@ -251,7 +251,7 @@ def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: Parameters ---------- - triangle_vertices: List[Vector] + triangle_vertices: List[List[float]] The vertices of the triangle. Returns @@ -260,8 +260,10 @@ def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None: """ import pyvista as pv - if len(triangle_vertices) != 3 or any(len(vertice) != 3 for vertice in triangle_vertices): - raise ValueError("provided triangle vertices must have 3 elements") + if len(triangle_vertices) != 3 or any(len(vertex) != 3 for vertex in triangle_vertices): + raise ValueError( + "triangle_vertices is expected to be composed of 3 vertices with 3 elements each." + ) faces = [[3, 0, 1, 2]] self.__data = self.__data.append_polydata(pv.PolyData(triangle_vertices, faces)) @@ -271,7 +273,7 @@ def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None: Parameters ---------- - rectangle_vertices: List[Vector] + rectangle_vertices: List[List[float]] The vertices of the rectangle. Returns @@ -280,6 +282,8 @@ def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None: """ import pyvista as pv - if len(rectangle_vertices) != 3 or any(len(vertice) != 3 for vertice in rectangle_vertices): - raise ValueError("provided rectangle vertices must have 3 elements") + if len(rectangle_vertices) != 3 or any(len(vertex) != 3 for vertex in rectangle_vertices): + raise ValueError( + "rectangle_vertices is expected to be composed of 3 vertices with 3 elements each." + ) self.__data = self.__data.append_polydata(pv.Rectangle(rectangle_vertices)) From c654005f57ad8743fba2e7dbdcf6842f7bd33f40 Mon Sep 17 00:00:00 2001 From: plu Date: Tue, 15 Apr 2025 10:27:17 +0100 Subject: [PATCH 27/33] come correction to allow to inherit --- src/ansys/speos/core/generic/general_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/speos/core/generic/general_methods.py b/src/ansys/speos/core/generic/general_methods.py index 58a7e6585..99094985f 100644 --- a/src/ansys/speos/core/generic/general_methods.py +++ b/src/ansys/speos/core/generic/general_methods.py @@ -36,7 +36,7 @@ from ansys.speos.core.generic.constants import DEFAULT_VERSION from ansys.tools.path import get_available_ansys_installations -__GRAPHICS_AVAILABLE = None +_GRAPHICS_AVAILABLE = None GRAPHICS_ERROR = ( "Preview unsupported without 'ansys-tools-visualization_interface' installed. " From cb2fc1b4094f7a88a2ad522c79764bfe48931738 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 16 Apr 2025 08:21:26 +0100 Subject: [PATCH 28/33] remove the "graphic_required" decorator from sensor visual_data method --- src/ansys/speos/core/sensor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 589f588d5..790b164e1 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -1604,7 +1604,6 @@ def __init__( self.set_axis_system() @property - @general_methods.graphics_required def visual_data(self) -> _VisualData: """Property containing camera sensor visualization data. @@ -1987,7 +1986,6 @@ def __init__( self.set_axis_system().set_ray_file_type_none().set_layer_type_none() @property - @general_methods.graphics_required def visual_data(self) -> _VisualData: """Property containing irradiance sensor visualization data. @@ -2646,7 +2644,6 @@ def __init__( self.set_axis_system().set_layer_type_none() @property - @general_methods.graphics_required def visual_data(self) -> _VisualData: """Property containing radiance sensor visualization data. From 60777b4617b8c4f451a0fe21b9326b4890d2b7f4 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 16 Apr 2025 08:36:20 +0100 Subject: [PATCH 29/33] change from "if instance" to "match case" --- src/ansys/speos/core/project.py | 69 +++++++++++++++++---------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 36dc6d4c7..123a672b1 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -949,40 +949,41 @@ def _create_preview(self, viz_args=None) -> Plotter: # Add sensor at the root part for feature in self._features: - if isinstance(feature, SensorIrradiance): - p.plot( - feature.visual_data.data, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.plot(feature.visual_data.coordinates.x_axis, color="red") - p.plot(feature.visual_data.coordinates.y_axis, color="green") - p.plot(feature.visual_data.coordinates.z_axis, color="blue") - if isinstance(feature, SensorRadiance): - p.plot( - feature.visual_data.data, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.plot(feature.visual_data.coordinates.x_axis, color="red") - p.plot(feature.visual_data.coordinates.y_axis, color="green") - if isinstance(feature, SensorCamera): - p.plot( - feature.visual_data.data, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.plot(feature.visual_data.coordinates.x_axis, color="red") - p.plot(feature.visual_data.coordinates.y_axis, color="green") + match feature: + case SensorRadiance(): + p.plot( + feature.visual_data.data, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.plot(feature.visual_data.coordinates.x_axis, color="red") + p.plot(feature.visual_data.coordinates.y_axis, color="green") + case SensorIrradiance(): + p.plot( + feature.visual_data.data, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.plot(feature.visual_data.coordinates.x_axis, color="red") + p.plot(feature.visual_data.coordinates.y_axis, color="green") + p.plot(feature.visual_data.coordinates.z_axis, color="blue") + case SensorCamera(): + p.plot( + feature.visual_data.data, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + p.plot(feature.visual_data.coordinates.x_axis, color="red") + p.plot(feature.visual_data.coordinates.y_axis, color="green") return p @graphics_required From 45b2d08b33d910a93a5422d28bd284489a19be17 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 16 Apr 2025 09:14:49 +0100 Subject: [PATCH 30/33] refactor the plot speos feature preview to private function --- src/ansys/speos/core/project.py | 78 ++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 123a672b1..7bcaec1c0 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -904,6 +904,46 @@ def local2absolute(local_vertice: np.ndarray, coordinates) -> np.ndarray: part_mesh_info = part_mesh_info.append_polydata(face_mesh_data) return part_mesh_info + def _create_speos_feature_preview( + self, plotter: Plotter, speos_feature: Union[SensorCamera, SensorRadiance, SensorIrradiance] + ) -> Plotter: + """Add speos feature visual preview to pyvista plotter object. + + Parameters + ---------- + plotter: Plotter + ansys.tools.visualization_interface.Plotter + speos_feature: Union[SensorCamera, SensorRadiance, SensorIrradiance] + speos feature whose visual data will be added. + + Returns + ------- + Plotter + ansys.tools.visualization_interface.Plotter + """ + if not isinstance(speos_feature, (SensorIrradiance, SensorRadiance, SensorCamera)): + return plotter + plotter.plot( + speos_feature.visual_data.data, + show_edges=True, + line_width=2, + edge_color="red", + color="orange", + opacity=0.5, + ) + match speos_feature: + case SensorRadiance(): + plotter.plot(speos_feature.visual_data.coordinates.x_axis, color="red") + plotter.plot(speos_feature.visual_data.coordinates.y_axis, color="green") + case SensorIrradiance(): + plotter.plot(speos_feature.visual_data.coordinates.x_axis, color="red") + plotter.plot(speos_feature.visual_data.coordinates.y_axis, color="green") + plotter.plot(speos_feature.visual_data.coordinates.z_axis, color="blue") + case SensorCamera(): + plotter.plot(speos_feature.visual_data.coordinates.x_axis, color="red") + plotter.plot(speos_feature.visual_data.coordinates.y_axis, color="green") + return plotter + @graphics_required def _create_preview(self, viz_args=None) -> Plotter: """Create preview pyvista plotter object. @@ -947,43 +987,9 @@ def _create_preview(self, viz_args=None) -> Plotter: viz_args["show_edges"] = True p.plot(_preview_mesh, **viz_args) - # Add sensor at the root part + # Add speos visual data at the root part for feature in self._features: - match feature: - case SensorRadiance(): - p.plot( - feature.visual_data.data, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.plot(feature.visual_data.coordinates.x_axis, color="red") - p.plot(feature.visual_data.coordinates.y_axis, color="green") - case SensorIrradiance(): - p.plot( - feature.visual_data.data, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.plot(feature.visual_data.coordinates.x_axis, color="red") - p.plot(feature.visual_data.coordinates.y_axis, color="green") - p.plot(feature.visual_data.coordinates.z_axis, color="blue") - case SensorCamera(): - p.plot( - feature.visual_data.data, - show_edges=True, - line_width=2, - edge_color="red", - color="orange", - opacity=0.5, - ) - p.plot(feature.visual_data.coordinates.x_axis, color="red") - p.plot(feature.visual_data.coordinates.y_axis, color="green") + p = self._create_speos_feature_preview(p, feature) return p @graphics_required From 4a71f39998229e482a27e23adc5797c4f5eb76e8 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 16 Apr 2025 09:20:49 +0100 Subject: [PATCH 31/33] merge the case SensorIrradiance and SensorCamera axis plot --- src/ansys/speos/core/project.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index 7bcaec1c0..8fad92a4a 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -935,13 +935,10 @@ def _create_speos_feature_preview( case SensorRadiance(): plotter.plot(speos_feature.visual_data.coordinates.x_axis, color="red") plotter.plot(speos_feature.visual_data.coordinates.y_axis, color="green") - case SensorIrradiance(): + case SensorIrradiance() | SensorCamera(): plotter.plot(speos_feature.visual_data.coordinates.x_axis, color="red") plotter.plot(speos_feature.visual_data.coordinates.y_axis, color="green") plotter.plot(speos_feature.visual_data.coordinates.z_axis, color="blue") - case SensorCamera(): - plotter.plot(speos_feature.visual_data.coordinates.x_axis, color="red") - plotter.plot(speos_feature.visual_data.coordinates.y_axis, color="green") return plotter @graphics_required From 2d69c1d27eb6cb60acc2098a059fee04f0c2c552 Mon Sep 17 00:00:00 2001 From: Pengyuan LU <89462462+pluAtAnsys@users.noreply.github.com> Date: Wed, 16 Apr 2025 09:42:11 +0100 Subject: [PATCH 32/33] Update src/ansys/speos/core/sensor.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Morais <146729917+SMoraisAnsys@users.noreply.github.com> --- src/ansys/speos/core/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 790b164e1..288853519 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -1661,7 +1661,7 @@ def visual_data(self) -> _VisualData: self._visual_data.add_data_triangle([p1, p3, p5]) self._visual_data.add_data_triangle([p2, p4, p5]) - # camera object field + # NOTE: camera object field to be added # current gRPC service not available to get object field open angle # camera_object_field_radius = 500 # camera_object_field_data = pv.Sphere( From d5ccc70a46b68b575fa7b0c6ef69d95379d6e705 Mon Sep 17 00:00:00 2001 From: plu Date: Wed, 16 Apr 2025 15:34:26 +0100 Subject: [PATCH 33/33] error message correction --- src/ansys/speos/core/generic/visualization_methods.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ansys/speos/core/generic/visualization_methods.py b/src/ansys/speos/core/generic/visualization_methods.py index f84a741db..f1a061700 100644 --- a/src/ansys/speos/core/generic/visualization_methods.py +++ b/src/ansys/speos/core/generic/visualization_methods.py @@ -97,7 +97,7 @@ def origin(self, value: List[float]) -> None: None """ if len(value) != 3: - raise ValueError("origin must be a list with three elements.") + raise ValueError("origin must be a List of three values.") self.__origin = value @property @@ -128,7 +128,7 @@ def x_axis(self, x_vector: List[float]) -> None: import pyvista as pv if len(x_vector) != 3: - raise ValueError("x_axis must be a list with three elements.") + raise ValueError("x_axis must be a List of three values.") self.__x_axis = pv.Arrow( start=self.__origin, direction=normalize_vector(vector=x_vector), @@ -166,7 +166,7 @@ def y_axis(self, y_vector: List[float]) -> None: import pyvista as pv if len(y_vector) != 3: - raise ValueError("y_axis must be a list with three elements.") + raise ValueError("y_axis must be a List of three values.") self.__y_axis = pv.Arrow( start=self.__origin, direction=normalize_vector(vector=y_vector), @@ -204,7 +204,7 @@ def z_axis(self, z_vector: List[float]) -> None: import pyvista as pv if len(z_vector) != 3: - raise ValueError("z_axis must be a list with three elements.") + raise ValueError("z_axis must be a List of three values.") self.__z_axis = pv.Arrow( start=self.__origin, direction=normalize_vector(vector=z_vector),