def my_constructor(self, PORTAL_CAMERA_INSTANCE, DISPLAY_GROUP,
                       USER_REPRESENTATION):

        # call base class constructor
        self.base_constructor(PORTAL_CAMERA_INSTANCE, DISPLAY_GROUP,
                              USER_REPRESENTATION,
                              "portal_cam_" + str(PORTAL_CAMERA_INSTANCE.id))

        ## @var portal
        # Portal display instance belonging to this representation.
        self.portal = Portal(
            PORTAL_MATRIX=avango.gua.make_identity_mat(),
            WIDTH=PORTAL_CAMERA_INSTANCE.portal_width,
            HEIGHT=PORTAL_CAMERA_INSTANCE.portal_height,
            VIEWING_MODE=PORTAL_CAMERA_INSTANCE.capture_viewing_mode,
            CAMERA_MODE="PERSPECTIVE",
            NEGATIVE_PARALLAX=PORTAL_CAMERA_INSTANCE.capture_parallax_mode,
            BORDER_MATERIAL="data/materials/White.gmd",
            TRANSITABLE=False,
            PORTAL_NODE_NAME_ATTACHMENT="w" +
            str(PORTAL_CAMERA_INSTANCE.WORKSPACE_INSTANCE.id) + "_dg" +
            str(self.DISPLAY_GROUP.id))

        ## @var portal_nav
        # Instance of PortalCameraNavigation in which the captured shots are to be loaded.
        self.portal_nav = PortalCameraNavigation()
        self.portal_nav.my_constructor(
            PORTAL_CAMERA_INSTANCE=self.TOOL_INSTANCE)

        ## @var portal_dg
        # DisplayGroup opened up by the portal of this representation.
        self.portal_dg = DisplayGroup(
            ID=None,
            DISPLAY_LIST=[self.portal],
            NAVIGATION_LIST=[self.portal_nav],
            VISIBILITY_TAG="portal",
            OFFSET_TO_WORKSPACE=avango.gua.make_identity_mat(),
            WORKSPACE_TRANSMITTER_OFFSET=avango.gua.make_identity_mat())

        ## @var assigned_shot
        # Shot instance which is currently displayed in the portal of this representation.
        self.assigned_shot = None

        ## @var highlighted
        # Boolean indicating if this representation is highlighted. Usually used to color the assigned user's representation.
        self.highlighted = False

        ## @var portal_matrix_connected
        # Boolean saying if the portal's portal matrix node was connected. Is done once it is present by evaluate().
        self.portal_matrix_connected = False
  def __init__(self):
    self.super(PortalCamera).__init__()

    ## @var captured_shots
    # List of Shot instances belonging to this PortalCamera.
    self.captured_shots = []

    ## @var current_shot
    # Shot instance which is currently displayed above the PortalCamera.
    self.current_shot = None

    ## @var in_capture_mode
    # Boolean saying if the portal camera is currently at capturing a photo.
    self.in_capture_mode = False

    ## @var capture_tool_representation
    # PortalCameraRepresentation instance which is used in capture mode. Might change during capturing.
    self.capture_tool_representation = None

    ## @var portal_width
    # Width of the portals displayed in this PortalCamera.
    self.portal_width = 0.35

    ## @var portal_height
    # Height of the portals displayed in this PortalCamera.
    self.portal_height = 0.35

    ## @var capture_viewing_mode
    # Viewing mode with which new portals will be created.
    self.capture_viewing_mode = "3D"

    ## @var capture_parallax_mode
    # Negative parallax mode with which new portals will be created.
    self.capture_parallax_mode = "False"

    ## @var gallery_activated
    # Boolean indicating if the gallery is currently visible for this PortalCamera.
    self.gallery_activated = False

    ## @var last_open_shot_index
    # Index within self.captured_shots saying which of the Shots was lastly opened by the PortalCamera.
    self.last_open_shot_index = None

    ## @var gallery_magification_factor
    # Factor with which the size of the portals will be multiplied when in gallery mode.
    self.gallery_magnification_factor = 1.5

    ## @var virtual_nav
    # Instance of PortalCameraNavigation in which the captured shots are to be loaded for all tool representations.
    self.virtual_nav = PortalCameraNavigation()
    self.virtual_nav.my_constructor(PORTAL_CAMERA_INSTANCE = self)
  def my_constructor(self, PORTAL_CAMERA_INSTANCE, DISPLAY_GROUP, USER_REPRESENTATION):
    
    # call base class constructor
    self.base_constructor(PORTAL_CAMERA_INSTANCE
                        , DISPLAY_GROUP
                        , USER_REPRESENTATION
                        , "portal_cam_" + str(PORTAL_CAMERA_INSTANCE.id))

    ## @var portal
    # Portal display instance belonging to this representation.
    self.portal = Portal(PORTAL_MATRIX = avango.gua.make_identity_mat()
                       , WIDTH = PORTAL_CAMERA_INSTANCE.portal_width
                       , HEIGHT = PORTAL_CAMERA_INSTANCE.portal_height
                       , VIEWING_MODE = PORTAL_CAMERA_INSTANCE.capture_viewing_mode
                       , CAMERA_MODE = "PERSPECTIVE"
                       , NEGATIVE_PARALLAX = PORTAL_CAMERA_INSTANCE.capture_parallax_mode
                       , BORDER_MATERIAL = "data/materials/White.gmd"
                       , TRANSITABLE = False
                       , PORTAL_NODE_NAME_ATTACHMENT = "w" + str(PORTAL_CAMERA_INSTANCE.WORKSPACE_INSTANCE.id) + "_dg" + str(self.DISPLAY_GROUP.id))

    ## @var portal_nav
    # Instance of PortalCameraNavigation in which the captured shots are to be loaded.
    self.portal_nav = PortalCameraNavigation()
    self.portal_nav.my_constructor(PORTAL_CAMERA_INSTANCE = self.TOOL_INSTANCE)

    ## @var portal_dg
    # DisplayGroup opened up by the portal of this representation.
    self.portal_dg = DisplayGroup(ID = None
                           , DISPLAY_LIST = [self.portal]
                           , NAVIGATION_LIST = [self.portal_nav]
                           , VISIBILITY_TAG = "portal"
                           , OFFSET_TO_WORKSPACE = avango.gua.make_identity_mat()
                           , WORKSPACE_TRANSMITTER_OFFSET = avango.gua.make_identity_mat()
                           )

    ## @var assigned_shot
    # Shot instance which is currently displayed in the portal of this representation.
    self.assigned_shot = None

    ## @var highlighted
    # Boolean indicating if this representation is highlighted. Usually used to color the assigned user's representation.
    self.highlighted = False

    ## @var portal_matrix_connected
    # Boolean saying if the portal's portal matrix node was connected. Is done once it is present by evaluate().
    self.portal_matrix_connected = False
class PortalCameraRepresentation(ToolRepresentation):

  ## @var sf_portal_matrix
  # Field to which the portal entry matrices are connected to in order to appear above the PortalCamera.
  sf_portal_matrix = avango.gua.SFMatrix4()
  sf_portal_matrix.value = avango.gua.make_identity_mat()

  ## Default constructor.
  def __init__(self):
    self.super(PortalCameraRepresentation).__init__()

  ## Custom constructor.
  # @param PORTAL_CAMERA_INSTANCE An instance of PortalCamera to which this PortalCameraRepresentation is associated.
  # @param DISPLAY_GROUP DisplayGroup instance for which this PortalCameraRepresentation is responsible for. 
  # @param USER_REPRESENTATION Corresponding UserRepresentation instance under which's view_transform_node the PortalCameraRepresentation is appended.
  def my_constructor(self, PORTAL_CAMERA_INSTANCE, DISPLAY_GROUP, USER_REPRESENTATION):
    
    # call base class constructor
    self.base_constructor(PORTAL_CAMERA_INSTANCE
                        , DISPLAY_GROUP
                        , USER_REPRESENTATION
                        , "portal_cam_" + str(PORTAL_CAMERA_INSTANCE.id))

    ## @var portal
    # Portal display instance belonging to this representation.
    self.portal = Portal(PORTAL_MATRIX = avango.gua.make_identity_mat()
                       , WIDTH = PORTAL_CAMERA_INSTANCE.portal_width
                       , HEIGHT = PORTAL_CAMERA_INSTANCE.portal_height
                       , VIEWING_MODE = PORTAL_CAMERA_INSTANCE.capture_viewing_mode
                       , CAMERA_MODE = "PERSPECTIVE"
                       , NEGATIVE_PARALLAX = PORTAL_CAMERA_INSTANCE.capture_parallax_mode
                       , BORDER_MATERIAL = "data/materials/White.gmd"
                       , TRANSITABLE = False
                       , PORTAL_NODE_NAME_ATTACHMENT = "w" + str(PORTAL_CAMERA_INSTANCE.WORKSPACE_INSTANCE.id) + "_dg" + str(self.DISPLAY_GROUP.id))

    ## @var portal_nav
    # Instance of PortalCameraNavigation in which the captured shots are to be loaded.
    self.portal_nav = PortalCameraNavigation()
    self.portal_nav.my_constructor(PORTAL_CAMERA_INSTANCE = self.TOOL_INSTANCE)

    ## @var portal_dg
    # DisplayGroup opened up by the portal of this representation.
    self.portal_dg = DisplayGroup(ID = None
                           , DISPLAY_LIST = [self.portal]
                           , NAVIGATION_LIST = [self.portal_nav]
                           , VISIBILITY_TAG = "portal"
                           , OFFSET_TO_WORKSPACE = avango.gua.make_identity_mat()
                           , WORKSPACE_TRANSMITTER_OFFSET = avango.gua.make_identity_mat()
                           )

    ## @var assigned_shot
    # Shot instance which is currently displayed in the portal of this representation.
    self.assigned_shot = None

    ## @var highlighted
    # Boolean indicating if this representation is highlighted. Usually used to color the assigned user's representation.
    self.highlighted = False

    ## @var portal_matrix_connected
    # Boolean saying if the portal's portal matrix node was connected. Is done once it is present by evaluate().
    self.portal_matrix_connected = False

  ## Computes the WorldTransform of a scenegraph node manually without using the pre-defined field.
  # @param NODE The scenegraph node to compute the world transformation for.
  def compute_world_transform(self, NODE):

    if NODE == None:
      return avango.gua.make_identity_mat()
    else:   
      return self.compute_world_transform(NODE.Parent.value) * NODE.Transform.value

  ## Evaluated every frame.
  def evaluate(self):

    # base class evaluate
    self.perform_tool_node_transformation()

    # wait for portal matrix node, then connect it if not already done
    if self.portal_matrix_connected == False:

      try:
        self.portal.portal_matrix_node
      except:
        return

      self.portal.connect_portal_matrix(self.sf_portal_matrix)
      self.portal.portal_matrix_node.GroupNames.value.append(self.USER_REPRESENTATION.view_transform_node.Name.value)
      self.portal_matrix_connected = True
      self.portal.set_visibility(False)
  

    # update sf_portal_matrix
    self.sf_portal_matrix.value = self.compute_world_transform(self.tool_transform_node) * \
                                  avango.gua.make_trans_mat(0.0, self.TOOL_INSTANCE.portal_height/2, 0.0)

    # update border color according to highlight enabled
    if self.highlighted:
      if self.portal.border_material != "data/materials/" + self.USER_REPRESENTATION.DISPLAY_GROUP.navigations[self.USER_REPRESENTATION.connected_navigation_id].trace_material + "Shadeless.gmd":
        self.portal.set_border_material("data/materials/" + self.USER_REPRESENTATION.DISPLAY_GROUP.navigations[self.USER_REPRESENTATION.connected_navigation_id].trace_material + "Shadeless.gmd")
    else:
      if self.portal.border_material != "data/materials/White.gmd":
        self.portal.set_border_material("data/materials/White.gmd")

  ## Retrieves portal_width and portal_height from the PortalCamera instance and updates the sizes of the representation's parts.
  def update_size(self):

    self.portal.set_size(self.TOOL_INSTANCE.portal_width, self.TOOL_INSTANCE.portal_height)                                                  

  ## Loads a shot to this representation's portal and sets it visible.
  # @param SHOT The Shot instance to be loaded.
  def assign_shot(self, SHOT):

    # disconnect fields when a shot is already assigned
    if self.assigned_shot != None:
      self.assigned_shot.sf_abs_mat.disconnect()
      self.assigned_shot.sf_scale.disconnect()

    # copy shot values to navigation
    self.portal_nav.set_navigation_values(SHOT.sf_abs_mat.value, SHOT.sf_scale.value)

    # copy shot values to portal
    if SHOT.sf_viewing_mode.value != self.portal.viewing_mode:
      self.portal.switch_viewing_mode()

    if SHOT.sf_camera_mode.value != self.portal.camera_mode:
      self.portal.switch_camera_mode()

    if SHOT.sf_negative_parallax.value != self.portal.negative_parallax:
      self.portal.switch_negative_parallax()

    # establish field connections to copy updates done by the PortalCameraNavigation.
    SHOT.sf_abs_mat.disconnect()
    SHOT.sf_abs_mat.connect_from(self.portal_nav.sf_abs_mat)
    SHOT.sf_scale.disconnect()
    SHOT.sf_scale.connect_from(self.portal_nav.sf_scale)

    self.assigned_shot = SHOT
    self.portal.set_visibility(True)

  ## Removes the assigned shot of this representation and makes the portal invisible.
  def deassign_shot(self):

    self.assigned_shot.sf_abs_mat.disconnect()
    self.assigned_shot.sf_scale.disconnect()
    self.assigned_shot = None

    self.portal.set_visibility(False)

  ## Sets the viewing mode on the portal.
  # @param MODE The mode string to be set.
  def set_viewing_mode(self, MODE):

    if self.portal.viewing_mode != MODE:
      self.portal.switch_viewing_mode()

  ## Sets the negative parallax mode on the portal.
  # @param MODE The mode string to be set.
  def set_negative_parallax(self, MODE):

    if self.portal.negative_parallax != MODE:
      self.portal.switch_negative_parallax()


  ## Appends a string to the GroupNames field of this PortalCameraRepresentation's visualization.
  # @param STRING The string to be appended.
  def append_to_visualization_group_names(self, STRING):
    
    # do not add portal head group nodes for visibility of this portal
    if not STRING.startswith("portal"):
      self.portal.portal_matrix_node.GroupNames.value.append(STRING)


  ## Removes a string from the GroupNames field of this PortalCameraRepresentation's visualization.
  # @param STRING The string to be removed.
  def remove_from_visualization_group_names(self, STRING):
    
    self.portal.portal_matrix_node.GroupNames.value.remove(STRING)

  ## Resets the GroupNames field of this PortalCameraRepresentation's visualization to the user representation's view_transform_node.
  def reset_visualization_group_names(self):

    self.portal.portal_matrix_node.GroupNames.value = [self.USER_REPRESENTATION.view_transform_node.Name.value]

  ## Enables the highlight for this PortalCameraRepresentation.
  def enable_highlight(self):
    
    self.highlighted = True

  ## Disables the highlight for this PortalCameraRepresentation.
  def disable_highlight(self):
    
    self.highlighted = False
class PortalCamera(Tool):
 
  ## @var sf_tracking_mat
  # Tracking matrix of the PortalCamera within the platform coordinate system.
  sf_tracking_mat = avango.gua.SFMatrix4()

  ## @var sf_world_border_mat_no_scale
  # World transformation of the camera frame without scaling. Used for Portal instantiation.
  sf_world_border_mat_no_scale = avango.gua.SFMatrix4()

  # button fields
  ## @var sf_focus_button
  # Boolean field to check if the focus button was pressed.
  sf_focus_button = avango.SFBool()

  ## @var sf_capture_button
  # Boolean field to check if the capture button was pressed.
  sf_capture_button = avango.SFBool()

  ## @var sf_next_rec_button
  # Boolean field to check if the next recording button was pressed.
  sf_next_rec_button = avango.SFBool()

  ## @var sf_prior_rec_button
  # Boolean field to check if the prior recording button was pressed.
  sf_prior_rec_button = avango.SFBool()

  ## @var sf_scale_up_button
  # Boolean field to check if the scale up button was pressed.
  sf_scale_up_button = avango.SFBool()

  ## @var sf_scale_down_button
  # Boolean field to check if the scale down button was pressed.
  sf_scale_down_button = avango.SFBool()

  ## @var sf_open_close_button
  # Boolean field to check if the open and close button was pressed.
  sf_open_close_button = avango.SFBool()

  ## @var sf_delete_button
  # Boolean field to check if the delete button was pressed.
  sf_delete_button = avango.SFBool()

  ## @var sf_gallery_button
  # Boolean field to check if the gallery button was pressed.
  sf_gallery_button = avango.SFBool()

  ## @var sf_size_up_button
  # Boolean field to check if the size up button was pressed.
  sf_size_up_button = avango.SFBool()

  ## @var sf_size_down_button
  # Boolean field to check if the size down button was pressed.
  sf_size_down_button = avango.SFBool()

  ## @var sf_2D_mode_button
  # Boolean field to check if the 2D mode button was pressed.
  sf_2D_mode_button = avango.SFBool()

  ## @var sf_3D_mode_button
  # Boolean field to check if the 3D mode button was pressed.
  sf_3D_mode_button = avango.SFBool()

  ## @var sf_negative_parallax_on_button
  # Boolean field to check if the negative parallax on button was pressed.
  sf_negative_parallax_on_button = avango.SFBool()

  ## @var sf_negative_parallax_off_button
  # Boolean field to check if the negative parallax off button was pressed.
  sf_negative_parallax_off_button = avango.SFBool()


  ## Default constructor.
  def __init__(self):
    self.super(PortalCamera).__init__()

    ## @var captured_shots
    # List of Shot instances belonging to this PortalCamera.
    self.captured_shots = []

    ## @var current_shot
    # Shot instance which is currently displayed above the PortalCamera.
    self.current_shot = None

    ## @var in_capture_mode
    # Boolean saying if the portal camera is currently at capturing a photo.
    self.in_capture_mode = False

    ## @var capture_tool_representation
    # PortalCameraRepresentation instance which is used in capture mode. Might change during capturing.
    self.capture_tool_representation = None

    ## @var portal_width
    # Width of the portals displayed in this PortalCamera.
    self.portal_width = 0.35

    ## @var portal_height
    # Height of the portals displayed in this PortalCamera.
    self.portal_height = 0.35

    ## @var capture_viewing_mode
    # Viewing mode with which new portals will be created.
    self.capture_viewing_mode = "3D"

    ## @var capture_parallax_mode
    # Negative parallax mode with which new portals will be created.
    self.capture_parallax_mode = "False"

    ## @var gallery_activated
    # Boolean indicating if the gallery is currently visible for this PortalCamera.
    self.gallery_activated = False

    ## @var last_open_shot_index
    # Index within self.captured_shots saying which of the Shots was lastly opened by the PortalCamera.
    self.last_open_shot_index = None

    ## @var gallery_magification_factor
    # Factor with which the size of the portals will be multiplied when in gallery mode.
    self.gallery_magnification_factor = 1.5

    ## @var virtual_nav
    # Instance of PortalCameraNavigation in which the captured shots are to be loaded for all tool representations.
    self.virtual_nav = PortalCameraNavigation()
    self.virtual_nav.my_constructor(PORTAL_CAMERA_INSTANCE = self)


  ## Custom constructor.
  # @param WORKSPACE_INSTANCE The instance of Workspace to which this Tool belongs to.
  # @param TOOL_ID The identification number of this Tool within the workspace.
  # @param CAMERA_TRACKING_STATION The tracking target name of this PortalCamera.
  # @param CAMERA_DEVICE_STATION The device station name of this PortalCamera.
  # @param VISIBILITY_TABLE A matrix containing visibility rules according to the DisplayGroups' visibility tags.
  def my_constructor(self, WORKSPACE_INSTANCE, TOOL_ID, CAMERA_TRACKING_STATION, CAMERA_DEVICE_STATION, VISIBILITY_TABLE):

    # call base class constructor
    self.base_constructor(WORKSPACE_INSTANCE, TOOL_ID, CAMERA_TRACKING_STATION, VISIBILITY_TABLE)

    ## @var device_sensor
    # Device sensor for the PortalCamera's button inputs.
    self.device_sensor = avango.daemon.nodes.DeviceSensor(DeviceService = avango.daemon.DeviceService())
    self.device_sensor.Station.value = CAMERA_DEVICE_STATION

    # init field connections
    self.sf_focus_button.connect_from(self.device_sensor.Button0)
    self.sf_capture_button.connect_from(self.device_sensor.Button1)
    self.sf_next_rec_button.connect_from(self.device_sensor.Button5)
    self.sf_prior_rec_button.connect_from(self.device_sensor.Button4)
    self.sf_scale_up_button.connect_from(self.device_sensor.Button9)
    self.sf_scale_down_button.connect_from(self.device_sensor.Button11)
    self.sf_open_close_button.connect_from(self.device_sensor.Button6)
    self.sf_delete_button.connect_from(self.device_sensor.Button15)
    self.sf_gallery_button.connect_from(self.device_sensor.Button11)
    self.sf_size_up_button.connect_from(self.device_sensor.Button3)
    self.sf_size_down_button.connect_from(self.device_sensor.Button2)
    self.sf_2D_mode_button.connect_from(self.device_sensor.Button7)
    self.sf_3D_mode_button.connect_from(self.device_sensor.Button8)
    self.sf_negative_parallax_on_button.connect_from(self.device_sensor.Button12)
    self.sf_negative_parallax_off_button.connect_from(self.device_sensor.Button13)

    ## @var frame_trigger
    # Triggers framewise evaluation of frame_callback method
    self.frame_trigger = avango.script.nodes.Update(Callback = self.frame_callback, Active = True)


  ## Creates a PortalCamearRepresentation for this RayPointer at a DISPLAY_GROUP.
  # @param DISPLAY_GROUP The DisplayGroup instance to create the representation for.
  # @param USER_REPRESENTATION The UserRepresentation this representation will belong to.
  # @param IN_VIRTUAL_DISPLAY Boolean saying if the new tool representation is valid in a virtual display.
  def create_tool_representation_for(self, DISPLAY_GROUP, USER_REPRESENTATION, IN_VIRTUAL_DISPLAY):

    _portal_cam_repr = PortalCameraRepresentation()
    _portal_cam_repr.my_constructor(self, DISPLAY_GROUP, USER_REPRESENTATION, IN_VIRTUAL_DISPLAY)
    self.tool_representations.append(_portal_cam_repr)
    return _portal_cam_repr

  ## Selects a list of potentially currently active PortalCameraRepresentations by checking the assigned user of them.
  def create_candidate_list(self):
    
    _candidate_list = []

    # only go on if a user is assigned to the PortalCamera
    if self.assigned_user != None:

      # iterate over all tool representations of assigned user
      for _tool_repr in self.tool_representations:

        if _tool_repr.user_id == self.assigned_user.id:

          _candidate_list.append(_tool_repr)


    return _candidate_list

  ## Chooses one PortalCameraRepresentation among the potentially active ones in a candidate list.
  # @param CANDIDATE_LIST The list of candidates to be checked.
  def choose_from_candidate_list(self, CANDIDATE_LIST):
    
    _dg_hit_by_user = self.assigned_user.last_seen_display_group

    for _tool_repr in CANDIDATE_LIST:

      if _tool_repr.DISPLAY_GROUP == _dg_hit_by_user:
        return _tool_repr

    return None

  ## Create candidate list and select one of the candidates.
  def get_active_tool_representation(self):

    _candidate_representations = self.create_candidate_list()
    _chosen_tool_representation = self.choose_from_candidate_list(_candidate_representations)
    return _chosen_tool_representation

  ## Evaluated every frame.
  def frame_callback(self):

    # do not start until virtual display nodes are present
    if len(self.tool_representations) == 0:
      return

    # handle portal updates in capture mode
    if self.in_capture_mode:

      # get active tool mechanism by decision algorithm
      _active_tool_representation = self.get_active_tool_representation()

      if self.capture_tool_representation != _active_tool_representation:

        self.capture_tool_representation = _active_tool_representation

      # compute shot parameters and assign them
      _active_navigation = _active_tool_representation.DISPLAY_GROUP.navigations[_active_tool_representation.USER_REPRESENTATION.connected_navigation_id]

      # compute matrix
      _shot_platform_matrix = _active_tool_representation.sf_prior_entry_matrix.value * \
                              avango.gua.make_inverse_mat(avango.gua.make_scale_mat(_active_navigation.sf_scale.value))

      self.virtual_nav.set_navigation_values(_shot_platform_matrix, _active_navigation.sf_scale.value)

    # update user assignment
    self.check_for_user_assignment()

    # apply size changes
    if self.sf_size_up_button.value == True:
      self.portal_width += 0.005
      self.portal_height += 0.005

      if self.portal_width > 1.0:
        self.portal_width = 1.0

      if self.portal_height > 1.0:
        self.portal_height = 1.0

      for _tool_repr in self.tool_representations:
        _tool_repr.update_size()

    if self.sf_size_down_button.value == True:
      self.portal_width -= 0.005
      self.portal_height -= 0.005
      
      if self.portal_width < 0.15:
        self.portal_width = 0.15

      if self.portal_height < 0.15:
        self.portal_height = 0.15

      for _tool_repr in self.tool_representations:
        _tool_repr.update_size()

    # feed back changes to current shot
    if self.current_shot != None:
      
      if self.current_shot.sf_abs_mat.value != self.virtual_nav.sf_abs_mat.value:
        self.current_shot.sf_abs_mat.value = self.virtual_nav.sf_abs_mat.value
      
      if self.current_shot.sf_scale.value != self.virtual_nav.sf_scale.value:
        self.current_shot.sf_scale.value = self.virtual_nav.sf_scale.value


  ## Sets the scale of the currently active shot or returns when no shot is active.
  # @param SCALE The new scale to be set.
  def set_current_shot_scale(self, SCALE):

    if self.current_shot == None:
      return
    else:

      self.current_shot.sf_scale.value = SCALE

      self.virtual_nav.set_navigation_values(self.virtual_nav.sf_abs_mat.value, SCALE)

  ## Loads a given Shot instances to all representations.
  # @param SHOT The Shot instance to be loaded.
  def set_current_shot(self, SHOT):

    # copy shot values to navigation
    self.virtual_nav.set_navigation_values(SHOT.sf_abs_mat.value, SHOT.sf_scale.value)

    for _tool_repr in self.tool_representations:
      _tool_repr.assign_shot(SHOT)

    self.current_shot = SHOT

  ## Clears the Shot currently loaded to all representations.
  def clear_current_shot(self):

    for _tool_repr in self.tool_representations:
      _tool_repr.deassign_shot()

    self.current_shot = None


  ## Called whenever sf_focus_button changes.
  @field_has_changed(sf_focus_button)
  def sf_focus_button_changed(self):

    # show and hide camera frame
    if self.sf_focus_button.value == True and self.current_shot == None:

      # get active tool mechanism by decision algorithm
      _active_tool_representation = self.get_active_tool_representation()
      self.capture_tool_representation = _active_tool_representation

      # break when no lastly seen display is available
      if self.assigned_user.last_seen_display_group == None:
        return

      # create shot and assign it
      _active_navigation = _active_tool_representation.DISPLAY_GROUP.navigations[_active_tool_representation.USER_REPRESENTATION.connected_navigation_id]

      # compute matrix
      _shot_platform_matrix = _active_tool_representation.sf_entry_matrix.value * \
                              avango.gua.make_inverse_mat(avango.gua.make_scale_mat(_active_navigation.sf_scale.value))

      _shot = Shot()
      _shot.my_constructor(_shot_platform_matrix,
                           _active_navigation.sf_scale.value,
                           self.capture_viewing_mode,
                           "PERSPECTIVE",
                           self.capture_parallax_mode)

      self.set_current_shot(_shot)             

      self.in_capture_mode = True

    else:

      if self.in_capture_mode == True:
        self.in_capture_mode = False
        self.clear_current_shot()


  ## Called whenever sf_capture_button changes.
  @field_has_changed(sf_capture_button)
  def sf_capture_button_changed(self):
    if self.sf_capture_button.value == True and self.in_capture_mode:

      # store the preview shot currently loaded
      self.captured_shots.append(self.current_shot)
      self.in_capture_mode = False

  ## Called whenever sf_next_rec_button changes.
  @field_has_changed(sf_next_rec_button)
  def sf_next_rec_button_changed(self):
    if self.sf_next_rec_button.value == True:
      
      # move to next recording in open mode
      if self.current_shot != None:

        _current_index = self.captured_shots.index(self.current_shot)
        _current_index += 1
        _current_index = _current_index % len(self.captured_shots)

        _new_shot = self.captured_shots[_current_index]
        self.set_current_shot(_new_shot)


  ## Called whenever sf_prior_rec_button changes.
  @field_has_changed(sf_prior_rec_button)
  def sf_prior_rec_button_changed(self):
    if self.sf_prior_rec_button.value == True:
      
      # move to prior recording in open mode
      if self.current_shot != None:

        _current_index = self.captured_shots.index(self.current_shot)
        _current_index -= 1
        _current_index = _current_index % len(self.captured_shots)

        _new_shot = self.captured_shots[_current_index]
        self.set_current_shot(_new_shot)


  ## Called whenever sf_open_button changes.
  @field_has_changed(sf_open_close_button)
  def sf_open_button_changed(self):
    if self.sf_open_close_button.value == True:

      # open lastly opened portal when no portal is opened
      if self.current_shot == None and len(self.captured_shots) > 0:
        _new_shot = self.captured_shots[self.last_open_shot_index]
        self.set_current_shot(_new_shot)

      # close currently opened portal
      elif self.current_shot != None:
        self.last_open_shot_index = self.captured_shots.index(self.current_shot)
        self.clear_current_shot()


  ## Called whenever sf_delete_button changes.
  @field_has_changed(sf_delete_button)
  def sf_delete_button_changed(self):
    if self.sf_delete_button.value == True:

      # delete current portal
      if self.current_shot != None:
        _shot_to_delete = self.current_shot
        self.last_open_shot_index = max(self.captured_shots.index(_shot_to_delete) - 1, 0)
        self.clear_current_shot()

        self.captured_shots.remove(_shot_to_delete)
        del _shot_to_delete


  ## Called whenever sf_2D_mode_button changes.
  @field_has_changed(sf_2D_mode_button)
  def sf_2D_mode_button_changed(self):
    if self.sf_2D_mode_button.value == True:
      
      # switch mode of currently opened shot
      if self.current_shot != None:

        self.current_shot.sf_viewing_mode.value = "2D"

        for _tool_repr in self.tool_representations:
          _tool_repr.set_viewing_mode("2D")

      # switch capture_viewing_mode
      else:
        self.capture_viewing_mode = "2D"

  ## Called whenever sf_3D_mode_button changes.
  @field_has_changed(sf_3D_mode_button)
  def sf_3D_mode_button_changed(self):
    if self.sf_3D_mode_button.value == True:
      
      # switch mode of currently opened shot
      if self.current_shot != None:
        
        self.current_shot.sf_viewing_mode.value = "3D"

        for _tool_repr in self.tool_representations:
          _tool_repr.set_viewing_mode("3D")
      
      # switch capture_viewing_mode
      else:
        self.capture_viewing_mode = "3D"


  ## Called whenever sf_negative_parallax_on_button changes.
  @field_has_changed(sf_negative_parallax_on_button)
  def sf_negative_parallax_on_button_changed(self):
    if self.sf_negative_parallax_on_button.value == True:
      
      # switch mode of currently opened shot
      if self.current_shot != None:

        self.current_shot.sf_negative_parallax.value = "True"

        for _tool_repr in self.tool_representations:
          _tool_repr.set_negative_parallax("True")

      # switch capture_parallax_mode
      else:
        self.capture_parallax_mode = "True"


  ## Called whenever sf_negative_parallax_off_button changes.
  @field_has_changed(sf_negative_parallax_off_button)
  def sf_negative_parallax_off_button_changed(self):
    if self.sf_negative_parallax_off_button.value == True:
      
      # switch mode of currently opened shot
      if self.current_shot != None:

        self.current_shot.sf_negative_parallax.value = "False"

        for _tool_repr in self.tool_representations:
          _tool_repr.set_negative_parallax("False")

      # switch capture_parallax_mode
      else:
        self.capture_parallax_mode = "False"
class PortalCameraRepresentation(ToolRepresentation):

    ## @var sf_portal_matrix
    # Field to which the portal entry matrices are connected to in order to appear above the PortalCamera.
    sf_portal_matrix = avango.gua.SFMatrix4()
    sf_portal_matrix.value = avango.gua.make_identity_mat()

    ## Default constructor.
    def __init__(self):
        self.super(PortalCameraRepresentation).__init__()

    ## Custom constructor.
    # @param PORTAL_CAMERA_INSTANCE An instance of PortalCamera to which this PortalCameraRepresentation is associated.
    # @param DISPLAY_GROUP DisplayGroup instance for which this PortalCameraRepresentation is responsible for.
    # @param USER_REPRESENTATION Corresponding UserRepresentation instance under which's view_transform_node the PortalCameraRepresentation is appended.
    def my_constructor(self, PORTAL_CAMERA_INSTANCE, DISPLAY_GROUP,
                       USER_REPRESENTATION):

        # call base class constructor
        self.base_constructor(PORTAL_CAMERA_INSTANCE, DISPLAY_GROUP,
                              USER_REPRESENTATION,
                              "portal_cam_" + str(PORTAL_CAMERA_INSTANCE.id))

        ## @var portal
        # Portal display instance belonging to this representation.
        self.portal = Portal(
            PORTAL_MATRIX=avango.gua.make_identity_mat(),
            WIDTH=PORTAL_CAMERA_INSTANCE.portal_width,
            HEIGHT=PORTAL_CAMERA_INSTANCE.portal_height,
            VIEWING_MODE=PORTAL_CAMERA_INSTANCE.capture_viewing_mode,
            CAMERA_MODE="PERSPECTIVE",
            NEGATIVE_PARALLAX=PORTAL_CAMERA_INSTANCE.capture_parallax_mode,
            BORDER_MATERIAL="data/materials/White.gmd",
            TRANSITABLE=False,
            PORTAL_NODE_NAME_ATTACHMENT="w" +
            str(PORTAL_CAMERA_INSTANCE.WORKSPACE_INSTANCE.id) + "_dg" +
            str(self.DISPLAY_GROUP.id))

        ## @var portal_nav
        # Instance of PortalCameraNavigation in which the captured shots are to be loaded.
        self.portal_nav = PortalCameraNavigation()
        self.portal_nav.my_constructor(
            PORTAL_CAMERA_INSTANCE=self.TOOL_INSTANCE)

        ## @var portal_dg
        # DisplayGroup opened up by the portal of this representation.
        self.portal_dg = DisplayGroup(
            ID=None,
            DISPLAY_LIST=[self.portal],
            NAVIGATION_LIST=[self.portal_nav],
            VISIBILITY_TAG="portal",
            OFFSET_TO_WORKSPACE=avango.gua.make_identity_mat(),
            WORKSPACE_TRANSMITTER_OFFSET=avango.gua.make_identity_mat())

        ## @var assigned_shot
        # Shot instance which is currently displayed in the portal of this representation.
        self.assigned_shot = None

        ## @var highlighted
        # Boolean indicating if this representation is highlighted. Usually used to color the assigned user's representation.
        self.highlighted = False

        ## @var portal_matrix_connected
        # Boolean saying if the portal's portal matrix node was connected. Is done once it is present by evaluate().
        self.portal_matrix_connected = False

    ## Computes the WorldTransform of a scenegraph node manually without using the pre-defined field.
    # @param NODE The scenegraph node to compute the world transformation for.
    def compute_world_transform(self, NODE):

        if NODE == None:
            return avango.gua.make_identity_mat()
        else:
            return self.compute_world_transform(
                NODE.Parent.value) * NODE.Transform.value

    ## Evaluated every frame.
    def evaluate(self):

        # base class evaluate
        self.perform_tool_node_transformation()

        # wait for portal matrix node, then connect it if not already done
        if self.portal_matrix_connected == False:

            try:
                self.portal.portal_matrix_node
            except:
                return

            self.portal.connect_portal_matrix(self.sf_portal_matrix)
            self.portal.portal_matrix_node.GroupNames.value.append(
                self.USER_REPRESENTATION.view_transform_node.Name.value)
            self.portal_matrix_connected = True
            self.portal.set_visibility(False)

        # update sf_portal_matrix
        self.sf_portal_matrix.value = self.compute_world_transform(self.tool_transform_node) * \
                                      avango.gua.make_trans_mat(0.0, self.TOOL_INSTANCE.portal_height/2, 0.0)

        # update border color according to highlight enabled
        if self.highlighted:
            if self.portal.border_material != "data/materials/" + self.USER_REPRESENTATION.DISPLAY_GROUP.navigations[
                    self.USER_REPRESENTATION.
                    connected_navigation_id].trace_material + "Shadeless.gmd":
                self.portal.set_border_material(
                    "data/materials/" +
                    self.USER_REPRESENTATION.DISPLAY_GROUP.navigations[
                        self.USER_REPRESENTATION.connected_navigation_id].
                    trace_material + "Shadeless.gmd")
        else:
            if self.portal.border_material != "data/materials/White.gmd":
                self.portal.set_border_material("data/materials/White.gmd")

    ## Retrieves portal_width and portal_height from the PortalCamera instance and updates the sizes of the representation's parts.
    def update_size(self):

        self.portal.set_size(self.TOOL_INSTANCE.portal_width,
                             self.TOOL_INSTANCE.portal_height)

    ## Loads a shot to this representation's portal and sets it visible.
    # @param SHOT The Shot instance to be loaded.
    def assign_shot(self, SHOT):

        # disconnect fields when a shot is already assigned
        if self.assigned_shot != None:
            self.assigned_shot.sf_abs_mat.disconnect()
            self.assigned_shot.sf_scale.disconnect()

        # copy shot values to navigation
        self.portal_nav.set_navigation_values(SHOT.sf_abs_mat.value,
                                              SHOT.sf_scale.value)

        # copy shot values to portal
        if SHOT.sf_viewing_mode.value != self.portal.viewing_mode:
            self.portal.switch_viewing_mode()

        if SHOT.sf_camera_mode.value != self.portal.camera_mode:
            self.portal.switch_camera_mode()

        if SHOT.sf_negative_parallax.value != self.portal.negative_parallax:
            self.portal.switch_negative_parallax()

        # establish field connections to copy updates done by the PortalCameraNavigation.
        SHOT.sf_abs_mat.disconnect()
        SHOT.sf_abs_mat.connect_from(self.portal_nav.sf_abs_mat)
        SHOT.sf_scale.disconnect()
        SHOT.sf_scale.connect_from(self.portal_nav.sf_scale)

        self.assigned_shot = SHOT
        self.portal.set_visibility(True)

    ## Removes the assigned shot of this representation and makes the portal invisible.
    def deassign_shot(self):

        self.assigned_shot.sf_abs_mat.disconnect()
        self.assigned_shot.sf_scale.disconnect()
        self.assigned_shot = None

        self.portal.set_visibility(False)

    ## Sets the viewing mode on the portal.
    # @param MODE The mode string to be set.
    def set_viewing_mode(self, MODE):

        if self.portal.viewing_mode != MODE:
            self.portal.switch_viewing_mode()

    ## Sets the negative parallax mode on the portal.
    # @param MODE The mode string to be set.
    def set_negative_parallax(self, MODE):

        if self.portal.negative_parallax != MODE:
            self.portal.switch_negative_parallax()

    ## Appends a string to the GroupNames field of this PortalCameraRepresentation's visualization.
    # @param STRING The string to be appended.
    def append_to_visualization_group_names(self, STRING):

        # do not add portal head group nodes for visibility of this portal
        if not STRING.startswith("portal"):
            self.portal.portal_matrix_node.GroupNames.value.append(STRING)

    ## Removes a string from the GroupNames field of this PortalCameraRepresentation's visualization.
    # @param STRING The string to be removed.
    def remove_from_visualization_group_names(self, STRING):

        self.portal.portal_matrix_node.GroupNames.value.remove(STRING)

    ## Resets the GroupNames field of this PortalCameraRepresentation's visualization to the user representation's view_transform_node.
    def reset_visualization_group_names(self):

        self.portal.portal_matrix_node.GroupNames.value = [
            self.USER_REPRESENTATION.view_transform_node.Name.value
        ]

    ## Enables the highlight for this PortalCameraRepresentation.
    def enable_highlight(self):

        self.highlighted = True

    ## Disables the highlight for this PortalCameraRepresentation.
    def disable_highlight(self):

        self.highlighted = False