class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle('Micromouse maze simulator') self.resize(600, 600) frame = QtWidgets.QFrame() layout = QtWidgets.QVBoxLayout(frame) self.graphics = PlotWidget() self.graphics.setAspectLocked() self.item = QtWidgets.QGraphicsRectItem(0, 0, 1, 1) self.item.setBrush(mkBrush('r')) self.item.setPen(mkPen(None)) self.graphics.addItem(self.item) self.graphics.setRange(rect=QtCore.QRectF(-10, -10, 20, 20)) self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) self.slider.setSingleStep(1) self.slider.setPageStep(10) self.slider.setRange(0, 10) self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove) self.slider.valueChanged.connect(self.slider_value_changed) self.slider.setValue(1) layout.addWidget(self.graphics) layout.addWidget(self.slider) self.setCentralWidget(frame) def slider_value_changed(self, value): self.item.setPos(value, 0)
class MainWindow(QWidget): def __init__(self, appinst, profilecon, settingscon, *args, **kwargs): super().__init__(*args, **kwargs) self.app = appinst self.profilecon = profilecon # connector class instance for reading/writing profile settings self.settingscon = settingscon self.gcode = None self.machine = None self.backgroundTask = None self.postBackgroundTask = None self.coord_plot_items = list( ) # list of all plot items added to the coord plot self.mainlayout = QVBoxLayout(self) self.mainlayout.setContentsMargins(0, 0, 0, 0) self.toolBar = QToolBar() self.toolBar.setStyleSheet("""QToolBar {background-color: white; border-top: 1px solid black}""" ) self.mainlayout.addWidget(self.toolBar) self.add_toolbar_action("./res/folder.svg", "Open", self.open_file_dialog) self.add_toolbar_action("./res/x-square.svg", "Close", self.close_file) self.add_toolbar_action("./res/save.svg", "Export", self.export) self.toolBar.addSeparator() self.add_toolbar_action("./res/sliders.svg", "Settings", self.open_settings_dialog) self.add_toolbar_action("./res/play.svg", "Simulate", self.start_simulation) self.toolBar.addSeparator() self.add_toolbar_action("./res/maximize.svg", "Fit to View", self.fit_plot_to_window) self.add_toolbar_action("./res/maximize-2.svg", "Reset View", self.reset_plot_view) self.toolBar.addSeparator() self.profileSelector = QComboBox() for name in self.profilecon.list_profiles(): self.profileSelector.addItem(name) self.profileSelector.setCurrentText( self.settingscon.get_value("Current_Profile")) self.profilecon.select_profile( self.settingscon.get_value("Current_Profile")) self.toolBar.addWidget(self.profileSelector) self.profileSelector.currentTextChanged.connect( self.selected_profile_changed) divider = QWidget() divider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolBar.addWidget(divider) self.add_toolbar_action("./res/info.svg", "About", self.open_about_dialog) self.contentLayout = QHBoxLayout() self.contentLayout.setContentsMargins(10, 10, 10, 10) self.mainlayout.addLayout(self.contentLayout) self.layerSlider = QSlider() self.layerSlider.setMinimum(0) self.layerSlider.setValue(0) self.layerSlider.setDisabled(True) self.layerSlider.valueChanged.connect(self.show_layer) self.contentLayout.addWidget(self.layerSlider) self.coordPlot = PlotWidget() self.coordPlot.setAspectLocked(True) # self.coordPlot.setLimits(xMin=0, yMin=0) self.configure_plot( ) # is done in a seperate funciton because values need to be updated after settings are changed self.contentLayout.addWidget(self.coordPlot) self.sidebarlayout = QVBoxLayout() self.contentLayout.addLayout(self.sidebarlayout) self.sidebarheader = QLabel("Options") self.sidebarheader.setFixedSize(300, 50) self.sidebarlayout.addWidget(self.sidebarheader) def configure_plot(self): self.coordPlot.invertX( self.profilecon.get_value("invert_x") ) # needs to be done before setting the axis ranges because self.coordPlot.invertY( self.profilecon.get_value("invert_y") ) # inverting does not update the viewbox, but setting the range does self.coordPlot.setXRange(self.profilecon.get_value("bed_min_x"), self.profilecon.get_value("bed_max_x")) self.coordPlot.setYRange(self.profilecon.get_value("bed_min_y"), self.profilecon.get_value("bed_max_y")) def selected_profile_changed(self, new_profile): # select the new profile in the settings connector and update the ui accordingly self.profilecon.select_profile(new_profile) self.settingscon.set_value("Current_Profile", new_profile) # remember selected profile self.settingscon.save_to_file() self.configure_plot() def add_toolbar_action(self, icon, text, function): # wrapper function for adding a toolbar button and connecting it to trigger a function open_icon = QIcon(icon) action = self.toolBar.addAction(open_icon, text) action.triggered.connect(function) def finish_background_task(self): # function is called when a background task finishes if self.postBackgroundTask: # run cleanup task (i.e. ui update); runs on main ui thread! self.postBackgroundTask() # reset variables self.postBackgroundTask = None self.backgroundTask = None def run_in_background(self, task, after=None, args=None): # wrapper function for creating and starting a thread to run a function in the background # arguments can be passed to the function in the thread and a cleanup function can be specified # which is run on the main ui thread when the background task is finished self.backgroundTask = BackgroundTask(task) if args: self.backgroundTask.set_arguments(args) self.backgroundTask.finished.connect(self.finish_background_task) self.postBackgroundTask = after self.backgroundTask.start() def open_file_dialog(self): # in case a file is open already, close it properly first if self.machine: ret = self.close_file() if not ret: # user canceled closing of current file; can't open new one return # open dialog for selecting a gcode file to be loaded dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) filters = ["G-code (*.gcode)", "Any files (*)"] dialog.setNameFilters(filters) dialog.selectNameFilter(filters[0]) dialog.setViewMode(QFileDialog.Detail) filename = None if dialog.exec_(): filename = dialog.selectedFiles() if filename: self.run_in_background(self.load_data, after=self.show_layer, args=filename) def open_settings_dialog(self): # open a dialog with settings dialog = SettingsDialog(self, self.profilecon) dialog.exec() # update settings self.configure_plot() def open_about_dialog(self): # open the about dialog dialog = QDialog() dialog.setWindowTitle("About...") layout = QVBoxLayout() dialog.setLayout(layout) text = QLabel(strings.about) layout.addWidget(text) dialog.exec() def close_file(self): # close the current gcode file, discard all data # Before, ask for user confirmation cfmsgbox = QMessageBox() cfmsgbox.setWindowTitle("Close file?") cfmsgbox.setText( "Are you sure you want to close the current file and discard all unsaved data?" ) cfmsgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) cfmsgbox.setDefaultButton(QMessageBox.No) ret = cfmsgbox.exec() if ret == QMessageBox.Yes: for item in self.coord_plot_items: self.coordPlot.removeItem(item) self.machine = None self.gcode = None # TODO: fix: this will not terminate a running background process return True return False def export(self): pass def start_simulation(self): pass def fit_plot_to_window(self): x, y = self.machine.get_path_coordinates( layer_number=self.layerSlider.value()) self.coordPlot.setRange(xRange=(min(x), max(x)), yRange=(min(y), max(y))) def reset_plot_view(self): self.coordPlot.setXRange(self.profilecon.get_value("bed_min_x"), self.profilecon.get_value("bed_max_x")) self.coordPlot.setYRange(self.profilecon.get_value("bed_min_y"), self.profilecon.get_value("bed_max_y")) def load_data(self, filename): # initalizes a virtual machine from the gcode in the file given # all path data for this gcode is calculated; this is a cpu intensive task! self.gcode = GCode() self.gcode.load_file(filename) self.machine = Machine(self.gcode, self.profilecon) self.machine.create_path() # set the layer sliders maximum to represent the given amount of layers and enable the slider self.layerSlider.setMaximum(len(self.machine.layers) - 1) self.layerSlider.setEnabled(True) def show_layer(self): # plot path for the layer selected by the layer slider x, y = self.machine.get_path_coordinates( layer_number=self.layerSlider.value()) pltitm = self.coordPlot.plot(x, y, clear=True) self.coord_plot_items.append(pltitm)
class Ui_MainWindow(object): def setupUi(self, MainWindow): #MainWindow.setWindowTitle('PictureWorkshop') MainWindow.setObjectName("Pupil Dilation Tracker") MainWindow.resize(1603, 1158) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( MainWindow.sizePolicy().hasHeightForWidth()) MainWindow.setSizePolicy(sizePolicy) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.centralwidget.sizePolicy().hasHeightForWidth()) self.centralwidget.setSizePolicy(sizePolicy) self.centralwidget.setObjectName("centralwidget") self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout.setObjectName("gridLayout") self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.gridLayout.addLayout(self.verticalLayout, 1, 0, 1, 1) self.video_title = QtWidgets.QLabel(self.centralwidget) self.video_title.setStyleSheet("font: 75 11pt \"Adobe Devanagari\";") self.video_title.setObjectName("video_title") self.gridLayout.addWidget(self.video_title, 0, 0, 1, 2, QtCore.Qt.AlignHCenter) self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) self.tabWidget.setObjectName("tabWidget") self.tab_FrameViewer = QtWidgets.QWidget() sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.tab_FrameViewer.sizePolicy().hasHeightForWidth()) self.tab_FrameViewer.setSizePolicy(sizePolicy) self.tab_FrameViewer.setObjectName("tab_FrameViewer") self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_FrameViewer) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName("gridLayout_2") self.frame = QtWidgets.QFrame(self.tab_FrameViewer) self.frame.setMinimumSize(QtCore.QSize(0, 100)) self.frame.setSizeIncrement(QtCore.QSize(0, 0)) self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame.setFrameShadow(QtWidgets.QFrame.Raised) self.frame.setObjectName("frame") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.frame) self.verticalLayout_3.setObjectName("verticalLayout_3") self.frame_2 = QtWidgets.QFrame(self.frame) self.frame_2.setMinimumSize(QtCore.QSize(0, 70)) self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_2.setObjectName("frame_2") self.gridLayout_3 = QtWidgets.QGridLayout(self.frame_2) self.gridLayout_3.setObjectName("gridLayout_3") self.horizontalSlider = QtWidgets.QSlider(self.frame_2) self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal) self.horizontalSlider.setObjectName("horizontalSlider") self.gridLayout_3.addWidget(self.horizontalSlider, 0, 1, 1, 2) self.label_frameNum = QtWidgets.QLabel(self.frame_2) self.label_frameNum.setStyleSheet("font: 75 9pt \"Adobe Devanagari\";") self.label_frameNum.setObjectName("label_frameNum") self.gridLayout_3.addWidget(self.label_frameNum, 2, 1, 1, 2, QtCore.Qt.AlignHCenter) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_3.addItem(spacerItem, 1, 0, 1, 1) self.L_button = QtWidgets.QPushButton(self.frame_2) self.L_button.setMinimumSize(QtCore.QSize(100, 100)) self.L_button.setMaximumSize(QtCore.QSize(100, 100)) self.L_button.setStyleSheet("font: 16pt \"MS Shell Dlg 2\";") self.L_button.setObjectName("L_button") self.gridLayout_3.addWidget(self.L_button, 1, 1, 1, 1) self.R_button = QtWidgets.QPushButton(self.frame_2) self.R_button.setMinimumSize(QtCore.QSize(100, 100)) self.R_button.setMaximumSize(QtCore.QSize(100, 100)) self.R_button.setStyleSheet("font: 16pt \"MS Shell Dlg 2\";") self.R_button.setObjectName("R_button") self.gridLayout_3.addWidget(self.R_button, 1, 2, 1, 1) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_3.addItem(spacerItem1, 1, 3, 1, 1) self.verticalLayout_3.addWidget(self.frame_2) self.gridLayout_2.addWidget(self.frame, 2, 1, 1, 1) self.checkBox_UsePrevData = QtWidgets.QCheckBox(self.tab_FrameViewer) self.checkBox_UsePrevData.setObjectName("checkBox_UsePrevData") self.gridLayout_2.addWidget(self.checkBox_UsePrevData, 0, 1, 1, 1, QtCore.Qt.AlignRight) self.start_push_button = QtWidgets.QPushButton(self.tab_FrameViewer) # self.start_push_button.setGeometry(QtCore.QRect(350, 10, 131, 40)) self.start_push_button.setGeometry(QtCore.QRect(180, 10, 131, 40)) self.start_push_button.setObjectName("start_push_button") self.custom_range_box = QtWidgets.QLineEdit(self.tab_FrameViewer) self.custom_range_box.setGeometry(QtCore.QRect(90, 10, 71, 41)) self.custom_range_box.setObjectName("custom_range_box") self.threshold_box = QtWidgets.QLineEdit(self.tab_FrameViewer) # self.threshold_box.setGeometry(QtCore.QRect(260, 10, 61, 41)) self.threshold_box.setGeometry(QtCore.QRect(580, 10, 61, 41)) self.threshold_box.setObjectName("threshold_box") self.threshold_label = QtWidgets.QLabel(self.tab_FrameViewer) # self.threshold_label.setGeometry(QtCore.QRect(170, 10, 81, 23)) self.threshold_label.setGeometry(QtCore.QRect(490, 10, 81, 23)) self.threshold_label.setObjectName("threshold_label") self.label_2 = QtWidgets.QLabel(self.tab_FrameViewer) self.label_2.setGeometry(QtCore.QRect(20, 10, 80, 23)) self.label_2.setObjectName("label_2") self.customRange_label = QtWidgets.QLabel(self.tab_FrameViewer) self.customRange_label.setGeometry(QtCore.QRect(30, 30, 80, 23)) self.customRange_label.setObjectName("customRange_label") self.progressBar = QtWidgets.QProgressBar(self.tab_FrameViewer) #self.progressBar.setGeometry(QtCore.QRect(500, 20, 124, 25)) self.progressBar.setGeometry(QtCore.QRect(320, 20, 124, 25)) self.progressBar.setProperty("value", 24) self.progressBar.setObjectName("progressBar") self.threshold_label_2 = QtWidgets.QLabel(self.tab_FrameViewer) # self.threshold_label_2.setGeometry(QtCore.QRect(170, 30, 81, 23)) self.threshold_label_2.setGeometry(QtCore.QRect(490, 30, 81, 23)) self.threshold_label_2.setObjectName("threshold_label_2") #self.graphicsView = PlotWidget(self.tab_FrameViewer) #self.graphicsView.setObjectName("graphicsView") #self.graphicsView.setAspectLocked(True) #keeps aspect locked for imported video frames, otherwise it stretches everything out. self.graphicsView = PlotWidget(self.tab_FrameViewer) self.graphicsView = self.graphicsView self.graphicsView.setObjectName("graphicsView") self.graphicsView.setAspectLocked( True ) #keeps aspect locked for imported video frames, otherwise it stretches everything out. self.gridLayout_2.setContentsMargins( 0, 40, 0, 0 ) # Lowers grphicsView a little, so the options are visible at the top self.gridLayout_2.addWidget(self.graphicsView, 1, 1, 1, 1) self.tabWidget.addTab(self.tab_FrameViewer, "") self.tab_Data = QtWidgets.QWidget() self.tab_Data.setObjectName("tab_Data") self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.tab_Data) self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_2.setObjectName("horizontalLayout_2") #self.graphicsView_Plot = QtWidgets.QGraphicsView(self.tab_Data) self.graphicsView_Plot = PlotWidget(self.tab_Data) self.graphicsView_Plot.setObjectName("graphicsView_Plot") #sizePolicy.setHeightForWidth(self.graphicsView_Plot.sizeAdjustPolicy()) self.horizontalLayout_2.addWidget(self.graphicsView_Plot) self.dataDisplay = QtWidgets.QTextBrowser(self.tab_Data) self.dataDisplay.setMaximumSize(QtCore.QSize(640, 16777215)) self.dataDisplay.setObjectName("dataDisplay") self.horizontalLayout_2.addWidget(self.dataDisplay) self.tabWidget.addTab(self.tab_Data, "") self.gridLayout.addWidget(self.tabWidget, 1, 1, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 1603, 47)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") self.menuOptions = QtWidgets.QMenu(self.menubar) self.menuOptions.setObjectName("menuOptions") self.menuHelp = QtWidgets.QMenu(self.menubar) self.menuHelp.setObjectName("menuHelp") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.actionUpload_new = QtWidgets.QAction(MainWindow) self.actionUpload_new.setObjectName("actionUpload_new") self.actionOpen = QtWidgets.QAction(MainWindow) self.actionOpen.setObjectName("actionOpen") self.actionSave = QtWidgets.QAction(MainWindow) self.actionSave.setObjectName("actionSave") self.actionKalman = QtWidgets.QAction(MainWindow) self.actionKalman.setObjectName("actionKalman") self.actionProperties = QtWidgets.QAction(MainWindow) self.actionProperties.setObjectName("actionProperties") self.actionTutorial = QtWidgets.QAction(MainWindow) self.actionTutorial.setObjectName("actionTutorial") self.actionEllipseFitting = QtWidgets.QAction(MainWindow) self.actionEllipseFitting.setObjectName("actionEllipseFitting") self.actionEllipseFitting.setCheckable(True) self.actionEllipseFitting.setChecked(True) # Commented out for now - until we can implement this feature in the future ''' self.actionManualSelection = QtWidgets.QAction(MainWindow) self.actionManualSelection.setCheckable(True) self.actionManualSelection.setChecked(False) self.actionManualSelection.setObjectName("actionManualSelection") ''' ''' self.action1 = QtWidgets.QAction(MainWindow) self.action1.setCheckable(True) self.action1.setObjectName("action1") self.action2 = QtWidgets.QAction(MainWindow) self.action2.setObjectName("action2") self.action3 = QtWidgets.QAction(MainWindow) self.action3.setCheckable(True) self.action3.setObjectName("action3") ''' # Adding menu actions under "FILE" self.menuFile.addAction(self.actionUpload_new) self.menuFile.addAction(self.actionOpen) self.menuFile.addSeparator() self.menuFile.addAction(self.actionSave) self.menuFile.addAction(self.actionKalman) #Adding menu actions under "OPTIONS" # self.menuOptions.addAction(self.actionEllipseFitting) # self.menuOptions.addAction(self.actionManualSelection) #Creating QActionGroup to have radio buttons in the file menu, code w/o radio buttons commented out above self.ag = QtGui.QActionGroup(MainWindow, exclusive=True) self.a1 = self.ag.addAction(self.actionEllipseFitting) self.menuOptions.addAction(self.a1) # self.a2 = self.ag.addAction(self.actionManualSelection) # self.menuOptions.addAction(self.a2) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuOptions.menuAction()) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle( _translate("MainWindow", "Pupil Dilation Tracker")) self.video_title.setText( _translate("MainWindow", "Click FILE to upload frames")) self.label_frameNum.setText( _translate("MainWindow", "No frames to display")) self.L_button.setText(_translate("MainWindow", "🡰")) self.R_button.setText(_translate("MainWindow", "🡲")) self.checkBox_UsePrevData.setText( _translate("MainWindow", "Use Previous Frame Data")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_FrameViewer), _translate("MainWindow", "Video frames")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_Data), _translate("MainWindow", "Data")) self.menuFile.setTitle(_translate("MainWindow", "File")) self.menuOptions.setTitle(_translate("MainWindow", "Options")) # self.actionManualSelection.setText(_translate("MainWindow", "Manual Selection")) self.actionEllipseFitting.setText( _translate("MainWindow", "Ellipse Fitting")) self.menuHelp.setTitle(_translate("MainWindow", "Help")) self.actionUpload_new.setText(_translate("MainWindow", "Upload video")) self.actionOpen.setText(_translate("MainWindow", "Open frames")) self.actionSave.setText(_translate("MainWindow", "Save")) self.actionKalman.setText( _translate("MainWindow", "Apply Kalman Filter")) self.actionProperties.setText(_translate("MainWindow", "Properties")) self.actionTutorial.setText(_translate("MainWindow", "Tutorial")) self.start_push_button.setText(_translate("MainWindow", "START")) self.threshold_label.setText(_translate("MainWindow", " Custom")) self.threshold_label_2.setText(_translate("MainWindow", "Threshold")) self.customRange_label.setText(_translate("MainWindow", "Range")) self.label_2.setText(_translate("MainWindow", "Custom")) self.threshold_box.setText(_translate("MainWindow", "0.5")) self.progressBar.setProperty("value", 0) '''