Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ _autosummary


# Testing
.cov/
.coverage
.tox/
*,cover
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/673.test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Further updates/support for intensity sensor
20 changes: 19 additions & 1 deletion src/ansys/speos/core/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
SensorCamera,
SensorIrradiance,
SensorRadiance,
SensorXMPIntensity,
)
from ansys.speos.core.simulation import (
SimulationDirect,
Expand Down Expand Up @@ -315,7 +316,9 @@ def create_sensor(
description: str = "",
feature_type: type = SensorIrradiance,
metadata: Optional[Mapping[str, str]] = None,
) -> Union[SensorCamera, SensorRadiance, SensorIrradiance, Sensor3DIrradiance]:
) -> Union[
SensorCamera, SensorRadiance, SensorIrradiance, Sensor3DIrradiance, SensorXMPIntensity
]:
"""Create a new Sensor feature.

Parameters
Expand Down Expand Up @@ -361,6 +364,13 @@ def create_sensor(
description=description,
metadata=metadata,
)
case "SensorXMPIntensity":
feature = SensorXMPIntensity(
project=self,
name=name,
description=description,
metadata=metadata,
)
case "SensorRadiance":
feature = SensorRadiance(
project=self,
Expand Down Expand Up @@ -835,6 +845,13 @@ def _fill_features(self):
sensor_instance=ssr_inst,
default_values=False,
)
elif ssr_inst.HasField("intensity_properties"):
ssr_feat = SensorXMPIntensity(
project=self,
name=ssr_inst.name,
sensor_instance=ssr_inst,
default_values=False,
)
elif ssr_inst.HasField("camera_properties"):
ssr_feat = SensorCamera(
project=self,
Expand Down Expand Up @@ -970,6 +987,7 @@ def _create_speos_feature_preview(
SensorRadiance,
SensorCamera,
Sensor3DIrradiance,
SensorXMPIntensity,
SourceLuminaire,
SourceRayFile,
SourceSurface,
Expand Down
175 changes: 166 additions & 9 deletions src/ansys/speos/core/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3822,19 +3822,157 @@ def __init__(
self.set_type_photometric()
self.set_orientation_x_as_meridian()
self.set_viewing_direction_from_source()
self._set_default_dimension_values()
# Default values properties
self.set_axis_system().set_layer_type_none()
self.set_axis_system()
self.set_layer_type_none()

@property
def visual_data(self) -> _VisualData:
"""Property containing intensity sensor visualization data.

Returns
-------
_VisualData
Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems.
"""

def rm(theta, u):
# make sure u is normalized
norm = np.linalg.norm(u)
ux, uy, uz = u / norm

# build and return the rotation matrix
r11 = np.cos(theta) + ux * ux * (1 - np.cos(theta))
r12 = ux * uy * (1 - np.cos(theta)) - uz * np.sin(theta)
r13 = ux * uz * (1 - np.cos(theta)) + uy * np.sin(theta)
r21 = ux * uy * (1 - np.cos(theta)) + uz * np.sin(theta)
r22 = np.cos(theta) + uy * uy * (1 - np.cos(theta))
r23 = uy * uz * (1 - np.cos(theta)) - ux * np.sin(theta)
r31 = ux * uz * (1 - np.cos(theta)) - uy * np.sin(theta)
r32 = uy * uz * (1 - np.cos(theta)) + ux * np.sin(theta)
r33 = np.cos(theta) + uz * uz * (1 - np.cos(theta))
return np.array([[r11, r12, r13], [r21, r22, r23], [r31, r32, r33]])

if self._visual_data.updated:
return self._visual_data

feature_pos_info = self.get(key="axis_system")
feature_pos = np.array(feature_pos_info[:3])
feature_x_dir = np.array(feature_pos_info[3:6])
feature_y_dir = np.array(feature_pos_info[6:9])
feature_z_dir = np.array(feature_pos_info[9:12])
feature_vis_radius = 5

if self._sensor_template.intensity_sensor_template.HasField(
"intensity_orientation_conoscopic"
):
# intensity sensor; non-conoscopic case
# simply fix vis sampling to 15 (radial) x 30 (azimuth)
# determine the set of visualization triangles vertices
feature_theta = float(self.get(key="theta_max"))
coord_transform = np.transpose(np.array([feature_x_dir, feature_y_dir, feature_z_dir]))
samp_1 = 30 # azimuth sampling
samp_2 = 15 # radial sampling
vertices = np.zeros(((samp_2 * samp_1), 3))
thetas = (np.pi / 180) * np.linspace(0, feature_theta, num=samp_2, endpoint=False)
phis = np.linspace(0, 2 * np.pi, num=samp_1, endpoint=False)

# compute all the vertices
iter = 0
for theta in thetas:
for phi in phis:
# spherical to cartesian
x = feature_vis_radius * np.sin(theta) * np.cos(phi)
y = feature_vis_radius * np.sin(theta) * np.sin(phi)
z = feature_vis_radius * np.cos(theta)
# transform to intensity sensor coords
vertices[iter, :] = np.matmul(coord_transform, [x, y, z])
iter += 1

# shift all vertices by the intensity sensor origin
vertices = vertices + feature_pos

# add "wrap around" squares to the visualizer
for j in range(0, (samp_2 - 1)):
p1 = vertices[j * samp_1 + (samp_1 - 1), :]
p2 = vertices[j * samp_1, :]
p3 = vertices[(j + 1) * samp_1 + (samp_1 - 1), :]
p4 = vertices[(j + 1) * samp_1, :]
self._visual_data.add_data_triangle([p1, p2, p3])
self._visual_data.add_data_triangle([p2, p3, p4])

else:
# intensity sensor; non-conoscopic case
# simply fix the number of vertices to 15x15
# determine the set of visualization squares' vertices
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"))
samp_1 = 15 # x sampling
samp_2 = 15 # y sampling
x_tilts = (np.pi / 180) * np.linspace(
feature_y_start, feature_y_end, num=samp_1, endpoint=True
)
y_tilts = (np.pi / 180) * np.linspace(
feature_x_start, feature_x_end, num=samp_2, endpoint=True
)
vertices = np.zeros((int(samp_1 * samp_2), 3))
u = feature_vis_radius * feature_z_dir

# compute all the vertices
iter = 0
if self._sensor_template.intensity_sensor_template.HasField(
"intensity_orientation_x_as_meridian"
):
for x_tilt in x_tilts:
tilted_x = np.matmul(rm(x_tilt, feature_x_dir), u)
for y_tilt in y_tilts:
vertices[iter, :] = np.matmul(rm(y_tilt, feature_y_dir), tilted_x)
iter += 1
else:
for y_tilt in y_tilts:
tilted_y = np.matmul(rm(y_tilt, feature_y_dir), u)
for x_tilt in x_tilts:
vertices[iter, :] = np.matmul(rm(x_tilt, feature_x_dir), tilted_y)
iter += 1

# shift all vertices by the intensity sensor origin
vertices = vertices + feature_pos

# add squares to the visualizer
for j in range(0, (samp_2 - 1)):
for i in range(0, (samp_1 - 1)):
index1 = j * samp_1 + i
index2 = j * samp_1 + (i + 1)
index3 = (j + 1) * samp_1 + i
index4 = (j + 1) * samp_1 + (i + 1)
p1 = vertices[index1, :]
p2 = vertices[index2, :]
p3 = vertices[index3, :]
p4 = vertices[index4, :]
self._visual_data.add_data_triangle([p1, p2, p3])
self._visual_data.add_data_triangle([p2, p3, p4])

# intensity direction
self._visual_data.coordinates.origin = feature_pos
self._visual_data.coordinates.x_axis = feature_x_dir
self._visual_data.coordinates.y_axis = feature_y_dir

self._visual_data.updated = True
return self._visual_data

@property
def nearfield(self) -> bool:
def near_field(self) -> bool:
"""Property containing if the sensor is positioned in nearfield or infinity."""
if self._sensor_template.intensity_sensor_template.HasField("near_field"):
return True
else:
return False

@nearfield.setter
def nearfield(self, value):
@near_field.setter
def near_field(self, value):
if value:
if not self._sensor_template.intensity_sensor_template.HasField("near_field"):
self._sensor_template.intensity_sensor_template.near_field.SetInParent()
Expand All @@ -3850,14 +3988,14 @@ def cell_distance(self):

By default, ``10``
"""
if self.nearfield:
if self.near_field:
return self._sensor_template.intensity_sensor_template.near_field.cell_distance
else:
return None

@cell_distance.setter
def cell_distance(self, value):
if self.nearfield:
if self.near_field:
self._sensor_template.intensity_sensor_template.near_field.cell_distance = value

else:
Expand All @@ -3869,7 +4007,7 @@ def cell_diameter(self):

By default, ``0.3491``
"""
if self.nearfield:
if self.near_field:
diameter = self.cell_distance * np.tan(
np.radians(
self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle
Expand All @@ -3881,7 +4019,7 @@ def cell_diameter(self):

@cell_diameter.setter
def cell_diameter(self, value):
if self.nearfield:
if self.near_field:
self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle = (
np.degrees(np.arctan(value / 2 / self.cell_distance))
)
Expand Down Expand Up @@ -3959,6 +4097,25 @@ def layer(
"""
return self._layer_type

def set_axis_system(self, axis_system: Optional[List[float]] = None) -> SensorXMPIntensity:
"""Set position of the sensor.

Parameters
----------
axis_system : Optional[List[float]]
Position of the sensor [Ox Oy Oz Xx Xy Xz Yx Yy Yz Zx Zy Zz].
By default, ``[0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]``.

Returns
-------
ansys.speos.core.sensor.SensorXMPIntensity
intensity sensor.
"""
if axis_system is None:
axis_system = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]
self._sensor_instance.intensity_properties.axis_system[:] = axis_system
return self

def set_orientation_x_as_meridian(self):
"""Set Orientation type: X As Meridian, Y as Parallel."""
self._sensor_template.intensity_sensor_template.intensity_orientation_x_as_meridian.SetInParent()
Expand Down Expand Up @@ -4294,7 +4451,7 @@ def set_type_spectral(self) -> BaseSensor.Spectral:
)
return self._type

def set_layer_type_none(self) -> SensorRadiance:
def set_layer_type_none(self) -> SensorXMPIntensity:
"""Define layer separation type as None.

Returns
Expand Down
Loading