Beispiel #1
0
 def create_task_tree(self, task_id, processor):
     self.task_tree_widget.clear()
     self.task_tree_widget.setColumnCount(2)
     self.task_tree_widget.hideColumn(1)
     item = QTreeWidgetItem(self.task_tree_widget, ["Task {}".format(task_id), str(task_id)])
     item.setExpanded(True)
     return item
Beispiel #2
0
 def on_progress_update(self, msg):
     self._update_progress_table(msg)
     self._save_log(msg, "skill")
     # Update buttons
     if msg.type.find("Root") >= 0:
         if not self.skill_stop_button.isEnabled():
             self.create_task_tree(msg.id, msg.processor)
             self._toggle_task_active()
             for manager in self._sli.agents.values():
                 manager.reset_tick_rate()
         if abs(msg.progress_code) == 1:
             self._toggle_task_active()
     # Update task tree
     with self._task_mutex:
         items = self.task_tree_widget.findItems(str(msg.id), Qt.MatchRecursive | Qt.MatchFixedString, 1)
         #int(msg.progress_period*1000)
         if items:
             items[0].setData(0, 0, "{}({}) {}".format(msg.label, State(msg.state).name, "! SLOW !" if msg.progress_period>0.04 else ""))
         else:
             parents = self.task_tree_widget.findItems(str(msg.parent_id), Qt.MatchRecursive | Qt.MatchFixedString, 1)
             if not parents:
                 log.error("No parent found. Debug: {}".format(msg))
                 return
             item = QTreeWidgetItem(parents[0], ["{}({})".format(msg.label, State(msg.state).name), str(msg.id)])
             item.setIcon(0, self.get_icon(msg.processor))
             item.setExpanded(True)
Beispiel #3
0
 def _add_wm_node(self, elem):
     #print "Adding {}".format(elem.id)
     parent_rel = elem.getRelation(pred=self._wmi.get_sub_properties('skiros:spatiallyRelated'), obj='-1')
     to_expand = True
     if not parent_rel:
         parent_rel = elem.getRelation(pred=self._wmi.get_sub_properties('skiros:skillProperty'), obj='-1')
         to_expand = False
     if not parent_rel:
         to_expand = False
         parent_rel = elem.getRelation(pred='skiros:hasSkill', obj='-1')
         if not parent_rel:
             log.warn("[add_wm_node]", "Skipping element without declared parent: {}".format(elem.id))
             return True
         parent_id = '{}_skills'.format(parent_rel['src'])
         item = self.wm_tree_widget.findItems(parent_id, Qt.MatchRecursive | Qt.MatchFixedString, 1)
         if not item:  # In case it is still not existing i create the "support" skill node
             item = self.wm_tree_widget.findItems(parent_rel['src'], Qt.MatchRecursive | Qt.MatchFixedString, 1)[0]
             item = QTreeWidgetItem(item, ['Skills', parent_id])
         else:
             item = item[0]
     else:
         items = self.wm_tree_widget.findItems(parent_rel['src'], Qt.MatchRecursive | Qt.MatchFixedString, 1)
         if not items:
             log.warn("[add_wm_node]", "Parent {} of node {} is not in the known tree.".format(parent_rel['src'], elem.id))
             return False
         item = items[0]
     name = utils.ontology_type2name(elem.id) if not elem.label else utils.ontology_type2name(elem.label)
     item = QTreeWidgetItem(item, [name, elem.id])
     item.setExpanded(to_expand)
     return True
Beispiel #4
0
 def _add_available_skill(self, s):
     stype = self.skill_tree_widget.findItems(s.type, Qt.MatchRecursive | Qt.MatchFixedString, 1)
     if not stype: #If it is the first of its type, add the parents hierarchy to the tree
         hierarchy = self._wmi.query_ontology('SELECT ?x {{ {} rdfs:subClassOf*  ?x }}'.format(s.type))
         hierarchy = hierarchy[:hierarchy.index("skiros:Skill")]
         hierarchy.reverse()
         parent = self.skill_tree_widget.findItems("All", Qt.MatchRecursive | Qt.MatchFixedString, 1)[0]
         for c in hierarchy:
             child = self.skill_tree_widget.findItems(c, Qt.MatchRecursive | Qt.MatchFixedString, 1)
             if child:
                 parent = child[0]
             else:
                 parent = QTreeWidgetItem(parent, [c.replace("skiros:", ""), c])
                 parent.setExpanded(True)
     else:
         parent = stype[0]
     skill = QTreeWidgetItem(parent, [s.name, s.name])
     skill.setData(2, 0, s)
Beispiel #5
0
    def _create_wm_tree(self, item, scene, elem):
        name = utils.ontology_type2name(elem.id) if not elem.label else utils.ontology_type2name(elem.label)
        item = QTreeWidgetItem(item, [name, elem.id])

        spatialRel = sorted(elem.getRelations(subj='-1', pred=self._wmi.get_sub_properties('skiros:spatiallyRelated')), key=lambda r: r['dst'])
        for rel in spatialRel:
            if rel['dst'] in scene:
                self._create_wm_tree(item, scene, scene[rel['dst']])
                item.setExpanded(True)

        skillRel = sorted(elem.getRelations(subj='-1', pred='skiros:hasSkill'), key=lambda r: r['dst'])
        if skillRel:
            skillItem = QTreeWidgetItem(item, ['Skills', '{}_skills'.format(elem.id)])
            for rel in skillRel:
                if rel['dst'] in scene:
                    self._create_wm_tree(skillItem, scene, scene[rel['dst']])
                    skillItem.setExpanded(True)

        skillPropRel = sorted(elem.getRelations(subj='-1', pred=self._wmi.get_sub_properties('skiros:skillProperty')), key=lambda r: r['dst'])
        for rel in skillPropRel:
            if rel['dst'] in scene:
                self._create_wm_tree(item, scene, scene[rel['dst']])
Beispiel #6
0
 def refresh_timer_cb(self):
     """
     Keeps ui updated
     """
     # Update skill list
     if self._sli.has_changes:
         self.skill_tree_widget.setSortingEnabled(False)
         self.skill_tree_widget.sortByColumn(0, Qt.AscendingOrder)
         self.skill_tree_widget.clear()
         self.skill_tree_widget.setColumnCount(3)
         self.skill_tree_widget.hideColumn(2)
         self.skill_tree_widget.hideColumn(1)
         fu = QTreeWidgetItem(self.skill_tree_widget, ["Frequently used", "fu"])
         fu.setExpanded(True)
         root = QTreeWidgetItem(self.skill_tree_widget, ["All", "All"])
         root.setExpanded(True)
         for ak, e in self._sli._agents.iteritems():
             for s in e._skill_list.values():
                 s.manager = ak
                 self._add_available_skill(s)
         # simplifies hierarchy
         self.simplify_tree_hierarchy(root)
         self.skill_tree_widget.setSortingEnabled(True)
         # select last skill
         s = self.skill_tree_widget.findItems(self.last_executed_skill, Qt.MatchRecursive | Qt.MatchFixedString, 1)
         self.skill_params_table.setRowCount(0)
         if s:
             self.skill_tree_widget.setCurrentItem(s[0])
     # Update robot BT rate
     if self._sli.agents:
         robot_info = ""
         for name, manager in self._sli.agents.iteritems():
             robot_info += "{}: {:0.1f}hz ".format(name.replace("/", ""), manager.get_tick_rate())
         self.robot_rate_info.setText(robot_info)
         self.robot_output.setText(self.robot_text)
     else:
         self.robot_rate_info.setText("No robot connected.")
         self.robot_output.setText("")
class RecordUI(Plugin):
    ''' class containing the UI part of the framework'''
    def __init__(self, context):
        super(RecordUI, self).__init__(context)


        self._widget = QMainWindow()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('bitbots_recordui_rqt'), 'resource', 'RecordUI.ui')
        loadUi(ui_file, self._widget, {})

        self._recorder = Recorder()
        self._sliders = {}
        self._textFields = {}
        self._motorSwitched = {}
        self._selected_frame  = None

        self._currentGoals = {}                 # this is the data about the current unsaved frame
        self._currentDuration = 1.0
        self._currentPause = 0.0
        self._currentName = "new frame"

        self._workingValues = {}                # this is the data about the frame that is displayed
        self._workingDuration = 1.0
        self._workingPause = 0.0
        self._workingName = self._currentName

        self._current = True

        self._saveDir = None

        self._robot_anim_path = None

        #save current frame when switching to other frames for reference
        #working frame

        self._treeItems = {}
        self._motorCheckBody = QTreeWidgetItem(self._widget.motorTree)
        self._motorCheckLegs = QTreeWidgetItem(self._motorCheckBody)
        self._motorCheckArms = QTreeWidgetItem(self._motorCheckBody)
        self._motorCheckHead = QTreeWidgetItem(self._motorCheckBody)
        self._motorCheckLArm = QTreeWidgetItem(self._motorCheckArms)
        self._motorCheckRArm = QTreeWidgetItem(self._motorCheckArms)
        self._motorCheckLLeg = QTreeWidgetItem(self._motorCheckLegs)
        self._motorCheckRLeg = QTreeWidgetItem(self._motorCheckLegs)

        #saves configuration of the trees checkboxes for each of the tree modes.
        self._checkBoxesSave = {}
        self._checkBoxesPower = {}
        self._previousTreeMode = 0

        self.setObjectName('Record Animation')

        self._initial_joints = None

        self._widget.frameList = DragDropList(self._widget, self)
        self._widget.verticalLayout_2.insertWidget(0, self._widget.frameList)
        self._widget.frameList.setDragDropMode(QAbstractItemView.InternalMove)

        self.state_sub = rospy.Subscriber("/joint_states", JointState, self.state_update, queue_size=1, tcp_nodelay=True)
        self._joint_pub = rospy.Publisher("/record_motor_goals", JointCommand, queue_size=1)
        self.effort_pub = rospy.Publisher("/ros_control/set_torque_individual", JointTorque, queue_size=1)

        self.ids = {"HeadPan": 0,
               "HeadTilt": 1,
               "LShoulderPitch": 2,
               "LShoulderRoll": 3,
               "LElbow": 4,
               "RShoulderPitch": 5,
               "RShoulderRoll": 6,
               "RElbow": 7,
               "LHipYaw": 8,
               "LHipRoll": 9,
               "LHipPitch": 10,
               "LKnee": 11,
               "LAnklePitch": 12,
               "LAnkleRoll": 13,
               "RHipYaw": 14,
               "RHipRoll": 15,
               "RHipPitch": 16,
               "RKnee": 17,
               "RAnklePitch": 18,
               "RAnkleRoll": 19}

        self._initial_joints = JointState()

        self.update_time = rospy.Duration(0.1)

        for k,v in self.ids.iteritems():
            rospy.loginfo(k)
            self._initial_joints.name.append(k)
            self._initial_joints.position.append(0)

        while not self._initial_joints:
            if not rospy.is_shutdown():
                time.rospy.sleep(0.5)
                rospy.logwarn("wait")
            else:
                return

        self.initialize()

        context.add_widget(self._widget)
        self._widget.statusBar.showMessage("Initialization complete.")

    def initialize(self):
        for i in range(0, len(self._initial_joints.name)):
            self._currentGoals[self._initial_joints.name[i]] = self._initial_joints.position[i]
            self._workingValues[self._initial_joints.name[i]] = self._initial_joints.position[i]
            self._motorSwitched[self._initial_joints.name[i]] = True


        host = os.environ['ROS_MASTER_URI'].split('/')[2].split(':')[0]
        self._robot_anim_path = "bitbots@{}:~/wolfgang_ws/src/wolfgang_robot/wolfgang_animations/animations/motion/".format(host)
                
        rospy.loginfo("Set animation path to: "+str(self._robot_anim_path+"record.json"))

        initTorque = {}
        for k, v in self._workingValues.items():
            initTorque[k] = 2

        self._checkBoxesPower['#CURRENT_FRAME'] = initTorque

        self.motor_controller()
        self.motor_switcher()
        self.action_connect()
        self.box_ticked()
        self.frame_list()
        self.update_frames()
        self.set_sliders_and_text_fields(manual=True)

    def state_update(self, joint_states):
        '''
        Callback method for /joint_states. Updates the sliders to the actual values of the motors when the robot moves.
        '''
        if not self._initial_joints:
            self._initial_joints = joint_states
            time.rospy.sleep(1)
        else:
            for i in range(0, len(joint_states.name)):
                if (not self._motorSwitched[joint_states.name[i]]):
                    self._workingValues[joint_states.name[i]] = joint_states.position[i]


        rospy.sleep(self.update_time)
        self.set_sliders_and_text_fields(manual=False)


    def motor_controller(self):
        '''
        Sets up the GUI in the middle of the Screen to control the motors. 
        Uses self._motorValues to determine which motors are present.
        '''
        i = 0
        for k, v in sorted(self._currentGoals.items()):
            group = QGroupBox()
            slider = QSlider(Qt.Horizontal)
            slider.setTickInterval(1)
            slider.setMinimum(-181)
            slider.setMaximum(181)
            slider.valueChanged.connect(self.slider_update)
            self._sliders[k] = slider

            textfield = QLineEdit()
            textfield.setText('0')
            textfield.textEdited.connect(self.textfield_update)
            self._textFields[k] = textfield

            label = QLabel()
            label.setText(k)

            layout = QVBoxLayout()
            layout.addWidget(label)
            layout.addWidget(self._sliders[k])
            layout.addWidget(self._textFields[k])
            group.setLayout(layout)
            self._widget.motorControlLayout.addWidget(group, i / 5, i % 5)
            i = i+1

    def action_connect(self):
        '''
        Connects the actions in the top bar to the corresponding functions, and sets their shortcuts
        :return: 
        '''
        self._widget.actionNew.triggered.connect(self.new)
        self._widget.actionOpen.triggered.connect(self.open)
        self._widget.actionSave.triggered.connect(self.save)
        self._widget.actionSave_as.triggered.connect(self.save_as)
        self._widget.actionInit.triggered.connect(self.goto_init)
        self._widget.actionCurrent_Frame.triggered.connect(self.goto_frame)
        self._widget.actionNext_Frame.triggered.connect(self.goto_next)
        self._widget.actionAnimation.triggered.connect(self.play)
        self._widget.actionAnimation_until_Frame.triggered.connect(self.play_until)
        self._widget.actionDuplicate_Frame.triggered.connect(self.duplicate)
        self._widget.actionDelete_Frame.triggered.connect(self.delete)
        self._widget.actionLeft.triggered.connect(lambda: self.mirrorFrame("L"))
        self._widget.actionRight.triggered.connect(lambda: self.mirrorFrame("R"))
        self._widget.actionInvert.triggered.connect(self.invertFrame)
        self._widget.actionUndo.triggered.connect(self.undo)
        self._widget.actionRedo.triggered.connect(self.redo)
        self._widget.actionHelp.triggered.connect(self.help)

        self._widget.buttonRecord.clicked.connect(self.record)
        self._widget.buttonRecord.shortcut = QShortcut(QKeySequence("Space"), self._widget)
        self._widget.buttonRecord.shortcut.activated.connect(self.record)


        self._widget.frameList.keyPressed.connect(self.delete)

        self._widget.treeModeSelector.currentIndexChanged.connect(self.treeModeChanged)

    def help(self):
        '''
        Prints out the help dialogue
        '''
        helpDialog = QMessageBox.about(self._widget, "About RecordUI", "This is RecordUI, a tool to record robot animations.\n \n Keyboard shortcuts: \n \n \
            New: Ctrl + N \n \
            Open: Ctrl + O \n \
            Save: Ctrl + S \n \
            Save as: Ctrl + Shift + S \n \n \
            Play Init: Ctrl + I \n \
            Play Frame: Ctrl + P \n \
            Play Next: Alt + P \n \
            Play Animation: Ctrl + Shift + P \n \
            Play Until: Ctrl + Alt + P \n \n \
            Record Frame: Space \n \
            Duplicate Frame: '+' \n \
            Delete Frame: '-' \n \
            Undo: Ctrl + Z \n \
            Redo: Ctrl + Y \n \
            Help: Ctrl + H \n \n \
            Mirror to Left: Ctrl + Left Arrow \n \
            Mirror to Right: Ctrl + Right Arrow \n \
            Invert: Ctrl + Down Arrow")

    def new(self):
        '''
        Deletes all currently recorded frames
        '''
        if len(self._widget.frameList) > 1:
            message = "This will discard your current Animation. Continue?"
            sure = QMessageBox.question(self._widget, 'Sure?', message, QMessageBox.Yes | QMessageBox.No)
            if sure == QMessageBox.Yes:
                self._recorder.clear()
                self.update_frames()

    def save(self):
        '''
        Saves all currently recorded frames into a json file
        '''
        self.treeModeChanged(self._previousTreeMode)
        self.set_metadata()
        if not self._saveDir:
            self._saveDir = QFileDialog.getExistingDirectory()
        status = self._recorder.save_animation(self._saveDir, self._widget.lineAnimationName.text(), self._checkBoxesSave)
        self._widget.statusBar.showMessage(status)

    def save_as(self):
        '''
        Saves all currently recorded frames into a json file, which is saved at a user specified location
        '''
        self.treeModeChanged(self._previousTreeMode)
        self._saveDir = QFileDialog.getExistingDirectory()
        self._recorder.save_animation(self._saveDir, self._widget.lineAnimationName.text(), self._checkBoxesSave)

    def set_metadata(self):
        status = self._recorder.set_meta_data(self._widget.lineAnimationName.text(),
                                     self._widget.lineVersion.text(),
                                     self._widget.lineAuthor.text(),
                                     self._widget.fieldDescription.toPlainText())
        self._widget.statusBar.showMessage(status)
    def open(self):
        '''
        Deletes all current frames and instead loads an animation from a json file
        '''
        if len(self._widget.frameList) > 1:
            message = "This will discard your current Animation. Continue?"
            sure = QMessageBox.question(self._widget, 'Sure?', message, QMessageBox.Yes | QMessageBox.No)
            if sure == QMessageBox.No:
                return
        my_file = QFileDialog.getOpenFileName()[0]
        if my_file:
            status = self._recorder.load_animation(my_file)
            if status == "":
                status = "Load successful."
            self._widget.statusBar.showMessage(status)

        animstate = self._recorder.get_animation_state()
        for i in animstate:
            try:
                self._checkBoxesPower[i['name']] = i['torque']
            except KeyError:
                self._checkBoxesPower[i['name']] = {}
                for key in self.ids:
                    self._checkBoxesPower[i['name']][key] = 2



        self.update_frames()

        metadata = self._recorder.get_meta_data()

        self._widget.lineAnimationName.setText(metadata[0])
        self._widget.lineAuthor.setText(metadata[2])
        self._widget.lineVersion.setText(str(metadata[1]))
        self._widget.fieldDescription.setPlainText(metadata[3])

    def play(self):
        '''
        Plays the animation
        '''
        status = self._recorder.play(self._robot_anim_path)
        self._widget.statusBar.showMessage(status)

    def play_until(self):
        '''
        Plays the animation up to a certain frame
        '''
        steps = self._recorder.get_animation_state()
        j = 0
        for i in range(0, len(steps)):
            j += 1
            rospy.loginfo(steps[i]["name"])
            if steps[i]["name"] == self._selected_frame["name"]:
                break
        self._recorder.play(self._robot_anim_path, j)

    def goto_frame(self):
        '''
        Plays one single frame
        '''
        self.set_all_joints_stiff()

        pos_msg = JointCommand()
        pos_msg.joint_names = []
        pos_msg.velocities = [1.0] * 20
        pos_msg.positions = []
        pos_msg.accelerations = [-1.0] * 20
        pos_msg.max_currents = [-1.0] * 20


        for k,v in self._workingValues.items():
            pos_msg.joint_names.append(k)
            pos_msg.positions.append(v)

        torque_msg = JointTorque()
        torque_msg.joint_names = []
        torque_msg.on = []

        for k, v in self._checkBoxesPower[self._widget.frameList.currentItem().text()].items():
            torque_msg.joint_names.append(k)
            torque_msg.on.append(v)

        self.effort_pub.publish(torque_msg)
        self._joint_pub.publish(pos_msg)

    def goto_next(self):
        if self._widget.frameList.currentRow() < self._widget.frameList.count() - 2:
            self._widget.frameList.setCurrentRow(self._widget.frameList.currentRow()+1)
            self.goto_frame()

    def goto_init(self):
        '''
        Plays init frame, sets all motor values to 0
        '''
        self.set_all_joints_stiff()

        if self._widget.frameList.currentItem().text() == "#CURRENT_FRAME":
            for k, v in self._workingValues.iteritems():
                self._workingValues[k] = 0.0
        for k, v in self._currentGoals.iteritems():
            self._currentGoals[k] = 0.0
        self.set_sliders_and_text_fields(manual=False)

        pos_msg = JointCommand()
        pos_msg.joint_names = self._initial_joints.name
        pos_msg.velocities = [1.0] * len(self._initial_joints.name)
        pos_msg.positions = [0.0] * len(self._initial_joints.name)
        pos_msg.accelerations = [-1.0] * len(self._initial_joints.name)
        pos_msg.max_currents = [-1.0] * len(self._initial_joints.name)

        self._joint_pub.publish(pos_msg)

    def set_all_joints_stiff(self):
        '''
        Enables torque for all motors
        '''
        if self._widget.frameList.currentItem().text() == '#CURRENT_FRAME':
            for k, v in self._treeItems.items():
                v.setCheckState(0, Qt.Checked)

        torque_msg = JointTorque()
        torque_msg.joint_names = []
        torque_msg.on = []

        for k, v in sorted(self._treeItems.items()):
            torque_msg.joint_names.append(k)
            torque_msg.on.append(True)

        self.effort_pub.publish(torque_msg)

    def duplicate(self):
        '''
        Creates a copy of the selected frame
        '''
        try:
            frame = self._widget.frameList.selectedItems()[0].text()
        except:
            return
        if frame:
            if not frame == "#CURRENT_FRAME":
                self._recorder.duplicate(frame)
                self._widget.statusBar.showMessage("Duplicated frame "+ frame)
            else:
                self._widget.statusBar.showMessage("Cannot duplicate current frame. Record first.")
            self.update_frames()

    def delete(self):
        '''
        Deletes a frame
        '''
        try:
            frame = self._widget.frameList.selectedItems()[0].text()
        except:
            return
        if frame:
            if not frame == "#CURRENT_FRAME":
                self._recorder.delete(frame)
                self._widget.statusBar.showMessage("Deleted frame "+ frame)
            else: 
                self._widget.statusBar.showMessage("Cannot delete current frame.")
            self.update_frames()

    def record(self, keep=False):
        '''
        Records a frame
        '''
        if not self._widget.frameList.currentItem() == None:
            if self._widget.frameList.currentItem().text() == "#CURRENT_FRAME":
                x = True
                n = 0
                for state in self._recorder.get_animation_state():
                    if self._workingName == state["name"]:
                        while(x):
                            x = False
                            for state in self._recorder.get_animation_state():
                                if self._workingName+str(n) == state["name"]:
                                    n+=1
                                    x = True
                        self._workingName = self._workingName+str(n)

                self.set_sliders_and_text_fields(manual=True)

                self._checkBoxesPower[self._workingName] = {}
                initTorque = {}
                for k, v in self._workingValues.items():
                    initTorque[k] = 2

                self._checkBoxesPower[self._workingName] = initTorque

                self._recorder.record(self._workingValues,
                                      initTorque,
                                      self._widget.lineFrameName.text(),
                                      self._widget.spinBoxDuration.value(),
                                      self._widget.spinBoxPause.value())
                self._currentGoals = deepcopy(self._workingValues)
            else:
                current_row = self._widget.frameList.currentRow()

                self._recorder.record(self._workingValues,
                                      self._checkBoxesPower[self._widget.frameList.currentItem().text()],
                                      self._widget.lineFrameName.text(),
                                      self._widget.spinBoxDuration.value(),
                                      self._widget.spinBoxPause.value(),
                                      current_row,
                                      True)

 
            self._widget.statusBar.showMessage("Recorded frame "+ self._workingName)
        self.update_frames(keep)


    def undo(self):
        '''
        Undos the previous action
        '''
        status = self._recorder.undo()
        self._widget.statusBar.showMessage(status)
        self.update_frames()

    def redo(self):
        '''
        Redos an action
        '''
        status = self._recorder.redo()
        self._widget.statusBar.showMessage(status)
        self.update_frames()

    def mirrorFrame(self, direction):
        '''
        Copies all motor values from one side of the robot to the other. Inverts values, if necessary
        '''
        opposite = "L"
        if direction == "L":
            opposite = "R"
        for k, v in self._workingValues.items():
            if k[0] == opposite:
                for k1, v1 in self._workingValues.items():
                    if k1 == str(direction)+k[1:]:
                        self._workingValues[k1] = v * -1

        boxmode = 0
        if self._widget.treeModeSelector.currentIndex() == 0:
            boxmode = self._checkBoxesPower[self._widget.frameList.currentItem().text()]
        elif self._widget.treeModeSelector.currentIndex() == 1:
            boxmode = self._checkBoxesSave[self._widget.frameList.currentItem().text()]

        for k, v in boxmode.items():
            if k[0] == opposite:
                for k1, v1 in boxmode.items():
                    if k1 == str(direction)+k[1:]:
                        if boxmode[k] == 0:
                            boxmode[k1] = 0
                        elif boxmode[k] ==2:
                            boxmode[k1] = 2

        self.updateTreeConfig(self._widget.treeModeSelector.currentIndex())
        self.box_ticked()
        self._widget.statusBar.showMessage("Mirrored frame to "+ direction)
        

    def invertFrame(self):
        '''
        Copies all values from the left side to the right and all values from the right side to the left. 
        Inverts values, if necessary
        '''
        tempDict={}
        for k, v in self._workingValues.items():
            if k[0] == "L":
                tempDict["R"+k[1:]] = -v
            elif k[0] == "R":
                tempDict["L"+k[1:]] = -v
        for k, v in tempDict.items():
            self._workingValues[k] = v

        boxmode = 0
        if self._widget.treeModeSelector.currentIndex() == 0:
            boxmode = self._checkBoxesPower
        elif self._widget.treeModeSelector.currentIndex() == 1:
            boxmode = self._checkBoxesSave

        for k, v in boxmode[self._widget.frameList.currentItem().text()].items():

            if k[0] == "L":
                if v == 2:
                    tempDict["R"+k[1:]] = 2
                elif v == 0:
                    tempDict["R"+k[1:]] = 0
            elif k[0] == "R":
                if v == 2:
                    tempDict["L"+k[1:]] = 2
                elif v == 0:
                    tempDict["L"+k[1:]] = 0

        boxmode[self._widget.frameList.currentItem().text()] = deepcopy(tempDict)
        self.updateTreeConfig(self._widget.treeModeSelector.currentIndex())
        self.box_ticked()
        self._widget.statusBar.showMessage("Inverted frame")

    def frame_list(self):
        '''
        Connects triggers from the frame list to their callback methods.
        '''
        self._widget.frameList.itemSelectionChanged.connect(self.frame_select)
        self._widget.lineFrameName.textEdited.connect(self.frame_meta_update)
        self._widget.spinBoxPause.valueChanged.connect(self.frame_meta_update)
        self._widget.spinBoxDuration.valueChanged.connect(self.frame_meta_update)

    def frame_meta_update(self):
        '''
        Updates the respective values for the two spin boxes and the frame name, when they are changed
        '''
        self._workingDuration = self._widget.spinBoxDuration.value()
        self._workingPause = self._widget.spinBoxPause.value()
        self._workingName = self._widget.lineFrameName.text()

    def frame_select(self):
        '''
        Loads all information on a specific frame into the working values, if the frame selection changes
        '''
        if not (self._widget.frameList.currentItem() == None):
            self.copyOldTreeConfig()
            selected_frame_name = self._widget.frameList.currentItem().text()
            self._selected_frame = None



            for v in self._recorder.get_animation_state():
                if v["name"] == selected_frame_name:
                    self._selected_frame = v
                    break

        #save current values to _currentValues if switching from current frame to different one
        #when switching from current to different, save current values


            if selected_frame_name == "#CURRENT_FRAME":
                self._widget.treeModeSelector.setCurrentIndex(0)
                self.updateTreeConfig(0)
                self._widget.treeModeSelector.setEnabled(False)
                self._workingValues = deepcopy(self._currentGoals)
                self._workingName = deepcopy(self._currentName)
                self._workingDuration = deepcopy(self._currentDuration)
                self._workingPause = deepcopy(self._currentPause)

                self._current = True
            else:
                if self._selected_frame == None:
                    return
                self._widget.treeModeSelector.setEnabled(True)
                if self._current:
                    self._currentGoals = deepcopy(self._workingValues)
                    self._currentName = deepcopy(self._workingName)
                    self._currentDuration = deepcopy(self._workingDuration)
                    self._currentPause = deepcopy(self._workingPause)

                self._workingValues = self._selected_frame["goals"]
                self._workingName = self._selected_frame["name"]
                self._workingPause = self._selected_frame["pause"]
                self._workingDuration = float(self._selected_frame["duration"])

                self._widget.lineFrameName.setText(self._widget.frameList.currentItem().text())
                self._current = False
                self.updateTreeConfig(self._previousTreeMode)
            
            if  self._widget.treeModeSelector.currentIndex() == 0:
                for k, v in self._treeItems.items():
                    self._motorSwitched[k] = (v.checkState(0) == 2)


            for k, v in self._motorSwitched.items():
                self._textFields[k].setEnabled(v)
                self._sliders[k].setEnabled(v)

        self.set_sliders_and_text_fields(manual=False)
        self.box_ticked()

    def motor_switcher(self):
        '''
        Loads the motors into the tree and adds the checkboxes
        '''
        self._widget.motorTree.setHeaderLabel("Stiff Motors")
        self._motorCheckBody.setText(0, "Body")
        self._motorCheckBody.setFlags(self._motorCheckBody.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckBody.setExpanded(True)
        self._motorCheckHead.setText(0, "Head")
        self._motorCheckHead.setFlags(self._motorCheckHead.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckHead.setExpanded(True)
        self._motorCheckArms.setText(0, "Arms")
        self._motorCheckArms.setFlags(self._motorCheckArms.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckArms.setExpanded(True)
        self._motorCheckLegs.setText(0, "Legs")
        self._motorCheckLegs.setFlags(self._motorCheckLegs.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckLegs.setExpanded(True)
        self._motorCheckLArm.setText(0, "Left Arm")
        self._motorCheckLArm.setFlags(self._motorCheckLArm.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckLArm.setExpanded(True)
        self._motorCheckRArm.setText(0, "Right Arm")
        self._motorCheckRArm.setFlags(self._motorCheckRArm.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckRArm.setExpanded(True)
        self._motorCheckLLeg.setText(0, "Left Leg")
        self._motorCheckLLeg.setFlags(self._motorCheckLLeg.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckLLeg.setExpanded(True)
        self._motorCheckRLeg.setText(0, "Right Leg")
        self._motorCheckRLeg.setFlags(self._motorCheckRLeg.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self._motorCheckRLeg.setExpanded(True)

        for k, v in self._currentGoals.items():
            parent = None
            if 'LHip' in k or 'LKnee' in k or 'LAnkle' in k:
                parent = self._motorCheckLLeg
            elif 'RHip' in k or 'RKnee' in k or 'RAnkle' in k:
                parent = self._motorCheckRLeg
            elif 'LShoulder' in k or 'LElbow' in k:
                parent = self._motorCheckLArm
            elif 'RShoulder' in k or 'RElbow' in k:
                parent = self._motorCheckRArm
            elif 'Head' in k:
                parent = self._motorCheckHead
            child = QTreeWidgetItem(parent)
            child.setText(0, k)
            child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
            child.setCheckState(0, Qt.Checked)
            self._treeItems[k] = child

        self._widget.motorTree.itemClicked.connect(self.box_ticked)

    def copyOldTreeConfig(self):
        '''
        Saves the current configuration of the motor tree checkboxes into the corresponding dictionary
        '''
        if not self._selected_frame == None:
            tempDict={}
            for k, v in self._treeItems.items():
                tempDict[k]= self._treeItems[k].checkState(0)

            if self._previousTreeMode == 1:
                self._checkBoxesSave[self._selected_frame["name"]] = deepcopy(tempDict)
            elif self._previousTreeMode == 0:
                self._checkBoxesPower[self._selected_frame["name"]] = deepcopy(tempDict)

    def updateTreeConfig(self, index):
        '''
        Loads the new configuration of the motor tree depending on the change
        '''
        tempDict2={}
        if not self._widget.frameList.currentItem() == None:
            if not self._widget.frameList.currentItem().text() == "#CURRENT_FRAME":
                if index == 1:
                    if self._selected_frame["name"] in self._checkBoxesSave.keys():
                        tempDict2 = deepcopy(self._checkBoxesSave[self._selected_frame["name"]])
                    else:
                        for k in self._workingValues:
                            tempDict2[k] = 2
                elif index == 0:
                    if self._selected_frame["name"] in self._checkBoxesPower.keys():
                        tempDict2 = deepcopy(self._checkBoxesPower[self._selected_frame["name"]])
                    else:
                        for k in self._workingValues:
                            tempDict2[k] = 2
            else:
                for k in self._workingValues:
                    tempDict2[k] = 2
            self._previousTreeMode = index
            for k, v in tempDict2.items():
                if v == 0:
                    self._treeItems[k].setCheckState(0, Qt.Unchecked)
                elif v == 1:
                    self._treeItems[k].setCheckState(0, Qt.PartiallyChecked)
                elif v == 2:
                    self._treeItems[k].setCheckState(0, Qt.Checked)

    def treeModeChanged(self, index):
        self.copyOldTreeConfig()
        self.updateTreeConfig(index)


    def slider_update(self):
        '''
        Updates all sliders, checks whether a value is too large, then replaces it
        '''
        for k, v in self._sliders.items():
            self._workingValues[k] = math.radians(v.value())
            if self._workingValues[k] < -math.pi:
                self._workingValues[k] = -math.pi
            elif self._workingValues[k] > math.pi:
                self._workingValues[k] = math.pi
        self.set_sliders_and_text_fields(manual=True)

    def textfield_update(self):
        '''
        Updates all textfields.
        '''
        for k, v in self._textFields.items():
            try:
                self._workingValues[k] = math.radians(float(v.text()))
            except ValueError:
                continue
        self.set_sliders_and_text_fields(manual=True)

    def set_sliders_and_text_fields(self, manual):
        '''
        Updates the text fields and sliders in self._sliders and self._textfields and also frame name and duration and pause 
        to the values in self._workingValues. 
        '''
        for k, v in self._workingValues.items():
            try:
                if not self._treeItems[k].checkState(0) == 0:
                    self._textFields[k].setText(str(int(round(math.degrees(v)))))
                    self._sliders[k].setValue(int(round(math.degrees(v))))

            except KeyError:
                rospy.logwarn("Found a key-value pair for motor (%s), which doesn't seem to exist (yet). Ignoring it." % k)
                self._widget.statusBar.showMessage("Found a key-value pair for motor (%s), which doesn't seem to exist (yet). Ignoring it.")
                continue
            except RuntimeError:
                rospy.logwarn("Tried to access a PyQt element that no longer exists. This happens when you close the framework.")
                self._widget.statusBar.showMessage("Tried to access a PyQt element that no longer exists. This happens when you close the framework.")
                break
        if manual:
            self._widget.lineFrameName.setText(self._workingName)
            self._widget.spinBoxDuration.setValue(self._workingDuration)
            self._widget.spinBoxPause.setValue(self._workingPause)

    def box_ticked(self):
        
        '''
        Controls whether a checkbox has been clicked, and reacts.
        '''
        for k, v in self._treeItems.items():
            self._motorSwitched[k] = (v.checkState(0) == 2)


        for k, v in self._motorSwitched.items():
            self._textFields[k].setEnabled(v)
            self._sliders[k].setEnabled(v)

        self.set_sliders_and_text_fields(manual=False)

        torque_msg = JointTorque()
        torque_msg.joint_names = []
        torque_msg.on = []

        if  self._widget.treeModeSelector.currentIndex() == 0:
            for k, v in sorted(self._treeItems.items()):
                torque_msg.joint_names.append(k)
                torque_msg.on.append(v.checkState(0) == 2)


        pos_msg = JointCommand()
        pos_msg.joint_names = []
        pos_msg.velocities = [1.0] * 20
        pos_msg.positions = []
        pos_msg.accelerations = [-1.0] * 20
        pos_msg.max_currents = [-1.0] * 20

        for k,v in self._workingValues.items():
            pos_msg.joint_names.append(k)
            pos_msg.positions.append(v)

        self._joint_pub.publish(pos_msg)
        self.effort_pub.publish(torque_msg)
        if not self._widget.frameList.currentItem() == None:
            if not self._widget.frameList.currentItem().text() == "#CURRENT_FRAME":
                self.treeModeChanged(self._widget.treeModeSelector.currentIndex())

    def update_frames(self, keep=False):
        
        '''
        updates the list of frames present in the current animation
        :return: 
        '''
        current_index = self._widget.frameList.currentIndex()
        current_mode = self._widget.treeModeSelector.currentIndex()
        current_state = self._recorder.get_animation_state()
        while self._widget.frameList.takeItem(0):
            continue

        for i in range(0, len(current_state)):
            item = QListWidgetItem()
            if "name" in current_state[i]:
                item.setText(current_state[i]["name"])
            else: #older animations without names for the frames
                item.setText(str(i))
            self._widget.frameList.addItem(item)

        current = QListWidgetItem()
        current.setText("#CURRENT_FRAME")
        self._widget.frameList.addItem(current)
        if keep:    
            self._widget.frameList.setCurrentIndex(current_index)
            self._widget.treeModeSelector.setCurrentIndex(current_mode)
        else:
            self._widget.frameList.setCurrentItem(current)
            self._current = True

    def change_frame_order(self, new_order):
        ''' Calls the recorder to update frame order and updates the gui'''
        self._recorder.change_frame_order(new_order)
        self.update_frames()

    def shutdown_plugin(self):
        '''Clean up by sending the HCM a signal that we are no longer recording'''
        self._joint_pub.publish(JointCommand())
        rospy.sleep(1)
class ChecklistWindow(QWidget):
    def __init__(self, aircraft_id):
        super(ChecklistWindow, self).__init__()
        # Set properties of the window
        self.setWindowTitle("BTO and BPO Checklist")
        self.resize(500, 700)
        self.move(200,100)
        self.checklist_state = 0

        # Relative path for the default BPO and BTO checklist
        BPO_checklist_file = os.path.join(rospkg.RosPack().get_path('yonah_rqt'), 'src/yonah_rqt', 'BPO_checklist.csv')
        BTO_checklist_file = os.path.join(rospkg.RosPack().get_path('yonah_rqt'), 'src/yonah_rqt', 'BTO_checklist.csv')
        
        # Check whether checklist is present, if not print a error message to terminal
        try:
            # If checklist is present, parse it and pass it to its respective variable
            self.BPO_checklist = self.excel_parser(BPO_checklist_file)
            self.BTO_checklist = self.excel_parser(BTO_checklist_file)
        except:
            rospy.logerr("Checklist files are missing or named wrongly. Please follow the original directory and naming")
            exit()
    
        # Create the layout
        self.main_layout = QVBoxLayout()
        self.buttons_layout = QHBoxLayout()
        self.tree_widget_layout = QHBoxLayout()
        
        # Create the widgets
        self.create_widget()
        self.has_message_opened = 0

        # Add the widgets into the layouts
        self.main_layout.addLayout(self.tree_widget_layout)
        self.main_layout.addLayout(self.buttons_layout)
        self.setLayout(self.main_layout)

    # Create the main layout of widget
    def create_widget(self):
        # Create tree structure
        self.create_tree()

        # Declare buttons and connect each of them to a function
        self.load_button = QPushButton('Load')
        self.ok_button = QPushButton('OK')
        self.cancel_button = QPushButton('Cancel')
        self.load_button.pressed.connect(self.load_clicked)
        self.ok_button.pressed.connect(self.ok_clicked)
        self.cancel_button.pressed.connect(self.cancel_clicked)
        
        # Add buttons into the layout
        self.buttons_layout.addWidget(self.load_button)
        self.buttons_layout.addWidget(self.cancel_button)
        self.buttons_layout.addWidget(self.ok_button)
        self.tree_widget_layout.addWidget(self.tree_widget)

    # Create the tree layout of widget inside of the main layout
    def create_tree(self):
        # Set up the main tree widget
        self.tree_widget = QTreeWidget()
        self.tree_widget.setColumnCount(2)
        self.tree_widget.setColumnWidth(0, 250)
        self.tree_widget.setHeaderLabels(['Parts', 'Status'])
        self.item = QTreeWidgetItem()
        
        # Create the BPO section
        self.BPO_header = QTreeWidgetItem(self.tree_widget)
        self.BPO_header.setFlags(self.BPO_header.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self.BPO_header.setText(0, 'BPO Checklist')
        self.BPO_header.setExpanded(True)
        self.create_item(self.BPO_header, self.BPO_checklist) # Adds the list of items into the section

        # Create the BTO section
        self.BTO_header = QTreeWidgetItem(self.tree_widget)
        self.BTO_header.setFlags(self.BTO_header.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
        self.BTO_header.setText(0, 'BTO Checklist')
        self.BTO_header.setExpanded(True)
        self.create_item(self.BTO_header, self.BTO_checklist) # Adds the list of items into the section

    # Populate the tree layout with items
    def create_item(self, parent, list):
        section_header = [] # List of the section headers
        for i in range (len(list)):
            if (list[i][1] == '' and list[i][0] != ''):
                section_header.append(list[i][0])
        k = 0

        # Iterate through the different sections
        for j in range (len(section_header)):
            # Child refers to the sections (mechanical, avionics, etc)
            child = QTreeWidgetItem(parent)
            child.setFlags(child.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
            child.setText(0, section_header[j])

            while k < len(list):
                if list[k][0] in section_header:
                    # When the while loop encounters the first title, continue the loop
                    if (list[k][0] == section_header[0]):
                        k += 1
                        continue
                    # When the while loop encounters the next titles, break the loop so that the value of j increases
                    k += 1 
                    break
                # when the list contains empty cells, skip the cell and continue
                elif list[k][0] == '':
                    k += 1
                    continue
                # Add the list items to the treewidgetitem
                # Grandchild refers to the items under each section (wing nuts, tail nuts, etc)
                grandchild = QTreeWidgetItem(child)
                grandchild.setText(0, list[k][0])
                grandchild.setText(1, list[k][1])
                grandchild.setCheckState(0, Qt.Unchecked) # Set all checkbox to its unchecked state
                k += 1

    # Read the excel sheet and parse it as an array
    def excel_parser(self, file_name):
        with open(file_name, 'r') as file:
            checklist = []
            reader = csv.reader(file)
            for row in reader:
                checklist.append(row)
            return checklist            
    
    # Determines what happens when load button is clicked
    def load_clicked(self):
        # Use QFileDialog to open the system's file browser
        filenames = QFileDialog.getOpenFileNames(
            self, self.tr('Load from Files'), '.', self.tr('csv files {.csv} (*.csv)'))
        # Iterate through the file names selected
        for filename in filenames[0]:
            # If the file names has the word BPO or BTO in it, remove current widget, add the loaded one
            if (filename.find('BPO') != -1):
                self.BPO_checklist = self.excel_parser(filename)
                self.remove_widget()
                self.create_widget()
            elif (filename.find('BTO') != -1):
                self.BTO_checklist = self.excel_parser(filename)
                self.remove_widget()
                self.create_widget()
            else:
                rospy.logerr('rqt: Checklist name must contain BPO or BTO')
                self.close()
    
    # Close all the main_layout
    def remove_widget(self):
        self.main_layout.removeWidget(self.tree_widget)
        self.tree_widget.deleteLater()
        self.buttons_layout.removeWidget(self.ok_button)
        self.buttons_layout.removeWidget(self.cancel_button)
        self.buttons_layout.removeWidget(self.load_button)
        self.ok_button.deleteLater()
        self.cancel_button.deleteLater()
        self.load_button.deleteLater()
    
     # Declare what will happen when ok button is clicked
    def ok_clicked(self):
        if self.BPO_header.checkState(0) != 2 or self.BTO_header.checkState(0) != 2: # User clicks ok without checking all
            self.dialog_window("Some items in the checklist are still unchecked", "Do you still want to continue?", True)
        else:
            self.checklist_state = 1
            self.close()

    # Declare what will happen when cancel button is clicked
    def cancel_clicked(self):
        if self.BPO_header.checkState(0) != 0 or self.BTO_header.checkState(0) != 0: # User clicks cancel with some boxes checked
            self.dialog_window('Some of your items are checked. Cancelling will uncheck all your items', 'Do you still want to continue?', False)
        else:
            self.BTO_header.setCheckState(0, Qt.Unchecked)
            self.BPO_header.setCheckState(0, Qt.Unchecked)
            self.close()
    
    # Create a pop up window when user pre-emptively cancelled or clicked ok without completing the checklist
    def dialog_window(self, message, detail, check):
        self.message = QMessageBox()
        self.has_message_opened = 1
        self.message.setIcon(QMessageBox.Warning)
        self.message.setText(message)
        self.message.setInformativeText(detail)
        self.message.setWindowTitle("Items are unchecked")
        self.message.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        self.message.show()
        if check == True: # Check == True means it is from ok_clicked
            self.message.buttonClicked.connect(self.message_action)
        else:
            self.message.buttonClicked.connect(self.message_action_uncheck)
    
    # Determines what happens after dialog_window pops up from ok_button
    def message_action(self, i):
        if i.text() == '&Yes':
            self.checklist_state = 1
            self.close()
        else:
            self.message.close()

    # Determines what happens after dialog_window pops up from cancel_button
    def message_action_uncheck(self, i):
        self.response = i.text()
        if self.response == '&Yes':
            self.checklist_state = 1
            self.BTO_header.setCheckState(0, Qt.Unchecked)
            self.BPO_header.setCheckState(0, Qt.Unchecked)
            self.close()
        else:
            self.message.close()

    # Shutdown function
    def shutdown(self):
        self.close()
        if self.has_message_opened == 1:
            self.message.close()