def __init__(self, environment): super(MainWindow, self).__init__() self.env = environment 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 self.resetLayoutFlag = False # If True, Program will resetore the default layout # 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 __init__(self): super(MainWindow, self).__init__() # Init self and objects. self.fileName = None self.loadData = [] #Set when file is loaded. Used to check if the user has changed anything and prompt 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 other GUI elements 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 the Dashboard as the main widget 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 __init__(self): super(MainWindow, self).__init__() # Init self and objects. self.fileName = None self.loadData = [] #Set when file is loaded. Used to check if the user has changed anything and prompt self.env = Environment() self.interpreter = Interpreter(self.env) # Set Global UI Variables 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.centralWidget = QtWidgets.QStackedWidget() self.controlPanel = ControlPanelGUI.ControlPanel(self.env, parent=self) self.cameraWidget = CameraWidget(self.env.getVStream().getFilteredWithID, parent=self) self.consoleWidget = Console(self.env.getSetting('consoleSettings'), parent=self) # Connect the consoleWidget with the global print function, so the consoleWidget prints everything Global.printRedirectFunc = self.consoleWidget.write self.consoleWidget.setExecFunction(self.interpreter.evaluateExpression) # Create Menu items, and set the Dashboard as the main widget self.initUI() self.setVideo("play") # 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")) if self.env.getSetting("windowState") is not None: # Restore window geometry from settings state = self.env.getSetting("windowGeometry") state = bytearray(state, 'utf-8') bArr = QtCore.QByteArray.fromHex(state) self.restoreGeometry(bArr) # Restore size and position of dockwidgets from settings state = self.env.getSetting("windowState") state = bytearray(state, 'utf-8') bArr = QtCore.QByteArray.fromHex(state) self.restoreState(bArr)
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
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.")