Пример #1
0
class MainWindow(QtWidgets.QMainWindow):
    # For debugging object count, use: print("CHILDREN: ", len(self.findChildren(QtCore.QObject)))
    def __init__(self):
        super(MainWindow, self).__init__()


        # Initialize the environment. Robot, camera, and objects will be loaded into the "logic" side of things
        self.env         = Environment(Paths.settings_txt, Paths.objects_dir, Paths.cascade_dir)
        if self.env.getSetting("language") is None:
            try:
                if locale.getdefaultlocale()[0] == 'zh_CN':
                    self.switchLanguage('zh_CN')
            except ValueError:
                printf("Can not detect system locale")
        elif self.env.getSetting("language") == 'zh_CN':
            self.switchLanguage('zh_CN')

        self.interpreter = Interpreter(self.env)



        # Set the ConsoleWidget parameters immediately, so even early prints are captured
        self.consoleWidget       = Console(self.env.getSetting('consoleSettings'), parent=self)
        Global.printRedirectFunc = self.consoleWidget.write
        self.consoleWidget.setExecFunction(self.interpreter.evaluateExpression)


        # Create GUI related class variables
        self.fileName        = None
        self.loadData        = []  #Set when file is loaded. Used to check if the user has changed anything when closing
        self.programTitle    = self.tr('uArm Creator Studio')
        self.scriptToggleBtn = QtWidgets.QAction(QtGui.QIcon(Paths.run_script), self.tr('Run'), self)
        self.devicesBtn      = QtWidgets.QAction(QtGui.QIcon(Paths.devices_neither), self.tr('Devices'), self)
        self.centralWidget   = QtWidgets.QStackedWidget()
        self.controlPanel    = ControlPanelGUI.ControlPanel(self.env, parent=self)
        self.cameraWidget    = CameraWidget(self.env.getVStream(), parent=self)
        self.floatingHint    = QtWidgets.QLabel()  # Used to display floating banners to inform the user of something


        # Create Menu items and set up the GUI
        self.cameraWidget.play()
        self.initUI()



        # After initUI: Restore the window geometry to the state it was when the user last closed the window
        if self.env.getSetting("windowGeometry") is not None:
            state = self.env.getSetting("windowGeometry")
            state = bytearray(state, 'utf-8')
            bArr = QtCore.QByteArray.fromHex(state)
            self.restoreGeometry(bArr)


        # After initUI: Restore size and position of dockwidgets to their previous state
        if self.env.getSetting("windowState") is not None:
            state = self.env.getSetting("windowState")
            state = bytearray(state, 'utf-8')
            bArr = QtCore.QByteArray.fromHex(state)
            self.restoreState(bArr)


        # If any file is specified in "lastOpenedFile" then load it.
        if self.env.getSetting("lastOpenedFile") is not None:
            self.loadTask(filename=self.env.getSetting("lastOpenedFile"))
        else:
            self.newTask(False)


        # Create a timer that checks the connected devices and updates the icon to reflect what is connected correctly
        self.refreshTimer = QtCore.QTimer()
        self.refreshTimer.timeout.connect(self.refreshDevicesIcon)
        self.refreshTimer.start(5000)  # Once every five seconds


    def initUI(self):
        # Create "File" Menu
        menuBar       = self.menuBar()
        menuBar.setNativeMenuBar(False)

        # Connect any slots that need connecting
        self.consoleWidget.settingsChanged.connect(lambda: self.env.updateSettings("consoleSettings",
                                                                                   self.consoleWidget.settings))

        # Create File Menu and actions
        fileMenu      = menuBar.addMenu(self.tr('File'))
        aboutAction   = QtWidgets.QAction(QtGui.QIcon(Paths.file_about), self.tr('About'       ) , self)
        newAction     = QtWidgets.QAction(QtGui.QIcon(Paths.file_new),  self.tr('New Task'    )  , self)
        saveAction    = QtWidgets.QAction(QtGui.QIcon(Paths.file_save), self.tr('Save Task'   )  , self)
        saveAsAction  = QtWidgets.QAction(QtGui.QIcon(Paths.file_save), self.tr('Save Task As')  , self)
        loadAction    = QtWidgets.QAction(QtGui.QIcon(Paths.file_load), self.tr('Load Task'   )  , self)

        saveAction.setShortcut("Ctrl+S")
        aboutAction.triggered.connect(  lambda : QtWidgets.QMessageBox.information(self, self.tr("About"), self.tr("Version: ")+ Version.version))
        newAction.triggered.connect(    lambda: self.newTask(promptSave=True))
        saveAction.triggered.connect(   self.saveTask)
        saveAsAction.triggered.connect( lambda: self.saveTask(True))
        loadAction.triggered.connect(   self.loadTask)

        fileMenu.addAction(aboutAction)
        fileMenu.addAction(newAction)
        fileMenu.addAction(saveAction)
        fileMenu.addAction(saveAsAction)
        fileMenu.addAction(loadAction)



        # Create Community Menu
        communityMenu = menuBar.addMenu(self.tr('Community'))
        forumAction   = QtWidgets.QAction(QtGui.QIcon(Paths.taskbar), self.tr('Visit the forum!'), self)
        redditAction  = QtWidgets.QAction(QtGui.QIcon(Paths.reddit_link), self.tr('Visit our subreddit!'), self)

        forumAction.triggered.connect(  lambda: webbrowser.open("https://forum.ufactory.cc/", new=0, autoraise=True))
        redditAction.triggered.connect(lambda: webbrowser.open("https://www.reddit.com/r/uArm/", new=0, autoraise=True))

        communityMenu.addAction(forumAction)
        communityMenu.addAction(redditAction)


        # Create Resources Menu
        resourceMenu = menuBar.addMenu(self.tr('New Resource'))
        visAction  = QtWidgets.QAction(QtGui.QIcon(Paths.event_recognize),  self.tr('Vision Object'     )    , self)
        grpAction  = QtWidgets.QAction(QtGui.QIcon(Paths.event_recognize),  self.tr('Vision Group'      )    , self)
        recAction  = QtWidgets.QAction(QtGui.QIcon(Paths.record_start),     self.tr('Movement Recording')    , self)
        fncAction  = QtWidgets.QAction(QtGui.QIcon(Paths.command_run_func), self.tr('Function'          )    , self)


        visAction.triggered.connect(  lambda: MakeObjectWindow(   None, self.env, parent=self))
        grpAction.triggered.connect(  lambda: MakeGroupWindow(    None, self.env, parent=self))
        recAction.triggered.connect(  lambda: MakeRecordingWindow(None, self.env, parent=self))
        fncAction.triggered.connect(  lambda: MakeFunctionWindow( None, self.env, parent=self))

        resourceMenu.addAction(visAction)
        resourceMenu.addAction(grpAction)
        resourceMenu.addAction(recAction)
        resourceMenu.addAction(fncAction)

        # Create Languages Menu
        languageMenu = menuBar.addMenu(self.tr('Languages'))
        enLanAction  = QtWidgets.QAction(QtGui.QIcon(Paths.languages_english),  self.tr('English'     )    , self)
        cnLanAction  = QtWidgets.QAction(QtGui.QIcon(Paths.languages_chinese), self.tr('Chinese'          )    , self)
        cnLanAction.triggered.connect(  lambda: self.switchLanguage('zh_CN'))
        enLanAction.triggered.connect(  lambda: app.removeTranslator(trans))


        languageMenu.addAction(enLanAction)
        languageMenu.addAction(cnLanAction)

        # Add menus to menuBar
        menuBar.addMenu(fileMenu)
        menuBar.addMenu(communityMenu)
        menuBar.addMenu(resourceMenu)
        menuBar.addMenu(languageMenu)


        # Create Toolbar
        toolbar = self.addToolBar(self.tr("MainToolbar"))
        toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)

        calibrateBtn = QtWidgets.QAction(QtGui.QIcon(Paths.calibrate),          self.tr('Calibrate'), self)
        objMngrBtn   = QtWidgets.QAction(QtGui.QIcon(Paths.objectManager),      self.tr('Resources'), self)

        self.scriptToggleBtn.setToolTip(self.tr('Run/Pause the command script (Ctrl+R)'))
        self.devicesBtn.setToolTip(self.tr('Open Camera and Robot settings'),)
        calibrateBtn.setToolTip(self.tr('Open Robot and Camera Calibration Center'))
        objMngrBtn.setToolTip(self.tr('Open Resource Manager'))

        self.scriptToggleBtn.setShortcut(self.tr('Ctrl+R'))


        self.scriptToggleBtn.triggered.connect(self.toggleScript)
        self.devicesBtn.triggered.connect(self.openDevices)
        calibrateBtn.triggered.connect(self.openCalibrations)
        objMngrBtn.triggered.connect(self.openObjectManager)

        toolbar.addAction(self.scriptToggleBtn)
        toolbar.addAction(self.devicesBtn)
        toolbar.addAction(calibrateBtn)
        toolbar.addAction(objMngrBtn)



        # Add Camera Widget, as a QDockWidget
        def createDockWidget(widget, name):
            dockWidget = QtWidgets.QDockWidget()
            dockWidget.setObjectName(name)  # Without this, self.restoreState() won't work
            dockWidget.setWindowTitle(name)
            dockWidget.setWidget(widget)
            dockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetFloatable |
                                   QtWidgets.QDockWidget.DockWidgetMovable)

            # titleBarWidget = QtWidgets.QWidget()
            # iconLbl = QtWidgets.QLabel()
            # iconLbl.setPixmap(QtGui.QPixmap(icon))
            # titleLbl = QtWidgets.QLabel(name)
            # mainHLayout = QtWidgets.QHBoxLayout()
            # mainHLayout.addWidget(iconLbl)
            # mainHLayout.addWidget(titleLbl)
            # titleBarWidget.setLayout(mainHLayout)
            # dockWidget.setTitleBarWidget(titleBarWidget)
            return dockWidget

        cameraDock = createDockWidget(self.cameraWidget, self.tr('Camera'))
        consoleDock = createDockWidget(self.consoleWidget, self.tr('Console'))


        # Add the consoleWidgets to the window, and tabify them
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, cameraDock)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, consoleDock)
        self.tabifyDockWidget(consoleDock, cameraDock)



        # Create the main layout
        self.setCentralWidget(self.controlPanel)


        # Final touches
        self.setWindowTitle(self.programTitle)
        self.setWindowIcon(QtGui.QIcon(Paths.taskbar))
        self.show()


    def setVideo(self, state):
        """
        Change the state of the videostream. The state can be play, pause, or simply "toggle
        :param state: "play", "pause", or "toggle"
        :return:
        """

        printf("GUI| Setting video to state: ", state)

        # Don't change anything if no camera ID has been added yet
        cameraID = self.env.getSetting("cameraID")
        if cameraID is None: return


        if state == "toggle":
            if self.env.getVStream().paused:
                self.setVideo("play")
                return
            else:
                self.setVideo("pause")
                return


        vStream = self.env.getVStream()
        if state == "play":
            # Make sure the videoStream object has a camera, or if the cameras changed, change it
            # if not vStream.connected() or not vStream.cameraID == cameraID:
            #     vStream.setNewCamera(cameraID)

            self.cameraWidget.play()

        if state == "pause":
            self.cameraWidget.pause()


    def toggleScript(self):
        # Run/pause the main script
        if self.interpreter.threadRunning():
            self.endScript()
        else:
            self.startScript()

    def startScript(self):
        if self.interpreter.threadRunning():
                printf("GUI| ERROR: Tried to start interpreter while it was already running.")
                return
        printf("GUI| Interpreter is ready. Loading script and starting task")


        # Load the script with the latest changes in the controlPanel, and get any relevant errors
        self.interpreter.cleanNamespace()   # Clear any changes the user did while it was running
        self.interpreter.setExiting(False)  # Make sure vision and robot are not in exiting mode



        errors = self.interpreter.initializeScript(self.controlPanel.getSaveData())


        # If there were errors during loading, present the user with the option to continue anyways
        if len(errors):
            errorText = ""
            for error, errorObjects in errors.items():
                errorText += "" + str(error) + "\n"
                for errObject in errorObjects:
                    errorText += "     " + str(errObject) + "\n"
                errorText += '\n'


            # Generate a message for the user to explain what parameters are missing
            errorStr = self.tr('Certain Events and Commands are missing the following requirements to work properly: \n\n') + \
                       ''.join(errorText) + \
                       self.tr('\nWould you like to continue anyways? Events and commands with errors will not activate.')


            # Warn the user
            reply = QtWidgets.QMessageBox.question(self, self.tr('Warning'), errorStr,
                                QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Cancel)


            if reply == QtWidgets.QMessageBox.Cancel:
                printf("GUI| Script run canceled by user before starting.")
                vision = self.env.getVision()
                vision.endAllTrackers()  # Clear any tracking that was started during interpreter initialization
                return


        # Prep the robot to start, so it always starts with servos attached and speed at 10
        robot  = self.env.getRobot()
        robot.setActiveServos(all=True)
        robot.setSpeed(10)


        # Stop you from moving stuff around while script is running, and activate the visual cmmnd highlighting
        self.interpreter.startThread(threaded=True)
        self.controlPanel.setScriptModeOn(self.interpreter, self.endScript)

        # Make sure the UI matches the state of the script
        self.scriptToggleBtn.setIcon(QtGui.QIcon(Paths.pause_script))
        self.scriptToggleBtn.setText("Stop")

    def endScript(self):
        # Tell the interpreter to exit the thread, then wait
        if self.interpreter.threadRunning():
            self.interpreter.setExiting(True)
            self.interpreter.mainThread.join(2)  # Give the thread 3 seconds to join


        # Check to make sure the thread closed correctly
        if self.interpreter.threadRunning():
            # Generate a message for the user to explain what parameters are missing
            errorStr = self.tr('The script was unable to end.\n' \
                       'This may mean the script crashed, or it is taking time finishing.\n\n' \
                       'If you are running Python code inside of this script, make sure you check isExiting() during' \
                       ' loops, to exit code quickly when the stop button is pressed.')

            # Warn the user
            reply = QtWidgets.QMessageBox.question(self, 'Error', errorStr, QtWidgets.QMessageBox.Ok)
            return


        # Turn off the gripper, just in case. Do this AFTER interpreter ends, so as to not use Serial twice...
        vision = self.env.getVision()

        robot  = self.env.getRobot()
        robot.setExiting(False)
        vision.setExiting(False)
        robot.setPump(False)

        # Since the robot might still be moving (and this activates servos continuously) wait for it to finish
        robot.stopMoving()
        robot.setActiveServos(all=False)

        # Make sure vision filters are stopped
        vision.endAllTrackers()


        self.scriptToggleBtn.setIcon(QtGui.QIcon(Paths.run_script))
        self.scriptToggleBtn.setText(self.tr("Start"))


    def refreshDevicesIcon(self):
        """
        This checks the status of the robot and camera, and updates the icon of the Devices toolbar button to reflect
        the connection status of the robot and camera.

        This should be run 5 seconds after the devices window closes, or 5 seconds after the program starts, to check
        if the robot thread was able to connect or not.
        """

        camera = self.env.getVStream()
        robot  = self.env.getRobot()

        robotErrors = robot.getErrorsToDisplay()
        if len(robotErrors) > 0:
            reply = QtWidgets.QMessageBox.question(self, self.tr('Communication Errors'), self.tr("The following errors have occured "
                            "communicating with your robot.\nTry reconnecting under the Devices menu."
                            "\n\nERROR:\n") + "\n".join(robotErrors), QtWidgets.QMessageBox.Ok)
            self.env.updateSettings("robotID", None)

        robCon = robot.connected()
        camCon = camera.connected()

        icon = ""

        if     robCon and     camCon: icon = Paths.devices_both
        if     robCon and not camCon: icon = Paths.devices_robot
        if not robCon and     camCon: icon = Paths.devices_camera
        if not robCon and not camCon: icon = Paths.devices_neither

        self.devicesBtn.setIcon(QtGui.QIcon(icon))


    def openDevices(self):
        # This handles the opening and closing of the Settings window.
        printf("GUI| Opening Devices Window")

        if self.interpreter.threadRunning(): self.endScript()

        self.cameraWidget.pause()

        deviceWindow = DeviceWindow(parent=self)
        accepted     = deviceWindow.exec_()

        self.cameraWidget.play()
        if not accepted:
            printf("GUI| Cancel clicked, no settings applied.")
            return

        printf("GUI| Apply clicked, applying settings...")
        if deviceWindow.getRobotSetting() is not None:
            self.env.updateSettings("robotID", deviceWindow.getRobotSetting())

        if deviceWindow.getCameraSetting() is not None:
            self.env.updateSettings("cameraID", deviceWindow.getCameraSetting())

        vStream = self.env.getVStream()
        vStream.setNewCamera(self.env.getSetting('cameraID'))


        # If the robots not connected, attempt to reestablish connection
        robot   = self.env.getRobot()
        if not robot.connected():
            robot.setUArm(self.env.getSetting('robotID'))



        self.cameraWidget.play()

    def openCalibrations(self):
        # This handles the opening and closing of the Calibrations window
        printf("GUI| Opening Calibrations Window")

        if self.interpreter.threadRunning(): self.endScript()
        self.cameraWidget.pause()

        coordSettings = self.env.getSetting("coordCalibrations")
        motionSettings = self.env.getSetting("motionCalibrations")
        calibrationsWindow = CalibrateWindow(coordSettings, motionSettings, self.env, parent=self)
        accepted           = calibrationsWindow.exec_()

        if accepted:
            # Update all the settings
            printf("GUI| Apply clicked, applying calibrations...")

            # Update the settings
            self.env.updateSettings("coordCalibrations", calibrationsWindow.getCoordSettings())
            self.env.updateSettings("motionCalibrations", calibrationsWindow.getMotionSettings())

        else:
            printf("GUI| Cancel clicked, no calibrations applied.")

        self.cameraWidget.play()

    def openObjectManager(self, openResourceWindow=None):
        # This handles the opening and closing of the ObjectManager window
        printf("GUI| Opening ObjectManager Window")

        if self.interpreter.threadRunning(): self.endScript()

        # Make sure video thread is active and playing, but that the actual cameraWidget
        self.setVideo("play")

        self.cameraWidget.pause()
        objMngrWindow = ObjectManagerWindow(self.env, self)
        accepted      = objMngrWindow.exec_()
        objMngrWindow.close()
        objMngrWindow.deleteLater()

        self.setVideo("play")

    def openResourceWindow(self, resourceWindowType):
        """
        This can open a MakeGroupWindow, MakeRecordingWindow, or MakeFunctionWindow, handle its closing and garbage
        collection, and create the object if the user clicks "finished"

        :param menuType: MenuType is the Type of the menu object, such as EditGroupWindow or MakeFunctionWindow
        :return:
        """


    def newTask(self, promptSave):
        if promptSave:
            cancelPressed = self.promptSave()
            if cancelPressed:  # If the user cancelled the close
                return

        self.controlPanel.loadData([])
        self.fileName = None
        self.loadData = deepcopy(self.controlPanel.getSaveData())
        self.setWindowTitle(self.programTitle)

    def saveTask(self, promptSaveLocation):
        printf("GUI| Saving project")

        # If there is no filename, ask for one
        if promptSaveLocation or self.fileName is None:
            Global.ensurePathExists(Paths.saves_dir)
            filename, _ = QtWidgets.QFileDialog.getSaveFileName(parent=self,
                                                                caption="Save Task",
                                                                filter="Task (*.task)",
                                                                directory=Paths.saves_dir)

            if filename == "": return False  #If user hit cancel
            self.fileName = filename
            self.env.updateSettings("lastOpenedFile", self.fileName)


        # Update the save file
        saveData = self.controlPanel.getSaveData()
        json.dump(saveData, open(self.fileName, 'w'), sort_keys=False, indent=3, separators=(',', ': '))

        self.loadData = deepcopy(saveData)  #Update what the latest saved changes are




        self.setWindowTitle(self.programTitle + '       ' + self.fileName)
        printf("GUI| Project Saved Successfully")
        return True

    def loadTask(self,  **kwargs):
        # Load a save file
        self.promptSave()  # Make sure the user isn't losing progress

        # Make sure a script isn't running while you try to load something
        if self.interpreter.threadRunning(): self.endScript()

        printf("GUI| Loading project")

        filename = kwargs.get("filename", None)

        # If there's no filename given, then prompt the user for what to name the task
        if filename is None:  #If no filename was specified, prompt the user for where to save
            filename, _ = QtWidgets.QFileDialog.getOpenFileName(parent=self,
                                                                caption="Load Task",
                                                                filter="Task (*.task)",
                                                                directory=Paths.saves_dir)
            if filename == "": return  #If user hit cancel


        try:
            self.loadData = json.load( open(filename))
        except IOError:
            printf("GUI| ERROR: Task file "), filename, self.tr("not found!")
            self.fileName = None
            self.env.updateSettings("lastOpenedFile", None)
            return



        # Load the data- BUT MAKE SURE TO DEEPCOPY otherwise any change in the program will change in self.loadData
        try:

            self.controlPanel.loadData(deepcopy(self.loadData))
            self.fileName = filename
            self.env.updateSettings("lastOpenedFile", filename)
            self.setWindowTitle(self.programTitle + '      ' + self.fileName)
            printf("GUI| Project loaded successfully")
        except Exception as e:
            printf("GUI| ERROR: Could not load task: ", e)
            self.newTask(promptSave=False)
            QtWidgets.QMessageBox.question(self, self.tr('Warning', "The program was unable to load the following script:\n") +
                                    filename + self.tr("\n\n The following error occured: ") + type(e).__name__ + ": " + str(e),
                                           QtWidgets.QMessageBox.Ok)



    def promptSave(self):
        # Prompts the user if they want to save, but only if they've changed something in the program
        # Returns True if the user presses Cancel or "X", and wants the window to stay open.


        if not self.loadData == self.controlPanel.getSaveData():
            printf("GUI| Prompting user to save changes")
            reply = QtWidgets.QMessageBox.question(self, self.tr('Warning'),
                                    self.tr("You have unsaved changes. Would you like to save before continuing?"),
                                    QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel,
                                    QtWidgets.QMessageBox.Yes)

            if reply == QtWidgets.QMessageBox.Yes:
                printf("GUI| Saving changes")
                success = self.saveTask(False)
                return not success

            if reply == QtWidgets.QMessageBox.No:
                printf("GUI| Not saving changes")

            if reply == QtWidgets.QMessageBox.Cancel:
                printf("GUI| User canceled- aborting close!")
                return True

    def closeEvent(self, event):
        """
            When window is closed, prompt for save, close the video stream, and close the control panel (thus script)
        """


        # Ask the user they want to save before closing anything
        cancelPressed = self.promptSave()
        if cancelPressed:  # If the user cancelled the close
            event.ignore()
            return


        # Save the window geometry as a string representation of a hex number
        saveGeometry = ''.join([str(char) for char in self.saveGeometry().toHex()])
        self.env.updateSettings("windowGeometry", saveGeometry)


        # Save the dockWidget positions/states as a string representation of a hex number
        saveState    = ''.join([str(char) for char in self.saveState().toHex()])
        self.env.updateSettings("windowState", saveState)


        # Deactivate the robots servos
        robot = self.env.getRobot()
        robot.setActiveServos(all=False)


        # End the script *after* prompting for save and deactivating servos on the robot. Script thread is a Daemon
        self.interpreter.setExiting(True)


        # Close and delete GUI objects, to stop their events from running
        self.refreshTimer.stop()
        self.cameraWidget.close()
        self.controlPanel.close()
        self.centralWidget.close()
        self.centralWidget.deleteLater()


        # Close threads
        self.env.close()

        printf("GUI| Done closing all objects and threads.")


    def switchLanguage(self, language):
        """
        This function will switch whole Program language
        :param language:
        :return:
        """
        if language == 'zh_CN':
            trans.load(Paths.language_pack_zh_CN)
            app.installTranslator(trans)
            self.env.updateSettings("language", language)
            if self.env.getSetting("language") is None:
                reply = QtWidgets.QMessageBox.question(self, self.tr('Warning'),
                                                       self.tr(
                                                           "Language switching need restart to apply, Would you like to continue?"),
                                                       QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel,
                                                       QtWidgets.QMessageBox.Yes)
            if reply == QtWidgets.QMessageBox.Yes:
                printf("GUI| Restart")
                success = self.saveTask(False)
                return not success

            if reply == QtWidgets.QMessageBox.No:
                printf("GUI| Not Restart")

            if reply == QtWidgets.QMessageBox.Cancel:
                printf("GUI| User canceled- aborting close!")
                return True
Пример #2
0

# Wait for the robot and camera to connect in their seperate threads
print("Waiting 8 seconds for robot and camera to connect")
sleep(8)


# Create the interpreter
interpreter = Interpreter(env)


# Load the .task file you want to run
saveFile = json.load(open(taskPath))


# Load the task into the interpreter, and print the errors
errors = interpreter.initializeScript(saveFile)
print("The following errors occured while initializing the script:\n", errors)
if str(input("Do you want to continue and start the script? (Y/N)")).lower() == "n": sys.exit()

# Before starting script, move the robot to a home position
robot = env.getRobot()
robot.setPos(**robot.home)


# Start the task in another thread
interpreter.startThread(threaded=False)

env.close()
print("Errors during interpreter run: ", interpreter.getExitErrors())
Пример #3
0
class MainWindow(QtWidgets.QMainWindow):
    # For debugging object count, use: print("CHILDREN: ", len(self.findChildren(QtCore.QObject)))
    def __init__(self):
        super(MainWindow, self).__init__()


        # Initialize the environment. Robot, camera, and objects will be loaded into the "logic" side of things
        self.env         = Environment(Paths.settings_txt, Paths.objects_dir, Paths.cascade_dir)
        self.interpreter = Interpreter(self.env)



        # Set the ConsoleWidget parameters immediately, so even early prints are captured
        self.consoleWidget       = Console(self.env.getSetting('consoleSettings'), parent=self)
        Global.printRedirectFunc = self.consoleWidget.write
        self.consoleWidget.setExecFunction(self.interpreter.evaluateExpression)


        # Create GUI related class variables
        self.fileName        = None
        self.loadData        = []  #Set when file is loaded. Used to check if the user has changed anything when closing
        self.programTitle    = 'uArm Creator Studio'
        self.scriptToggleBtn = QtWidgets.QAction(QtGui.QIcon(Paths.run_script), 'Run', self)
        self.videoToggleBtn  = QtWidgets.QAction(QtGui.QIcon(Paths.play_video), 'Video', self)
        self.devicesBtn      = QtWidgets.QAction(QtGui.QIcon(Paths.devices_neither), 'Devices', self)
        self.centralWidget   = QtWidgets.QStackedWidget()
        self.controlPanel    = ControlPanelGUI.ControlPanel(self.env, parent=self)
        self.cameraWidget    = CameraWidget(self.env.getVStream(), parent=self)
        self.floatingHint    = QtWidgets.QLabel()  # Used to display floating banners to inform the user of something


        # Create Menu items and set up the GUI
        self.initUI()
        self.setVideo("play")


        # After initUI: Restore the window geometry to the state it was when the user last closed the window
        if self.env.getSetting("windowGeometry") is not None:
            state = self.env.getSetting("windowGeometry")
            state = bytearray(state, 'utf-8')
            bArr = QtCore.QByteArray.fromHex(state)
            self.restoreGeometry(bArr)


        # After initUI: Restore size and position of dockwidgets to their previous state
        if self.env.getSetting("windowState") is not None:
            state = self.env.getSetting("windowState")
            state = bytearray(state, 'utf-8')
            bArr = QtCore.QByteArray.fromHex(state)
            self.restoreState(bArr)


        # If any file is specified in "lastOpenedFile" then load it.
        if self.env.getSetting("lastOpenedFile") is not None:
            self.loadTask(filename=self.env.getSetting("lastOpenedFile"))
        else:
            self.newTask(False)


        # Create a timer that checks the connected devices and updates the icon to reflect what is connected correctly
        self.refreshTimer = QtCore.QTimer()
        self.refreshTimer.timeout.connect(self.refreshDevicesIcon)
        self.refreshTimer.start(5000)  # Once every five seconds


    def initUI(self):
        # Create "File" Menu
        menuBar       = self.menuBar()

        # Connect any slots that need connecting
        self.consoleWidget.settingsChanged.connect(lambda: self.env.updateSettings("consoleSettings",
                                                                                   self.consoleWidget.settings))

        # Create File Menu and actions
        fileMenu      = menuBar.addMenu('File')
        newAction     = QtWidgets.QAction(QtGui.QIcon(Paths.file_new), "New Task", self)
        saveAction    = QtWidgets.QAction(QtGui.QIcon(Paths.file_save), "Save Task", self)
        saveAsAction  = QtWidgets.QAction(QtGui.QIcon(Paths.file_save), "Save Task As", self)
        loadAction    = QtWidgets.QAction(QtGui.QIcon(Paths.file_load), "Load Task", self)

        saveAction.setShortcut("Ctrl+S")

        newAction.triggered.connect(    lambda: self.newTask(promptSave=True))
        saveAction.triggered.connect(   self.saveTask)
        saveAsAction.triggered.connect( lambda: self.saveTask(True))
        loadAction.triggered.connect(   self.loadTask)


        fileMenu.addAction(newAction)
        fileMenu.addAction(saveAction)
        fileMenu.addAction(saveAsAction)
        fileMenu.addAction(loadAction)



        # Create Community Menu
        communityMenu = menuBar.addMenu('Community')
        forumAction   = QtWidgets.QAction(QtGui.QIcon(Paths.taskbar), "Visit the forum!", self)
        redditAction  = QtWidgets.QAction(QtGui.QIcon(Paths.reddit_link), "Visit our subreddit!", self)

        forumAction.triggered.connect(  lambda: webbrowser.open("https://forum.ufactory.cc/", new=0, autoraise=True))
        redditAction.triggered.connect(lambda: webbrowser.open("https://www.reddit.com/r/uArm/", new=0, autoraise=True))

        communityMenu.addAction(forumAction)
        communityMenu.addAction(redditAction)


        # Create Resources Menu
        resourceMenu = menuBar.addMenu('New Resource')
        visAction  = QtWidgets.QAction(QtGui.QIcon(Paths.event_recognize), "Vision Object", self)
        grpAction  = QtWidgets.QAction(QtGui.QIcon(Paths.event_recognize), "Vision Group", self)
        recAction  = QtWidgets.QAction(QtGui.QIcon(Paths.record_start), "Movement Recording", self)
        fncAction  = QtWidgets.QAction(QtGui.QIcon(Paths.command_run_func), "Function", self)


        visAction.triggered.connect(  lambda: MakeObjectWindow(   None, self.env, parent=self))
        grpAction.triggered.connect(  lambda: MakeGroupWindow(    None, self.env, parent=self))
        recAction.triggered.connect(  lambda: MakeRecordingWindow(None, self.env, parent=self))
        fncAction.triggered.connect(  lambda: MakeFunctionWindow( None, self.env, parent=self))

        resourceMenu.addAction(visAction)
        resourceMenu.addAction(grpAction)
        resourceMenu.addAction(recAction)
        resourceMenu.addAction(fncAction)



        # Add menus to menuBar
        menuBar.addMenu(fileMenu)
        menuBar.addMenu(communityMenu)
        menuBar.addMenu(resourceMenu)


        # Create Toolbar
        toolbar = self.addToolBar("MainToolbar")
        toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)

        calibrateBtn = QtWidgets.QAction(QtGui.QIcon(Paths.calibrate), 'Calibrate', self)
        objMngrBtn   = QtWidgets.QAction(QtGui.QIcon(Paths.objectManager), 'Resources', self)

        self.scriptToggleBtn.setToolTip('Run/Pause the command script (Ctrl+R)')
        self.videoToggleBtn.setToolTip('Play/Pause the video stream and Vision tracking')
        self.devicesBtn.setToolTip('Open Camera and Robot settings',)
        calibrateBtn.setToolTip('Open Robot and Camera Calibration Center')
        objMngrBtn.setToolTip('Open Resource Manager')

        self.scriptToggleBtn.setShortcut('Ctrl+R')


        self.scriptToggleBtn.triggered.connect(self.toggleScript)
        self.videoToggleBtn.triggered.connect(lambda: self.setVideo("toggle"))
        self.devicesBtn.triggered.connect(self.openDevices)
        calibrateBtn.triggered.connect(self.openCalibrations)
        objMngrBtn.triggered.connect(self.openObjectManager)

        toolbar.addAction(self.scriptToggleBtn)
        toolbar.addAction(self.videoToggleBtn)
        toolbar.addAction(self.devicesBtn)
        toolbar.addAction(calibrateBtn)
        toolbar.addAction(objMngrBtn)



        # Add Camera Widget, as a QDockWidget
        def createDockWidget(widget, name):
            dockWidget = QtWidgets.QDockWidget()
            dockWidget.setObjectName(name)  # Without this, self.restoreState() won't work
            dockWidget.setWindowTitle(name)
            dockWidget.setWidget(widget)
            dockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetFloatable |
                                   QtWidgets.QDockWidget.DockWidgetMovable)

            # titleBarWidget = QtWidgets.QWidget()
            # iconLbl = QtWidgets.QLabel()
            # iconLbl.setPixmap(QtGui.QPixmap(icon))
            # titleLbl = QtWidgets.QLabel(name)
            # mainHLayout = QtWidgets.QHBoxLayout()
            # mainHLayout.addWidget(iconLbl)
            # mainHLayout.addWidget(titleLbl)
            # titleBarWidget.setLayout(mainHLayout)
            # dockWidget.setTitleBarWidget(titleBarWidget)
            return dockWidget

        cameraDock = createDockWidget(self.cameraWidget, "Camera")
        consoleDock = createDockWidget(self.consoleWidget, "Console")


        # Add the consoleWidgets to the window, and tabify them
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, cameraDock)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, consoleDock)
        self.tabifyDockWidget(consoleDock, cameraDock)



        # Create the main layout
        self.setCentralWidget(self.controlPanel)


        # Final touches
        self.setWindowTitle(self.programTitle)
        self.setWindowIcon(QtGui.QIcon(Paths.taskbar))
        self.show()


    def setVideo(self, state):
        """
        Change the state of the videostream. The state can be play, pause, or simply "toggle
        :param state: "play", "pause", or "toggle"
        :return:
        """

        printf("GUI| Setting video to state: ", state)

        # Don't change anything if no camera ID has been added yet
        cameraID = self.env.getSetting("cameraID")
        if cameraID is None: return


        if state == "toggle":
            if self.env.getVStream().paused:
                self.setVideo("play")
                return
            else:
                self.setVideo("pause")
                return


        vStream = self.env.getVStream()

        if state == "play":
            # Make sure the videoStream object has a camera, or if the cameras changed, change it
            # if not vStream.connected() or not vStream.cameraID == cameraID:
            #     vStream.setNewCamera(cameraID)

            self.cameraWidget.play()
            vStream.setPaused(False)
            self.videoToggleBtn.setIcon(QtGui.QIcon(Paths.pause_video))
            self.videoToggleBtn.setText("Pause")

        if state == "pause":
            self.cameraWidget.pause()
            vStream.setPaused(True)
            self.videoToggleBtn.setIcon(QtGui.QIcon(Paths.play_video))
            self.videoToggleBtn.setText("Play")


    def toggleScript(self):
        # Run/pause the main script
        if self.interpreter.threadRunning():
            self.endScript()
        else:
            self.startScript()

    def startScript(self):
        if self.interpreter.threadRunning():
                printf("GUI| ERROR: Tried to start interpreter while it was already running.")
                return
        printf("GUI| Interpreter is ready. Loading script and starting task")


        # Load the script with the latest changes in the controlPanel, and get any relevant errors
        self.interpreter.cleanNamespace()   # Clear any changes the user did while it was running
        self.interpreter.setExiting(False)  # Make sure vision and robot are not in exiting mode



        errors = self.interpreter.initializeScript(self.controlPanel.getSaveData())


        # If there were errors during loading, present the user with the option to continue anyways
        if len(errors):
            errorText = ""
            for error, errorObjects in errors.items():
                errorText += "" + str(error) + "\n"
                for errObject in errorObjects:
                    errorText += "     " + str(errObject) + "\n"
                errorText += '\n'


            # Generate a message for the user to explain what parameters are missing
            errorStr = 'Certain Events and Commands are missing the following requirements to work properly: \n\n' + \
                       ''.join(errorText) + \
                       '\nWould you like to continue anyways? Events and commands with errors will not activate.'


            # Warn the user
            reply = QtWidgets.QMessageBox.question(self, 'Warning', errorStr,
                                QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Cancel)


            if reply == QtWidgets.QMessageBox.Cancel:
                printf("GUI| Script run canceled by user before starting.")
                vision = self.env.getVision()
                vision.endAllTrackers()  # Clear any tracking that was started during interpreter initialization
                return


        # Prep the robot to start, so it always starts with servos attached and speed at 10
        robot  = self.env.getRobot()
        robot.setActiveServos(all=True)
        robot.setSpeed(10)


        # Stop you from moving stuff around while script is running, and activate the visual cmmnd highlighting
        self.interpreter.startThread(threaded=True)
        self.controlPanel.setScriptModeOn(self.interpreter, self.endScript)

        # Make sure the UI matches the state of the script
        self.scriptToggleBtn.setIcon(QtGui.QIcon(Paths.pause_script))
        self.scriptToggleBtn.setText("Stop")

    def endScript(self):
        # Tell the interpreter to exit the thread, then wait
        if self.interpreter.threadRunning():
            self.interpreter.setExiting(True)
            self.interpreter.mainThread.join(2)  # Give the thread 3 seconds to join


        # Check to make sure the thread closed correctly
        if self.interpreter.threadRunning():
            # Generate a message for the user to explain what parameters are missing
            errorStr = 'The script was unable to end.\n' \
                       'This may mean the script crashed, or it is taking time finishing.\n\n' \
                       'If you are running Python code inside of this script, make sure you check isExiting() during' \
                       ' loops, to exit code quickly when the stop button is pressed.'

            # Warn the user
            reply = QtWidgets.QMessageBox.question(self, 'Error', errorStr, QtWidgets.QMessageBox.Ok)
            return


        # Turn off the gripper, just in case. Do this AFTER interpreter ends, so as to not use Serial twice...
        vision = self.env.getVision()

        robot  = self.env.getRobot()
        robot.setExiting(False)
        vision.setExiting(False)
        robot.setPump(False)

        # Since the robot might still be moving (and this activates servos continuously) wait for it to finish
        robot.stopMoving()
        robot.setActiveServos(all=False)

        # Make sure vision filters are stopped
        vision.endAllTrackers()


        self.scriptToggleBtn.setIcon(QtGui.QIcon(Paths.run_script))
        self.scriptToggleBtn.setText("Start")


    def refreshDevicesIcon(self):
        """
        This checks the status of the robot and camera, and updates the icon of the Devices toolbar button to reflect
        the connection status of the robot and camera.

        This should be run 5 seconds after the devices window closes, or 5 seconds after the program starts, to check
        if the robot thread was able to connect or not.
        """


        camera = self.env.getVStream()
        robot  = self.env.getRobot()


        robCon = robot.connected()
        camCon = camera.connected()

        icon = ""

        if     robCon and     camCon: icon = Paths.devices_both
        if     robCon and not camCon: icon = Paths.devices_robot
        if not robCon and     camCon: icon = Paths.devices_camera
        if not robCon and not camCon: icon = Paths.devices_neither

        self.devicesBtn.setIcon(QtGui.QIcon(icon))


    def openDevices(self):
        # This handles the opening and closing of the Settings window.
        printf("GUI| Opening Devices Window")

        if self.interpreter.threadRunning(): self.endScript()

        self.setVideo("pause")  # If you don't pause video, scanning for cameras may crash the program

        deviceWindow = DeviceWindow(parent=self)
        accepted     = deviceWindow.exec_()

        self.setVideo("play")
        if not accepted:
            printf("GUI| Cancel clicked, no settings applied.")
            return

        printf("GUI| Apply clicked, applying settings...")
        self.env.updateSettings("robotID", deviceWindow.getRobotSetting())
        self.env.updateSettings("cameraID", deviceWindow.getCameraSetting())

        vStream = self.env.getVStream()
        vStream.setNewCamera(self.env.getSetting('cameraID'))


        # If the robots not connected, attempt to reestablish connection
        robot   = self.env.getRobot()
        if not robot.connected():
            robot.setUArm(self.env.getSetting('robotID'))



        self.setVideo("play")

    def openCalibrations(self):
        # This handles the opening and closing of the Calibrations window
        printf("GUI| Opening Calibrations Window")

        if self.interpreter.threadRunning(): self.endScript()
        self.setVideo("pause")

        coordSettings = self.env.getSetting("coordCalibrations")
        motionSettings = self.env.getSetting("motionCalibrations")
        calibrationsWindow = CalibrateWindow(coordSettings, motionSettings, self.env, parent=self)
        accepted           = calibrationsWindow.exec_()

        if accepted:
            # Update all the settings
            printf("GUI| Apply clicked, applying calibrations...")

            # Update the settings
            self.env.updateSettings("coordCalibrations", calibrationsWindow.getCoordSettings())
            self.env.updateSettings("motionCalibrations", calibrationsWindow.getMotionSettings())

        else:
            printf("GUI| Cancel clicked, no calibrations applied.")

        self.setVideo("play")

    def openObjectManager(self, openResourceWindow=None):
        # This handles the opening and closing of the ObjectManager window
        printf("GUI| Opening ObjectManager Window")

        if self.interpreter.threadRunning(): self.endScript()

        # Make sure video thread is active and playing, but that the actual cameraWidget
        self.setVideo("play")

        self.cameraWidget.pause()
        objMngrWindow = ObjectManagerWindow(self.env, self)
        accepted      = objMngrWindow.exec_()
        objMngrWindow.close()
        objMngrWindow.deleteLater()

        self.setVideo("play")

    def openResourceWindow(self, resourceWindowType):
        """
        This can open a MakeGroupWindow, MakeRecordingWindow, or MakeFunctionWindow, handle its closing and garbage
        collection, and create the object if the user clicks "finished"

        :param menuType: MenuType is the Type of the menu object, such as EditGroupWindow or MakeFunctionWindow
        :return:
        """


    def newTask(self, promptSave):
        if promptSave:
            cancelPressed = self.promptSave()
            if cancelPressed:  # If the user cancelled the close
                return

        self.controlPanel.loadData([])
        self.fileName = None
        self.loadData = deepcopy(self.controlPanel.getSaveData())
        self.setWindowTitle(self.programTitle)

    def saveTask(self, promptSaveLocation):
        printf("GUI| Saving project")

        # If there is no filename, ask for one
        if promptSaveLocation or self.fileName is None:
            Global.ensurePathExists(Paths.saves_dir)
            filename, _ = QtWidgets.QFileDialog.getSaveFileName(parent=self,
                                                                caption="Save Task",
                                                                filter="Task (*.task)",
                                                                directory=Paths.saves_dir)

            if filename == "": return False  #If user hit cancel
            self.fileName = filename
            self.env.updateSettings("lastOpenedFile", self.fileName)


        # Update the save file
        saveData = self.controlPanel.getSaveData()
        json.dump(saveData, open(self.fileName, 'w'), sort_keys=False, indent=3, separators=(',', ': '))

        self.loadData = deepcopy(saveData)  #Update what the latest saved changes are




        self.setWindowTitle(self.programTitle + '       ' + self.fileName)
        printf("GUI| Project Saved Successfully")
        return True

    def loadTask(self,  **kwargs):
        # Load a save file
        self.promptSave()  # Make sure the user isn't losing progress

        # Make sure a script isn't running while you try to load something
        if self.interpreter.threadRunning(): self.endScript()

        printf("GUI| Loading project")

        filename = kwargs.get("filename", None)

        # If there's no filename given, then prompt the user for what to name the task
        if filename is None:  #If no filename was specified, prompt the user for where to save
            filename, _ = QtWidgets.QFileDialog.getOpenFileName(parent=self,
                                                                caption="Load Task",
                                                                filter="Task (*.task)",
                                                                directory=Paths.saves_dir)
            if filename == "": return  #If user hit cancel


        try:
            self.loadData = json.load( open(filename))
        except IOError:
            printf("GUI| ERROR: Task file ", filename, "not found!")
            self.fileName = None
            self.env.updateSettings("lastOpenedFile", None)
            return



        # Load the data- BUT MAKE SURE TO DEEPCOPY otherwise any change in the program will change in self.loadData
        try:

            self.controlPanel.loadData(deepcopy(self.loadData))
            self.fileName = filename
            self.env.updateSettings("lastOpenedFile", filename)
            self.setWindowTitle(self.programTitle + '      ' + self.fileName)
            printf("GUI| Project loaded successfully")
        except Exception as e:
            printf("GUI| ERROR: Could not load task: ", e)
            self.newTask(promptSave=False)
            QtWidgets.QMessageBox.question(self, 'Warning', "The program was unable to load the following script:\n" +
                                    filename + "\n\n The following error occured: " + type(e).__name__ + ": " + str(e),
                                           QtWidgets.QMessageBox.Ok)



    def promptSave(self):
        # Prompts the user if they want to save, but only if they've changed something in the program
        # Returns True if the user presses Cancel or "X", and wants the window to stay open.


        if not self.loadData == self.controlPanel.getSaveData():
            printf("GUI| Prompting user to save changes")
            reply = QtWidgets.QMessageBox.question(self, 'Warning',
                                    "You have unsaved changes. Would you like to save before continuing?",
                                    QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel,
                                    QtWidgets.QMessageBox.Yes)

            if reply == QtWidgets.QMessageBox.Yes:
                printf("GUI| Saving changes")
                success = self.saveTask(False)
                return not success

            if reply == QtWidgets.QMessageBox.No:
                printf("GUI| Not saving changes")

            if reply == QtWidgets.QMessageBox.Cancel:
                printf("GUI| User canceled- aborting close!")
                return True

    def closeEvent(self, event):
        """
            When window is closed, prompt for save, close the video stream, and close the control panel (thus script)
        """


        # Ask the user they want to save before closing anything
        cancelPressed = self.promptSave()
        if cancelPressed:  # If the user cancelled the close
            event.ignore()
            return


        # Save the window geometry as a string representation of a hex number
        saveGeometry = ''.join([str(char) for char in self.saveGeometry().toHex()])
        self.env.updateSettings("windowGeometry", saveGeometry)


        # Save the dockWidget positions/states as a string representation of a hex number
        saveState    = ''.join([str(char) for char in self.saveState().toHex()])
        self.env.updateSettings("windowState", saveState)


        # Deactivate the robots servos
        robot = self.env.getRobot()
        robot.setActiveServos(all=False)


        # End the script *after* prompting for save and deactivating servos on the robot. Script thread is a Daemon
        self.interpreter.setExiting(True)


        # Close and delete GUI objects, to stop their events from running
        self.refreshTimer.stop()
        self.cameraWidget.close()
        self.controlPanel.close()
        self.centralWidget.close()
        self.centralWidget.deleteLater()


        # Close threads
        self.env.close()

        printf("GUI| Done closing all objects and threads.")
Пример #4
0
env = Environment(settingsPath=settingsPath,
                  objectsPath=objectsPath,
                  cascadePath=cascadePath)

# Wait for the robot and camera to connect in their seperate threads
print("Waiting 8 seconds for robot and camera to connect")
sleep(8)

# Create the interpreter
interpreter = Interpreter(env)

# Load the .task file you want to run
saveFile = json.load(open(taskPath))

# Load the task into the interpreter, and print the errors
errors = interpreter.initializeScript(saveFile)
print("The following errors occured while initializing the script:\n", errors)
if str(input(
        "Do you want to continue and start the script? (Y/N)")).lower() == "n":
    sys.exit()

# Before starting script, move the robot to a home position
robot = env.getRobot()
robot.setPos(**robot.home)

# Start the task in another thread
interpreter.startThread(threaded=False)

env.close()
print("Errors during interpreter run: ", interpreter.getExitErrors())