class ExampleGui(QWidget): """" This is simple pyqt5 gui with the ability to create threads and stop them, that is harder than it sounds. """ def __init__(self, example_ins): super().__init__() self.title = 'example gui' self.left = 40 self.top = 40 self.width = 320 self.height = 200 self.example_ins = example_ins self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.setAutoFillBackground(True) self.p = self.palette() self.set_color(Qt.red) self.make_button_1() self.make_button_2() self.show() def make_button_1(self): self.button = QPushButton('start button', self) self.button.setToolTip('This is an example button') self.button.move(10, 10) self.button.clicked.connect(self.on_click) def make_button_2(self): self.button_2 = QPushButton('end button', self) self.button_2.setToolTip('end the function') self.button_2.move(90, 10) self.button_2.clicked.connect(self.stop_on_click_function) def set_color(self, color): """ Set the color of the widget :param color: a color you want the gui to be :type string """ self.p.setColor(self.backgroundRole(), color) self.setPalette(self.p) def on_click(self): #initialize a long(couple of seconds) test function. self.worker_thread = WorkThread(self.go_to_sleep) self.worker_thread.start() def stop_on_click_function(self): """ stop a thread if one is running """ if self.worker_thread.isRunning(): self.worker_thread.quit() self.worker_thread.wait() print('this function is going to stop the on_click function') else: return def go_to_sleep(self): """ function that starts the thread. """ print('button click') self.button.setEnabled(False) self.set_color(Qt.yellow) time.sleep(4) self.set_color(Qt.red) self.button.setEnabled(True)
class Thorlabs_motor_GUI(BaseGui): """ | The initialization of the single_thorlabs gui. | Serial number and name are in the settings given underneath, so thorlabs_instrument knows them. | Initialize of the instrument is already done by the init of the thorlabs_instrument, that runs with the with downstairs. """ def __init__(self, thorlabs_instrument, also_close_output=False): super().__init__() self.logger = logging.getLogger(__name__) self.overall_layout = QHBoxLayout() self.setLayout(self.overall_layout) self.motor = thorlabs_instrument self.logger.debug('You are connected to a {}'.format(self.motor.kind_of_device)) self.title = 'Thorlabs {} GUI'.format(self.motor._name) self.saved_position = None self.current_position = None self.distance = 1.0*ur('mm') self.min_distance = -12.0 * ur('mm') self.max_distance = 12.0 * ur('mm') self.stop = self.stop_moving self.initUI() self.timer = QTimer() self.timer.timeout.connect(self.set_current_motor_position_label) self.timer.start(100) #time in ms self.moving_thread = WorkThread(self.motor.move_absolute, self.current_position, True) def initUI(self): self.logger.debug('Setting up the Single Thorlabs Motor GUI') self.setWindowTitle(self.title) groupBox = QGroupBox() self.overall_layout.addWidget(groupBox) groupBox.setStyleSheet("QGroupBox {border: 1px solid green; border-radius: 9px;}") self.grid_layout = QGridLayout() groupBox.setLayout(self.grid_layout) self.make_buttons() self.make_boxes() self.fill_up_widget() self.show() def make_buttons(self): """This method makes all the buttons in this GUI and connects them to the correct methods. """ self.go_home_button = QPushButton("go home", self) self.go_home_button.setToolTip('go to home position') self.go_home_button.clicked.connect(self.go_home_motor) self.move_button = QPushButton('move to', self) self.move_button.setToolTip('move to given input') self.move_button.clicked.connect(self.go_to_input) self.keyboard_button = QPushButton("keyboard", self) self.keyboard_button.setToolTip("use the keyboard to move the thorlabs_motor,\nit works great") self.keyboard_button.clicked.connect(self.use_keyboard) self.save_pos_button = QPushButton("save pos", self) self.save_pos_button.setToolTip('save the current position of the thorlabs_motor') self.save_pos_button.clicked.connect(self.save_position) self.recover_pos_button = QPushButton("recover pos", self) self.recover_pos_button.setToolTip("recover the set position of the thorlabs_motor") self.recover_pos_button.clicked.connect(self.recover_position) self.stop_button = QPushButton("stop moving", self) self.stop_button.setToolTip("stop any moving") self.stop_button.clicked.connect(self.stop_moving) self.stop_button.setStyleSheet("background-color: red") def make_boxes(self): """This method makes the labels, spinbox and combobox to make them available for the rest of this class, and connects them to the correct methods. """ self.current_motor_position_label = QLabel(self) try: self.current_motor_position_label.setText(self.motor.position()) except Exception: self.current_motor_position_label.setText("currently/nunavailable") self.save_label = QLabel() self.save_label.setText('saved:') self.distance_spinbox = QDoubleSpinBox(self) if self.motor.kind_of_device == 'waveplate': self.distance_spinbox.setValue(self.distance.m_as('mm')) self.distance = self.distance.m_as('mm') * ur('degrees') self.min_distance = 0 * ur('degrees') self.max_distance = 360 * ur('degrees') else: self.distance_spinbox.setValue(self.distance.m_as('mm')) self.distance_spinbox.setMinimum(-999999999) # otherwise you cannot reach higher than 99 self.distance_spinbox.setMaximum(999999999) self.distance_spinbox.valueChanged.connect(self.set_distance) self.unit_combobox = QComboBox(self) if self.motor.kind_of_device == 'waveplate': self.unit_combobox.addItems(["degrees"]) self.unit_combobox.setCurrentText('degrees') self.unit_combobox.setEnabled(False) else: self.unit_combobox.addItems(["nm", "um", "mm"]) self.unit_combobox.setCurrentText('mm') self.unit_combobox.currentTextChanged.connect(self.set_distance) self.set_current_motor_position_label() def fill_up_widget(self): """This method puts all the widgets in the correct positions in the grid. """ self.grid_layout.addWidget(self.go_home_button, 0, 0) self.grid_layout.addWidget(self.move_button, 1, 0) self.grid_layout.addWidget(QLabel("use keyboard\n(w/s, q to quit)"), 2, 0) self.grid_layout.addWidget(self.current_motor_position_label, 0, 1) self.grid_layout.addWidget(self.distance_spinbox, 1, 1) self.grid_layout.addWidget(self.keyboard_button, 2, 1) self.grid_layout.addWidget(self.save_label, 0, 3) self.grid_layout.addWidget(self.unit_combobox, 1, 3) self.grid_layout.addWidget(self.save_pos_button, 0, 4) self.grid_layout.addWidget(self.recover_pos_button, 1, 4) self.grid_layout.addWidget(self.stop_button, 2, 4) def set_current_motor_position_label(self): """ In the instrument level, the current position is remembered and updated through self.position, which is called in the moving_loop during the moves. This method read this out (continuously, through the timer in the init) and displays the value. """ self.current_position = self.motor.current_position self.current_motor_position_label.setText(str(round(self.current_position, 2))) #---------------------------------------------------------------------------------------------------------------------- def set_distance(self): """| Reads the value that the user filled in the spinbox and combines it with the unit to make a pint quantity. | The pint quantity is saved in self.distance. | Also compares the wanted distance with the maximum and minimum values, | which are set in the init or changed to degrees in make_distance_spinbox. | If the user input is too high or low, the spinbox is changed to the maximum or minimum value. """ value = self.distance_spinbox.value() unit = self.unit_combobox.currentText() local_distance = ur(str(value)+unit) self.logger.debug('local distance value {}'.format(self.distance)) self.logger.debug("{}".format(value > self.max_distance.m_as(unit))) if value > self.max_distance.m_as(unit): self.logger.debug('value too high') local_max = self.max_distance.to(unit) self.logger.debug(str(local_max)) self.distance_spinbox.setValue(local_max.m_as(unit)) elif value < self.min_distance.m_as(unit): self.logger.debug('value too low') local_min = self.min_distance.to(unit) self.distance_spinbox.setValue(local_min.m_as(unit)) self.distance = local_distance self.logger.debug('dictionary distance changed to: ' + str(self.distance)) def go_home_motor(self): """Starts a thread and communicates to the instrument to move home. The instrument loop will take care of updating the current position and checking whether self.stop is True or False. """ self.moving_thread = WorkThread(self.motor.move_home, True) self.moving_thread.start() #self.motor.move_home(True) #self.set_current_motor_position_label() def go_to_input(self): """Starts a thread to make an absolute move with the distance that is read out in self.set_distance from the spinbox. Value error has become a little bit irrelevant, now that I changed to pint quantities for distance. """ try: self.moving_thread = WorkThread(self.motor.move_absolute, self.distance, True) self.moving_thread.start() #self.set_current_motor_position_label() except ValueError: self.logger.warning("The input is not a float, change this") return def save_position(self): """Saves the current position for the user. Makes sure the user knows the button is pressed by setting it to a different color. Gives an error if the thorlabs_motor position has not been found, could be because it is a piezo thorlabs_motor or because the software is not running as expected. """ self.save_pos_button.setStyleSheet("background-color: green") try: self.saved_position = self.motor.position() self.logger.debug(str(round(self.saved_position,2))) self.save_label.setText("saved: " + str(round(self.saved_position,2))) except Exception: self.logger.warning("the position has not been set yet") self.saved_position = None def recover_position(self): """Sets position of motors to the saved position with a thread. When done, changes the save button to default. """ self.logger.debug("current position: {}".format(self.current_position)) if self.saved_position == None: self.logger.warning("the positions have not been set!") return else: self.moving_thread = WorkThread(self.motor.move_absolute, self.saved_position, True) self.moving_thread.start() self.save_pos_button.setStyleSheet("default") def use_keyboard(self): """Set text of keyboard_label to using keyboard. Collect events until released. """ self.keyboard_label.setText("using keyboard/npress q to exit") self.worker_thread = WorkThread(self.create_keyboard_listener) self.worker_thread.start() #set the text back to you can use the keyboard. self.keyboard_label.setText("use keyboard\nto control selected\n combobox thorlabs_motor:") def create_keyboard_listener(self): with Listener(on_press=self.on_press, on_release=self.on_release) as listener: listener.join() def on_press(self, key): """ In this method if the w is pressed the thorlabs_motor selected in the combobox will move forward or if s is pressed the thorlabs_motor will move backward. The w and s are written as: "'w'"/"'s'" because of syntacs. """ if str(key) == "'w'": #forward self.set_current_motor_position_label() self.motor.controller.move_velocity(2) self.set_current_motor_position_label() elif str(key) == "'s'": #backwards self.set_current_motor_position_label() self.motor.controller.move_velocity(1) self.set_current_motor_position_label() def on_release(self, key): """ In this method if the w or s is released the thorlabs_motor will stop moving. If q is released the keyboard mode stops. """ if str(key) == "'w'" or str(key) == "'s'": #stop the thorlabs_motor from going self.motor.stop_moving() self.set_current_motor_position_label() elif str(key) == "'q'": # Stop listener if self.worker_thread.isRunning(): self.set_current_motor_position_label() self.worker_thread.quit() self.worker_thread.wait() return False def stop_moving(self): """| Stops movement of the current cube. | The moving_loop method in the instrument level checks whether the stop is True, and if so, breaks the loop. | The stop_moving method in the instrument actually stops the device. | Because of the moving_thread that is started in the method move in this class, | the loops in the methods in instrument actually keep checking for this stop value. """ self.logger.info('stop moving') self.motor.stop = True self.motor.stop_moving() if self.moving_thread.isRunning: self.logger.debug('Moving thread was running.') self.moving_thread.quit() self.motor.stop = False