From 8cd52c405b3f766eae995595cf26d73bd2827c02 Mon Sep 17 00:00:00 2001 From: sthoene Date: Thu, 3 Jul 2025 13:32:49 +0200 Subject: [PATCH 01/10] implement first draft --- src/ansys/speos/core/sensor.py | 469 +++++++++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index c2e11d219..777a3288d 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -3742,3 +3742,472 @@ def set_geometries(self, geometries: [List[GeoRef]]) -> Sensor3DIrradiance: gr.to_native_link() for gr in geometries ] return self + + +class SensorXMPIntensity(BaseSensor): + """Class for XMP intensity sensor. + + Parameters + ---------- + project + name + description + metadata + sensor_instance + default_values + """ + + def __init__( + self, + project: project.Project, + name: str, + description: str = "", + metadata: Optional[Mapping[str, str]] = None, + sensor_instance: Optional[ProtoScene.SensorInstance] = None, + default_values: bool = True, + ) -> None: + if metadata is None: + metadata = {} + + super().__init__( + project=project, + name=name, + description=description, + metadata=metadata, + sensor_instance=sensor_instance, + ) + + # Attribute gathering more complex intensity type + self._type = None + + # Attribute gathering orientation + self._orientation = None + self._nearfield = None + self._viewing_direction = None + + if default_values: + # Default values template + self.set_type_photometric().set_orientation_XAsMeridian() + # Default values properties + self.set_axis_system().set_layer_type_none() + + @property + def type(self) -> str: + """Type of sensor. + + Returns + ------- + str + Sensor type as string + """ + if type(self._type) is str: + return self._type + elif isinstance(self._type, BaseSensor.Colorimetric): + return "Colorimetric" + elif isinstance(self._type, BaseSensor.Spectral): + return "Spectral" + else: + return self._type + + @property + def colorimetric(self) -> Union[None, BaseSensor.Colorimetric]: + """Property containing all options in regard to the Colorimetric sensor properties. + + Returns + ------- + Union[None, ansys.speos.core.sensor.BaseSensor.Colorimetric] + Instance of Colorimetric Class for this sensor feature + """ + if isinstance(self._type, BaseSensor.Colorimetric): + return self._type + else: + return None + + @property + def spectral(self) -> Union[None, BaseSensor.Spectral]: + """Property containing all options in regard to the Spectral sensor properties. + + Returns + ------- + Union[None, ansys.speos.core.sensor.BaseSensor.Spectral] + Instance of Spectral Class for this sensor feature + """ + if isinstance(self._type, BaseSensor.Spectral): + return self._type + else: + return None + + @property + def layer( + self, + ) -> Union[ + None, + SensorIrradiance, + BaseSensor.LayerTypeFace, + BaseSensor.LayerTypeSequence, + BaseSensor.LayerTypeIncidenceAngle, + ]: + """Property containing all options in regard to the layer separation properties. + + Returns + ------- + Union[\ + None,\ + ansys.speos.core.sensor.SensorIrradiance,\ + ansys.speos.core.sensor.BaseSensor.LayerTypeFace,\ + ansys.speos.core.sensor.BaseSensor.LayerTypeSequence,\ + ansys.speos.core.sensor.BaseSensor.LayerTypeIncidenceAngle\ + ] + Instance of Layertype Class for this sensor feature + """ + return self._layer_type + + 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() + + def set_orientation_x_as_parallel(self): + """Set Orientation type: X as Parallel, Y As Meridian.""" + self._sensor_template.intensity_sensor_template.intensity_orientation_x_as_parallel.SetInParent() + + def set_orientation_conoscopic(self): + """Set Orientation type: Conoscopic.""" + self._sensor_template.intensity_sensor_template.intensity_orientation_conoscopic.SetInParent() + + @property + def x_start(self) -> float: + """The minimum value on x-axis.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no x_start dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + return template.intensity_orientation_x_as_parallel.intensity_dimensions.x_start + elif template.HasField("intensity_orientation_x_as_meridian"): + return template.intensity_orientation_x_as_meridian.intensity_dimensions.x_start + + @x_start.setter + def x_start(self, value: float = -45) -> SensorXMPIntensity.Dimensions: + """Set the minimum value on x-axis. + + Parameters + ---------- + value : float + Minimum value on x axis (deg). + By default, ``-45``. + + Returns + ------- + ansys.speos.core.sensor.SensorXMPIntensity.Dimensions + Dimensions. + """ + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no x_start dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + template.intensity_orientation_x_as_parallel.intensity_dimensions.x_start = value + elif template.HasField("intensity_orientation_x_as_meridian"): + template.intensity_orientation_x_as_meridian.intensity_dimensions.x_start = value + + @property + def x_end(self) -> float: + """The maximum value on x-axis.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no x_end dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + return template.intensity_orientation_x_as_parallel.intensity_dimensions.x_end + elif template.HasField("intensity_orientation_x_as_meridian"): + return template.intensity_orientation_x_as_meridian.intensity_dimensions.x_end + + @x_end.setter + def x_end(self, value: float = 45): + """Set the maximum value on x axis. + + Parameters + ---------- + value : float + Maximum value on x axis (deg). + By default, ``45``. + + Returns + ------- + ansys.speos.core.sensor.SensorXMPIntensity.Dimensions + Dimensions. + """ + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no x_end dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + template.intensity_orientation_x_as_parallel.intensity_dimensions.x_end = value + elif template.HasField("intensity_orientation_x_as_meridian"): + template.intensity_orientation_x_as_meridian.intensity_dimensions.x_end = value + + @property + def x_sampling(self) -> int: + """Pixel sampling along x-Axis.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no y_sampling dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + return template.intensity_orientation_x_as_parallel.intensity_dimensions.x_sampling + elif template.HasField("intensity_orientation_x_as_meridian"): + return template.intensity_orientation_x_as_meridian.intensity_dimensions.x_sampling + + @x_sampling.setter + def x_sampling(self, value: int = 100): + """Set the sampling along x-axis. + + Parameters + ---------- + value : int + sampling along x-axis. + By default, ``100``. + """ + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no x_sampling dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + template.intensity_orientation_x_as_parallel.intensity_dimensions.x_sampling = value + elif template.HasField("intensity_orientation_x_as_meridian"): + template.intensity_orientation_x_as_meridian.intensity_dimensions.x_sampling = value + + @property + def y_end(self) -> float: + """The maximum value on y-axis.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no y_end dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + return template.intensity_orientation_x_as_parallel.intensity_dimensions.y_end + elif template.HasField("intensity_orientation_x_as_meridian"): + return template.intensity_orientation_x_as_meridian.intensity_dimensions.y_end + + @y_end.setter + def y_end(self, value: float = 30): + """Set the maximum value on y-axis. + + Parameters + ---------- + value : float + Minimum value on x axis (deg). + By default, ``30``. + + Returns + ------- + ansys.speos.core.sensor.SensorXMPIntensity.Dimensions + Dimensions. + """ + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no y_end dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + template.intensity_orientation_x_as_parallel.intensity_dimensions.y_end = value + elif template.HasField("intensity_orientation_x_as_meridian"): + template.intensity_orientation_x_as_meridian.intensity_dimensions.y_end = value + + @property + def y_start(self) -> float: + """The minimum value on x axis.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no y_start dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + return template.intensity_orientation_x_as_parallel.intensity_dimensions.y_start + elif template.HasField("intensity_orientation_x_as_meridian"): + return template.intensity_orientation_x_as_meridian.intensity_dimensions.y_start + + @y_start.setter + def y_start(self, value: float = -30): + """Set the minimum value on y axis. + + Parameters + ---------- + value : float + Minimum value on y axis (deg). + By default, ``-30``. + + Returns + ------- + ansys.speos.core.sensor.SensorXMPIntensity.Dimensions + Dimensions. + """ + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no y_start dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + template.intensity_orientation_x_as_parallel.intensity_dimensions.y_start = value + elif template.HasField("intensity_orientation_x_as_meridian"): + template.intensity_orientation_x_as_meridian.intensity_dimensions.y_start = value + + @property + def y_sampling(self) -> int: + """Sampling along y-axis.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no y_sampling dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + return template.intensity_orientation_x_as_parallel.intensity_dimensions.y_sampling + elif template.HasField("intensity_orientation_x_as_meridian"): + return template.intensity_orientation_x_as_meridian.intensity_dimensions.y_sampling + + @y_sampling.setter + def y_sampling(self, value: int = 100): + """Set the sampling along y-axis. + + Parameters + ---------- + value : int + sampling along y-axis. + By default, ``100``. + """ + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + raise TypeError("Conoscopic Sensor has no y_sampling dimension") + elif template.HasField("intensity_orientation_x_as_parallel"): + template.intensity_orientation_x_as_parallel.intensity_dimensions.y_sampling = value + elif template.HasField("intensity_orientation_x_as_meridian"): + template.intensity_orientation_x_as_meridian.intensity_dimensions.y_sampling = value + + @property + def theta_max(self) -> float: + """Maximum theta angle on consocopic type.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + return ( + template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.theta_max + ) + else: + raise TypeError("Only Conoscopic Sensor has theta_max dimension") + + @theta_max.setter + def theta_max(self, value: float = 45): + """Set maximum theta angle on consocopic type.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.theta_max = ( + value + ) + else: + raise TypeError("Only Conoscopic Sensor has theta_max dimension") + + @property + def theta_max_sampling(self) -> int: + """Sampling on consocopic type.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + return ( + template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.sampling + ) + else: + raise TypeError("Only Conoscopic Sensor has theta_max dimension") + + @theta_max_sampling.setter + def theta_max_sampling(self, value: int = 90): + """Set Sampling on consocopic type.""" + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.sampling = ( + value + ) + else: + raise TypeError("Only Conoscopic Sensor has theta_max dimension") + + def set_type_photometric(self) -> SensorXMPIntensity: + """Set type photometric. + + The sensor considers the visible spectrum and gets the results in lm/m2 or lx. + + Returns + ------- + ansys.speos.core.sensor.SensorIrradiance + Irradiance sensor + """ + self._sensor_template.intensity_sensor_template.sensor_type_photometric.SetInParent() + self._type = "Photometric" + return self + + def set_type_colorimetric(self) -> BaseSensor.Colorimetric: + """Set type colorimetric. + + The sensor will generate color results without any spectral data or layer separation + in lx or W//m2. + + Returns + ------- + ansys.speos.core.sensor.BaseSensor.Colorimetric + Colorimetric type. + """ + if self._type is None and self._sensor_template.intensity_sensor_template.HasField( + "sensor_type_colorimetric" + ): + # Happens in case of project created via load of speos file + self._type = BaseSensor.Colorimetric( + sensor_type_colorimetric=self._sensor_template.intensity_sensor_template.sensor_type_colorimetric, + default_values=False, + stable_ctr=True, + ) + elif not isinstance(self._type, BaseSensor.Colorimetric): + # if the _type is not Colorimetric then we create a new type. + self._type = BaseSensor.Colorimetric( + sensor_type_colorimetric=self._sensor_template.intensity_sensor_template.sensor_type_colorimetric, + stable_ctr=True, + ) + elif ( + self._type._sensor_type_colorimetric + is not self._sensor_template.intensity_sensor_template.sensor_type_colorimetric + ): + # Happens in case of feature reset (to be sure to always modify correct data) + self._type._sensor_type_colorimetric = ( + self._sensor_template.intensity_sensor_template.sensor_type_colorimetric + ) + return self._type + + def set_type_radiometric(self) -> SensorXMPIntensity: + """Set type radiometric. + + The sensor considers the entire spectrum and gets the results in W/m2. + + Returns + ------- + ansys.speos.core.sensor.SensorIrradiance + Irradiance sensor. + """ + self._sensor_template.intensity_sensor_template.sensor_type_radiometric.SetInParent() + self._type = "Radiometric" + return self + + def set_type_spectral(self) -> BaseSensor.Spectral: + """Set type spectral. + + The sensor will generate color results and spectral data separated by wavelength + in lx or W/m2. + + Returns + ------- + ansys.speos.core.sensor.BaseSensor.Spectral + Spectral type. + """ + if self._type is None and self._sensor_template.intensity_sensor_template.HasField( + "sensor_type_spectral" + ): + # Happens in case of project created via load of speos file + self._type = BaseSensor.Spectral( + sensor_type_spectral=self._sensor_template.intensity_sensor_template.sensor_type_spectral, + default_values=False, + stable_ctr=True, + ) + elif not isinstance(self._type, BaseSensor.Spectral): + # if the _type is not Spectral then we create a new type. + self._type = BaseSensor.Spectral( + sensor_type_spectral=self._sensor_template.intensity_sensor_template.sensor_type_spectral, + stable_ctr=True, + ) + elif ( + self._type._sensor_type_spectral + is not self._sensor_template.intensity_sensor_template.sensor_type_spectral + ): + # Happens in case of feature reset (to be sure to always modify correct data) + self._type._sensor_type_spectral = ( + self._sensor_template.intensity_sensor_template.sensor_type_spectral + ) + return self._type From 34c009f949bde445c92b0844f46af51fa584bd23 Mon Sep 17 00:00:00 2001 From: sthoene Date: Thu, 3 Jul 2025 15:19:12 +0200 Subject: [PATCH 02/10] implement first draft --- src/ansys/speos/core/sensor.py | 186 ++++++++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 14 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 777a3288d..2e0999827 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -3779,18 +3779,65 @@ def __init__( # Attribute gathering more complex intensity type self._type = None - - # Attribute gathering orientation - self._orientation = None - self._nearfield = None - self._viewing_direction = None + self._layer_type = None if default_values: # Default values template - self.set_type_photometric().set_orientation_XAsMeridian() + self.set_type_photometric().set_orientation_x_as_meridian() # Default values properties self.set_axis_system().set_layer_type_none() + @property + def nearfield(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): + if value: + self._sensor_template.intensity_sensor_template.near_field.SetInParent() + else: + if self._sensor_template.intensity_sensor_template.HasField("near_field"): + self._sensor_template.intensity_sensor_template.ClearField("near_field") + + @property + def cell_distance(self): + """Distance of the Detector to origin in mm.""" + if self.nearfield: + 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: + self._sensor_template.intensity_sensor_template.near_field.cell_distance = value + else: + raise TypeError("Sensor position is not in nearfield") + + @property + def cell_integration_angle(self): + """Integration angle of the cell (deg). + + Used with cell_distance to calculate the cell diameter. + """ + if self.nearfield: + return self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle + else: + return None + + @cell_integration_angle.setter + def cell_integration_angle(self, value): + if self.nearfield: + self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle = ( + value + ) + else: + raise TypeError("Sensor position is not in nearfield") + @property def type(self) -> str: """Type of sensor. @@ -3879,7 +3926,7 @@ def x_start(self) -> float: """The minimum value on x-axis.""" template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): - raise TypeError("Conoscopic Sensor has no x_start dimension") + return None elif template.HasField("intensity_orientation_x_as_parallel"): return template.intensity_orientation_x_as_parallel.intensity_dimensions.x_start elif template.HasField("intensity_orientation_x_as_meridian"): @@ -3913,7 +3960,7 @@ def x_end(self) -> float: """The maximum value on x-axis.""" template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): - raise TypeError("Conoscopic Sensor has no x_end dimension") + return None elif template.HasField("intensity_orientation_x_as_parallel"): return template.intensity_orientation_x_as_parallel.intensity_dimensions.x_end elif template.HasField("intensity_orientation_x_as_meridian"): @@ -3947,7 +3994,7 @@ def x_sampling(self) -> int: """Pixel sampling along x-Axis.""" template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): - raise TypeError("Conoscopic Sensor has no y_sampling dimension") + return None elif template.HasField("intensity_orientation_x_as_parallel"): return template.intensity_orientation_x_as_parallel.intensity_dimensions.x_sampling elif template.HasField("intensity_orientation_x_as_meridian"): @@ -3976,7 +4023,7 @@ def y_end(self) -> float: """The maximum value on y-axis.""" template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): - raise TypeError("Conoscopic Sensor has no y_end dimension") + return None elif template.HasField("intensity_orientation_x_as_parallel"): return template.intensity_orientation_x_as_parallel.intensity_dimensions.y_end elif template.HasField("intensity_orientation_x_as_meridian"): @@ -4010,7 +4057,7 @@ def y_start(self) -> float: """The minimum value on x axis.""" template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): - raise TypeError("Conoscopic Sensor has no y_start dimension") + return None elif template.HasField("intensity_orientation_x_as_parallel"): return template.intensity_orientation_x_as_parallel.intensity_dimensions.y_start elif template.HasField("intensity_orientation_x_as_meridian"): @@ -4044,7 +4091,7 @@ def y_sampling(self) -> int: """Sampling along y-axis.""" template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): - raise TypeError("Conoscopic Sensor has no y_sampling dimension") + return None elif template.HasField("intensity_orientation_x_as_parallel"): return template.intensity_orientation_x_as_parallel.intensity_dimensions.y_sampling elif template.HasField("intensity_orientation_x_as_meridian"): @@ -4077,7 +4124,7 @@ def theta_max(self) -> float: template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.theta_max ) else: - raise TypeError("Only Conoscopic Sensor has theta_max dimension") + return None @theta_max.setter def theta_max(self, value: float = 45): @@ -4099,7 +4146,7 @@ def theta_max_sampling(self) -> int: template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.sampling ) else: - raise TypeError("Only Conoscopic Sensor has theta_max dimension") + return None @theta_max_sampling.setter def theta_max_sampling(self, value: int = 90): @@ -4211,3 +4258,114 @@ def set_type_spectral(self) -> BaseSensor.Spectral: self._sensor_template.intensity_sensor_template.sensor_type_spectral ) return self._type + + def set_layer_type_none(self) -> SensorRadiance: + """Define layer separation type as None. + + Returns + ------- + ansys.speos.core.sensor.SensorXMPIntensity + Intensity sensor + + """ + self._sensor_instance.intensity_properties.layer_type_none.SetInParent() + self._layer_type = None + return self + + def set_layer_type_source(self) -> SensorXMPIntensity: + """Define layer separation as by source. + + Returns + ------- + ansys.speos.core.sensor.SensorXMPIntensity + Intensity sensor + + """ + self._sensor_instance.intensity_properties.layer_type_source.SetInParent() + self._layer_type = None + return self + + def set_layer_type_face(self) -> BaseSensor.LayerTypeFace: + """Define layer separation as by face. + + Returns + ------- + ansys.speos.core.sensor.BaseSensor.LayerTypeFace + LayerTypeFace property instance + """ + if self._layer_type is None and self._sensor_instance.intensity_properties.HasField( + "layer_type_face" + ): + # Happens in case of project created via load of speos file + self._layer_type = BaseSensor.LayerTypeFace( + layer_type_face=self._sensor_instance.intensity_properties.layer_type_face, + default_values=False, + stable_ctr=True, + ) + elif not isinstance(self._layer_type, BaseSensor.LayerTypeFace): + # if the _layer_type is not LayerTypeFace then we create a new type. + self._layer_type = BaseSensor.LayerTypeFace( + layer_type_face=self._sensor_instance.intensity_properties.layer_type_face, + stable_ctr=True, + ) + elif ( + self._layer_type._layer_type_face + is not self._sensor_instance.intensity_properties.layer_type_face + ): + # Happens in case of feature reset (to be sure to always modify correct data) + self._layer_type._layer_type_face = ( + self._sensor_instance.intensity_properties.layer_type_face + ) + return self._layer_type + + def set_layer_type_sequence(self) -> BaseSensor.LayerTypeSequence: + """Define layer separation as by sequence. + + Returns + ------- + ansys.speos.core.sensor.BaseSensor.LayerTypeSequence + LayerTypeSequence property instance + """ + if self._layer_type is None and self._sensor_instance.intensity_properties.HasField( + "layer_type_sequence" + ): + # Happens in case of project created via load of speos file + self._layer_type = BaseSensor.LayerTypeSequence( + layer_type_sequence=self._sensor_instance.intensity_properties.layer_type_sequence, + default_values=False, + stable_ctr=True, + ) + elif not isinstance(self._layer_type, BaseSensor.LayerTypeSequence): + # if the _layer_type is not LayerTypeSequence then we create a new type. + self._layer_type = BaseSensor.LayerTypeSequence( + layer_type_sequence=self._sensor_instance.intensity_properties.layer_type_sequence, + stable_ctr=True, + ) + elif ( + self._layer_type._layer_type_sequence + is not self._sensor_instance.intensity_properties.layer_type_sequence + ): + # Happens in case of feature reset (to be sure to always modify correct data) + self._layer_type._layer_type_sequence = ( + self._sensor_instance.intensity_properties.layer_type_sequence + ) + 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 From 55274ad760164b5a26118af248289f78263d57ef Mon Sep 17 00:00:00 2001 From: sthoene Date: Thu, 3 Jul 2025 15:52:35 +0200 Subject: [PATCH 03/10] improve to prototype --- src/ansys/speos/core/sensor.py | 161 +++++++++++++++------------------ 1 file changed, 74 insertions(+), 87 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 2e0999827..7399e9e90 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -3783,7 +3783,9 @@ def __init__( if default_values: # Default values template - self.set_type_photometric().set_orientation_x_as_meridian() + self.set_type_photometric() + self.set_orientation_x_as_meridian() + self.set_viewing_direction_from_source() # Default values properties self.set_axis_system().set_layer_type_none() @@ -3912,18 +3914,52 @@ def layer( 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() + self._set_default_dimension_values() def set_orientation_x_as_parallel(self): """Set Orientation type: X as Parallel, Y As Meridian.""" self._sensor_template.intensity_sensor_template.intensity_orientation_x_as_parallel.SetInParent() + self._set_default_dimension_values() def set_orientation_conoscopic(self): """Set Orientation type: Conoscopic.""" self._sensor_template.intensity_sensor_template.intensity_orientation_conoscopic.SetInParent() + self._set_default_dimension_values() + + def set_viewing_direction_from_source(self): + """Set viewing direction from Source Looking At Sensor.""" + self._sensor_template.intensity_sensor_template.from_source_looking_at_sensor.SetInParent() + + def set_viewing_direction_from_sensor(self): + """Set viewing direction from Sensor Looking At Source.""" + self._sensor_template.intensity_sensor_template.from_sensor_looking_at_source.SetInParent() + + def _set_default_dimension_values(self): + template = self._sensor_template.intensity_sensor_template + if template.HasField("intensity_orientation_conoscopic"): + self.theta_max = 45 + self.theta_max_sampling = 90 + elif template.HasField("intensity_orientation_x_as_parallel"): + self.x_start = -30 + self.x_end = 30 + self.x_sampling = 120 + self.y_start = -45 + self.y_end = 45 + self.y_sampling = 180 + elif template.HasField("intensity_orientation_x_as_meridian"): + self.y_start = -30 + self.y_end = 30 + self.y_sampling = 120 + self.x_start = -45 + self.x_end = 45 + self.x_sampling = 180 @property def x_start(self) -> float: - """The minimum value on x-axis.""" + """The minimum value on x-axis (deg). + + By default, ``45``. + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return None @@ -3933,20 +3969,7 @@ def x_start(self) -> float: return template.intensity_orientation_x_as_meridian.intensity_dimensions.x_start @x_start.setter - def x_start(self, value: float = -45) -> SensorXMPIntensity.Dimensions: - """Set the minimum value on x-axis. - - Parameters - ---------- - value : float - Minimum value on x axis (deg). - By default, ``-45``. - - Returns - ------- - ansys.speos.core.sensor.SensorXMPIntensity.Dimensions - Dimensions. - """ + def x_start(self, value: float): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): raise TypeError("Conoscopic Sensor has no x_start dimension") @@ -3957,7 +3980,10 @@ def x_start(self, value: float = -45) -> SensorXMPIntensity.Dimensions: @property def x_end(self) -> float: - """The maximum value on x-axis.""" + """The maximum value on x-axis (deg). + + By default, ``45``. + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return None @@ -3967,20 +3993,7 @@ def x_end(self) -> float: return template.intensity_orientation_x_as_meridian.intensity_dimensions.x_end @x_end.setter - def x_end(self, value: float = 45): - """Set the maximum value on x axis. - - Parameters - ---------- - value : float - Maximum value on x axis (deg). - By default, ``45``. - - Returns - ------- - ansys.speos.core.sensor.SensorXMPIntensity.Dimensions - Dimensions. - """ + def x_end(self, value: float): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): raise TypeError("Conoscopic Sensor has no x_end dimension") @@ -3991,7 +4004,10 @@ def x_end(self, value: float = 45): @property def x_sampling(self) -> int: - """Pixel sampling along x-Axis.""" + """Pixel sampling along x-Axis. + + By default, ``100`` + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return None @@ -4001,15 +4017,7 @@ def x_sampling(self) -> int: return template.intensity_orientation_x_as_meridian.intensity_dimensions.x_sampling @x_sampling.setter - def x_sampling(self, value: int = 100): - """Set the sampling along x-axis. - - Parameters - ---------- - value : int - sampling along x-axis. - By default, ``100``. - """ + def x_sampling(self, value: int): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): raise TypeError("Conoscopic Sensor has no x_sampling dimension") @@ -4020,7 +4028,10 @@ def x_sampling(self, value: int = 100): @property def y_end(self) -> float: - """The maximum value on y-axis.""" + """The maximum value on y-axis (deg). + + By default, ``30``. + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return None @@ -4030,20 +4041,7 @@ def y_end(self) -> float: return template.intensity_orientation_x_as_meridian.intensity_dimensions.y_end @y_end.setter - def y_end(self, value: float = 30): - """Set the maximum value on y-axis. - - Parameters - ---------- - value : float - Minimum value on x axis (deg). - By default, ``30``. - - Returns - ------- - ansys.speos.core.sensor.SensorXMPIntensity.Dimensions - Dimensions. - """ + def y_end(self, value: float): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): raise TypeError("Conoscopic Sensor has no y_end dimension") @@ -4054,7 +4052,10 @@ def y_end(self, value: float = 30): @property def y_start(self) -> float: - """The minimum value on x axis.""" + """The minimum value on x-axis (deg). + + By default, ``-30``. + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return None @@ -4064,20 +4065,7 @@ def y_start(self) -> float: return template.intensity_orientation_x_as_meridian.intensity_dimensions.y_start @y_start.setter - def y_start(self, value: float = -30): - """Set the minimum value on y axis. - - Parameters - ---------- - value : float - Minimum value on y axis (deg). - By default, ``-30``. - - Returns - ------- - ansys.speos.core.sensor.SensorXMPIntensity.Dimensions - Dimensions. - """ + def y_start(self, value: float): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): raise TypeError("Conoscopic Sensor has no y_start dimension") @@ -4088,7 +4076,10 @@ def y_start(self, value: float = -30): @property def y_sampling(self) -> int: - """Sampling along y-axis.""" + """Sampling along y-axis. + + By default, ``100``. + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return None @@ -4098,15 +4089,7 @@ def y_sampling(self) -> int: return template.intensity_orientation_x_as_meridian.intensity_dimensions.y_sampling @y_sampling.setter - def y_sampling(self, value: int = 100): - """Set the sampling along y-axis. - - Parameters - ---------- - value : int - sampling along y-axis. - By default, ``100``. - """ + def y_sampling(self, value: int): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): raise TypeError("Conoscopic Sensor has no y_sampling dimension") @@ -4117,7 +4100,10 @@ def y_sampling(self, value: int = 100): @property def theta_max(self) -> float: - """Maximum theta angle on consocopic type.""" + """Maximum theta angle on consocopic type (in deg). + + By default, ``45``. + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return ( @@ -4127,8 +4113,7 @@ def theta_max(self) -> float: return None @theta_max.setter - def theta_max(self, value: float = 45): - """Set maximum theta angle on consocopic type.""" + def theta_max(self, value: float): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.theta_max = ( @@ -4139,7 +4124,10 @@ def theta_max(self, value: float = 45): @property def theta_max_sampling(self) -> int: - """Sampling on consocopic type.""" + """Sampling on conoscopic type. + + By default, ``90``. + """ template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): return ( @@ -4149,8 +4137,7 @@ def theta_max_sampling(self) -> int: return None @theta_max_sampling.setter - def theta_max_sampling(self, value: int = 90): - """Set Sampling on consocopic type.""" + def theta_max_sampling(self, value: int): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.sampling = ( From 682ed1db614860df0bc8a53ccc867efa4174978b Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:56:01 +0000 Subject: [PATCH 04/10] chore: adding changelog file 653.added.md [dependabot-skip] --- doc/changelog.d/653.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/653.added.md diff --git a/doc/changelog.d/653.added.md b/doc/changelog.d/653.added.md new file mode 100644 index 000000000..eb1531076 --- /dev/null +++ b/doc/changelog.d/653.added.md @@ -0,0 +1 @@ +Intensitysensor \ No newline at end of file From 305b3cdc8eaf4c0953967088e23ec043551afecc Mon Sep 17 00:00:00 2001 From: sthoene Date: Fri, 4 Jul 2025 11:51:17 +0200 Subject: [PATCH 05/10] convert axissystem to property change from cell integration angle to cell diameter rename theta_max_sampling --- src/ansys/speos/core/sensor.py | 62 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 4bb987605..a722cf9bc 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -25,12 +25,14 @@ from __future__ import annotations from difflib import SequenceMatcher +from math import radians from typing import List, Mapping, Optional, Union import uuid import warnings import grpc import numpy as np +from win32com.server.util import Collection from ansys.api.speos.sensor.v1 import camera_sensor_pb2, common_pb2, sensor_pb2 import ansys.speos.core as core @@ -3815,6 +3817,7 @@ def __init__( # Attribute gathering more complex intensity type self._type = None self._layer_type = None + self._cell_diameter = None if default_values: # Default values template @@ -3842,7 +3845,10 @@ def nearfield(self, value): @property def cell_distance(self): - """Distance of the Detector to origin in mm.""" + """Distance of the Detector to origin in mm. + + By default, ``10`` + """ if self.nearfield: return self._sensor_template.intensity_sensor_template.near_field.cell_distance else: @@ -3852,25 +3858,31 @@ def cell_distance(self): def cell_distance(self, value): if self.nearfield: self._sensor_template.intensity_sensor_template.near_field.cell_distance = value + else: raise TypeError("Sensor position is not in nearfield") @property - def cell_integration_angle(self): - """Integration angle of the cell (deg). + def cell_diameter(self): + """Cell diameter in mm. - Used with cell_distance to calculate the cell diameter. + By default, ``0.3491`` """ if self.nearfield: - return self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle + diameter = self.cell_distance * np.tan( + radians( + self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle + ) + ) + return diameter else: return None - @cell_integration_angle.setter - def cell_integration_angle(self, value): + @cell_diameter.setter + def cell_diameter(self, value): if self.nearfield: self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle = ( - value + np.degrees(np.arctan(value / 2 / self.cell_distance)) ) else: raise TypeError("Sensor position is not in nearfield") @@ -3973,7 +3985,7 @@ def _set_default_dimension_values(self): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): self.theta_max = 45 - self.theta_max_sampling = 90 + self.theta_sampling = 90 elif template.HasField("intensity_orientation_x_as_parallel"): self.x_start = -30 self.x_end = 30 @@ -4158,7 +4170,7 @@ def theta_max(self, value: float): raise TypeError("Only Conoscopic Sensor has theta_max dimension") @property - def theta_max_sampling(self) -> int: + def theta_sampling(self) -> int: """Sampling on conoscopic type. By default, ``90``. @@ -4171,8 +4183,8 @@ def theta_max_sampling(self) -> int: else: return None - @theta_max_sampling.setter - def theta_max_sampling(self, value: int): + @theta_sampling.setter + def theta_sampling(self, value: int): template = self._sensor_template.intensity_sensor_template if template.HasField("intensity_orientation_conoscopic"): template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.sampling = ( @@ -4373,21 +4385,21 @@ def set_layer_type_sequence(self) -> BaseSensor.LayerTypeSequence: ) return self._layer_type - def set_axis_system(self, axis_system: Optional[List[float]] = None) -> SensorXMPIntensity: - """Set position of the sensor. + @property + def axis_system(self) -> np.array: + """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]``. + 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. + np.array[float] + Axis system information as np.array. """ - 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 + return np.array(self._sensor_instance.intensity_properties.axis_system) + + @axis_system.setter + def axis_system(self, value: Collection[float]): + value = np.array(value) + self._sensor_instance.intensity_properties.axis_system[:] = value.flatten().tolist() From 2b2296652568963eaad43ad9420172886b597216 Mon Sep 17 00:00:00 2001 From: sthoene Date: Fri, 4 Jul 2025 12:18:28 +0200 Subject: [PATCH 06/10] fix sim export_unittest for windows add default values for nearfield sub elements --- src/ansys/speos/core/sensor.py | 5 ++++- tests/helper.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index a722cf9bc..c3383a86f 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -3838,7 +3838,10 @@ def nearfield(self) -> bool: @nearfield.setter def nearfield(self, value): if value: - self._sensor_template.intensity_sensor_template.near_field.SetInParent() + if not self._sensor_template.intensity_sensor_template.HasField("near_field"): + self._sensor_template.intensity_sensor_template.near_field.SetInParent() + self.cell_distance = 10 + self.cell_diameter = 0.3491 else: if self._sensor_template.intensity_sensor_template.HasField("near_field"): self._sensor_template.intensity_sensor_template.ClearField("near_field") diff --git a/tests/helper.py b/tests/helper.py index 21f53319c..d62b410a1 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -128,6 +128,15 @@ def remove_file(path): ---------- path (str) - path of the file. """ + + def rmtree(f: Path): + if f.is_file(): + f.unlink() + else: + for child in f.iterdir(): + rmtree(child) + f.rmdir() + if config.get("SpeosServerOnDocker"): subprocess.call( "docker exec " @@ -138,4 +147,4 @@ def remove_file(path): shell=True, ) else: - Path(path).unlink() + rmtree(Path(path)) From a5695daefc76fac46c45f84f735d64f19442fdad Mon Sep 17 00:00:00 2001 From: sthoene Date: Thu, 10 Jul 2025 09:05:21 +0200 Subject: [PATCH 07/10] Fix issue with import --- src/ansys/speos/core/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index c3383a86f..b03fb1aae 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -26,13 +26,12 @@ from difflib import SequenceMatcher from math import radians -from typing import List, Mapping, Optional, Union +from typing import Collection, List, Mapping, Optional, Union import uuid import warnings import grpc import numpy as np -from win32com.server.util import Collection from ansys.api.speos.sensor.v1 import camera_sensor_pb2, common_pb2, sensor_pb2 import ansys.speos.core as core From a8a59332a7fad9247f6f05cacef1d5017f22d1d6 Mon Sep 17 00:00:00 2001 From: sthoene Date: Thu, 10 Jul 2025 09:08:19 +0200 Subject: [PATCH 08/10] remove math import --- src/ansys/speos/core/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index b03fb1aae..11995a316 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -25,7 +25,6 @@ from __future__ import annotations from difflib import SequenceMatcher -from math import radians from typing import Collection, List, Mapping, Optional, Union import uuid import warnings @@ -3872,7 +3871,7 @@ def cell_diameter(self): """ if self.nearfield: diameter = self.cell_distance * np.tan( - radians( + np.radians( self._sensor_template.intensity_sensor_template.near_field.cell_integration_angle ) ) From b4ffedf1f649bbfffab14fce0c31f6b47d939dfa Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Mon, 27 Oct 2025 09:00:51 +0000 Subject: [PATCH 09/10] chore: adding changelog file 653.added.md [dependabot-skip] --- doc/changelog.d/653.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.d/653.added.md b/doc/changelog.d/653.added.md index eb1531076..a8ed3edc4 100644 --- a/doc/changelog.d/653.added.md +++ b/doc/changelog.d/653.added.md @@ -1 +1 @@ -Intensitysensor \ No newline at end of file +Intensitysensor From 6c58ab059a9f1a62d44fa0e05d27972a00aa7ddf Mon Sep 17 00:00:00 2001 From: ZachDerocher <166080731+ZachDerocher@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:22:51 +0900 Subject: [PATCH 10/10] test: further updates/support for intensity sensor (#673) ## Description Publishing a test branch for custom feature testing. sorry for duplicate... merging into the feature branch instead of main, and with additional update. ## Issue linked **Please mention the issue number or describe the problem this pull request addresses.** ## Checklist - [ ] I have tested my changes locally. - [ ] I have added necessary documentation or updated existing documentation. - [ ] I have followed the coding style guidelines of this project. - [ ] I have added appropriate tests (unit, integration, system). - [ ] I have reviewed my changes before submitting this pull request. - [ ] I have linked the issue or issues that are solved by the PR if any. - [ ] I have assigned this PR to myself. - [ ] I have made sure that the title of my PR follows [Conventional commits style](https://www.conventionalcommits.org/en/v1.0.0/#summary) (e.g. ``feat: add optical property``) - [ ] I have agreed with the Contributor License Agreement ([CLA](https://developer.ansys.com/form/cla-acceptance)). --------- Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> --- .gitignore | 1 + doc/changelog.d/673.test.md | 1 + src/ansys/speos/core/project.py | 20 +- src/ansys/speos/core/sensor.py | 175 +++++++++++++- tests/core/test_sensor.py | 398 +++++++++++++++++++++++++++++++- 5 files changed, 584 insertions(+), 11 deletions(-) create mode 100644 doc/changelog.d/673.test.md diff --git a/.gitignore b/.gitignore index ab653ee0c..94c90842a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ _autosummary # Testing +.cov/ .coverage .tox/ *,cover diff --git a/doc/changelog.d/673.test.md b/doc/changelog.d/673.test.md new file mode 100644 index 000000000..2bb20e2aa --- /dev/null +++ b/doc/changelog.d/673.test.md @@ -0,0 +1 @@ +Further updates/support for intensity sensor diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index b5c37fe8a..fd5f57222 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -47,6 +47,7 @@ SensorCamera, SensorIrradiance, SensorRadiance, + SensorXMPIntensity, ) from ansys.speos.core.simulation import ( SimulationDirect, @@ -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 @@ -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, @@ -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, @@ -970,6 +987,7 @@ def _create_speos_feature_preview( SensorRadiance, SensorCamera, Sensor3DIrradiance, + SensorXMPIntensity, SourceLuminaire, SourceRayFile, SourceSurface, diff --git a/src/ansys/speos/core/sensor.py b/src/ansys/speos/core/sensor.py index 0f21e64e1..de39ce4fe 100644 --- a/src/ansys/speos/core/sensor.py +++ b/src/ansys/speos/core/sensor.py @@ -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() @@ -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: @@ -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 @@ -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)) ) @@ -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() @@ -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 diff --git a/tests/core/test_sensor.py b/tests/core/test_sensor.py index 470471430..dcacea9ae 100644 --- a/tests/core/test_sensor.py +++ b/tests/core/test_sensor.py @@ -34,6 +34,7 @@ SensorCamera, SensorIrradiance, SensorRadiance, + SensorXMPIntensity, ) from ansys.speos.core.simulation import SimulationDirect from tests.conftest import test_path @@ -953,6 +954,305 @@ def test_create_radiance_sensor(speos: Speos): assert radiance_properties.HasField("layer_type_none") +@pytest.mark.supported_speos_versions(min=252) +def test_create_xmpintensity_sensor(speos: Speos): + """Test creation of XMP Intensity sensor.""" + p = Project(speos=speos) + + root_part = p.create_root_part() + body_b = root_part.create_body(name="TheBodyB") + body_b.create_face(name="TheFaceF").set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0]).set_facets( + [0, 1, 2] + ).set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1]) + body_c = root_part.create_body(name="TheBodyC") + body_c.create_face(name="TheFaceC1").set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0]).set_facets( + [0, 1, 2] + ).set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1]) + body_c.create_face(name="TheFaceC2").set_vertices([1, 0, 0, 2, 0, 0, 1, 1, 0]).set_facets( + [0, 1, 2] + ).set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1]) + root_part.commit() + + # Default value + sensor1 = p.create_sensor(name="Intensity.1", feature_type=SensorXMPIntensity) + sensor1.commit() + assert sensor1.sensor_template_link is not None + assert sensor1.sensor_template_link.get().HasField("intensity_sensor_template") + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + + assert sensor_template.HasField("sensor_type_photometric") + assert sensor_template.HasField("intensity_orientation_x_as_meridian") + assert sensor_template.intensity_orientation_x_as_meridian.HasField("intensity_dimensions") + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.x_start == -45.0 + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.x_end == 45.0 + assert ( + sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.x_sampling == 180 + ) + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.y_start == -30.0 + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.y_end == 30.0 + assert ( + sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.y_sampling == 120 + ) + + assert sensor1._sensor_instance.HasField("intensity_properties") + inte_properties = sensor1._sensor_instance.intensity_properties + assert inte_properties.axis_system == [ + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + ] + assert inte_properties.HasField("layer_type_none") + + # sensor_type_colorimetric + # default wavelengths range + sensor1.set_type_colorimetric() + sensor1.commit() + + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.HasField("sensor_type_colorimetric") + assert sensor_template.sensor_type_colorimetric.HasField("wavelengths_range") + assert sensor_template.sensor_type_colorimetric.wavelengths_range.w_start == 400 + assert sensor_template.sensor_type_colorimetric.wavelengths_range.w_end == 700 + assert sensor_template.sensor_type_colorimetric.wavelengths_range.w_sampling == 13 + # chosen wavelengths range + sensor1.set_type_colorimetric().set_wavelengths_range().set_start(value=450).set_end( + value=800 + ).set_sampling(value=15) + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.sensor_type_colorimetric.wavelengths_range.w_start == 450 + assert sensor_template.sensor_type_colorimetric.wavelengths_range.w_end == 800 + assert sensor_template.sensor_type_colorimetric.wavelengths_range.w_sampling == 15 + + # sensor_type_radiometric + sensor1.set_type_radiometric() + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.HasField("sensor_type_radiometric") + + # sensor_type_spectral + # default wavelengths range + sensor1.set_type_spectral() + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.HasField("sensor_type_spectral") + assert sensor_template.sensor_type_spectral.HasField("wavelengths_range") + assert sensor_template.sensor_type_spectral.wavelengths_range.w_start == 400 + assert sensor_template.sensor_type_spectral.wavelengths_range.w_end == 700 + assert sensor_template.sensor_type_spectral.wavelengths_range.w_sampling == 13 + # chosen wavelengths range + sensor1.set_type_spectral().set_wavelengths_range().set_start(value=450).set_end( + value=800 + ).set_sampling(value=15) + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.sensor_type_spectral.wavelengths_range.w_start == 450 + assert sensor_template.sensor_type_spectral.wavelengths_range.w_end == 800 + assert sensor_template.sensor_type_spectral.wavelengths_range.w_sampling == 15 + + # sensor_type_photometric + sensor1.set_type_photometric() + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.HasField("sensor_type_photometric") + + # dimensions: x-meridian + """ + sensor1.set_dimensions().set_x_start(value=-10).set_x_end(value=10).set_x_sampling( + value=60 + ).set_y_start(value=-20).set_y_end(value=20).set_y_sampling(value=120) + """ + sensor1.x_start = -10 + sensor1.x_end = 10 + sensor1.x_sampling = 60 + sensor1.y_start = -20 + sensor1.y_end = 20 + sensor1.y_sampling = 120 + sensor1.commit() + + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.intensity_orientation_x_as_meridian.HasField("intensity_dimensions") + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.x_start == -10.0 + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.x_end == 10.0 + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.x_sampling == 60 + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.y_start == -20.0 + assert sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.y_end == 20.0 + assert ( + sensor_template.intensity_orientation_x_as_meridian.intensity_dimensions.y_sampling == 120 + ) + + # dimensions: x-parallel + sensor1.set_orientation_x_as_parallel() + sensor1.x_start = -11 + sensor1.x_end = 11 + sensor1.x_sampling = 62 + sensor1.y_start = -21 + sensor1.y_end = 21 + sensor1.y_sampling = 122 + sensor1.commit() + + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.intensity_orientation_x_as_parallel.HasField("intensity_dimensions") + assert sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.x_start == -11.0 + assert sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.x_end == 11.0 + assert sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.x_sampling == 62 + assert sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.y_start == -21.0 + assert sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.y_end == 21.0 + assert ( + sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.y_sampling == 122 + ) + + # dimensions: conoscopic + sensor1.set_orientation_conoscopic() + sensor1.theta_max = 63 + sensor1.theta_sampling = 123 + sensor1.commit() + + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.intensity_orientation_conoscopic.HasField( + "conoscopic_intensity_dimensions" + ) + assert ( + sensor_template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.theta_max + == 63.0 + ) + assert ( + sensor_template.intensity_orientation_conoscopic.conoscopic_intensity_dimensions.sampling + == 123.0 + ) + + # dimensions: reset + sensor1.set_orientation_x_as_meridian() + + # properties + # axis_system + sensor1.set_axis_system([10, 50, 20, 1, 0, 0, 0, 1, 0, 0, 0, 1]) + sensor1.commit() + assert inte_properties.axis_system == [ + 10, + 50, + 20, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + ] + + # result file format + + # near field settings + sensor1.near_field = True + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.HasField("near_field") + + sensor1.cell_distance = 1 + sensor1.cell_diameter = 2 + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.near_field.cell_distance == 1 + assert sensor_template.near_field.cell_integration_angle == math.degrees(math.atan(2 / 2 / 1)) + + sensor1.near_field = False + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert ~sensor_template.HasField("near_field") + + # viewing direction + sensor1.set_viewing_direction_from_sensor() + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.HasField("from_sensor_looking_at_source") + + sensor1.set_viewing_direction_from_source() + sensor1.commit() + sensor_template = sensor1.sensor_template_link.get().intensity_sensor_template + assert sensor_template.HasField("from_source_looking_at_sensor") + + # layer_type_source + sensor1.set_layer_type_source() + sensor1.commit() + assert inte_properties.HasField("layer_type_source") + + # layer_type_face + sensor1.set_layer_type_face().set_sca_filtering_mode_intersected_one_time().set_layers( + values=[ + sensor.BaseSensor.FaceLayer( + name="Layer.1", geometries=[GeoRef.from_native_link("TheBodyB")] + ), + sensor.BaseSensor.FaceLayer( + name="Layer.2", + geometries=[ + GeoRef.from_native_link("TheBodyC/TheFaceC1"), + GeoRef.from_native_link("TheBodyC/TheFaceC2"), + ], + ), + ] + ) + sensor1.commit() + assert inte_properties.HasField("layer_type_face") + assert ( + inte_properties.layer_type_face.sca_filtering_mode + == inte_properties.layer_type_face.EnumSCAFilteringType.IntersectedOneTime + ) + assert len(inte_properties.layer_type_face.layers) == 2 + assert inte_properties.layer_type_face.layers[0].name == "Layer.1" + assert inte_properties.layer_type_face.layers[0].geometries.geo_paths == ["TheBodyB"] + assert inte_properties.layer_type_face.layers[1].name == "Layer.2" + assert inte_properties.layer_type_face.layers[1].geometries.geo_paths == [ + "TheBodyC/TheFaceC1", + "TheBodyC/TheFaceC2", + ] + + sensor1.set_layer_type_face().set_sca_filtering_mode_last_impact() + sensor1.commit() + assert ( + inte_properties.layer_type_face.sca_filtering_mode + == inte_properties.layer_type_face.EnumSCAFilteringType.LastImpact + ) + + # layer_type_sequence + sensor1.set_layer_type_sequence().set_maximum_nb_of_sequence( + value=5 + ).set_define_sequence_per_faces() + sensor1.commit() + assert inte_properties.HasField("layer_type_sequence") + assert inte_properties.layer_type_sequence.maximum_nb_of_sequence == 5 + assert ( + inte_properties.layer_type_sequence.define_sequence_per + == inte_properties.layer_type_sequence.EnumSequenceType.Faces + ) + + sensor1.set_layer_type_sequence().set_define_sequence_per_geometries() + sensor1.commit() + assert ( + inte_properties.layer_type_sequence.define_sequence_per + == inte_properties.layer_type_sequence.EnumSequenceType.Geometries + ) + + # layer_type_none + sensor1.set_layer_type_none() + sensor1.commit() + assert inte_properties.HasField("layer_type_none") + + # output_face_geometries + sensor1.delete() + + @pytest.mark.supported_speos_versions(min=252) def test_load_3d_irradiance_sensor(speos: Speos): """Test load of 3d irradiance sensor.""" @@ -1484,6 +1784,95 @@ def test_camera_modify_after_reset(speos: Speos): sensor1.delete() +@pytest.mark.supported_speos_versions(min=252) +def test_xmpintensity_modify_after_reset(speos: Speos): + """Test reset of intensity sensor, and then modify.""" + p = Project(speos=speos) + + # Create + commit + sensor1 = p.create_sensor(name="Sensor.1", feature_type=SensorXMPIntensity) + sensor1.set_layer_type_sequence() + sensor1.set_type_spectral() + sensor1.commit() + assert isinstance(sensor1, SensorXMPIntensity) + + # Ask for reset + sensor1.reset() + + # Modify after a reset + # Template + assert sensor1._sensor_template.intensity_sensor_template.HasField( + "intensity_orientation_x_as_meridian" + ) + sensor1.set_orientation_x_as_parallel() + assert sensor1._sensor_template.intensity_sensor_template.HasField( + "intensity_orientation_x_as_parallel" + ) + # Intermediate class for type : spectral + assert ( + sensor1._sensor_template.intensity_sensor_template.sensor_type_spectral.wavelengths_range.w_start + == 400 + ) + sensor1.set_type_spectral().set_wavelengths_range().set_start(value=500) + assert ( + sensor1._sensor_template.intensity_sensor_template.sensor_type_spectral.wavelengths_range.w_start + == 500 + ) + # Intermediate class for dimensions + assert ( + sensor1._sensor_template.intensity_sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.x_start + == -30 + ) + sensor1.x_start = -31 + assert ( + sensor1._sensor_template.intensity_sensor_template.intensity_orientation_x_as_parallel.intensity_dimensions.x_start + == -31 + ) + + # Props + assert sensor1._sensor_instance.intensity_properties.axis_system == [ + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + ] + sensor1.set_axis_system([50, 20, 10, 1, 0, 0, 0, 1, 0, 0, 0, 1]) + assert sensor1._sensor_instance.intensity_properties.axis_system == [ + 50, + 20, + 10, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + ] + # Intermediate class for layer type + assert ( + sensor1._sensor_instance.intensity_properties.layer_type_sequence.maximum_nb_of_sequence + == 10 + ) + sensor1.set_layer_type_sequence().set_maximum_nb_of_sequence(value=15) + assert ( + sensor1._sensor_instance.intensity_properties.layer_type_sequence.maximum_nb_of_sequence + == 15 + ) + + sensor1.delete() + + def test_delete_sensor(speos: Speos): """Test delete of sensor.""" p = Project(speos=speos) @@ -1509,12 +1898,13 @@ def test_delete_sensor(speos: Speos): assert sensor1._sensor_instance.HasField("irradiance_properties") # local -def test_get_sensor(speos: Speos, capsys): +def test_get_sensor(speos: Speos, capsys: pytest.CaptureFixture[str]): """Test get of a sensor.""" p = Project(speos=speos) sensor1 = p.create_sensor(name="Sensor.1", feature_type=SensorIrradiance) sensor2 = p.create_sensor(name="Sensor.2", feature_type=SensorRadiance) sensor3 = p.create_sensor(name="Sensor.3", feature_type=SensorCamera) + sensor4 = p.create_sensor(name="Sensor.4", feature_type=SensorXMPIntensity) # test when key exists name1 = sensor1.get(key="name") assert name1 == "Sensor.1" @@ -1522,6 +1912,8 @@ def test_get_sensor(speos: Speos, capsys): assert property_info is not None property_info = sensor3.get(key="axis_system") assert property_info is not None + property_info = sensor4.get(key="name") + assert property_info is not None # test when key does not exist get_result1 = sensor1.get(key="geometry") @@ -1536,3 +1928,7 @@ def test_get_sensor(speos: Speos, capsys): stdout, stderr = capsys.readouterr() assert get_result3 is None assert "Used key: geometry not found in key list" in stdout + get_result4 = sensor4.get(key="geometry") + stdout, stderr = capsys.readouterr() + assert get_result4 is None + assert "Used key: geometry not found in key list" in stdout