From 8cd52c405b3f766eae995595cf26d73bd2827c02 Mon Sep 17 00:00:00 2001 From: sthoene Date: Thu, 3 Jul 2025 13:32:49 +0200 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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