import os
from ansys_optical_automation.scdm_core.base import BaseSCDM
[docs]class Sensor(BaseSCDM):
    """
    Provides the parent class for all Speos sensor types.
    This class contains methods and properties that are common to all sensor types.
    It shouldn't be used by itself. Subclasses should be used instead.
    """
    def __init__(self, name, SpeosSim, SpaceClaim):
        """
        Initialize the base sensor object.
        The base sensor object has a name and some other attributes that are common to all
        Speos sensors. It does not have a Speos sensor object and cannot be used by
        simulations as is.
        This is an abstract class.
        Parameters
        ----------
        name : str
            Name of the sensor to create or find.
        SpeosSim : SpeosSim
            SpeosSim.
        SpaceClaim : SpaceClaim object
            SpaceClaim object.
        """
        super(Sensor, self).__init__(SpaceClaim, ["V19", "V20", "V21", "V22", "V23"])
        self.name = name
        self.speos_sim = SpeosSim
        self.speos_object = None
        self.axes = None
        self.origin = None
[docs]    def find_axes(self, origin=None):
        """
        Find a component with the same name as the sensor, look in it for an axis system and
        an origin point (if available), and save them as properties in self.
        Parameters
        ----------
        origin : SpaceClaim curve object, optional
            Name of the origin point to use for sensor positioning. For example, ``origin="EPP_195"``.
            This point must exist under ``Curves`` in the component of the same name as the sensor.
        """
        origin_point = None
        axes = None
        # go through parts and find part with the same name as self.name
        parts = self.GetRootPart().GetDescendants[self.IPart]()
        for part in parts:
            if self.name in part.Master.Name:
                # Find axis system
                axis_sys = part.GetDescendants[self.ICoordinateSystem]()[0]
                axes = axis_sys.GetDescendants[self.ICoordinateAxis]()
                # Find sensor origin
                if origin:  # if origin point is explicitly defined
                    curves = part.GetDescendants[self.IDesignCurve]()
                    for curve in curves:  # find the origin point under curves
                        if origin in curve.Master.Name:
                            origin_point = curve
                else:  # else define origin using axis system
                    origin_point = axis_sys
        self.axes = axes
        self.origin = origin_point 
[docs]    def set_position(self, x_reverse=False, y_reverse=False, origin=None, axes=None):
        """
        Set the origin and x and y directions of the sensor.
        For some intensity sensors, set the origin and polar-axis and V0/H0-axis.
        Parameters
        ----------
        x_reverse : bool, optional
            Whether to reverse the direction of the X-axis of the sensor. The default is ``False``.
        y_reverse : bool, optional
            Whether to reverse the direction of the Y-axis of the sensor. The default is ``False``.
        origin : text or integer, optional
            SpaceClaim axis system or a point. The default is ``None``.
        axes : list, optional
            List in the format ``[x-axis, y-axis]`` that defines the orientation of the axis, where
            ``x-axis`` and ``y-axis`` are SCDM axis objects (not an axis system). For IESNA and
            Elumdat intensity sensors, provide the polar-axis and V0/H0-axis instead.
        """
        if not self.speos_object:  # if self.speos_object (speos sensor speos_object) is not defined
            raise TypeError("No Speos object is defined.")
        if not (self.axes and self.origin) and not (axes and origin):  # if axes and/or origin not defined/provided
            raise NameError(
                "Axes or origin are not defined. Use the find_axes method or provide axes or origin as input."
            )
        if not (axes and origin):  # If no inputs provided, use self.axes and self.origin
            axes = self.axes
            origin = self.origin
        else:  # if axes and origin are provided as inputs, overwrite properties in self
            self.axes = axes
            self.origin = origin
        # Set sensor origin
        self.speos_object.OriginPoint.Set(origin)
        # Set sensor orientation (x and y directions)
        axis_x = axes[0]
        axis_y = axes[1]
        self.speos_object.XDirection.Set(axis_x)
        self.speos_object.YDirection.Set(axis_y)
        self.speos_object.XDirectionReverse = x_reverse
        self.speos_object.YDirectionReverse = y_reverse
        return self  
[docs]class Camera(Sensor):
    """
    Provides methods for defining the Speos camera sensor.
    """
    def __init__(self, name, SpeosSim, SpaceClaim):
        """
        Searches for a Speos camera sensor in the simulation tree. If the specified name is not found, a new
        Speos camera sensor is created with this name.
        Parameters
        ----------
        name : str
            Name of the sensor to find or create.
        """
        super(Camera, self).__init__(name, SpeosSim, SpaceClaim)
        speos_object = self.speos_sim.SensorCamera.Find(self.name)
        if not speos_object:  # if camera doesn't exist -> create a new camera
            speos_object = self.speos_sim.SensorCamera.Create()
            speos_object.Name = name
        self.speos_object = speos_object
[docs]    def set_distortion(self, distortion_file_name):
        """
        Parameters
        ----------
        distortion_file_name : str
            Name of the OPT distortion file.
        """
        distortion_path = os.path.join(".", "SPEOS input files", distortion_file_name)
        self.speos_object.DistorsionFile = distortion_path
        return self 
[docs]    def set_transmittance(self, transmittance_file_name):
        """
        Parameters
        ----------
        transmittance_file_name : str
            Name of the transmittance spectrum file.
        """
        transmittance_path = os.path.join(".", "SPEOS input files", transmittance_file_name)
        self.speos_object.TransmittanceFile = transmittance_path
        return self 
[docs]    def set_sensitivity(self, color, sensitivity_file_name):
        """
        Parameters
        ----------
        color : str
            Channel color. Options are ``"red"``, ``"green"``, and ``"blue"``.
        sensitivity_file_name : str
            Name of the sensitivity file.
        """
        sensitivity_path = os.path.join(".", "SPEOS input files", sensitivity_file_name)
        color = color.lower()
        if color == "red":
            self.speos_object.RedSpectrumFile = sensitivity_path
        elif color == "green":
            self.speos_object.GreenSpectrumFile = sensitivity_path
        elif color == "blue":
            self.speos_object.BlueSpectrumFile = sensitivity_path
        return self  
    # TODO: color mode.
    #  Camera1.ColorMode = SpeosSim.SensorCamera.EnumColorMode.Monochromatic
    #  Camera1.ColorMode = SpeosSim.SensorCamera.EnumColorMode.Color
    # TODO: set sensitivity for monochrome sensor. Camera1.SpectrumFile
[docs]class IntensitySensor(Sensor):
    """
    Provides methods for the Speos intensity sensor.
    """
    def __init__(self, name, SpeosSim, SpaceClaim):
        """
        Searches for a Speos intensity sensor in the simulation tree. If the specified name is
        not found, a new Speos intensity sensor is created with this name.
        Parameters
        ----------
        name : str
            Name of the sensor to find or create.
        SpeosSim : SpeosSim
            SpeosSim.
        SpaceClaim : SpaceClaim object
            SpaceClaim object.
        """
        super(IntensitySensor, self).__init__(name, SpeosSim, SpaceClaim)
        speos_object = self.speos_sim.SensorIntensity.Find(self.name)
        if not speos_object:
            speos_object = self.speos_sim.SensorIntensity.Create()
            speos_object.Name = name
        self.speos_object = speos_object
        self.sensor_format = None
[docs]    def set_range(self, x_start=None, x_end=None, y_start=None, y_end=None, x_mirrored=False, y_mirrored=False):
        """
        Set the sensor size.
        Parameters
        ----------
        x_start : int or float, optional
            X size of the sensor in millimeters for the positive part.
            The default is ``None``.
        x_end : int or float, optional
            X size of the sensor in millimeters for the negative part.
            The default is ``None``.
        y_start : int of float, optional
            Y size of the sensor in millimeters for the positive part.
            The default is ``None``.
        y_end : int or float, optional
            Y size of the sensor in millimeters for the negative part.
            The default is ``None``.
        x_mirrored : bool, optional
            Mirrored extend option of the X size of the sensor.
            The default is ``False``.
        y_mirrored : bool, optional
            Mirrored extend option of the Y size of the sensor.
            The default is ``False``.
        """
        if not all([x_start, x_end, y_start, y_end]):
            raise NameError("No inputs are provided.")
        if x_mirrored:
            self.speos_object.XIsMirrored = x_mirrored
        if y_mirrored:
            self.speos_object.YIsMirrored = y_mirrored
        if x_start and not self.speos_object.XIsMirrored:
            self.speos_object.XStart = x_start
        if x_end:
            self.speos_object.XEnd = x_end
        if y_start and not self.speos_object.YIsMirrored:
            self.speos_object.YStart = y_start
        if y_end:
            self.speos_object.YEnd = y_end 
[docs]    def set_sampling(self, x_sampling=None, y_sampling=None):
        """Set the number of samples on the axes.
        Parameters
        ----------
        x_sampling : int, optionl
            Number of samples on the X-axis. The default is ``None``.
        y_sampling : int
            Number of samples on the Y-axis. The default is ``None``.
        """
        if not x_sampling and not y_sampling:
            raise NameError("No inputs provided.")
        if x_sampling:
            self.speos_object.XNbSamples = x_sampling
        if y_sampling:
            self.speos_object.YNbSamples = y_sampling 
[docs]    def set_resolution(self, x_resolution=None, y_resolution=None):
        """
        Set the resolution of the sensor.
        Parameters
        ----------
        x_resolution : int, optional
            X resolution of the sensor in millimeters. The default is ``None``.
        y_resolution : int, optional
            Y resolution of the sensor in millimeters. The default is ``None``.
        """
        if not x_resolution and not y_resolution:
            raise NameError("No inputs are provided.")
        if x_resolution:
            self.speos_object.XResolution = x_resolution
        if y_resolution:
            self.speos_object.YResolution = y_resolution 
[docs]    def set_type(self, sensor_type):
        """Set the sensor type.
        Parameters
        ----------
        sensor_type : str
            Type of the sensor. Options are ``"photometric"``, ``"colorimetric"``, ``"radiometric"``
            and ``"spectral"``.
        """
        sensor_type = sensor_type.lower()
        if sensor_type == "photometric":
            self.speos_object.SensorType = self.speos_sim.SensorIntensity.EnumSensorType.Photometric
        elif sensor_type == "colorimetric":
            self.speos_object.SensorType = self.speos_sim.SensorIntensity.EnumSensorType.Colorimetric
        elif sensor_type == "radiometric":
            self.speos_object.SensorType = self.speos_sim.SensorIntensity.EnumSensorType.Radiometric
        elif sensor_type == "spectral":
            self.speos_object.SensorType = self.speos_sim.SensorIntensity.EnumSensorType.Spectral
        else:
            error_message = (
                "Unsupported sensor type. Supported types: photometric, colorimetric, radiometric, spectral."
            )
            raise ValueError(error_message) 
[docs]    def set_wavelength(self, w_start=None, w_end=None, w_sampling=None, w_resolution=None):
        """Set the wavelength of the sensor.
        Parameters
        ----------
        w_start : int, optional
            Start of the wavelength band in nanometers. The default is ``None``.
        w_end : int, optional
            End of the wavelength band in nanometers. The default is ``None``.
        w_sampling : int, optional
            Number of spectral samples. The default is ``None``.
        w_resolution : float, optional
            Spectral sampling or resolution (size of one sample) in nanometers.
            The default is ``None``.
        """
        if not w_start and not w_end and not w_sampling and not w_resolution:
            raise NameError("No inputs provided.")
        if w_start:
            self.speos_object.WavelengthStart = w_start
        if w_end:
            self.speos_object.WavelengthEnd = w_end
        if w_sampling:
            self.speos_object.WavelengthNbSamples = w_sampling
        if w_resolution:
            self.speos_object.WavelengthResolution = w_resolution 
[docs]    def set_layer(self, layer_type):
        """Set the layer type of the sensor.
        Parameters
        ----------
        layer_type : str
            Layer type of the sensor. Options are ``"source"``, ``"face"``, ``"sequence"``,
            and ``"none"``.
        """
        layer_type = layer_type.lower()
        if layer_type == "source":
            self.speos_object.LayerType = self.speos_sim.SensorIntensity.EnumLayerType.Source
        elif layer_type == "face":
            self.speos_object.LayerType = self.speos_sim.SensorIntensity.EnumLayerType.Face
        elif layer_type == "sequence":
            self.speos_object.LayerType = self.speos_sim.SensorIntensity.EnumLayerType.Sequence
        elif layer_type == "none":
            self.speos_object.LayerType = getattr(self.speos_sim.SensorIntensity.EnumLayerType, "None")
        else:
            error_message = "Unsupported layer type. Supported types: source, face, sequence, none."
            raise ValueError(error_message) 
[docs]    def set_integration_angle(self, integration_value):
        """
        set integration angle of the radiance sensor
        Parameters
        ----------
        integration_value
        """
        if self.sensor_format == "xmp" or self.sensor_format is None:
            raise ValueError("Cannot assign integration values")
        else:
            self.speos_object.IntegrationAngle = integration_value  
[docs]class RadianceSensor(Sensor):
    """
    Provides methods for the Speos Radiance sensor.
    """
    def __init__(self, name, SpeosSim, SpaceClaim):
        """
        Searches for a Speos Radiance sensor in the simulation tree. If the specified name is
        not found, a new Speos Radiance sensor is created with this name.
        Parameters
        ----------
        name : str
            Name of the sensor to find or create.
        SpeosSim : SpeosSim
            SpeosSim.
        SpaceClaim : SpaceClaim object
            SpaceClaim object.
        """
        super(RadianceSensor, self).__init__(name, SpeosSim, SpaceClaim)
        speos_object = self.speos_sim.SensorRadiance.Find(self.name)
        if not speos_object:
            speos_object = self.speos_sim.SensorRadiance.Create()
            speos_object.Name = name
        self.speos_object = speos_object
[docs]    def set_focal_value(self, focal_value):
        """
        Set focal value of the radiance sensor
        Parameters
        ----------
        focal_value : float
        """
        opt_sensor_type = self.speos_object.ObserverType.ToString()
        if opt_sensor_type == "Observer":
            error_message = "current radiance sensor observer type is not set to be focal"
            raise ValueError(error_message)
        else:
            self.speos_object.Focal = focal_value 
[docs]    def set_type(self, sensor_type):
        """
        set type of the radiance sensor
        Parameters
        ----------
        sensor_type : str
        """
        sensor_type = sensor_type.lower()
        if sensor_type == "photometric":
            self.speos_object.SensorType = self.speos_sim.SensorRadiance.EnumSensorType.Photometric
        elif sensor_type == "radiometric":
            self.speos_object.SensorType = self.speos_sim.SensorRadiance.EnumSensorType.Radiometric
        elif sensor_type == "colorimetric":
            self.speos_object.SensorType = self.speos_sim.SensorRadiance.EnumSensorType.Colorimetric
        elif sensor_type == "spectral":
            self.speos_object.SensorType = self.speos_sim.SensorRadiance.EnumSensorType.Spectral
        else:
            error_message = "please provide a valid radiance sensor type"
            raise ValueError(error_message) 
[docs]    def set_layer(self, layer_type):
        """
        set the layer type of the radiance sensor
        Parameters
        ----------
        layer_type : str
        """
        layer_type = layer_type.lower()
        if layer_type == "source":
            self.speos_object.LayerType = self.speos_sim.SensorRadiance.EnumLayerType.Source
        elif layer_type == "face":
            self.speos_object.LayerType = self.speos_sim.SensorRadiance.EnumLayerType.Face
        elif layer_type == "sequence":
            self.speos_object.LayerType = self.speos_sim.SensorRadiance.EnumLayerType.Sequence
        elif layer_type == "none":
            self.speos_object.LayerType = getattr(self.speos_sim.SensorRadiance.EnumLayerType, "None")
        else:
            error_message = "please provide a valid radiance layer type"
            raise ValueError(error_message) 
[docs]    def set_observer_type(self, observer_type):
        """
        set observer type of the radiance sensor
        Parameters
        ----------
        observer_type : str
        """
        observer_type = observer_type.lower()
        if observer_type == "observer":
            self.speos_object = self.speos_sim.SensorRadiance.EnumObserverType.Observer
        elif observer_type == "focal":
            self.speos_object = self.speos_sim.SensorRadiance.EnumObserverType.Focal
        else:
            error_message = "please provide a radiance type as observer or focal"
            raise ValueError(error_message) 
[docs]    def set_definition_type(self, definition_type):
        """
        set radiancec sensor definition type.
        Parameters
        ----------
        definition_type : str
            type as observer or frame
        Returns
        -------
        """
        definition_type = definition_type.lower()
        if definition_type.lower() == "observer":
            self.speos_object.DefinitionFrom = self.speos_sim.SensorRadiance.EnumDefinitionFrom.Observer
        elif definition_type.lower() == "frame":
            self.speos_object.DefinitionFrom = self.speos_sim.SensorRadiance.EnumDefinitionFrom.Frame
        else:
            error_message = "please provide a radiance type as observer or frame"
            raise ValueError(error_message) 
[docs]    def set_xmp_template(self, xml_file, take_dimension=False, take_display=False):
        """
        set the xmp template.
        Parameters
        ----------
        xml_file : str
            path of xml file
        take_dimension : bool
            True if setting of dimension used from xml, otherwise False
        take_display : bool
            True if setting of XMP display used from xml, otherwise False
        Returns
        -------
        """
        if os.path.isfile(xml_file):
            self.speos_object.XMPTemplateFile = xml_file
        else:
            msg = "Provided XML file is not found"
            raise ValueError(msg)
        if take_dimension:
            self.speos_object.DimensionFromFile = True
        if take_display:
            self.speos_object.DisplayPropertiesFromFile = True 
[docs]    def set_range(self, x_start=None, x_end=None, y_start=None, y_end=None, x_mirrored=False, y_mirrored=False):
        """
        Set the sensor size.
        Parameters
        ----------
        x_start : int or float, optional
            X size of the sensor in millimeters for the positive part.
            The default is ``None``.
        x_end : int or float, optional
            X size of the sensor in millimeters for the negative part.
            The default is ``None``.
        y_start : int of float, optional
            Y size of the sensor in millimeters for the positive part.
            The default is ``None``.
        y_end : int or float, optional
            Y size of the sensor in millimeters for the negative part.
            The default is ``None``.
        x_mirrored : bool, optional
            Mirrored extend option of the X size of the sensor.
            The default is ``False``.
        y_mirrored : bool, optional
            Mirrored extend option of the Y size of the sensor.
            The default is ``False``.
        """
        if not all([x_start, x_end, y_start, y_end]):
            raise NameError("No inputs are provided.")
        if x_mirrored:
            self.speos_object.XIsMirrored = x_mirrored
        if y_mirrored:
            self.speos_object.YIsMirrored = y_mirrored
        if x_start and not self.speos_object.XIsMirrored:
            self.speos_object.XStart = x_start
        if x_end:
            self.speos_object.XEnd = x_end
        if y_start and not self.speos_object.YIsMirrored:
            self.speos_object.YStart = y_start
        if y_end:
            self.speos_object.YEnd = y_end 
[docs]    def set_sampling(self, x_sampling, y_sampling):
        """
        set x and y sampling of the radiance sensor
        Parameters
        ----------
        x_sampling : int
        y_sampling : int
        """
        self.speos_object.XNbSamples = x_sampling
        self.speos_object.YNbSamples = y_sampling 
[docs]    def set_resolution(self, x_resolution, y_resolution):
        """
        set x and y resolution of the radiance sensor
        Parameters
        ----------
        x_resolution
        y_resolution
        """
        self.speos_object.XResolution = x_resolution
        self.speos_object.YResolution = y_resolution 
[docs]    def set_wavelength_resolution(self, resolution):
        """
        set wavelength resolution of the radiance sensor
        Parameters
        ----------
        resolution : float
        """
        opt_sensor_type = self.speos_object.SensorType.ToString()
        if opt_sensor_type != "Colorimetric" and opt_sensor_type != "Spectral":
            error_message = "current radiance sensor type does not have Wavelength attribute"
            raise ValueError(error_message)
        if resolution >= 100:
            error_message = "resolution not recommended"
            raise ValueError(error_message)
        else:
            self.speos_object.WavelengthResolution = resolution 
[docs]    def set_integration_angle(self, integration_value):
        """
        set integration angle of the radiance sensor
        Parameters
        ----------
        integration_value
        """
        self.speos_object.IntegrationAngle = integration_value 
[docs]    def set_observer_point(self, observer_point):
        """
        set observer point of the radiance sensor (Definition from Observer)
        Parameters
        ----------
        observer_point : point or origin (coordinate system)
        """
        self.speos_object.ObserverPoint.Set(observer_point) 
[docs]    def set_observer_directions(self, front_direction, top_direction):
        """
        set front and top directions of the radiance sensor (Definition from Observer)
        Parameters
        ----------
        front_direction : axis or line
        top_direction : axis or line
        """
        self.speos_object.FrontDirection.Set(front_direction)
        self.speos_object.TopDirection.Set(top_direction) 
[docs]    def set_fov(self, horizontal_fov, vertical_fov, horizontal_sampling, vertical_sampling):
        """
        set field of view and sampling of the radiance sensor (Definition from Observer)
        Parameters
        ----------
        horizontal_fov : float
        vertical_fov : float
        horizontal_sampling : float
        vertical_sampling : float
        """
        self.speos_object.HPlane = horizontal_fov
        self.speos_object.VPlane = vertical_fov
        self.speos_object.HNbSamples = horizontal_sampling
        self.speos_object.VNbSamples = vertical_sampling