Source code for vizSupport

#
#  ISC License
#
#  Copyright (c) 2016, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#
#  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
#  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
#  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
#  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
#  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
#  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

import os

import numpy as np
from Basilisk import __path__
from Basilisk.architecture import messaging
from Basilisk.simulation import spacecraft
from Basilisk.utilities import deprecated
from Basilisk.utilities import quadMapSupport as qms
from Basilisk.utilities import unitTestSupport
from matplotlib import colors
from matplotlib.colors import is_color_like

try:
    from Basilisk.simulation import vizInterface

    vizFound = True
except ImportError:
    vizFound = False

pauseFlag = False
endFlag = False

bskPath = __path__[0]

firstSpacecraftName = ""


def requires_viz(func):
    def wrapper(*args, **kwargs):
        if not vizFound:
            print("vizFound is false. Skipping this method.")
            return
        return func(*args, **kwargs)

    return wrapper


[docs] def assert_option(value, low, high, default=None): """Check if the value is in [low, high] inclusive.""" if value is None and default is not None: value = default if value < low or value > high: raise ValueError(f"Value must be between {low} and {high}, not {value}") return value
[docs] def assert_trinary(value, default=0): """Check if the value is in [-1, 0, 1] and map False to -1 or 0 depending on default.""" if value is False: # Map false to which isn't the default value = {-1: 0, 0: -1}[default] if value is None: value = default if value not in [-1, 0, 1]: raise ValueError("Value must be -1, 0, or 1") return value
def toRGBA255(color, alpha=None): if isinstance(color, str): # convert color name to 4D array of values with 0-255 if is_color_like(color): answer = np.array(colors.to_rgba(color, alpha=alpha)) * 255 answer = [round(a) for a in answer] else: raise ValueError("toRGBA255() was provided unknown color name " + color) else: if not isinstance(color, list): raise ValueError("ERROR: vizSupport: color must be a 4D array of integers") if max(color) > 255 or min(color) < 0: raise ValueError("ERROR: vizSupport: color values must be between [0,255]") answer = color return answer
[docs] def setSprite(shape, color=None): """ Helper function to set the sprite shape and optional sprite color. :param shape: Sprite shape, must be either "CIRCLE", "SQUARE", "TRIANGLE", "STAR", or "bskSat" :param kwargs: RGBA color, can be either color name string or a 4D list of [0,255] values :return: string of the protobuffer sprite setting """ shapeList = ["CIRCLE", "SQUARE", "TRIANGLE", "STAR", "bskSat"] if shape not in shapeList: raise KeyError( "The setSprite() method was provided this unknown sprite shape primitive: " + shape ) answer = shape if color is not None: if shape == "bskSat": raise ValueError("cannot set a color for the bskSat sprite option") colorValues = toRGBA255(color) answer += " " + " ".join(map(str, colorValues)) return answer
[docs] def lla2fixedframe(lla_GP, radEquator, radRatio): """ This method receives a latitude/longitude/altitude point above a reference ellipsoid with equatorial radius and flattening ratio, then converts to body-fixed frame coordinates. Parameters ---------- lla_GP: [rad,rad,m] position vector of the location G relative to the parent body in lat/lon/alt components radEquator: [m] equatorial radius of the parent body radRatio: ratio of polar radius to equatorial radius Returns ------- 3-element list [m] r_GP_P, position vector of the location G relative to parent body frame P in P frame components """ if len(lla_GP) != 3: raise ValueError(f"lla_GP must be a list of three floats, not {lla_GP}") if lla_GP[0] > np.pi / 2 or lla_GP[0] < -np.pi / 2: raise ValueError( f"Latitude must be between -pi/2 and pi/2 radians, not {lla_GP[0]}" ) lat = lla_GP[0] lon = lla_GP[1] alt = lla_GP[2] N = radEquator / np.sqrt(1 - (1 - radRatio**2) * np.sin(lat) ** 2) X = (N + alt) * np.cos(lat) * np.cos(lon) Y = (N + alt) * np.cos(lat) * np.sin(lon) Z = (N * (1 - (1 - radRatio**2)) + alt) * np.sin(lat) r_GP_P = [X, Y, Z] return r_GP_P
[docs] def fixedframe2lla(r_GP_P, radEquator, radRatio): """ This method receives a cartesian point above a reference ellipsoid with equatorial radius and flattening ratio, then converts to lat/lon/alt. Parameters ---------- r_GP_P: [m] position vector of the location G relative to the parent body radEquator: [m] equatorial radius of the parent body radRatio: ratio of polar radius to equatorial radius Returns ------- 3-element list [rad,rad,m] lla_GP, position vector of the location G relative to parent body frame P in lat/lon/alt """ X = r_GP_P[0] Y = r_GP_P[1] Z = r_GP_P[2] a = radEquator b = radEquator * radRatio e2 = 1 - (b / a) ** 2 # First eccentricity squared ep2 = (a**2 - b**2) / b**2 # Second eccentricity squared r = np.sqrt(X**2 + Y**2) lon = np.arctan2(Y, X) theta = np.arctan2(Z * a, r * b) stheta = np.sin(theta) ctheta = np.cos(theta) lat = np.arctan2(Z + ep2 * b * stheta**3, r - e2 * a * ctheta**3) N = a / np.sqrt(1 - e2 * np.sin(lat) ** 2) h = r / np.cos(lat) - N lla_GP = [lat, lon, h] return lla_GP
locationList = [] @requires_viz def addLocation( viz, stationName, parentBodyName, r_GP_P=None, lla_GP=None, gHat_P=None, fieldOfView=None, color=None, range=None, markerScale=None, isHidden=None, ): """ This method creates a Location instance on a parent body. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ stationName: str Location text label parentBodyName: str Name of the parent body P (spacecraft or planet) on which the location G is positioned. r_GP_P: 3-element double-list Position of G relative to parent body frame P. Required, if lla_GP not provided lla_GP: 3-element double-list Position of G relative to parent body in lat/lon/alt coordinates. Required, if r_GP_P not provided gHat_P: 3-element double-list Location normal relative to parent body frame. fieldOfView: double [rad] FOV angle measured edge-to-edge. color: int-list Color of the Location. Can be 4 RGBA integer value (0-255) or a color string. range: double [m] Range of the ground Location. markerScale: double Value will be multiplied by default marker scale, values less than 1.0 will decrease size, greater will increase. isHidden: bool True to hide Location, false to show (vizDefault) """ vizElement = vizInterface.LocationPbMsg() # Set location if r_GP_P is None == lla_GP is None: # xor raise ValueError("Either r_GP_P or lla_GP must be provided") if r_GP_P is not None: try: vizElement.r_GP_P = r_GP_P except TypeError: vizElement.r_GP_P = unitTestSupport.EigenVector3d2np(r_GP_P).tolist() if lla_GP is not None: # find gravity body gravBody = next( (s for s in viz.gravBodyInformation if s.bodyName == parentBodyName), None ) if gravBody is None: raise ValueError(f"Cannot use LLA to set location for {parentBodyName}") # convert lat/lon/altitude to fixed frame r_GP_P = lla2fixedframe(lla_GP, gravBody.radEquator, gravBody.radiusRatio) vizElement.r_GP_P = r_GP_P if gHat_P is None: gHat_P = r_GP_P / np.linalg.norm(r_GP_P) vizElement.stationName = stationName vizElement.parentBodyName = parentBodyName vizElement.gHat_P = gHat_P if color is not None: vizElement.color = toRGBA255(color) if range is not None: vizElement.range = range if markerScale is not None: if markerScale < 0.0: raise ValueError("markerScale must be a positive float") vizElement.markerScale = markerScale if isHidden is not None: vizElement.isHidden = isHidden if fieldOfView is not None: if fieldOfView > np.pi or fieldOfView < 0.0: raise ValueError( f"fieldOfView must be a value between 0 and Pi, not {fieldOfView}" ) vizElement.fieldOfView = fieldOfView # Pass to Vizard locationList.append(vizElement) del viz.locations[:] # clear settings list to replace it with updated list viz.locations = vizInterface.LocationConfig(locationList) quadMapList = [] @requires_viz def addQuadMap(viz, ID, parentBodyName, vertices, color, isHidden=None, label=None): """ This method creates a QuadMap element for displaying shaded regions in Vizard. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ ID: int The reference ID of the QuadMap instance. Once instantiated, can be used to change the QuadMap settings. parentBodyName: str Name of the parent body to draw the QuadMap in reference to. vertices: single or double-list Specifies the internal mesh coordinates of the QuadMap, in body-fixed frame. color: int list Color of the QuadMap. Can be 4 RGBA integer value (0-255) or a color string. isHidden: bool Flag if the QuadMap should be hidden (1) or shown (-1). Optional. Default: 0 - if not provided, then the Vizard default settings are used. label: str Label to display in the center of QuadMap region. Optional. Send "NOLABEL" to delete label. """ vizElement = vizInterface.QuadMap() vizElement.ID = ID vizElement.parentBodyName = parentBodyName vizElement.vertices = vizInterface.DoubleVector(vertices) vizElement.color = vizInterface.IntVector(toRGBA255(color)) if isHidden is not None: vizElement.isHidden = isHidden if label is not None: vizElement.label = label quadMapList.append(vizElement) del viz.quadMaps[:] # clear settings list to replace it with updated list viz.quadMaps = vizInterface.QuadMapVector(quadMapList) return pointLineList = [] @requires_viz def createPointLine(viz, toBodyName, lineColor, fromBodyName=None): """ This method creates a PointLine between two bodies. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ toBodyName: str Body which the PointLine points to. lineColor: int list Color of the PointLine. Can be 4 RGBA integer value (0-255) or a color string. fromBodyName: str Body from which PointLine originates. Optional, default selects ``firstSpacecraftName`` """ global firstSpacecraftName vizElement = vizInterface.PointLine() if fromBodyName is None: fromBodyName = firstSpacecraftName vizElement.fromBodyName = fromBodyName vizElement.toBodyName = toBodyName vizElement.lineColor = toRGBA255(lineColor) pointLineList.append(vizElement) # clear settings list to replace it with updated list del viz.settings.pointLineList[:] viz.settings.pointLineList = vizInterface.PointLineConfig(pointLineList) return targetLineList = [] @requires_viz def createTargetLine(viz, toBodyName, lineColor, fromBodyName=None): """ This method creates a TargetLine between two bodies. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ toBodyName: str Body which the PointLine points to. lineColor: int list Color of the PointLine. Can be 4 RGBA integer value (0-255) or a color string. fromBodyName: str Body from which PointLine originates. Optional, default selects ``firstSpacecraftName`` """ global firstSpacecraftName vizElement = vizInterface.PointLine() if fromBodyName is None: fromBodyName = firstSpacecraftName vizElement.fromBodyName = fromBodyName vizElement.toBodyName = toBodyName vizElement.lineColor = toRGBA255(lineColor) targetLineList.append(vizElement) updateTargetLineList(viz) return def updateTargetLineList(viz): # clear settings list to replace it with updated list del viz.liveSettings.targetLineList[:] viz.liveSettings.targetLineList = vizInterface.PointLineConfig(targetLineList) return customModelList = [] @requires_viz def createCustomModel( viz, modelPath, simBodiesToModify=None, offset=None, rotation=None, scale=None, customTexturePath="", normalMapPath="", shader=-1, color=None, ): """ This method creates a CustomModel. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ modelPath: str Path to model obj -OR- ``CUBE``, ``CYLINDER``, or ``SPHERE`` to use a primitive shape simBodiesToModify: list Which bodies in scene to replace with this model, use ``ALL_SPACECRAFT`` to apply custom model to all spacecraft in simulation Optional, default modifies ``firstSpacecraftName`` offset: 3-element double-list [m] Offset to use to draw the model Optional, default is [0.0, 0.0, 0.0] rotation: 3-element double-list [rad] 3-2-1 Euler angles to rotate CAD about z, y, x axes Optional, default is [0.0, 0.0, 0.0] scale: 3-element double-list Desired model scaling factor along the body x, y, z, axes in spacecraft CS Optional, default is [1.0, 1.0, 1.0] customTexturePath: str Path to texture to apply to model (note that a custom model's .mtl will be automatically imported with its textures during custom model import) Optional normalMapPath: str Path to the normal map for the customTexture shader: int Value of -1 to use viz default, 0 for Unity Specular Standard Shader, 1 for Unity Standard Shader color: int list Send desired RGBA as values between 0 and 255, default is gray, and will be applied to the albedo color setting """ global firstSpacecraftName vizElement = vizInterface.CustomModel() if offset is None: offset = [0.0, 0.0, 0.0] if rotation is None: rotation = [0.0, 0.0, 0.0] if scale is None: scale = [1.0, 1.0, 1.0] if simBodiesToModify is None: simBodiesToModify = [firstSpacecraftName] vizElement.modelPath = modelPath vizElement.offset = offset vizElement.rotation = rotation vizElement.scale = scale vizElement.customTexturePath = customTexturePath vizElement.normalMapPath = normalMapPath vizElement.shader = assert_option(shader, low=-1, high=1) if len(simBodiesToModify) == 0: raise ValueError("simBodiesToModify must be a non-empty list of strings") vizElement.simBodiesToModify = vizInterface.StringVector(simBodiesToModify) if color is not None: vizElement.color = vizInterface.IntVector(color) customModelList.append(vizElement) # clear settings list to replace it with updated list del viz.settings.customModelList[:] viz.settings.customModelList = vizInterface.CustomModelConfig(customModelList) return actuatorGuiSettingList = [] @requires_viz def setActuatorGuiSetting( viz, spacecraftName=None, viewThrusterPanel=None, viewThrusterHUD=None, viewRWPanel=None, viewRWHUD=None, showThrusterLabels=None, showRWLabels=None, ): """ This method sets the actuator GUI properties for a particular spacecraft. If no ``spacecraftName`` is provided, then the name of the first spacecraft in the simulation is assumed. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ spacecraftName: str The name of the spacecraft for which the actuator GUI options are set. Default: If not provided, then the name of the first spacecraft in the simulation is used. viewThrusterPanel: bool flag if the GUI panel should be shown illustrating the thruster states Default: if not provided, then the Vizard default settings are used viewRWPanel: bool flag if the GUI panel should be shown illustrating the reaction wheel states Default: if not provided, then the Vizard default settings are used viewThrusterHUD: bool flag if the HUD visualization of the thruster states should be shown Default: if not provided, then the Vizard default settings are used viewRWHUD: bool flag if the HUD visualization of the reaction wheel states should be shown Default: if not provided, then the Vizard default settings are used showThrusterLabels: bool flag if the thruster labels should be shown Default: if not provided, then the Vizard default settings are used showRWLabels: bool flag if the reaction wheel labels should be shown Default: if not provided, then the Vizard default settings are used """ global firstSpacecraftName vizElement = vizInterface.ActuatorGuiSettings() if spacecraftName is None: spacecraftName = firstSpacecraftName vizElement.spacecraftName = spacecraftName if viewThrusterPanel is not None: vizElement.viewThrusterPanel = viewThrusterPanel if viewThrusterHUD is not None: vizElement.viewThrusterHUD = viewThrusterHUD if viewRWPanel is not None: vizElement.viewRWPanel = viewRWPanel if viewRWHUD is not None: vizElement.viewRWHUD = viewRWHUD if showThrusterLabels is not None: vizElement.showThrusterLabels = showThrusterLabels if showRWLabels is not None: vizElement.showRWLabels = showRWLabels actuatorGuiSettingList.append(vizElement) # clear settings list to replace it with updated list del viz.settings.actuatorGuiSettingsList[:] viz.settings.actuatorGuiSettingsList = vizInterface.ActuatorGuiSettingsConfig( actuatorGuiSettingList ) return instrumentGuiSettingList = [] @requires_viz def setInstrumentGuiSetting( viz, spacecraftName=None, viewCSSPanel=0, viewCSSCoverage=0, viewCSSBoresight=0, showCSSLabels=0, showGenericSensorLabels=0, showTransceiverLabels=0, showTransceiverFrustrum=0, showGenericStoragePanel=0, showMultiShapeLabels=0, ): """ This method sets the instrument GUI properties for a particular spacecraft. If no ``spacecraftName`` is provided, then the name of the first spacecraft in the simulation is assumed. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ spacecraftName: str The name of the spacecraft for which the actuator GUI options are set. Default: 0 - If not provided, then the name of the first spacecraft in the simulation is used. viewCSSPanel: int flag if the GUI panel should be shown (1) or hidden (-1) illustrating the CSS states Default: 0 - if not provided, then the Vizard default settings are used viewCSSCoverage: int flag if the HUD spherical coverage of the CSS states should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used viewCSSBoresight: int flag if the HUD boresight axes of the CSS states should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used showCSSLabels: int flag if the CSS labels should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used showGenericSensorLabels: int flag if the generic sensor labels should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used showTransceiverLabels: int flag if the generic sensor labels should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used showTransceiverFrustrum: int flag if the generic sensor labels should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used showGenericStoragePanel: int flag if the generic sensor labels should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used showMultiShapeLabels: int flag if the generic sensor labels should be shown (1) or hidden (-1) Default: 0 - if not provided, then the Vizard default settings are used """ global firstSpacecraftName vizElement = vizInterface.InstrumentGuiSettings() if spacecraftName is None: spacecraftName = firstSpacecraftName vizElement.spacecraftName = spacecraftName vizElement.viewCSSPanel = assert_trinary(viewCSSPanel, default=0) vizElement.viewCSSCoverage = assert_trinary(viewCSSCoverage, default=0) vizElement.viewCSSBoresight = assert_trinary(viewCSSBoresight, default=0) vizElement.showCSSLabels = assert_trinary(showCSSLabels, default=0) vizElement.showGenericSensorLabels = assert_trinary( showGenericSensorLabels, default=0 ) vizElement.showTransceiverLabels = assert_trinary(showTransceiverLabels, default=0) vizElement.showTransceiverFrustrum = assert_trinary( showTransceiverFrustrum, default=0 ) vizElement.showGenericStoragePanel = assert_trinary( showGenericStoragePanel, default=0 ) vizElement.showMultiShapeLabels = assert_trinary(showMultiShapeLabels, default=0) instrumentGuiSettingList.append(vizElement) # clear settings list to replace it with updated list del viz.settings.instrumentGuiSettingsList[:] viz.settings.instrumentGuiSettingsList = vizInterface.InstrumentGuiSettingsConfig( instrumentGuiSettingList ) return coneInOutList = [] @requires_viz def createConeInOut( viz, toBodyName, coneColor, isKeepIn, normalVector_B, incidenceAngle, coneHeight, fromBodyName=None, position_B=None, coneName="", ): """ This method creates a ``KeepOutInCone``. :param viz: copy of the vizInterface module :return: void Keyword Args ------------ fromBodyName: str Name of body to attach cone onto. Optional, default selects ``firstSpacecraftName`` toBodyName: str Detect changes if this body has impingement on cone. coneColor: int list Color of the KeepOutInCone. Can be 4 RGBA integer value (0-255) or a color string. isKeepIn: bool True -> keep-in cone created, False -> keep-out cone created position_B: 3-element double-list [m] Cone start relative to from-body coordinate frame. Optional, default [0.0, 0.0, 0.0] normalVector_B: 3-element double-list Cone normal direction vector incidenceAngle: double [rad] Cone incidence angle coneHeight: double [m] Sets height of visible cone (aesthetic only, does not impact function) coneName: str Cone name, if unspecified, viz will autogenerate name """ global firstSpacecraftName vizElement = vizInterface.KeepOutInCone() if fromBodyName is None: fromBodyName = firstSpacecraftName if position_B is None: position_B = [0.0, 0.0, 0.0] vizElement.fromBodyName = fromBodyName vizElement.toBodyName = toBodyName vizElement.coneColor = toRGBA255(coneColor) vizElement.isKeepIn = isKeepIn vizElement.position_B = position_B vizElement.normalVector_B = normalVector_B vizElement.incidenceAngle = incidenceAngle vizElement.coneHeight = coneHeight vizElement.coneName = coneName coneInOutList.append(vizElement) del viz.settings.coneList[:] # clear settings list to replace it with updated list viz.settings.coneList = vizInterface.KeepOutInConeConfig(coneInOutList) return stdCameraList = [] @requires_viz def createStandardCamera( viz, spacecraftName=None, setMode=None, showHUDElementsInImage=None, setView=None, fieldOfView=-1, bodyTarget="", pointingVector_B=None, position_B=None, displayName=None, ): """ This method creates a Standard Camera. :param viz: copy of the vizInterface module :return: camera instance Keyword Args ------------ spacecraftName: str Name of spacecraft to attach camera onto. Optional, default selects ``firstSpacecraftName`` setMode: int 0 -> body targeting, 1 -> pointing vector (default). showHUDElementsInImage: int Value of 0 (protobuffer default) to use viz default, -1 for false, 1 for true setView: int 0 -> nadir (default), 1 -> orbit normal, 2 -> along track. This is a setting for body targeting mode. fieldOfView: double [rad] FOV angle measured edge-to-edge, -1 to use viz default. bodyTarget: str Name of body camera should point to (default to first celestial body in messages). This is a setting for body targeting mode. pointingVector_B: 3-element double-list Camera pointing vector in the spacecraft body frame. Optional, default [1.0, 0.0, 0.0] position_B: 3-element double-list If a non-zero vector, this determines the location of the camera. If a zero vector, then the camera is placed outside the spacecraft along the pointing vector direction. Optional, default [0.0, 0.0, 0.0] displayName: str Name of the standard camera panel. Optional """ global firstSpacecraftName cam = vizInterface.StdCameraSettings() if spacecraftName is None: spacecraftName = firstSpacecraftName if pointingVector_B is None: pointingVector_B = [1.0, 0.0, 0.0] if position_B is None: position_B = [0.0, 0.0, 0.0] cam.spacecraftName = spacecraftName cam.showHUDElementsInImage = assert_trinary(showHUDElementsInImage, default=0) cam.fieldOfView = fieldOfView cam.bodyTarget = bodyTarget cam.pointingVector_B = pointingVector_B cam.position_B = position_B if setMode is not None: cam.setMode = assert_option(setMode, low=0, high=1) if setView is not None: cam.setView = assert_option(setView, low=0, high=2) if displayName is not None: cam.displayName = displayName stdCameraList.append(cam) # clear settings list to replace it with updated list del viz.settings.stdCameraList[:] viz.settings.stdCameraList = vizInterface.StdCameraConfig(stdCameraList) return cam @requires_viz def createCameraConfigMsg( viz, cameraID, fieldOfView, resolution, cameraPos_B, sigma_CB, parentName=None, renderRate=None, skyBox="", postProcessingOn=0, ppFocusDistance=None, ppAperature=None, ppFocalLength=None, ppMaxBlurSize=None, updateCameraParameters=False, renderMode=False, depthMapClippingPlanes=None, showHUDElementsInImage=None, ): """ This method configures camera settings. :param viz: copy of the vizInterface module :return: camera instance Keyword Args ------------ cameraID: int ID of the camera that took the snapshot. parentName: str Name of the parent body to which the camera should be attached Optional, default is ``firstSpacecraftName`` fieldOfView: double [rad] Camera FOV, edge-to-edge along camera y-axis. resolution: 2-element int-list Camera resolution, width/height in pixels. renderRate: int [ns] Frame time interval at which to capture images. cameraPos_B: 3-element double-list [m] Camera position in body frame. sigma_CB: 3-element double-list MRP defining the orientation of the camera frame relative to the body frame. skyBox: str String containing the star field preference. postProcessingOn: int Enable post-processing of camera image. Value of 0 (protobuffer default) to use viz default which is off, -1 for false, 1 for true. ppFocusDistance: double Distance to the point of focus, minimum value of 0.1, Value of 0 to turn off this parameter entirely. ppAperature: double Ratio of the aperture (known as f-stop or f-number). The smaller the value is, the shallower the depth of field is. Valid Setting Range: 0.05 to 32. Value of 0 to turn off this parameter entirely. ppFocalLength: double Valid setting range: 0.001m to 0.3m. Value of 0 to turn off this parameter entirely. ppMaxBlurSize: int Convolution kernel size of the bokeh filter, which determines the maximum radius of bokeh. It also affects the performance (the larger the kernel is, the longer the GPU time is required). Depth textures Value of 1 for Small, 2 for Medium, 3 for Large, 4 for Extra Large. Value of 0 to turn off this parameter entirely. updateCameraParameters: int If true, commands camera to update Instrument Camera to current message's parameters. renderMode: int Value of 0 to render visual image (default), value of 1 to render depth buffer to image. depthMapClippingPlanes: 2-element double-list [m] Set the bounds of rendered depth map by setting the near and far clipping planes when in renderMode=1 (depthMap mode). Default values of 0.1 and 100. showHUDElementsInImage: int Value of 0 (protobuffer default) to use viz default, -1 for false, 1 for true """ global firstSpacecraftName cameraConfigMsgPayload = messaging.CameraConfigMsgPayload() if parentName is None: parentName = firstSpacecraftName cameraConfigMsgPayload.cameraID = cameraID cameraConfigMsgPayload.parentName = parentName cameraConfigMsgPayload.fieldOfView = fieldOfView cameraConfigMsgPayload.resolution = resolution cameraConfigMsgPayload.cameraPos_B = cameraPos_B cameraConfigMsgPayload.sigma_CB = sigma_CB cameraConfigMsgPayload.skyBox = skyBox cameraConfigMsgPayload.postProcessingOn = assert_trinary( postProcessingOn, default=0 ) cameraConfigMsgPayload.cameraID = int(updateCameraParameters) cameraConfigMsgPayload.renderMode = int(renderMode) cameraConfigMsgPayload.showHUDElementsInImage = assert_trinary( showHUDElementsInImage, default=0 ) if renderRate is not None: # convert to nano-seconds cameraConfigMsgPayload.renderRate = int(renderRate * 1e9) if ppFocusDistance is not None: if ppFocusDistance != 0 and ppFocusDistance < 0.1: raise ValueError("ppFocusDistance must be 0 or greater than 0.1") cameraConfigMsgPayload.ppFocusDistance = ppFocusDistance if ppAperature is not None: if ppAperature != 0 and (ppAperature < 0.05 or ppAperature > 32): raise ValueError("ppAperature must be 0 or within [0.05, 32]") cameraConfigMsgPayload.ppAperture = ppAperature if ppFocalLength is not None: if ppFocalLength != 0 and (ppFocalLength < 0.001 or ppFocalLength > 0.3): raise ValueError("ppFocalLength must be 0 or within [0.001, 0.3]") cameraConfigMsgPayload.ppFocalLength = ppFocalLength if ppMaxBlurSize is not None: cameraConfigMsgPayload.ppMaxBlurSize = assert_option( ppMaxBlurSize, low=0, high=4 ) if depthMapClippingPlanes is not None: if not cameraConfigMsgPayload.renderMode: raise ValueError( "depthMapClippingPlanes can only be set when renderMode is 1 (depthMap)." ) cameraConfigMsgPayload.depthMapClippingPlanes = depthMapClippingPlanes else: cameraConfigMsgPayload.depthMapClippingPlanes = [-1.0, -1.0] cameraConfigMsg = messaging.CameraConfigMsg().write(cameraConfigMsgPayload) # need to add code to retain camera config msg in memory. Below # the function makes vizInterface subscribe to the pointer to this Msg object viz.addCamMsgToModule(cameraConfigMsg) return cameraConfigMsgPayload def ensure_correct_len_list(input, length, depth=1): # Allow lists of all None to pass through as long as they are the correct length if ( isinstance(input, list) and all([i is None for i in input]) and len(input) == length ): return input current_depth = 0 level = input while isinstance(level, list): current_depth += 1 # Skip over Nones when checking shapes at a given level level = next((item for item in level if item is not None), None) for _ in range(current_depth, depth): input = [input] if len(input) != length: raise ValueError(f"List length should be {length}, not {len(input)}") return input @requires_viz def enableUnityVisualization( scSim, simTaskName, scList, saveFile=None, rwEffectorList=None, thrEffectorList=None, thrColors=None, cssList=None, genericSensorList=None, ellipsoidList=None, lightList=None, genericStorageList=None, transceiverList=None, spriteList=None, modelDictionaryKeyList=None, logoTextureList=None, oscOrbitColorList=None, trueOrbitColorList=None, msmInfoList=None, trueOrbitColorInMsgList=None, liveStream=False, broadcastStream=False, noDisplay=False, ): """ This method creates an instance of the vizInterface() modules and sets up associated Vizard configuration setting messages. Parameters ---------- scSim: variable with the simulationBaseClass copy simTaskName: task to which to add the vizInterface module scList: :ref:`spacecraft` objects. Can be a single object or list of objects Keyword Args ------------ saveFile: str can be a single python file name, or a full path + file name. In both cases a local results are stored in a local sub-folder called ``_VizFiles``. If a data file name is provided directly (i.e. it ends with ``.bin``), then the associated file path and name are used explicitly. Default: empty string resulting in the data not being saved to a file rwEffectorList: single or list of ``ReactionWheelStateEffector`` The list must have the same length ``scList``. Each entry is the :ref:`ReactionWheelStateEffector` instance for the spacecraft, or ``None`` if the spacecraft has no RW devices. thrEffectorList: single or double-list of :ref:`ThrusterDynamicEffector` The list must have the same length ``scList``. Each entry is a list of :ref:`ReactionWheelStateEffector` instances for the spacecraft denoting a thruster cluster, or ``None`` if the spacecraft has no thruster devices. thrColors: single or vector of int(4) array of RGBA color values for each thruster set. The list must have the same length as ``scList``. Each list entry is a list of RGBA array values for each cluster set. cssList: list of lists of :ref:`CoarseSunSensor` objects. The outer list length must match ``scList``. genericSensorList: list of lists of ``GenericSensor`` structures. The outer list length must match ``scList``. ellipsoidList: list of lists of ``Ellipsoid`` structures. The outer list length must match ``scList``. lightList: list of lists of ``Light`` structures. The outer list length must match ``scList``. genericStorageList: list of lists of ``GenericStorage`` structures. The outer list length must match ``scList``. transceiverList: list of lists of :ref:`Transceiver` objects. The outer list length must match ``scList``. spriteList: list of sprite information for each spacecraft. The outer list length must match ``scList``. modelDictionaryKeyList: list of the spacecraft model dictionary. The outer list length must match ``scList``. logoTextureList: list of the spacecraft logo texture file paths. The outer list length must match ``scList``. oscOrbitColorList: list of spacecraft osculating orbit colors. Can be 4 RGBA integer value (0-255), a color string, or ``None`` if default values should be used. The array must be of the length of the spacecraft list trueOrbitColorList: list of spacecraft true or actual orbit colors. Can be 4 RGBA integer value (0-255), a color string, or ``None`` if default values should be used. The array must be of the length of the spacecraft list trueOrbitColorInMsgList: list of color messages to read and provide the true orbit color at each time step. This overwrites the values set with trueOrbitColorList. msmInfoList: list of MSM configuration messages liveStream: bool flag if live data streaming to Vizard should be used broadcastStream: bool flag if messages should be broadcast for listener Vizards to pick up. noDisplay: bool flag if Vizard should run performance opNav (no Vizard display) Returns ------- :ref:`vizInterface` object copy of the vizInterface instance """ # clear the list of point line elements del pointLineList[:] del actuatorGuiSettingList[:] del coneInOutList[:] global firstSpacecraftName # set up the Vizard interface module vizMessenger = vizInterface.VizInterface() vizMessenger.settings = vizInterface.VizSettings() vizMessenger.ModelTag = "vizMessenger" scSim.AddModelToTask(simTaskName, vizMessenger) # ensure the spacecraft object list is a list if not isinstance(scList, list): scList = [scList] scListLength = len(scList) firstSpacecraftName = scList[0].ModelTag if rwEffectorList is not None: rwEffectorList = ensure_correct_len_list(rwEffectorList, scListLength) if thrEffectorList is not None: thrEffectorList = ensure_correct_len_list( thrEffectorList, scListLength, depth=2 ) if thrColors is not None: thrColors = ensure_correct_len_list(thrColors, scListLength, depth=3) if cssList is not None: cssList = ensure_correct_len_list(cssList, scListLength, depth=2) if genericSensorList is not None: genericSensorList = ensure_correct_len_list( genericSensorList, scListLength, depth=2 ) if ellipsoidList is not None: ellipsoidList = ensure_correct_len_list(ellipsoidList, scListLength, depth=2) if lightList is not None: lightList = ensure_correct_len_list(lightList, scListLength, depth=2) if genericStorageList is not None: genericStorageList = ensure_correct_len_list( genericStorageList, scListLength, depth=2 ) if transceiverList is not None: transceiverList = ensure_correct_len_list( transceiverList, scListLength, depth=2 ) if spriteList is not None: spriteList = ensure_correct_len_list(spriteList, scListLength) if modelDictionaryKeyList is not None: modelDictionaryKeyList = ensure_correct_len_list( modelDictionaryKeyList, scListLength ) if logoTextureList is not None: logoTextureList = ensure_correct_len_list(logoTextureList, scListLength) if oscOrbitColorList is not None: oscOrbitColorList = ensure_correct_len_list( oscOrbitColorList, scListLength, depth=2 ) if trueOrbitColorList is not None: trueOrbitColorList = ensure_correct_len_list( trueOrbitColorList, scListLength, depth=2 ) if trueOrbitColorInMsgList is not None: trueOrbitColorInMsgList = ensure_correct_len_list( trueOrbitColorInMsgList, scListLength ) if msmInfoList is not None: msmInfoList = ensure_correct_len_list(msmInfoList, scListLength) # loop over all spacecraft to associated states and msg information planetNameList = [] planetInfoList = [] spiceMsgList = [] vizMessenger.scData.clear() c = 0 spacecraftParentName = "" for sc in scList: # create spacecraft information container scData = vizInterface.VizSpacecraftData() # link to spacecraft state message if isinstance(sc, type(spacecraft.Spacecraft())): # set spacecraft name scData.spacecraftName = sc.ModelTag spacecraftParentName = sc.ModelTag scData.scStateInMsg.subscribeTo(sc.scStateOutMsg) # link to celestial bodies information for gravBody in sc.gravField.gravBodies: # check if the celestial object has already been added if gravBody.planetName not in planetNameList: planetNameList.append(gravBody.planetName) planetInfo = vizInterface.GravBodyInfo() if gravBody.displayName == "": planetInfo.bodyName = gravBody.planetName else: planetInfo.bodyName = gravBody.displayName planetInfo.mu = gravBody.mu planetInfo.radEquator = gravBody.radEquator planetInfo.radiusRatio = gravBody.radiusRatio planetInfo.modelDictionaryKey = gravBody.modelDictionaryKey planetInfoList.append(planetInfo) spiceMsgList.append(gravBody.planetBodyInMsg) else: # the scList object is an effector belonging to the parent spacecraft scData.parentSpacecraftName = spacecraftParentName ModelTag = sc[0] effStateOutMsg = sc[1] scData.spacecraftName = ModelTag scData.scStateInMsg.subscribeTo(effStateOutMsg) # process RW effectors if rwEffectorList: rwList = [] if rwEffectorList[c] is not None: # RWs have been added to this spacecraft for rwLogMsg in rwEffectorList[c].rwOutMsgs: rwList.append(rwLogMsg.addSubscriber()) scData.rwInMsgs = messaging.RWConfigLogMsgInMsgsVector(rwList) # process THR effectors if thrEffectorList: thrList = [] thrInfo = [] if ( thrEffectorList[c] is not None ): # THR clusters have been added to this spacecraft clusterCounter = 0 for thrEff in thrEffectorList[ c ]: # loop over the THR effectors attached to this spacecraft thSet = vizInterface.ThrClusterMap() thSet.thrTag = ( thrEff.ModelTag ) # set the label for this cluster of THR devices if thrColors: if thrColors[c] is not None: thSet.color = thrColors[c][clusterCounter] for thrLogMsg in ( thrEff.thrusterOutMsgs ): # loop over the THR cluster log message thrList.append(thrLogMsg.addSubscriber()) thrInfo.append(thSet) clusterCounter += 1 scData.thrInMsgs = messaging.THROutputMsgInMsgsVector(thrList) scData.thrInfo = vizInterface.ThrClusterVector(thrInfo) # process CSS information if cssList: cssDeviceList = [] if cssList[c] is not None: # CSS list has been added to this spacecraft for css in cssList[c]: cssDeviceList.append(css.cssConfigLogOutMsg.addSubscriber()) scData.cssInMsgs = messaging.CSSConfigLogMsgInMsgsVector(cssDeviceList) # process generic sensor HUD information if genericSensorList: gsList = [] if ( genericSensorList[c] is not None ): # generic sensor(s) have been added to this spacecraft for gs in genericSensorList[c]: gsList.append(gs) scData.genericSensorList = vizInterface.GenericSensorVector(gsList) # process spacecraft ellipsoids if ellipsoidList: elList = [] if ( ellipsoidList[c] is not None ): # generic sensor(s) have been added to this spacecraft for el in ellipsoidList[c]: elList.append(el) scData.ellipsoidList = vizInterface.EllipsoidVector(elList) # process spacecraft lights if lightList: liList = [] if ( lightList[c] is not None ): # light objects(s) have been added to this spacecraft for li in lightList[c]: liList.append(li) scData.lightList = vizInterface.LightVector(liList) # process generic storage HUD information if genericStorageList: gsdList = [] if ( genericStorageList[c] is not None ): # generic storage device(s) have been added to this spacecraft for gsd in genericStorageList[c]: if len(gsd.color) > 1: if len(gsd.color) / 4 != len(gsd.thresholds) + 1: print( "ERROR: vizSupport: generic storage " + gsd.label + " threshold list does not have the correct dimension. " "It should be 1 smaller than the list of colors." ) exit(1) else: if len(gsd.thresholds) > 0: print( "ERROR: vizSupport: generic storage " + gsd.label + " threshold list is set, but no multiple of colors are provided." ) exit(1) gsdList.append(gsd) scData.genericStorageList = vizInterface.GenericStorageVector(gsdList) # process transceiver HUD information if transceiverList: tcList = [] if ( transceiverList[c] is not None ): # transceiver(s) have been added to this spacecraft for tc in transceiverList[c]: tcList.append(tc) scData.transceiverList = vizInterface.TransceiverVector(tcList) # process sprite information if spriteList: if spriteList[c] is not None: scData.spacecraftSprite = spriteList[c] # process modelDictionaryKey information if modelDictionaryKeyList: if modelDictionaryKeyList[c] is not None: scData.modelDictionaryKey = modelDictionaryKeyList[c] # process logoTexture information if logoTextureList: if logoTextureList[c] is not None: scData.logoTexture = logoTextureList[c] if oscOrbitColorList: if oscOrbitColorList[c] is not None: scData.oscOrbitLineColor = vizInterface.IntVector(oscOrbitColorList[c]) if trueOrbitColorList: if trueOrbitColorList[c] is not None: scData.trueTrajectoryLineColor = vizInterface.IntVector( trueOrbitColorList[c] ) if trueOrbitColorInMsgList: if trueOrbitColorInMsgList[c] is not None: scData.trueTrajectoryLineColorInMsg = trueOrbitColorInMsgList[c] # process MSM information if msmInfoList: if msmInfoList[c] is not None: # MSM have been added to this spacecraft scData.msmInfo = msmInfoList[c] vizMessenger.scData.push_back(scData) c += 1 vizMessenger.gravBodyInformation = vizInterface.GravBodyInfoVector(planetInfoList) vizMessenger.spiceInMsgs = messaging.SpicePlanetStateMsgInMsgsVector(spiceMsgList) # note that the following logic can receive a single python file name, or a full path + file name. # In both cases a local results are stored in a local sub-folder. # If a "*.bin" file is provided, then the provided path and name are used to store the data. vizMessenger.saveFile = False if saveFile is not None: if os.path.splitext(os.path.basename(saveFile))[1].lower() == ".bin": # here the provide file path, file name and file extension are used explicitly vizFileNamePath = saveFile else: # here the file path and name string are split into file path and a file name # next, the `_VizFiles` folder is created, if needed, and the binary data file # has the name of the provided file name with `_UnityViz.bin` appended fileName = os.path.splitext(os.path.basename(saveFile))[0] filePath = os.path.dirname(saveFile) if filePath == "": filePath = "." if not os.path.isdir(filePath + "/_VizFiles"): os.mkdir(filePath + "/_VizFiles") vizFileNamePath = filePath + "/_VizFiles/" + fileName + "_UnityViz.bin" vizMessenger.saveFile = True vizMessenger.protoFilename = vizFileNamePath if (liveStream or broadcastStream) and noDisplay: raise ValueError( "noDisplay mode cannot be used with liveStream or broadcastStream." ) vizMessenger.liveStream = liveStream vizMessenger.broadcastStream = broadcastStream vizMessenger.noDisplay = noDisplay return vizMessenger