class NPGPull(ZMQPull): def __init__(self, host, port, imgModel, table, opts, flags): ZMQPull.__init__(self, host, port, opts=[], flags=flags) self.socketTimer = QTimer() self.socketTimer.timeout.connect(self.receive) self.imgModel = imgModel self.table = table def start(self): self.connect() self.socketTimer.start(100) def stop(self): self.close() self.socketTimer.stop() def receive(self): try: data = self.puller.recv_json(flags=zmq.NOBLOCK) fn = str(data['fn'].strip()) path = str(data['path']) index = int(data['index']) total = int(data['total']) N = int(data['processed']) hit = int(data['hit']) self.imgModel.updateData(fn, path, int(index)) self.table.progress((total, N, hit)) return except zmq.error.Again: return
class Main(APIS): sequence = 2 # 插件加载顺序(重要,顺序不同可能导致界面混乱) name = ProgressUi.__name__ # 模块名 author = __Author__ # 作者 version = __Version__ # 版本 description = "自定义标题栏" # 描述 def __init__(self, parent = None): super(Main, self).__init__(parent) self.progressui = ProgressUi(self.parent) self.progressui.setVisible(False) # 默认不可见 self.setStyleSheet(self.progressui, self.name) # 测试加载进度 self.i = 0 self.progressui.setVisible(True) # 测试改为可见 self.timer = QTimer(timeout = self.onTimeout) self.timer.start(50) def onTimeout(self): self.i += 1 self.progressui.setValue(self.i) if self.i > 100: self.timer.stop() self.progressui.setValue(0) self.progressui.setVisible(False) def run(self): self.parent.vLayout.insertWidget(self.parent.vLayout.count() - 1, self.progressui) def stop(self): self.timer.stop() self.progressui.close() self.parent.vLayout.removeWidget(self.progressui)
class GenericTerminalOutputBox(QtWidgets.QLineEdit): def __init__(self) -> None: super().__init__() self.animate = False self.timer = QTimer(self) self.timer.setInterval(5) self.timer.timeout.connect(self._add_character) self.buffer = '' def set_timer_interval(self, num: int) -> None: self.timer.setInterval(num) def _add_character(self) -> None: if not self.buffer: self.timer.stop() return super().setText(self.text() + self.buffer[0]) self.buffer = self.buffer[1:] def setText(self, text: str) -> None: if not self.animate or not text: super().setText(text) return super().setText(text[0]) if len(text) > 1: self.buffer = text[1:] self.timer.start()
def _processPendingEvents(): """Process pending application events.""" # Create an event loop to run in. Otherwise, we need to use the # QApplication main loop, which may already be running and therefore # unusable. qe = QEventLoop() # Create a single-shot timer. Could use QTimer.singleShot(), # but can't cancel this / disconnect it. timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(qe.quit) timer.start(1) # Wait for an emitted signal. qe.exec_() # Clean up: don't allow the timer to call qe.quit after this # function exits, which would produce "interesting" behavior. timer.stop() # Stopping the timer may not cancel timeout signals in the # event queue. Disconnect the signal to be sure that loop # will never receive a timeout after the function exits. timer.timeout.disconnect(qe.quit)
class _StatusBar(QStatusBar): """Extended status bar. Supports HTML messages """ def __init__(self, *args): QStatusBar.__init__(self, *args) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding) self.setSizeGripEnabled(False) self.setStyleSheet("QStatusBar {border: 0} QStatusBar::item {border: 0}") self._label = QLabel(self) self._label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self._label.setStyleSheet("color: red") self.addWidget(self._label) self._timer = QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self.clearMessage) def term(self): self._timer.stop() def showMessage(self, text, timeout=0): """QStatusBar.showMessage() """ self._label.setText(text) self._timer.stop() if timeout > 0: self._timer.start(timeout) def clearMessage(self): """QStatusBar.clearMessage() """ self._label.clear() def currentMessage(self): return self._label.text()
class QtWaitForSelection(QMessageBox): """Window to wait for the user to select an object""" def __init__(self, parent=None): super(QtWaitForSelection, self).__init__(QMessageBox.Question, "Warte auf Auswahl", "Bitte wählen sie ein Object im 3d view aus", buttons=QMessageBox.Cancel, parent=parent) self.move(0,0) #create a timer self.timer = QTimer(self) self.timer.setInterval(50) #connect the timer to checking is anything was selected self.timer.timeout.connect(self.pollSelection) self.timer.start() self.selected = None def pollSelection(self): """Check if anything was selected""" try: #get the selected object nowSelected = getSelectedObject() except ValueError: QMessageBox.critical(self, "Mehrer Objekt Ausgewählt", "Es darf nicht mehr als ein Objekt ausgewählt sein.") for obj in bpy.data.objects: obj.select = False else: if not nowSelected is None: #stotre the selected object self.selected = nowSelected #stop the timer and close the window self.timer.stop() self.accept()
class ConnectionHandler(QObject): value_changed = pyqtSignal(bool) def __init__(self, frequency_check, time_before_time_out, parent=None): super(ConnectionHandler, self).__init__(parent) self.frequency_check = frequency_check self.time_before_time_out = time_before_time_out self.internet_ok = None self.timer = QTimer(self) def start(self): self.timer.setInterval(self.frequency_check) self.timer.timeout.connect(self.update) self.timer.start() def stop(self): self.timer.stop() def emit_if_changed(self, new_value): if not self.internet_ok is new_value: self.internet_ok = new_value self.value_changed.emit(new_value) def check_internet_call(self): urllib2.urlopen('http://www.demerio.com', timeout=self.time_before_time_out) @pyqtSlot() def update(self): try: self.check_internet_call() except Exception as e: self.emit_if_changed(False) else: self.emit_if_changed(True)
class Runner(object): """Useful class for running jobs with a delay""" def __init__(self, delay=2000): self._timer = QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self._execute_job) self._delay = delay self._job = None self._args = [] self._kw = {} def cancel(self): """Cancel the current job""" self._timer.stop() self._job = None self._args = [] self._kw = {} def run(self, job, *args, **kw): """Request a job run. If there is a job, cancel and run the new job with a delay specified in __init__""" self.cancel() self._job = job self._args = args self._kw = kw self._timer.start(self._delay) def _execute_job(self): """Execute job after the timer has timeout""" self._timer.stop() self._job(*self._args, **self._kw)
def run_gui_tests(tstcls, gui_test_bag): assert tstcls.app is None, "Should only encounter every class once. Check Sorting" tst_queue = queue.Queue() app = tstcls.app = QApplication([]) app.setQuitOnLastWindowClosed(False) ThreadRouter.app_is_shutting_down = False app.thread_router = ThreadRouter(app) tstcls.shell = launchShell(None, [], []) platform_str = platform.platform().lower() if 'ubuntu' in platform_str or 'fedora' in platform_str: QApplication.setAttribute(Qt.AA_X11InitThreads, True) if ilastik.config.cfg.getboolean("ilastik", "debug"): QApplication.setAttribute(Qt.AA_DontUseNativeMenuBar, True) # Note on the class test execution lifecycle # pytest infers that finalizer teardown_class should be called when # nextitem is None for item, nextitem in pairwise(gui_test_bag, tail=None): tst_queue.put((item, nextitem)) # Spawn a suite runner as a interval task suite = GuiTestSuite(tst_queue, tstcls.shell) timer = QTimer() # This timer will fire only after application is started running timer.timeout.connect(suite.poll) timer.start(100) # Every 100 ms app.exec_() timer.stop() tst_queue.join()
class DeviceReader(QThread): """Used for polling data from the Input layer during configuration""" raw_axis_data_signal = pyqtSignal(object) raw_button_data_signal = pyqtSignal(object) mapped_values_signal = pyqtSignal(object) def __init__(self, input): QThread.__init__(self) self._input = input self._read_timer = QTimer() self._read_timer.setInterval(25) self._read_timer.timeout.connect(self._read_input) def stop_reading(self): """Stop polling data""" self._read_timer.stop() def start_reading(self): """Start polling data""" self._read_timer.start() def _read_input(self): [rawaxis, rawbuttons, mapped_values] = self._input.read_raw_values() self.raw_axis_data_signal.emit(rawaxis) self.raw_button_data_signal.emit(rawbuttons) self.mapped_values_signal.emit(mapped_values)
class TimerObject(object): def __init__(self, interval, callback, times): self.times = times self.callback = callback self._timer = QTimer() # logger.info("register timer %s" % self) self._timer.timeout.connect(self.expired) self._timer.start(interval * 1000) def expired(self): # logger.info("callback from %s" % self) self.callback() self.times -= 1 if self._timer is None: # logger.info("dead timer %s called, ignore" % self) return if self.times == 0: self._timer.stop() # logger.info("delete timer %s" % self) self._timer = None def cancel(self): # logger.info("cancel timer %s" % self) if self._timer: self._timer.stop() self._timer = None
def askPasswordDialog(parent, title, prompt, timeout = None): if parent is None: app = qttools.createQApplication() translator = qttools.translator() app.installTranslator(translator) import icon dialog = QInputDialog() timer = QTimer() if not timeout is None: timer.timeout.connect(dialog.reject) timer.setInterval(timeout * 1000) timer.start() dialog.setWindowIcon(icon.BIT_LOGO) dialog.setWindowTitle(title) dialog.setLabelText(prompt) dialog.setTextEchoMode(QLineEdit.Password) QApplication.processEvents() ret = dialog.exec_() timer.stop() if ret: password = dialog.textValue() else: password = '' del(dialog) return(password)
class _GlobalUpdateWordSetTimer: """Timer updates word set, when editor is idle. (5 sec. after last change) Timer is global, for avoid situation, when all instances update set simultaneously """ _IDLE_TIMEOUT_MS = 1000 def __init__(self): self._timer = QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self._onTimer) self._scheduledMethods = [] def schedule(self, method): if not method in self._scheduledMethods: self._scheduledMethods.append(method) self._timer.start(self._IDLE_TIMEOUT_MS) def cancel(self, method): """Cancel scheduled method Safe method, may be called with not-scheduled method""" if method in self._scheduledMethods: self._scheduledMethods.remove(method) if not self._scheduledMethods: self._timer.stop() def _onTimer(self): method = self._scheduledMethods.pop() method() if self._scheduledMethods: self._timer.start(self._IDLE_TIMEOUT_MS)
class Clipboard(QObject): changed = pyqtSignal(dict) def __init__(self): QObject.__init__(self) clipboard = QGuiApplication.clipboard() clipboard.changed.connect(self.onClipboardChanged) self.clipboardTimer = QTimer() self.clipboardTimer.setInterval(500) self.clipboardTimer.setSingleShot(True) self.clipboardTimer.timeout.connect(self.onClipboardChangedAfterDelay) self.selectionTimer = QTimer() self.selectionTimer.setInterval(1000) self.selectionTimer.setSingleShot(True) self.selectionTimer.timeout.connect(self.onSelectionChangedAfterDelay) self.formats = set([mimeText]) def setFormats(self, formats): self.formats = set(formats) def onClipboardChanged(self, mode): if mode == QClipboard.Clipboard: if not QGuiApplication.clipboard().ownsClipboard(): self.clipboardTimer.start() else: self.clipboardTimer.stop() elif mode == QClipboard.Selection: if not QGuiApplication.clipboard().ownsSelection(): self.selectionTimer.start() else: self.selectionTimer.stop() def onClipboardChangedAfterDelay(self): self.emitChanged(QClipboard.Selection) def onSelectionChangedAfterDelay(self): self.emitChanged(QClipboard.Selection) def emitChanged(self, mode): clipboard = QGuiApplication.clipboard() mimeData = clipboard.mimeData() data = {} for format in self.formats: if mimeData.hasFormat(format): data[format] = mimeData.data(format) self.changed.emit(data) @pyqtProperty(str) def text(self): clipboard = QGuiApplication.clipboard() return clipboard.text() @text.setter def text(self, text): clipboard = QGuiApplication.clipboard() return clipboard.setText(text)
class Alarm(QWidget): def __init__(self): super(Alarm, self).__init__() self.ui = Ui_Form() self.ui.setupUi(self) self.ui.lineEdit.textChanged.connect(self.onTextChanged) self.ui.timeEdit.timeChanged.connect(self.onTimeChanged) self.ui.groupBox.toggled.connect(self.onGBToggle) self.ui.toolButton.clicked.connect(self.onToolButton) self.go = True self.ui.timeEdit.setTime(QTime.currentTime().addSecs(60)) self.timer = QTimer(self) self.timer.timeout.connect(self.__alarm) self.timer.start(Alarm.__get_diff(self.ui.timeEdit.time())) self.music = "alarm.mp3" self.ui.track_label.setText("DEFAULT") def __get_diff(time): return QTime.currentTime().msecsTo(time) def onTextChanged(self, text): self.ui.groupBox.setTitle(text) def onTimeChanged(self, time): c_time = QTime.currentTime() if (c_time > time): self.ui.timeEdit.setTime(c_time) return diff = Alarm.__get_diff(self.ui.timeEdit.time().addSecs(60)) print("TIME CHANGED", time, diff) self.timer.timer.start(diff + (60*1000)) def onGBToggle(self, checked): print("ALARM ON IS", checked) self.go = checked def __alarm(self): if not self.go: return; self.timer.stop() player = QMediaPlayer(self); player.setMedia(QMediaContent(QUrl.fromLocalFile(self.music))); player.setVolume(100); player.play(); self.setEnabled(False) QMessageBox.critical(self, "ALERTA", "TIME TO DIE<br>" + self.ui.groupBox.title(), QMessageBox.Yes) self.setEnabled(True) player.stop() player.deleteLater() def onToolButton(self): self.music = QFileDialog.getOpenFileName(self, "Open sound", "", "Sound (*.mp3 *.flac *.ogg)")[0] self.ui.track_label.setText(QFileInfo(self.music).baseName()) print(self.music)
class MessageView(QWidget): """Widget which stacks error/warning/info messages.""" update_geometry = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self._vbox = QVBoxLayout(self) self._vbox.setContentsMargins(0, 0, 0, 0) self._vbox.setSpacing(0) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self._clear_timer = QTimer() self._clear_timer.timeout.connect(self._clear_messages) self._set_clear_timer_interval() objreg.get('config').changed.connect(self._set_clear_timer_interval) self._last_text = None self._messages = [] def sizeHint(self): """Get the proposed height for the view.""" height = sum(label.sizeHint().height() for label in self._messages) # The width isn't really relevant as we're expanding anyways. return QSize(-1, height) @config.change_filter('ui', 'message-timeout') def _set_clear_timer_interval(self): """Configure self._clear_timer according to the config.""" self._clear_timer.setInterval(config.get('ui', 'message-timeout')) @pyqtSlot() def _clear_messages(self): """Hide and delete all messages.""" for widget in self._messages: self._vbox.removeWidget(widget) widget.hide() widget.deleteLater() self._messages = [] self._last_text = None self.hide() self._clear_timer.stop() @pyqtSlot(usertypes.MessageLevel, str) def show_message(self, level, text): """Show the given message with the given MessageLevel.""" if text == self._last_text: return widget = Message(level, text, parent=self) self._vbox.addWidget(widget) widget.show() self._clear_timer.start() self._messages.append(widget) self._last_text = text self.show() self.update_geometry.emit()
class MusicPosition(plugin.ViewSpacePlugin): def __init__(self, space): self._timer = QTimer(singleShot=True, timeout=self.slotTimeout) self._waittimer = QTimer(singleShot=True, timeout=self.slotTimeout) self._label = QLabel() space.status.layout().insertWidget(1, self._label) self._view = lambda: None space.viewChanged.connect(self.slotViewChanged) view = space.activeView() if view: self.slotViewChanged(view) def slotViewChanged(self, view): old = self._view() if old: self.disconnectView(old) self._view = weakref.ref(view) self.connectView(view) self.startTimer() def connectView(self, view): view.cursorPositionChanged.connect(self.startTimer) view.document().contentsChanged.connect(self.startWaitTimer) def disconnectView(self, view): view.cursorPositionChanged.disconnect(self.startTimer) view.document().contentsChanged.disconnect(self.startWaitTimer) def startWaitTimer(self): """Called when the document changes, waits longer to prevent stutter.""" self._waittimer.start(900) self._timer.stop() def startTimer(self): """Called when the cursor moves.""" if not self._waittimer.isActive(): self._timer.start(100) def slotTimeout(self): """Called when one of the timers fires.""" view = self._view() if view: d = view.document() c = view.textCursor() import documentinfo m = documentinfo.music(d) import ly.duration if c.hasSelection(): cursortools.strip_selection(c) length = m.time_length(c.selectionStart(), c.selectionEnd()) text = _("Length: {length}").format( length=ly.duration.format_fraction(length)) if length is not None else '' else: pos = m.time_position(c.position()) text = _("Pos: {pos}").format( pos=ly.duration.format_fraction(pos)) if pos is not None else '' self._label.setText(text) self._label.setVisible(bool(text))
class AnimatedClock(QGraphicsView): def __init__(self, parent=None): QGraphicsView.__init__(self, parent) self.updateSecs = 0.5 # Border self.setLineWidth(0) self.setFrameShape(QtWidgets.QFrame.NoFrame) # Size sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHeightForWidth(True) self.setSizePolicy(sizePolicy) # No scrollbars self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # Scene self.scene = QGraphicsScene() self.setScene(self.scene) self.setBackgroundBrush(QColor("black")) # Text of clock self.textItem = QGraphicsTextItem() self.textItem.color = QColor(QColor("black")) self.textItem.setFont(QFont("Segoe UI", 80)) self.textItem.setDefaultTextColor(QColor("white")) self.textItem.setHtml("") self.textItem.setZValue(20) self.scene.addItem(self.textItem) # Start ticking self.start() def sizeHint(self): return QSize(300, 150) def start(self): self.updateTimer = QTimer() self.updateTimer.setInterval(self.updateSecs * 990) self.updateTimer.timeout.connect(self.updateClock) self.updateTimer.start() print("Animated clock - starting") def stop(self): if self.updateTimer != None: self.updateTimer.stop() print("Animated clock - stopping") def updateClock(self): localtime = time.localtime() timeString = time.strftime("%H:%M:%S", localtime) self.textItem.setHtml(timeString) width = self.frameGeometry().width() self.textItem.setFont(QFont("Segoe UI", width / 8)) self.textItem.update() def heightForWidth(self, width): return width * .32 def keyPressEvent(self, event): #QKeyEvent event.ignore()
class GalleryDownloaderItem(QObject): """ Receives a HenItem """ d_item_ready = pyqtSignal(object) def __init__(self, hitem): super().__init__() assert isinstance(hitem, pewnet.HenItem) self.d_item_ready.connect(self.done) self.item = hitem url = self.item.gallery_url self.profile_item = QTableWidgetItem(self.item.name) self.profile_item.setData(Qt.UserRole+1, hitem) self.profile_item.setToolTip(url) def set_profile(item): self.profile_item.setIcon(QIcon(item.thumb)) self.item.thumb_rdy.connect(set_profile) # status self.status_item = QTableWidgetItem('In queue...') self.status_item.setToolTip(url) def set_finished(item): self.status_item.setText('Finished!') self.d_item_ready.emit(self) self.item.file_rdy.connect(set_finished) # other self.cost_item = QTableWidgetItem(self.item.cost) self.cost_item.setToolTip(url) self.size_item = QTableWidgetItem(self.item.size) self.size_item.setToolTip(url) type = 'Archive' if hitem.download_type == 0 else 'Torrent' self.type_item = QTableWidgetItem(type) self.type_item.setToolTip(url) self.status_timer = QTimer() self.status_timer.timeout.connect(self.check_progress) self.status_timer.start(500) def check_progress(self): if self.item.current_state == self.item.DOWNLOADING: btomb = 1048576 self.status_item.setText("{0:.2f}/{1:.2f} MB".format(self.item.current_size/btomb, self.item.total_size/btomb)) self.size_item.setText("{0:.2f} MB".format(self.item.total_size/btomb)) elif self.item.current_state == self.item.CANCELLED: self.status_item.setText("Cancelled!") self.status_timer.stop() def done(self): self.status_timer.stop() if self.item.download_type == 0: self.status_item.setText("Sent to library!") else: self.status_item.setText("Sent to torrent client!")
class MessageView(QWidget): """Widget which stacks error/warning/info messages.""" reposition = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self._vbox = QVBoxLayout(self) self._vbox.setContentsMargins(0, 0, 0, 0) self._vbox.setSpacing(0) self._clear_timer = QTimer() self._clear_timer.timeout.connect(self._clear_messages) self._set_clear_timer_interval() objreg.get('config').changed.connect(self._set_clear_timer_interval) self._last_text = None self._messages = [] @config.change_filter('ui', 'message-timeout') def _set_clear_timer_interval(self): """Configure self._clear_timer according to the config.""" self._clear_timer.setInterval(config.get('ui', 'message-timeout')) def message_height(self): """Get the total height of all messages.""" return sum(label.sizeHint().height() for label in self._messages) @pyqtSlot() def _clear_messages(self): """Hide and delete all messages.""" for widget in self._messages: self._vbox.removeWidget(widget) widget.hide() widget.deleteLater() self._messages = [] self._last_text = None self.hide() self._clear_timer.stop() @pyqtSlot(usertypes.MessageLevel, str) def show_message(self, level, text): """Show the given message with the given MessageLevel.""" if text == self._last_text: return widget = Message(level, text, parent=self) self._vbox.addWidget(widget) widget.show() self._clear_timer.start() self._messages.append(widget) self._last_text = text self.show() self.reposition.emit()
class LogviewDlgHelper(object): def __init__(self, logfilename, dlg, dlgUI): self.logfilename = logfilename self.dlgUI = dlgUI self.qtDlg = dlg self.readlog() self.update() self.dlgUI.logPTE.setReadOnly(True) self.dlgUI.closeBtn.clicked.connect(self.close) self.dlgUI.clearBtn.clicked.connect(self.clear) self.refresh_timer = QTimer(self.qtDlg) # noinspection PyUnresolvedReferences self.refresh_timer.timeout.connect(self.refresh) self.refresh_timer.start(1000) # update every 1 seconds def refresh(self): self.readlog() self.update() def readlog(self): with open(self.logfilename, 'r') as fl: self.logtext = fl.read() def close(self): self.refresh_timer.stop() self.qtDlg.accept() def clear(self): with open(self.logfilename, 'w'): pass self.readlog() self.update() def update(self): prev_cur_pos = self.dlgUI.logPTE.verticalScrollBar().value() self.dlgUI.logPTE.setPlainText(self.logtext) if QApplication.activeWindow() != self.qtDlg: self.dlgUI.logPTE.moveCursor(QTextCursor.End) self.dlgUI.logPTE.ensureCursorVisible() else: self.dlgUI.logPTE.moveCursor(QTextCursor.End) new_cur_pos = self.dlgUI.logPTE.verticalScrollBar().setValue(prev_cur_pos)
class OperatorViewWindow(QDialog): def __init__(self, *args, **kwargs): super(OperatorViewWindow, self).__init__(*args) warning = kwargs.get('warn', [0,0,0]) failure = kwargs.get('fail', [0,0,0]) tail = kwargs.get('tail', 10) remove_old = kwargs.get('remove_old', False) descriptions = kwargs.get('descriptions', [None] * 3) self.timer = None self.ui = form_class() self.ui.setupUi(self) self.status_bar = QStatusBar(self) self.ui.windowLayout.addWidget(self.status_bar) self.engine = kwargs['callback'] self.graphs = [DynamicRiskCanvas(self, coordinate=i + 1, warning=warning[i], failure=failure[i], tail=tail, remove_old=remove_old, description=descriptions[i]) for i in range(3)] for graph in self.graphs: self.ui.y_layout.addWidget(graph) def initial_graphics_fill(self, real_values, predicted_values, risk_values, time_ticks): for i, graph in enumerate(self.graphs): graph.compute_initial_figure(real_values.T[i], predicted_values[i], risk_values[i], time_ticks) def update_graphics(self, real_value, predicted_values, risk_values, forecast_ticks): for i, graph in enumerate(self.graphs): graph.update_figure(real_value[i], predicted_values[i], risk_values[i], forecast_ticks) def closeEvent(self, event): if self.timer and self.timer.isActive(): self.timer.stop() self.timer.disconnect() self.timer.deleteLater() super(QDialog, self).closeEvent(event) @pyqtSlot() def manipulate_timer(self): if not self.timer: self.ui.start_button.setText('Pause') self.timer = QTimer(self) self.timer.timeout.connect(self.execute_iteration) self.timer.start(50) elif self.timer.isActive(): self.ui.start_button.setText('Continue') self.timer.stop() else: self.ui.start_button.setText('Pause') self.timer.start() @pyqtSlot() def execute_iteration(self): self.engine.launch()
class AutoSave: def __init__(self, application): self._application = application self._application.getPreferences().preferenceChanged.connect(self._triggerTimer) self._global_stack = None self._application.getPreferences().addPreference("cura/autosave_delay", 1000 * 10) self._change_timer = QTimer() self._change_timer.setInterval(int(self._application.getPreferences().getValue("cura/autosave_delay"))) self._change_timer.setSingleShot(True) self._enabled = True self._saving = False def initialize(self): # only initialise if the application is created and has started self._change_timer.timeout.connect(self._onTimeout) self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() self._triggerTimer() def _triggerTimer(self, *args): if not self._saving: self._change_timer.start() def setEnabled(self, enabled: bool) -> None: self._enabled = enabled if self._enabled: self._change_timer.start() else: self._change_timer.stop() def _onGlobalStackChanged(self): if self._global_stack: self._global_stack.propertyChanged.disconnect(self._triggerTimer) self._global_stack.containersChanged.disconnect(self._triggerTimer) self._global_stack = self._application.getGlobalContainerStack() if self._global_stack: self._global_stack.propertyChanged.connect(self._triggerTimer) self._global_stack.containersChanged.connect(self._triggerTimer) def _onTimeout(self): self._saving = True # To prevent the save process from triggering another autosave. Logger.log("d", "Autosaving preferences, instances and profiles") self._application.saveSettings() self._saving = False
class StateSetting(Setting): """Stores the last state of application. The state after start-up is determined programmatically; the value set during configuration loading will be ignored. """ name = 'state_on' state = None require = { ModeSettings, EnableNightMode } @property def value(self): if self.mode_settings.mode == 'manual': return self.enable_night_mode.value else: return self.mode_settings.is_active @value.setter def value(self, value): pass def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # check the state every 60 seconds # (maybe a bit suboptimal, but the most reliable) from aqt import mw as main_window self.timer = QTimer(main_window) self.timer.setInterval(60 * 100) # 1000 milliseconds self.timer.timeout.connect(self.maybe_enable_maybe_disable) def on_load(self): if self.value: self.app.on() self.update_state() self.timer.start() def on_save(self): self.timer.stop() def maybe_enable_maybe_disable(self): if self.value != self.state: self.app.refresh() self.update_state() def update_state(self): self.state = self.value
class gameWidget(QWidget): updateScore = pyqtSignal(int, name='updateScore') updateCurrentScore = pyqtSignal(int, name='updateCurrentScore') started = pyqtSignal() finished = pyqtSignal() def __init__(self, cfg): super(gameWidget, self).__init__() self.cfg = cfg self.totalScore = 0 self.curScore = 0 self.questionsAsked = 0 self.runState = 0 self.correct = 0 self.wrong = 0 self.timer = QTimer() self.timer.setInterval(500) self.timer.setSingleShot(False) self.startTime = time.time() self.timer.timeout.connect(self.updateTimer) def updateTimer(self): dt = time.time() - self.startTime if self.cfg['level'] < 4: dt = 0 if dt > self.cfg['maxTime']: self.timer.stop() curScore = max(self.cfg['minPoints'], int((self.cfg['maxTime']-dt)*self.cfg['pointsPerSec'])) self.updateCurrentScore.emit(curScore) def startGame(self): raise RuntimeError('Game objects must implement start') def start(self): startScore = self.cfg['pointsPerSec']*self.cfg['maxTime'] self.updateCurrentScore.emit(startScore) self.started.emit() self.startGame() def finishGame(self): raise RuntimeError('Game objects must implement finishGame') def finish(self): self.timer.stop() self.finished.emit() self.finishGame()
class VariableManager(plugin.DocumentPlugin): """Caches variables in the document and monitors for changes. The changed() Signal is emitted some time after the list of variables has been changed. It is recommended to not change the document itself in response to this signal. """ changed = signals.Signal() # without argument def __init__(self, doc): self._updateTimer = QTimer(singleShot=True, timeout=self.slotTimeout) self._variables = self.readVariables() if doc.__class__ == document.EditorDocument: doc.contentsChange.connect(self.slotContentsChange) doc.closed.connect(self._updateTimer.stop) # just to be sure def slotTimeout(self): variables = self.readVariables() if variables != self._variables: self._variables = variables self.changed() def slotContentsChange(self, position, removed, added): """Called if the document changes.""" if (self.document().findBlock(position).blockNumber() < _LINES or self.document().findBlock(position + added).blockNumber() > self.document().blockCount() - _LINES): self._updateTimer.start(500) def variables(self): """Returns the document variables (cached) as a dictionary. This method is recommended.""" if self._updateTimer.isActive(): # an update is pending, force it self._updateTimer.stop() self.slotTimeout() return self._variables def readVariables(self): """Reads the variables from the document and returns a dictionary. Internal.""" count = self.document().blockCount() blocks = [self.document().firstBlock()] if count > _LINES * 2: blocks.append(self.document().findBlockByNumber(count - _LINES)) count = _LINES def lines(block): for i in range(count): yield block.text() block = block.next() variables = {} for block in blocks: variables.update(m.group(1, 2) for n, m in positions(lines(block))) return variables
class EvMessageBox(QMessageBox): """ QMessageBox with timer. Parameters ---------- text : string Text of message. title : string Title of message window. wicon : QIcon object Icon of message window. icon : QMessageBox.Icon int Icon of message body. timeout : int Time for message has being shown. Useful attributes ----------------- timer : QTimer object Timer attached to message. """ def __init__(self, text, title, wicon, icon, timeout): super().__init__() self.timeout = timeout self.setText(text) self.setWindowTitle(title) self.setWindowIcon(wicon) self.setIcon(icon) self.addButton(QPushButton(conf.lang.REPEAT.format( parseSeconds(conf.tdelta))), QMessageBox.YesRole) self.addButton(QPushButton(conf.lang.CLOSE), QMessageBox.NoRole) self.setWindowFlags(Qt.WindowStaysOnTopHint) # self.setTextFormat(Qt.RichText) self.timer = QTimer() self.timer.timeout.connect(self.timerTick) def showEvent(self, event): """ Start timer on message showEvent. """ self.currentTime = 0 self.timer.start(1000) def timerTick(self): """ Done message on timeout. """ self.currentTime += 1 if self.currentTime >= self.timeout: self.timer.stop() self.done(-1)
class CountDownWidget(QWidget): ''' classdocs ''' def __init__(self, parent=None): ''' Constructor ''' QWidget.__init__(self, parent=parent) self.interval = 10 self.setup_ui() ''' 実際の初期化関数 ''' def setup_ui(self): self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.timer = QTimer(parent=self) self.timer.setInterval(self.interval) self.timer.timeout.connect(self.do_countdown) self.lcd_number = QLCDNumber(parent=self) self.lcd_number.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.lcd_number.setFrameStyle(QFrame.NoFrame) self.lcd_number.setSegmentStyle(QLCDNumber.Flat) self.lcd_number.setDigitCount(6) layout = QVBoxLayout() layout.addWidget(self.lcd_number) self.setLayout(layout) self.reset_count() def update_display(self): self.lcd_number.display("%6.2f" % (self.count / 100)) self.lcd_number.update() def do_countdown(self): self.count -= 1 self.update_display() if self.count <= 0: self.stopcountdown() def start_countdown(self): if self.count > 0: self.timer.start() def stop_countdown(self): self.timer.stop() def reset_count(self): self.count = 18000 self.update_display()
class MemUsageDialog(QDialog): def __init__(self, parent=None, update=True): QDialog.__init__(self, parent=parent) layout = QVBoxLayout() self.tree = QTreeWidget() layout.addWidget(self.tree) self.setLayout(layout) self._mgr = CacheMemoryManager() self._tracked_caches = {} # tree setup code self.tree.setHeaderLabels( ["cache", "memory", "roi", "dtype", "type", "info", "id"]) self._idIndex = self.tree.columnCount() - 1 self.tree.setColumnHidden(self._idIndex, True) self.tree.setSortingEnabled(True) self.tree.clear() self._root = TreeNode() # refresh every x seconds (see showEvent()) self.timer = QTimer(self) if update: self.timer.timeout.connect(self._updateReport) def _updateReport(self): # we keep track of dirty reports so we just have to update the tree # instead of reconstructing it reports = [] for c in self._mgr.getFirstClassCaches(): r = MemInfoNode() c.generateReport(r) reports.append(r) self._root.handleChildrenReports( reports, root=self.tree.invisibleRootItem()) def hideEvent(self, event): self.timer.stop() def showEvent(self, show): # update once so we don't have to wait for initial report self._updateReport() # update every 5 sec. self.timer.start(5*1000)
class WorkThread(QObject): trigger = pyqtSignal() def __init__(self): super(WorkThread, self).__init__() self.timer = QTimer(self) self.timer.timeout.connect(self.timeOut) def beginRun(self, in_parameters): self.p = Process(target=mainProcess, kwargs=in_parameters) self.p.start() self.timer.start(1000) def timeOut(self): if not self.p.is_alive(): # 当前子进程完成后 self.trigger.emit() # 发出处理完成信息 self.timer.stop() # 并停止计时器,否则在同一进程下再次运行会导致过早判断子进程的状态
class EventRequestManager(QNetworkAccessManager): """ The EventRequestManager class handles the events connection over which important events in Tribler are pushed. """ received_search_result_channel = pyqtSignal(object) received_search_result_torrent = pyqtSignal(object) tribler_started = pyqtSignal() upgrader_tick = pyqtSignal(str) upgrader_started = pyqtSignal() upgrader_finished = pyqtSignal() new_version_available = pyqtSignal(str) discovered_channel = pyqtSignal(object) discovered_torrent = pyqtSignal(object) torrent_finished = pyqtSignal(object) received_market_ask = pyqtSignal(object) received_market_bid = pyqtSignal(object) expired_market_ask = pyqtSignal(object) expired_market_bid = pyqtSignal(object) market_transaction_complete = pyqtSignal(object) market_payment_received = pyqtSignal(object) market_payment_sent = pyqtSignal(object) market_iom_input_required = pyqtSignal(object) events_started = pyqtSignal(object) low_storage_signal = pyqtSignal(object) def __init__(self, api_port): QNetworkAccessManager.__init__(self) url = QUrl("http://localhost:%d/events" % api_port) self.request = QNetworkRequest(url) self.failed_attempts = 0 self.connect_timer = QTimer() self.current_event_string = "" self.tribler_version = "Unknown" self.reply = None self.emitted_tribler_started = False # We should only emit tribler_started once self.shutting_down = False self._logger = logging.getLogger('TriblerGUI') def on_error(self, error, reschedule_on_err): self._logger.info("Got Tribler core error: %s" % error) if error == QNetworkReply.ConnectionRefusedError: if self.failed_attempts == 40: raise RuntimeError( "Could not connect with the Tribler Core within 20 seconds" ) self.failed_attempts += 1 if reschedule_on_err: # Reschedule an attempt self.connect_timer = QTimer() self.connect_timer.setSingleShot(True) self.connect_timer.timeout.connect(self.connect) self.connect_timer.start(500) def on_read_data(self): if self.receivers(self.finished) == 0: self.finished.connect(lambda reply: self.on_finished()) self.connect_timer.stop() data = self.reply.readAll() self.current_event_string += data if len(self.current_event_string ) > 0 and self.current_event_string[-1] == '\n': for event in self.current_event_string.split('\n'): if len(event) == 0: continue json_dict = json.loads(str(event)) received_events.insert(0, (json_dict, time.time())) if len(received_events ) > 100: # Only buffer the last 100 events received_events.pop() if json_dict["type"] == "search_result_channel": self.received_search_result_channel.emit( json_dict["event"]["result"]) elif json_dict["type"] == "search_result_torrent": self.received_search_result_torrent.emit( json_dict["event"]["result"]) elif json_dict[ "type"] == "tribler_started" and not self.emitted_tribler_started: self.tribler_started.emit() self.emitted_tribler_started = True elif json_dict["type"] == "new_version_available": self.new_version_available.emit( json_dict["event"]["version"]) elif json_dict["type"] == "upgrader_started": self.upgrader_started.emit() elif json_dict["type"] == "upgrader_finished": self.upgrader_finished.emit() elif json_dict["type"] == "upgrader_tick": self.upgrader_tick.emit(json_dict["event"]["text"]) elif json_dict["type"] == "channel_discovered": self.discovered_channel.emit(json_dict["event"]) elif json_dict["type"] == "torrent_discovered": self.discovered_torrent.emit(json_dict["event"]) elif json_dict["type"] == "events_start": self.events_started.emit(json_dict["event"]) self.tribler_version = json_dict["event"]["version"] if json_dict["event"][ "tribler_started"] and not self.emitted_tribler_started: self.tribler_started.emit() self.emitted_tribler_started = True elif json_dict["type"] == "torrent_finished": self.torrent_finished.emit(json_dict["event"]) elif json_dict["type"] == "market_ask": self.received_market_ask.emit(json_dict["event"]) elif json_dict["type"] == "market_bid": self.received_market_bid.emit(json_dict["event"]) elif json_dict["type"] == "market_ask_timeout": self.expired_market_ask.emit(json_dict["event"]) elif json_dict["type"] == "market_bid_timeout": self.expired_market_bid.emit(json_dict["event"]) elif json_dict["type"] == "market_transaction_complete": self.market_transaction_complete.emit(json_dict["event"]) elif json_dict["type"] == "market_payment_received": self.market_payment_received.emit(json_dict["event"]) elif json_dict["type"] == "market_payment_sent": self.market_payment_sent.emit(json_dict["event"]) elif json_dict["type"] == "market_iom_input_required": self.market_iom_input_required.emit(json_dict["event"]) elif json_dict["type"] == "signal_low_space": self.low_storage_signal.emit(json_dict["event"]) elif json_dict["type"] == "tribler_exception": raise RuntimeError(json_dict["event"]["text"]) self.current_event_string = "" def on_finished(self): """ Somehow, the events connection dropped. Try to reconnect. """ if self.shutting_down: return self._logger.warning( "Events connection dropped, attempting to reconnect") self.failed_attempts = 0 self.connect_timer = QTimer() self.connect_timer.setSingleShot(True) self.connect_timer.timeout.connect(self.connect) self.connect_timer.start(500) def connect(self, reschedule_on_err=True): self._logger.info("Will connect to events endpoint") self.reply = self.get(self.request) self.reply.readyRead.connect(self.on_read_data) self.reply.error.connect(lambda error: self.on_error( error, reschedule_on_err=reschedule_on_err))
class Player(QMainWindow): """A simple Media Player using VLC and Qt """ def __init__(self, master=None): QMainWindow.__init__(self, master) self.setWindowTitle("Video Annotation Tool") # creating a basic vlc instance self.instance = vlc.Instance() # creating an empty vlc media player self.mediaplayer = self.instance.media_player_new() self.createUI() self.isPaused = False self.setMouseTracking(True) self.size = None def createUI(self): """Set up the user interface, signals & slots """ self.widget = QWidget(self) self.setCentralWidget(self.widget) # In this widget, the video will be drawn if sys.platform == "darwin": # for MacOS from PyQt5.QtWidgets import QMacCocoaViewContainer self.videoframe = QMacCocoaViewContainer(0) else: self.videoframe = QFrame() self.palette = self.videoframe.palette() self.palette.setColor(QPalette.Window, QColor(0, 0, 0)) self.videoframe.setPalette(self.palette) self.videoframe.setAutoFillBackground(True) self.positionslider = QSlider(Qt.Horizontal, self) self.positionslider.setToolTip("Position") self.positionslider.setMaximum(1000) self.positionslider.sliderMoved.connect(self.setPosition) self.hbuttonbox = QHBoxLayout() self.playbutton = QPushButton("Play") self.hbuttonbox.addWidget(self.playbutton) self.playbutton.clicked.connect(self.PlayPause) self.stopbutton = QPushButton("Stop") self.hbuttonbox.addWidget(self.stopbutton) self.stopbutton.clicked.connect(self.Stop) self.time_label = QLabel("<center>0</center>") self.coordinates = QLabel("<center>x, y</center>") self.hbuttonbox.addWidget(self.time_label) self.hbuttonbox.addWidget(self.coordinates) self.hbuttonbox.addStretch(1) self.volumeslider = QSlider(Qt.Horizontal, self) self.volumeslider.setMaximum(100) self.volumeslider.setValue(self.mediaplayer.audio_get_volume()) self.volumeslider.setToolTip("Volume") self.hbuttonbox.addWidget(self.volumeslider) self.volumeslider.valueChanged.connect(self.setVolume) self.vboxlayout = QVBoxLayout() self.vboxlayout.addWidget(self.videoframe) self.vboxlayout.addWidget(self.positionslider) self.vboxlayout.addLayout(self.hbuttonbox) self.widget.setLayout(self.vboxlayout) open = QAction("&Open", self) open.triggered.connect(self.OpenFile) exit = QAction("&Exit", self) exit.triggered.connect(sys.exit) menubar = self.menuBar() filemenu = menubar.addMenu("&File") filemenu.addAction(open) filemenu.addSeparator() filemenu.addAction(exit) self.timer = QTimer(self) self.timer.setInterval(100) self.timer.timeout.connect(self.updateUI) def PlayPause(self): """Toggle play/pause status """ if self.mediaplayer.is_playing(): self.mediaplayer.pause() self.playbutton.setText("Play") self.isPaused = True else: if self.mediaplayer.play() == -1: self.OpenFile() return self.mediaplayer.play() self.playbutton.setText("Pause") self.timer.start() self.isPaused = False def Stop(self): """Stop player """ self.mediaplayer.stop() self.playbutton.setText("Play") def OpenFile(self, filename=None): """Open a media file in a MediaPlayer """ if filename is None: filename = QFileDialog.getOpenFileName(self, "Open File", os.path.expanduser('~'))[0] if not filename: filename = QFileDialog.getOpenFileName(self, "Open File", os.path.expanduser('~'))[0] #return # create the media if sys.version < '3': filename = unicode(filename) self.media = self.instance.media_new(filename) # put the media in the media player self.mediaplayer.set_media(self.media) # parse the metadata of the file self.media.parse() # set the title of the track as window title self.setWindowTitle(self.media.get_meta(0)) # the media player has to be 'connected' to the QFrame # (otherwise a video would be displayed in it's own window) # this is platform specific! # you have to give the id of the QFrame (or similar object) to # vlc, different platforms have different functions for this if sys.platform.startswith('linux'): # for Linux using the X Server self.mediaplayer.set_xwindow(self.videoframe.winId()) elif sys.platform == "win32": # for Windows self.mediaplayer.set_hwnd(self.videoframe.winId()) elif sys.platform == "darwin": # for MacOS self.mediaplayer.set_nsobject(int(self.videoframe.winId())) self.size = self.mediaplayer.video_get_size() self.PlayPause() def setVolume(self, Volume): """Set the volume """ self.mediaplayer.audio_set_volume(Volume) def setPosition(self, position): """Set the position """ # setting the position to where the slider was dragged self.mediaplayer.set_position(position / 1000.0) # the vlc MediaPlayer needs a float value between 0 and 1, Qt # uses integer variables, so you need a factor; the higher the # factor, the more precise are the results # (1000 should be enough) def updateUI(self): """updates the user interface""" # setting the slider to the desired position self.positionslider.setValue(self.mediaplayer.get_position() * 1000) self.time_label.setText( f'Time {str(self.mediaplayer.get_time() / 1000)} seconds') #self.size = self.mediaplayer.video_get_size() if not self.mediaplayer.is_playing(): # no need to call this function if nothing is played self.timer.stop() if not self.isPaused: # after the video finished, the play button stills shows # "Pause", not the desired behavior of a media player # this will fix it self.Stop() def get_coordinates(self): return self.mediaplayer.video_get_cursor() def mouseMoveEvent(self, event): print(self.mediaplayer.video_get_cursor()) if isinstance(self.size, tuple): video_width, video_height = self.size else: video_width, video_height = 0, 0 # self.coordinates.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(distance_from_center)) #self.coordinates.setText('Coordinates: ( %d : %d )' % (event.x() - 9, event.y() - 30) + "Distance from center: ") self.coordinates.setText( f'Coordinates: {self.get_coordinates()[0]}, {self.get_coordinates()[1]}. Video size {video_height} {video_width}' ) self.pos = event.pos() self.update()
class Ycm(QObject, CategoryMixin): """YCMD instance control""" YCMD_CMD = ['ycmd'] """Base ycmd command. Useful if ycmd is not in `PATH` or set permanent arguments """ IDLE_SUICIDE = 120 """Maximum time after which ycmd should quit if it has received no requests. A periodic ping is sent by `Ycm` objects. """ CHECK_REPLY_SIGNATURE = True TIMEOUT = 10 def __init__(self, **kwargs): super(Ycm, self).__init__(**kwargs) self.addr = None """Address of the ycmd server.""" self.port = 0 """TCP port of the ycmd server.""" self._ready = False self.secret = '' self.config = {} self.proc = QProcess() self.proc.started.connect(self.procStarted) self.proc.errorOccurred.connect(self.procError) self.proc.finished.connect(self.procFinished) self.pingTimer = QTimer(self) self.pingTimer.timeout.connect(self.ping) self.network = QNetworkAccessManager() qApp().aboutToQuit.connect(self.stop) self.addCategory('ycm_control') def makeConfig(self): self.secret = generate_key() self.config['hmac_secret'] = b64encode(self.secret).decode('ascii') fd, path = tempfile.mkstemp() with open(path, 'w') as fd: fd.write(json.dumps(self.config)) fd.flush() return path def checkReply(self, reply): """Check the ycmd reply is a success. Checks the `reply` has a HTTP 200 status code and the signature is valid. In case of error, raises a :any:`ServerError`. :type reply: QNetworkReply """ reply.content = bytes(reply.readAll()) if reply.error(): raise ServerError(reply.error() + 1000, reply.errorString(), reply.content) status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if status_code != 200: data = reply.content.decode('utf-8') try: data = json.loads(data) except (ValueError, JSONDecodeError): LOGGER.info('ycmd replied non-json body: %r', data) raise ServerError(status_code, data) if not self.CHECK_REPLY_SIGNATURE: return actual = b64decode(bytes(reply.rawHeader(HMAC_HEADER))) expected = self._hmacDigest(reply.content) if not hmac.compare_digest(expected, actual): raise RuntimeError('Server signature did not match') def _jsonReply(self, reply): body = reply.content.decode('utf-8') return json.loads(body) def _hmacDigest(self, msg): return hmac.new(self.secret, msg, hashlib.sha256).digest() def _sign(self, verb, path, body=b''): digests = [self._hmacDigest(part) for part in [verb, path, body]] return self._hmacDigest(b''.join(digests)) def _doGet(self, path): url = urlunsplit(('http', self.addr, path, '', '')) sig = self._sign(b'GET', path.encode('utf-8'), b'') headers = {HMAC_HEADER: b64encode(sig)} request = QNetworkRequest(QUrl(url)) for hname in headers: request.setRawHeader(hname, headers[hname]) LOGGER_REQUESTS.debug('GET %r', url) reply = self.network.get(request) return reply def _doPost(self, path, **params): url = urlunsplit(('http', self.addr, path, '', '')) body = json.dumps(params).encode('utf-8') sig = self._sign(b'POST', path.encode('utf-8'), body) headers = { HMAC_HEADER: b64encode(sig), b'Content-Type': b'application/json' } request = QNetworkRequest(QUrl(url)) for hname in headers: request.setRawHeader(hname, headers[hname]) LOGGER_REQUESTS.debug('POST %r with data %r', url, body) reply = self.network.post(request, body) return reply def ping(self): def handleReply(): self.checkReply(reply) if not self._ready: self._ready = True self.pingTimer.start(60000) self.ready.emit() reply = self._doGet('/healthy') reply.finished.connect(handleReply) reply.finished.connect(reply.deleteLater) def start(self): if not self.port: self.port = generate_port() self.addr = 'localhost:%s' % self.port path = self.makeConfig() _, outlogpath = tempfile.mkstemp(prefix='eye-ycm', suffix='.out.log') _, errlogpath = tempfile.mkstemp(prefix='eye-ycm', suffix='.err.log') LOGGER.info('ycmd will log to %r and %r', outlogpath, errlogpath) cmd = (self.YCMD_CMD + [ '--idle_suicide_seconds', str(self.IDLE_SUICIDE), '--port', str(self.port), '--options_file', path, '--stdout', outlogpath, '--stderr', errlogpath, ]) LOGGER.debug('will run %r', cmd) self.proc.start(cmd[0], cmd[1:]) self._ready = False @Slot() def stop(self, wait=0.2): if self.proc.state() == QProcess.NotRunning: return self.proc.terminate() if self.proc.state() == QProcess.NotRunning: return time.sleep(wait) self.proc.kill() def isRunning(self): return self.proc.state() == QProcess.Running def connectTo(self, addr): self.addr = addr self._ready = False self.pingTimer.start(1000) @Slot() def procStarted(self): LOGGER.debug('daemon has started') self.pingTimer.start(1000) @Slot(int, QProcess.ExitStatus) def procFinished(self, code, status): LOGGER.info('daemon has exited with status %r and code %r', status, code) self.pingTimer.stop() self._ready = False @Slot(QProcess.ProcessError) def procError(self, error): LOGGER.warning('daemon failed to start (%r): %s', error, self.errorString()) ready = Signal() def _commonPostDict(self, filepath, filetype, contents, line=1, column=1): d = { 'filepath': filepath, 'filetype': filetype, 'file_data': { filepath: { 'filetypes': [filetype], 'contents': contents } }, 'line_num': line, 'column_num': column, } return d def _postSimpleRequest(self, urlpath, filepath, filetype, contents, **kwargs): d = self._commonPostDict(filepath, filetype, contents) d.update(**kwargs) return self._doPost(urlpath, **d) def acceptExtraConf(self, filepath, filetype, contents): reply = self._postSimpleRequest('/load_extra_conf_file', filepath, filetype, contents) reply.finished.connect(reply.deleteLater) def rejectExtraConf(self, filepath, filetype, contents): reply = self._postSimpleRequest('/ignore_extra_conf_file', filepath, filetype, contents, _ignore_body=True) reply.finished.connect(reply.deleteLater) def sendParse(self, filepath, filetype, contents, retry_extra=True): d = {'event_name': 'FileReadyToParse'} reply = self._postSimpleRequest('/event_notification', filepath, filetype, contents, **d) def handleReply(): try: self.checkReply(reply) except ServerError as exc: excdata = exc.args[1] if (isinstance(excdata, dict) and 'exception' in excdata and excdata['exception']['TYPE'] == 'UnknownExtraConf' and retry_extra): confpath = excdata['exception']['extra_conf_file'] LOGGER.info( 'ycmd encountered %r and wonders if it should be loaded', confpath) accepted = sendIntent(None, 'queryExtraConf', conf=confpath) if accepted: LOGGER.info('extra conf %r will be loaded', confpath) self.acceptExtraConf(confpath, filetype, contents) else: LOGGER.info('extra conf %r will be rejected', confpath) self.rejectExtraConf(confpath, filetype, contents) return self.sendParse(filepath, filetype, contents, retry_extra=False) raise reply.finished.connect(handleReply) reply.finished.connect(reply.deleteLater) def querySubcommandsList(self, filepath, filetype, contents, line, col): return self._postSimpleRequest('/defined_subcommands', filepath, filetype, contents) def querySubcommand(self, filepath, filetype, contents, line, col, *args): d = { 'command_arguments': list(args), 'line_num': line, 'column_num': col, } return self._postSimpleRequest('/run_completer_command', filepath, filetype, contents, **d) def queryCompletions(self, filepath, filetype, contents, line, col): d = { 'line_num': line, 'column_num': col, } return self._postSimpleRequest('/completions', filepath, filetype, contents, **d) if 0: def queryDiagnostic(self, filepath, filetype, contents, line, col): return self._postSimpleRequest('/detailed_diagnostic', filepath, filetype, contents) def queryDebug(self, filepath, filetype, contents, line, col): return self._postSimpleRequest('/debug_info', filepath, filetype, contents)
class QVTKRenderWindowInteractor(QVTKRWIBaseClass): """ A QVTKRenderWindowInteractor for Python and Qt. Uses a vtkGenericRenderWindowInteractor to handle the interactions. Use GetRenderWindow() to get the vtkRenderWindow. Create with the keyword stereo=1 in order to generate a stereo-capable window. The user interface is summarized in vtkInteractorStyle.h: - Keypress j / Keypress t: toggle between joystick (position sensitive) and trackball (motion sensitive) styles. In joystick style, motion occurs continuously as long as a mouse button is pressed. In trackball style, motion occurs when the mouse button is pressed and the mouse pointer moves. - Keypress c / Keypress o: toggle between camera and object (actor) modes. In camera mode, mouse events affect the camera position and focal point. In object mode, mouse events affect the actor that is under the mouse pointer. - Button 1: rotate the camera around its focal point (if camera mode) or rotate the actor around its origin (if actor mode). The rotation is in the direction defined from the center of the renderer's viewport towards the mouse position. In joystick mode, the magnitude of the rotation is determined by the distance the mouse is from the center of the render window. - Button 2: pan the camera (if camera mode) or translate the actor (if object mode). In joystick mode, the direction of pan or translation is from the center of the viewport towards the mouse position. In trackball mode, the direction of motion is the direction the mouse moves. (Note: with 2-button mice, pan is defined as <Shift>-Button 1.) - Button 3: zoom the camera (if camera mode) or scale the actor (if object mode). Zoom in/increase scale if the mouse position is in the top half of the viewport; zoom out/decrease scale if the mouse position is in the bottom half. In joystick mode, the amount of zoom is controlled by the distance of the mouse pointer from the horizontal centerline of the window. - Keypress 3: toggle the render window into and out of stereo mode. By default, red-blue stereo pairs are created. Some systems support Crystal Eyes LCD stereo glasses; you have to invoke SetStereoTypeToCrystalEyes() on the rendering window. Note: to use stereo you also need to pass a stereo=1 keyword argument to the constructor. - Keypress e: exit the application. - Keypress f: fly to the picked point - Keypress p: perform a pick operation. The render window interactor has an internal instance of vtkCellPicker that it uses to pick. - Keypress r: reset the camera view along the current view direction. Centers the actors and moves the camera so that all actors are visible. - Keypress s: modify the representation of all actors so that they are surfaces. - Keypress u: invoke the user-defined function. Typically, this keypress will bring up an interactor that you can type commands in. - Keypress w: modify the representation of all actors so that they are wireframe. """ # Map between VTK and Qt cursors. _CURSOR_MAP = { 0: Qt.ArrowCursor, # VTK_CURSOR_DEFAULT 1: Qt.ArrowCursor, # VTK_CURSOR_ARROW 2: Qt.SizeBDiagCursor, # VTK_CURSOR_SIZENE 3: Qt.SizeFDiagCursor, # VTK_CURSOR_SIZENWSE 4: Qt.SizeBDiagCursor, # VTK_CURSOR_SIZESW 5: Qt.SizeFDiagCursor, # VTK_CURSOR_SIZESE 6: Qt.SizeVerCursor, # VTK_CURSOR_SIZENS 7: Qt.SizeHorCursor, # VTK_CURSOR_SIZEWE 8: Qt.SizeAllCursor, # VTK_CURSOR_SIZEALL 9: Qt.PointingHandCursor, # VTK_CURSOR_HAND 10: Qt.CrossCursor, # VTK_CURSOR_CROSSHAIR } def __init__(self, parent=None, **kw): # the current button self._ActiveButton = Qt.NoButton # private attributes self.__saveX = 0 self.__saveY = 0 self.__saveModifiers = Qt.NoModifier self.__saveButtons = Qt.NoButton self.__wheelDelta = 0 # do special handling of some keywords: # stereo, rw try: stereo = bool(kw['stereo']) except KeyError: stereo = False try: rw = kw['rw'] except KeyError: rw = None # create base qt-level widget if QVTKRWIBase == "QWidget": if "wflags" in kw: wflags = kw['wflags'] else: wflags = Qt.WindowFlags() QWidget.__init__(self, parent, wflags | Qt.MSWindowsOwnDC) elif QVTKRWIBase == "QGLWidget": QGLWidget.__init__(self, parent) if rw: # user-supplied render window self._RenderWindow = rw else: self._RenderWindow = vtk.vtkRenderWindow() WId = self.winId() # Python2 if type(WId).__name__ == 'PyCObject': from ctypes import pythonapi, c_void_p, py_object pythonapi.PyCObject_AsVoidPtr.restype = c_void_p pythonapi.PyCObject_AsVoidPtr.argtypes = [py_object] WId = pythonapi.PyCObject_AsVoidPtr(WId) # Python3 elif type(WId).__name__ == 'PyCapsule': from ctypes import pythonapi, c_void_p, py_object, c_char_p pythonapi.PyCapsule_GetName.restype = c_char_p pythonapi.PyCapsule_GetName.argtypes = [py_object] name = pythonapi.PyCapsule_GetName(WId) pythonapi.PyCapsule_GetPointer.restype = c_void_p pythonapi.PyCapsule_GetPointer.argtypes = [py_object, c_char_p] WId = pythonapi.PyCapsule_GetPointer(WId, name) self._RenderWindow.SetWindowInfo(str(int(WId))) if stereo: # stereo mode self._RenderWindow.StereoCapableWindowOn() self._RenderWindow.SetStereoTypeToCrystalEyes() try: self._Iren = kw['iren'] except KeyError: self._Iren = vtk.vtkGenericRenderWindowInteractor() self._Iren.SetRenderWindow(self._RenderWindow) # do all the necessary qt setup self.setAttribute(Qt.WA_OpaquePaintEvent) self.setAttribute(Qt.WA_PaintOnScreen) self.setMouseTracking(True) # get all mouse events self.setFocusPolicy(Qt.WheelFocus) self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self._Timer = QTimer(self) self._Timer.timeout.connect(self.TimerEvent) self._Iren.AddObserver('CreateTimerEvent', self.CreateTimer) self._Iren.AddObserver('DestroyTimerEvent', self.DestroyTimer) self._Iren.GetRenderWindow().AddObserver('CursorChangedEvent', self.CursorChangedEvent) #Create a hidden child widget and connect its destroyed signal to its #parent ``Finalize`` slot. The hidden children will be destroyed before #its parent thus allowing cleanup of VTK elements. self._hidden = QWidget(self) self._hidden.hide() self._hidden.destroyed.connect(self.Finalize) def __getattr__(self, attr): """Makes the object behave like a vtkGenericRenderWindowInteractor""" if attr == '__vtk__': return lambda t=self._Iren: t elif hasattr(self._Iren, attr): return getattr(self._Iren, attr) else: raise AttributeError(self.__class__.__name__ + " has no attribute named " + attr) def Finalize(self): ''' Call internal cleanup method on VTK objects ''' self._RenderWindow.Finalize() def CreateTimer(self, obj, evt): self._Timer.start(10) def DestroyTimer(self, obj, evt): self._Timer.stop() return 1 def TimerEvent(self): self._Iren.TimerEvent() def CursorChangedEvent(self, obj, evt): """Called when the CursorChangedEvent fires on the render window.""" # This indirection is needed since when the event fires, the current # cursor is not yet set so we defer this by which time the current # cursor should have been set. QTimer.singleShot(0, self.ShowCursor) def HideCursor(self): """Hides the cursor.""" self.setCursor(Qt.BlankCursor) def ShowCursor(self): """Shows the cursor.""" vtk_cursor = self._Iren.GetRenderWindow().GetCurrentCursor() qt_cursor = self._CURSOR_MAP.get(vtk_cursor, Qt.ArrowCursor) self.setCursor(qt_cursor) def closeEvent(self, evt): self.Finalize() def sizeHint(self): return QSize(400, 400) def paintEngine(self): return None def paintEvent(self, ev): self._Iren.Render() def resizeEvent(self, ev): w = self.width() h = self.height() vtk.vtkRenderWindow.SetSize(self._RenderWindow, w, h) self._Iren.SetSize(w, h) self._Iren.ConfigureEvent() self.update() def _GetCtrlShift(self, ev): ctrl = shift = False if hasattr(ev, 'modifiers'): if ev.modifiers() & Qt.ShiftModifier: shift = True if ev.modifiers() & Qt.ControlModifier: ctrl = True else: if self.__saveModifiers & Qt.ShiftModifier: shift = True if self.__saveModifiers & Qt.ControlModifier: ctrl = True return ctrl, shift @staticmethod def _getPixelRatio(): if PyQtImpl == "PyQt5": # Source: https://stackoverflow.com/a/40053864/3388962 pos = QCursor.pos() for screen in QApplication.screens(): rect = screen.geometry() if rect.contains(pos): return screen.devicePixelRatio() # Should never happen, but try to find a good fallback. return QApplication.screens()[0].devicePixelRatio() else: # Qt4 seems not to provide any cross-platform means to get the # pixel ratio. return 1. def _setEventInformation(self, x, y, ctrl, shift, key, repeat=0, keysum=None): scale = self._getPixelRatio() self._Iren.SetEventInformation(int(round(x*scale)), int(round((self.height()-y-1)*scale)), ctrl, shift, key, repeat, keysum) def enterEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None) self._Iren.EnterEvent() def leaveEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None) self._Iren.LeaveEvent() def mousePressEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) repeat = 0 if ev.type() == QEvent.MouseButtonDblClick: repeat = 1 self._setEventInformation(ev.x(), ev.y(), ctrl, shift, chr(0), repeat, None) self._ActiveButton = ev.button() if self._ActiveButton == Qt.LeftButton: self._Iren.LeftButtonPressEvent() elif self._ActiveButton == Qt.RightButton: self._Iren.RightButtonPressEvent() elif self._ActiveButton == Qt.MidButton: self._Iren.MiddleButtonPressEvent() def mouseReleaseEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(ev.x(), ev.y(), ctrl, shift, chr(0), 0, None) if self._ActiveButton == Qt.LeftButton: self._Iren.LeftButtonReleaseEvent() elif self._ActiveButton == Qt.RightButton: self._Iren.RightButtonReleaseEvent() elif self._ActiveButton == Qt.MidButton: self._Iren.MiddleButtonReleaseEvent() def mouseMoveEvent(self, ev): self.__saveModifiers = ev.modifiers() self.__saveButtons = ev.buttons() self.__saveX = ev.x() self.__saveY = ev.y() ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(ev.x(), ev.y(), ctrl, shift, chr(0), 0, None) self._Iren.MouseMoveEvent() def keyPressEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) if ev.key() < 256: key = str(ev.text()) else: key = chr(0) keySym = _qt_key_to_key_sym(ev.key()) if shift and len(keySym) == 1 and keySym.isalpha(): keySym = keySym.upper() self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, key, 0, keySym) self._Iren.KeyPressEvent() self._Iren.CharEvent() def keyReleaseEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) if ev.key() < 256: key = chr(ev.key()) else: key = chr(0) self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, key, 0, None) self._Iren.KeyReleaseEvent() def wheelEvent(self, ev): if hasattr(ev, 'delta'): self.__wheelDelta += ev.delta() else: self.__wheelDelta += ev.angleDelta().y() if self.__wheelDelta >= 120: self._Iren.MouseWheelForwardEvent() self.__wheelDelta = 0 elif self.__wheelDelta <= -120: self._Iren.MouseWheelBackwardEvent() self.__wheelDelta = 0 def GetRenderWindow(self): return self._RenderWindow def Render(self): self.update()
class MainWindow(QtWidgets.QWidget): def __init__(self, event_list): QtWidgets.QWidget.__init__(self) self.left = 0 self.top = 0 self.width = args.width self.height = args.height self.setGeometry(self.left, self.top, self.width, self.height) self.setWindowTitle('一锤定音') # variables initialization self.all_event_list=event_list self.passed_days = 0 self.page = 0 self.start_time = datetime.strptime('20110301', '%Y%m%d') self.step=0 self.total_times = args.total_times self.tolerance = args.tolerance self.hit_times=0 self.cnt=0 # progress bar initialization self.pbar = QProgressBar(self) self.pbar_width = self.width * 0.95 self.pbar_height = 20 self.pbar_left = (self.width - self.pbar_width) / 2 self.pbar_top = self.height * 0.4 self.pbar_max = 250 self.pbar.setGeometry(self.pbar_left, self.pbar_top, self.pbar_width, self.pbar_height) self.pbar.setRange(0, self.pbar_max) # textedits and polylines initialization self.textedit_list=[] self.points_list = [] self.textedit_height = self.height * 0.3 self.textedit_num = 5 self.base_html = "<html><body><h2>h2_placeholder</h2><p>p_placeholder</p></body></html>" for i in range(self.textedit_num): textedit = QTextEdit(self) textedit.setGeometry(self.left + self.width / self.textedit_num * i, self.top, self.width / self.textedit_num, self.textedit_height) self.textedit_list.append(textedit) # self.nextPage() # push button initialization self.start_button = QPushButton('Start', self) self.start_button.setFocusPolicy(Qt.NoFocus) self.button_width = 100 self.button_height = 50 self.start_button_left = self.width*0.4 self.start_button_top = self.height * 0.5 self.start_button.setGeometry(self.start_button_left, self.start_button_top, self.button_width, self.button_height) self.reset_button = QPushButton('Reset', self) self.reset_button.setFocusPolicy(Qt.NoFocus) self.reset_button_left = self.start_button_left self.reset_button_top = self.height * 0.7 self.reset_button.setGeometry(self.reset_button_left, self.reset_button_top, self.button_width, self.button_height) # label initialization self.label1=QLabel(self) self.label_width = 400 self.label_height=50 self.label1_left=self.width*0.7 self.label1_top=self.height*0.5 self.label1.setGeometry(self.label1_left,self.label1_top,self.label_width,self.label_height) # self.label1.setText("你击中了{}次".format(self.cnt)) self.label2=QLabel(self) self.label2_left=self.width*0.7 self.label2_top=self.height*0.7 self.label2.setGeometry(self.label2_left,self.label2_top,self.label_width,self.label_height) # self.label2.setText("你还有{}次机会".format(self.total_times-self.hit_times)) self.label3 = QLabel(self) self.label3_left = self.width * 0.7 self.label3_top = self.height * 0.6 self.label3.setGeometry(self.label3_left, self.label3_top, self.label_width, self.label_height) # self.label3.setText("上次你距离最近的事件只差了{}天".format('x')) self.label4 = QLabel(self) self.label4_left = self.width * 0.1 self.label4_top = self.height * 0.5 self.label4.setGeometry(self.label4_left, self.label4_top, self.label_width, self.label_height * 2) self.label4.setText("11-14次准确停下时光机可以获得1个章\n\n15-18次准确停下时光机可以获得2个章\n\n19-25次准确停下时光机可以获得3个章") # timer initialization self.timer = QTimer() self.timeout_interval = args.timeout_interval # connect signals and slots self.start_button.clicked.connect(self.hit) self.reset_button.clicked.connect(self.reset) self.timer.timeout.connect(self.stepPlus) self.show() self.reset() # QMessageBox.information(self, "游戏规则", "本游戏你一共有{}次按钮机暂停时间的机会,当击中的日期和最近的事件日期相差{}天以内时,即算击中,总共有{}个事件,最后将根据击中事件数的多少发放奖励".format(self.total_times,self.tolerance,len(self.all_event_list)), QMessageBox.Ok) def keyPressEvent(self, QKeyEvent): if QKeyEvent.key()==Qt.Key_Return: self.hit() elif QKeyEvent.key()==Qt.Key_Escape: self.close() def paintEvent(self, QPaintEvent): painter = QPainter() painter.begin(self) pen = QPen(Qt.black, 2, Qt.SolidLine) painter.setPen(pen) for points in self.points_list: painter.drawPolyline(QPolygon(points)) painter.end() def stepPlus(self): if self.step>self.pbar_max: if self.page < 5: self.step = 0 self.passed_days += self.days_this_page self.nextPage() else: self.timer.stop() self.start_button.setText('Start') QMessageBox.information(self, "", "时光机回到了现在,本次游戏中你一共成功了{}次,学院的发展离不开大家的支持,让我们一起努力吧,也许有一天你也能成为这些进展的主角!".format(self.cnt), QMessageBox.Ok) self.reset() else: self.step = self.step + 1 self.pbar.setValue(self.step) now = self.start_time + timedelta(int(self.step / self.pbar_max * self.days_this_page + self.passed_days)) now_str = now.strftime("%Y{}%m{}%d{}").format('年', '月', '日') self.pbar.setFormat(now_str) def hit(self): if self.timer.isActive(): if self.hit_times < self.total_times: self.hit_times += 1 self.timer.stop() self.start_button.setText('Start') self.testHit(int(self.pbar.value() / self.pbar_max * self.days_this_page + self.passed_days)) self.updateLabel() if self.hit_times == self.total_times: QMessageBox.information(self, "", "时光机回到了现在,本次游戏中你一共成功了{}次,学院的发展离不开大家的支持,让我们一起努力吧,也许有一天你也能成为这些进展的主角!".format(self.cnt), QMessageBox.Ok) self.reset() else: self.timer.start(self.timeout_interval) self.start_button.setText('Stop') def reset(self): self.timer.stop() self.start_button.setText('Start') self.pbar.reset() self.step = 0 self.hit_times=0 self.cnt=0 self.page = 0 self.passed_days = 0 self.nextPage() self.updateLabel() self.update() QMessageBox.information(self, "时光穿梭机-游戏规则", "过去的十年里,我们学院取得了一些进展,现在让我们一起坐着时光机来回顾一下吧。。。本游戏你一共有{}次暂停时光机的机会,当时光机停下的日期和最近的事件日期相差{}天以内时,即算成功1次,总共有{}个事件,最后将根据成功次数发放奖励".format( self.total_times, self.tolerance, len(self.all_event_list)), QMessageBox.Ok) def testHit(self, value): diffs=[] # print((self.start_time+timedelta(value)).strftime('%Y%m%d')) for event in self.event_list: days = (event.event_time - self.start_time).days diffs.append(abs(value-days)) # print(diffs) if min(diffs) <= self.tolerance: self.label3.setText("上次你的时光机停在了正确的时间哦") self.cnt += 1 else: self.label3.setText("上次你的时光机距离最近的事件只相差了{}天".format(min(diffs))) def nextPage(self): self.event_list = self.all_event_list[self.page * 5:(self.page + 1) * 5] self.days_this_page = int(((self.event_list[4].event_time - self.start_time).days - self.passed_days) * 1.01) self.points_list = [] for i in range(len(self.event_list)): html = self.base_html.replace('h2_placeholder', datetime.strftime(self.event_list[i].event_time, "%Y{}%m{}%d{}").format('年', '月', '日')) html = html.replace('p_placeholder', self.event_list[i].event_name) self.textedit_list[i].setHtml(html) self.textedit_list[i].setReadOnly(True) days = (self.event_list[i].event_time - self.start_time).days - self.passed_days points = [] points.append( QPoint(self.left + self.width / self.textedit_num * (i + 0.5), self.top + self.textedit_height)) points.append(QPoint(self.left + self.width / self.textedit_num * (i + 0.5), self.pbar_top - (self.pbar_top - self.top - self.textedit_height) / (self.textedit_num + 1) * (i + 1))) points.append(QPoint(self.pbar_left + days / self.days_this_page * self.pbar_width, self.pbar_top - (self.pbar_top - self.top - self.textedit_height) / (self.textedit_num + 1) * (i + 1))) points.append(QPoint(self.pbar_left + days / self.days_this_page * self.pbar_width, self.pbar_top)) self.points_list.append(points) self.update() self.page += 1 def updateLabel(self): self.label1.setText("你已经{}次把时光机停在了正确的位置".format(self.cnt)) self.label2.setText("你还有{}次暂停时光机的机会哦".format(self.total_times - self.hit_times))
class InviteSenderDialog(QDialog): done = pyqtSignal(QWidget) closed = pyqtSignal(QWidget) def __init__(self, gateway, gui, folder_names=None): super().__init__() self.gateway = gateway self.gui = gui self.folder_names = folder_names self.folder_names_humanized = humanized_list(folder_names, "folders") self.settings = {} self.pending_invites = [] self.use_tor = self.gateway.use_tor self.setMinimumSize(500, 300) header_icon = QLabel(self) if self.folder_names: icon = QFileIconProvider().icon( QFileInfo( self.gateway.get_magic_folder_directory( self.folder_names[0] ) ) ) else: icon = QIcon(os.path.join(gateway.nodedir, "icon")) if not icon.availableSizes(): icon = QIcon(resource("tahoe-lafs.png")) header_icon.setPixmap(icon.pixmap(50, 50)) header_text = QLabel(self) if self.folder_names: header_text.setText(self.folder_names_humanized) else: header_text.setText(self.gateway.name) header_text.setFont(Font(18)) header_text.setAlignment(Qt.AlignCenter) header_layout = QGridLayout() header_layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 1 ) header_layout.addWidget(header_icon, 1, 2) header_layout.addWidget(header_text, 1, 3) header_layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 4 ) self.subtext_label = QLabel(self) self.subtext_label.setFont(Font(10)) self.subtext_label.setStyleSheet("color: grey") self.subtext_label.setWordWrap(True) self.subtext_label.setAlignment(Qt.AlignCenter) self.noise_label = QLabel() font = Font(16) font.setFamily("Courier") font.setStyleHint(QFont.Monospace) self.noise_label.setFont(font) self.noise_label.setStyleSheet("color: grey") self.noise_timer = QTimer() self.noise_timer.timeout.connect( lambda: self.noise_label.setText(b58encode(os.urandom(16))) ) self.noise_timer.start(75) self.code_label = QLabel() self.code_label.setFont(Font(18)) self.code_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.code_label.hide() self.box_title = QLabel(self) self.box_title.setAlignment(Qt.AlignCenter) self.box_title.setFont(Font(16)) self.box = QGroupBox() self.box.setAlignment(Qt.AlignCenter) self.box.setStyleSheet("QGroupBox {font-size: 16px}") self.copy_button = QToolButton() self.copy_button.setIcon(QIcon(resource("copy.png"))) self.copy_button.setToolTip("Copy to clipboard") self.copy_button.setStyleSheet("border: 0px; padding: 0px;") self.copy_button.hide() box_layout = QGridLayout(self.box) box_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 1) box_layout.addWidget(self.noise_label, 1, 2) box_layout.addWidget(self.code_label, 1, 3) box_layout.addWidget(self.copy_button, 1, 4) box_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 5) self.close_button = QPushButton("Close and cancel invite") self.close_button.setAutoDefault(False) self.checkmark = QLabel() self.checkmark.setPixmap(Pixmap("green_checkmark.png", 32)) self.checkmark.setAlignment(Qt.AlignCenter) self.checkmark.hide() self.tor_label = QLabel() self.tor_label.setToolTip( "This connection is being routed through the Tor network." ) self.tor_label.setPixmap(Pixmap("tor-onion.png", 24)) self.tor_label.hide() self.progress_bar = QProgressBar() self.progress_bar.setMaximum(2) self.progress_bar.setTextVisible(False) self.progress_bar.hide() layout = QGridLayout(self) layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 0, 0) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 1) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 2) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 3) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 4) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 5) layout.addLayout(header_layout, 1, 3) layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 2, 1) layout.addWidget(self.box_title, 3, 2, 1, 3) layout.addWidget(self.checkmark, 3, 3) layout.addWidget( self.tor_label, 4, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter ) layout.addWidget(self.box, 4, 2, 1, 3) layout.addWidget(self.progress_bar, 4, 2, 1, 3) layout.addWidget(self.subtext_label, 5, 2, 1, 3) layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 6, 1) layout.addWidget(self.close_button, 7, 3) layout.addItem(QSpacerItem(0, 0, 0, QSizePolicy.Expanding), 8, 1) self.copy_button.clicked.connect(self.on_copy_button_clicked) self.close_button.clicked.connect(self.close) self.set_box_title("Generating invite code...") self.subtext_label.setText("Creating folder invite(s)...\n\n") if self.use_tor: self.tor_label.show() self.progress_bar.setStyleSheet( "QProgressBar::chunk {{ background-color: {}; }}".format( TOR_PURPLE ) ) self.go() # XXX def set_box_title(self, text): if sys.platform == "darwin": self.box_title.setText(text) self.box_title.show() else: self.box.setTitle(text) def on_copy_button_clicked(self): code = self.code_label.text() for mode in get_clipboard_modes(): set_clipboard_text(code, mode) self.subtext_label.setText( "Copied '{}' to clipboard!\n\n".format(code) ) def on_got_code(self, code): self.noise_timer.stop() self.noise_label.hide() self.set_box_title("Your invite code is:") self.code_label.setText(code) self.code_label.show() self.copy_button.show() if self.folder_names: if len(self.folder_names) == 1: abilities = 'download "{}" and modify its contents'.format( self.folder_names[0] ) else: abilities = "download {} and modify their contents".format( self.folder_names_humanized ) else: abilities = 'connect to "{}" and upload new folders'.format( self.gateway.name ) self.subtext_label.setText( "Entering this code on another device will allow it to {}.\n" "This code can only be used once.".format(abilities) ) def on_got_introduction(self): if sys.platform == "darwin": self.box_title.hide() self.box.hide() self.progress_bar.show() self.progress_bar.setValue(1) self.subtext_label.setText("Connection established; sending invite...") def on_send_completed(self): self.box.hide() self.progress_bar.show() self.progress_bar.setValue(2) self.checkmark.show() self.close_button.setText("Finish") if self.folder_names: target = self.folder_names_humanized else: target = self.gateway.name text = "Your invitation to {} was accepted".format(target) self.subtext_label.setText( "Invite successful!\n {} at {}".format( text, datetime.now().strftime("%H:%M") ) ) if get_preference("notifications", "invite") != "false": self.gui.show_message("Invite successful", text) if self.folder_names: for view in self.gui.main_window.central_widget.views: if view.gateway.name == self.gateway.name: for folder in self.folder_names: # Immediately tell the Model that there are at least 2 # members for this folder, i.e., that it is now shared view.model().on_members_updated(folder, [None, None]) def handle_failure(self, failure): if failure.type == wormhole.errors.LonelyError: return logging.error(str(failure)) show_failure(failure, self) self.invite_sender.cancel() self.close() def on_created_invite(self): self.subtext_label.setText("Opening wormhole...\n\n") def go(self): self.invite_sender = InviteSender(self.use_tor) self.invite_sender.created_invite.connect(self.on_created_invite) self.invite_sender.got_code.connect(self.on_got_code) self.invite_sender.got_introduction.connect(self.on_got_introduction) self.invite_sender.send_completed.connect(self.on_send_completed) self.invite_sender.send(self.gateway, self.folder_names).addErrback( self.handle_failure ) def closeEvent(self, event): if self.code_label.text() and self.progress_bar.value() < 2: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle("Cancel invitation?") msg.setText( 'Are you sure you wish to cancel the invitation to "{}"?'.format( self.gateway.name ) ) msg.setInformativeText( 'The invite code "{}" will no longer be valid.'.format( self.code_label.text() ) ) msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg.setDefaultButton(QMessageBox.No) if msg.exec_() == QMessageBox.Yes: self.invite_sender.cancel() event.accept() self.closed.emit(self) else: event.ignore() else: event.accept() if self.noise_timer.isActive(): self.noise_timer.stop() self.closed.emit(self) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.close()
class NetworkSDRInterfacePlugin(SDRPlugin): NETWORK_SDR_NAME = "Network SDR" # Display text for device combo box rcv_index_changed = pyqtSignal( int, int ) # int arguments are just for compatibility with native and grc backend show_proto_sniff_dialog_clicked = pyqtSignal() sending_status_changed = pyqtSignal(bool) sending_stop_requested = pyqtSignal() current_send_message_changed = pyqtSignal(int) class MyTCPHandler(socketserver.BaseRequestHandler): def handle(self): received = self.request.recv(4096) self.data = received while received: received = self.request.recv(4096) self.data += received #print("{} wrote:".format(self.client_address[0])) #print(self.data) if hasattr(self.server, "received_bits"): self.server.received_bits.append( NetworkSDRInterfacePlugin.bytearray_to_bit_str(self.data)) else: received = np.frombuffer(self.data, dtype=np.complex64) self.server.receive_buffer[self.server.current_receive_index: self.server.current_receive_index + len(received)] = received self.server.current_receive_index += len(received) def __init__(self, raw_mode=False): """ :param raw_mode: If true, sending and receiving raw samples if false bits are received/sent """ super().__init__(name="NetworkSDRInterface") self.client_ip = self.qsettings.value("client_ip", defaultValue="127.0.0.1", type=str) self.server_ip = "" self.client_port = self.qsettings.value("client_port", defaultValue=2222, type=int) self.server_port = self.qsettings.value("server_port", defaultValue=4444, type=int) self.receive_check_timer = QTimer() self.receive_check_timer.setInterval(250) # need to make the connect for the time in constructor, as create connects is called elsewhere in base class self.receive_check_timer.timeout.connect(self.__emit_rcv_index_changed) self.__is_sending = False self.__sending_interrupt_requested = False self.sending_repeats = 1 # only used in raw mode self.current_sent_sample = 0 self.current_sending_repeat = 0 self.raw_mode = raw_mode if self.raw_mode: # Take 60% of avail memory> num_samples = constants.SETTINGS.value( 'ram_threshold', 0.6, float) * (psutil.virtual_memory().available / 8) self.receive_buffer = np.zeros(int(num_samples), dtype=np.complex64, order='C') else: self.received_bits = [] @property def is_sending(self) -> bool: return self.__is_sending @is_sending.setter def is_sending(self, value: bool): if value != self.__is_sending: self.__is_sending = value self.sending_status_changed.emit(self.__is_sending) @property def received_data(self): if self.raw_mode: return self.receive_buffer[:self.current_receive_index] else: return self.received_bits @property def current_receive_index(self): if hasattr(self.server, "current_receive_index"): return self.server.current_receive_index else: return 0 @current_receive_index.setter def current_receive_index(self, value): if hasattr(self.server, "current_receive_index"): self.server.current_receive_index = value else: pass def free_data(self): if self.raw_mode: self.receive_buffer = np.empty(0) else: self.received_bits[:] = [] def create_connects(self): self.settings_frame.lineEditClientIP.setText(self.client_ip) self.settings_frame.spinBoxClientPort.setValue(self.client_port) self.settings_frame.spinBoxServerPort.setValue(self.server_port) self.settings_frame.lineEditClientIP.editingFinished.connect( self.on_linedit_client_ip_editing_finished) self.settings_frame.lineEditServerIP.editingFinished.connect( self.on_linedit_server_ip_editing_finished) self.settings_frame.spinBoxClientPort.editingFinished.connect( self.on_spinbox_client_port_editing_finished) self.settings_frame.spinBoxServerPort.editingFinished.connect( self.on_spinbox_server_port_editing_finished) self.settings_frame.lOpenProtoSniffer.linkActivated.connect( self.on_lopenprotosniffer_link_activated) def start_tcp_server_for_receiving(self): self.server = socketserver.TCPServer( (self.server_ip, self.server_port), self.MyTCPHandler, bind_and_activate=False) if self.raw_mode: self.server.receive_buffer = self.receive_buffer self.server.current_receive_index = 0 else: self.server.received_bits = self.received_bits self.server.allow_reuse_address = True # allow reusing addresses if the server is stopped and started again self.server.server_bind( ) # only necessary, because we disabled bind_and_activate above self.server.server_activate( ) # only necessary, because we disabled bind_and_activate above self.receive_check_timer.start() self.server_thread = threading.Thread(target=self.server.serve_forever) self.server_thread.daemon = True self.server_thread.start() def stop_tcp_server(self): if hasattr(self, "server"): self.server.shutdown() if hasattr(self, "server_thread"): self.server_thread.join() self.receive_check_timer.stop() def send_data(self, data) -> str: # Create a socket (SOCK_STREAM means a TCP socket) try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # Connect to server and send data sock.connect((self.client_ip, self.client_port)) sock.sendall(data) return "" except Exception as e: return str(e) def send_raw_data(self, data: np.ndarray, num_repeats: int): byte_data = data.tostring() if num_repeats == -1: # forever rng = iter(int, 1) else: rng = range(0, num_repeats) for _ in rng: self.send_data(byte_data) self.current_sent_sample = len(data) self.current_sending_repeat += 1 def __send_messages(self, messages, sample_rates): """ :type messages: list of Message :type sample_rates: list of int :param sample_rates: Sample Rate for each messages, this is needed to calculate the wait time, as the pause for a message is given in samples :return: """ self.is_sending = True for i, msg in enumerate(messages): if self.__sending_interrupt_requested: break assert isinstance(msg, Message) wait_time = msg.pause / sample_rates[i] self.current_send_message_changed.emit(i) error = self.send_data( self.bit_str_to_bytearray(msg.encoded_bits_str)) if not error: logger.debug("Sent message {0}/{1}".format( i + 1, len(messages))) logger.debug( "Waiting message pause: {0:.2f}s".format(wait_time)) if self.__sending_interrupt_requested: break time.sleep(wait_time) else: self.is_sending = False Errors.generic_error("Could not connect to {0}:{1}".format( self.client_ip, self.client_port), msg=error) break logger.debug("Sending finished") self.is_sending = False def start_message_sending_thread(self, messages, sample_rates): """ :type messages: list of Message :type sample_rates: list of int :param sample_rates: Sample Rate for each messages, this is needed to calculate the wait time, as the pause for a message is given in samples :return: """ self.__sending_interrupt_requested = False self.sending_thread = threading.Thread(target=self.__send_messages, args=(messages, sample_rates)) self.sending_thread.daemon = True self.sending_thread.start() def start_raw_sending_thread(self): self.__sending_interrupt_requested = False self.sending_thread = threading.Thread(target=self.send_raw_data, args=(self.samples_to_send, self.sending_repeats)) self.sending_thread.daemon = True self.sending_thread.start() def stop_sending_thread(self): self.__sending_interrupt_requested = True self.sending_stop_requested.emit() @staticmethod def bytearray_to_bit_str(arr: bytearray) -> str: return "".join("{:08b}".format(a) for a in arr) @staticmethod def bit_str_to_bytearray(bits: str) -> bytearray: bits += "0" * ((8 - len(bits) % 8) % 8) return bytearray( (int(bits[i:i + 8], 2) for i in range(0, len(bits), 8))) def on_linedit_client_ip_editing_finished(self): ip = self.settings_frame.lineEditClientIP.text() self.client_ip = ip self.qsettings.setValue('client_ip', self.client_ip) def on_linedit_server_ip_editing_finished(self): # Does nothing, because field is disabled ip = self.settings_frame.lineEditServerIP.text() self.server_ip = ip self.qsettings.setValue('server_ip', self.server_ip) def on_spinbox_client_port_editing_finished(self): self.client_port = self.settings_frame.spinBoxClientPort.value() self.qsettings.setValue('client_port', str(self.client_port)) def on_spinbox_server_port_editing_finished(self): self.server_port = self.settings_frame.spinBoxServerPort.value() self.qsettings.setValue('server_port', str(self.server_port)) def __emit_rcv_index_changed(self): # for updating received bits in protocol sniffer if hasattr(self, "received_bits") and self.received_bits: self.rcv_index_changed.emit( 0, 0 ) # int arguments are just for compatibility with native and grc backend @pyqtSlot(str) def on_lopenprotosniffer_link_activated(self, link: str): if link == "open_proto_sniffer": self.show_proto_sniff_dialog_clicked.emit()
class SlidingStackedWidget(QStackedWidget): LEFT2RIGHT, RIGHT2LEFT, TOP2BOTTOM, BOTTOM2TOP, AUTOMATIC = range(5) clicked_release = pyqtSignal() def __init__(self, *args, **kwargs): super(SlidingStackedWidget, self).__init__(*args, **kwargs) self._pnow = QPoint(0, 0) # 动画速度 self._speed = 500 # 当前索引 self._now = 0 # 自动模式的当前索引 self._current = 0 # 下一个索引 self._next = 0 # 是否激活 self._active = 0 # 动画方向(默认是横向) self._orientation = Qt.Horizontal # 动画曲线类型 self._easing = QEasingCurve.Linear # 初始化动画 self._initAnimation() def setSpeed(self, speed=500): """设置动画速度 :param speed: 速度值,默认值为500 :type speed: int """ self._speed = speed @pyqtProperty(int, fset=setSpeed) def speed(self): return self._speed def setOrientation(self, orientation=Qt.Horizontal): """设置动画的方向(横向和纵向) :param orientation: 方向(Qt.Horizontal或Qt.Vertical) :type orientation: http://doc.qt.io/qt-5/qt.html#Orientation-enum """ self._orientation = orientation @pyqtProperty(int, fset=setOrientation) def orientation(self): return self._orientation def setEasing(self, easing=QEasingCurve.OutBack): """设置动画的曲线类型 :param easing: 默认为QEasingCurve.OutBack :type easing: http://doc.qt.io/qt-5/qeasingcurve.html#Type-enum """ self._easing = easing @pyqtProperty(int, fset=setEasing) def easing(self): return self._easing def slideInNext(self): """滑动到下一页""" now = self.currentIndex() if now < self.count() - 1: self.slideInIdx(now + 1) self._current = now + 1 def slideInPrev(self): """滑动到上一页""" now = self.currentIndex() if now > 0: self.slideInIdx(now - 1) self._current = now - 1 def slideInIdx(self, idx, direction=4): """滑动到指定序号 :param idx: 序号 :type idx: int :param direction: 方向,默认是自动AUTOMATIC=4 :type direction: int """ if idx > self.count() - 1: direction = self.TOP2BOTTOM if self._orientation == Qt.Vertical else self.RIGHT2LEFT idx = idx % self.count() elif idx < 0: direction = self.BOTTOM2TOP if self._orientation == Qt.Vertical else self.LEFT2RIGHT idx = (idx + self.count()) % self.count() self.slideInWgt(self.widget(idx), direction) def slideInWgt(self, widget, direction): """滑动到指定的widget :param widget: QWidget, QLabel, etc... :type widget: QWidget Base Class :param direction: 方向 :type direction: int """ if self._active: return self._active = 1 _now = self.currentIndex() _next = self.indexOf(widget) if _now == _next: self._active = 0 return w_now = self.widget(_now) w_next = self.widget(_next) # 自动判断方向 if _now < _next: directionhint = self.TOP2BOTTOM if self._orientation == Qt.Vertical else self.RIGHT2LEFT else: directionhint = self.BOTTOM2TOP if self._orientation == Qt.Vertical else self.LEFT2RIGHT if direction == self.AUTOMATIC: direction = directionhint # 计算偏移量 offsetX = self.frameRect().width() offsetY = self.frameRect().height() w_next.setGeometry(0, 0, offsetX, offsetY) if direction == self.BOTTOM2TOP: offsetX = 0 offsetY = -offsetY elif direction == self.TOP2BOTTOM: offsetX = 0 elif direction == self.RIGHT2LEFT: offsetX = -offsetX offsetY = 0 elif direction == self.LEFT2RIGHT: offsetY = 0 # 重新定位显示区域外部/旁边的下一个窗口小部件 pnext = w_next.pos() pnow = w_now.pos() self._pnow = pnow # 移动到指定位置并显示 w_next.move(pnext.x() - offsetX, pnext.y() - offsetY) w_next.show() w_next.raise_() self._animnow.setTargetObject(w_now) self._animnow.setDuration(self._speed) self._animnow.setEasingCurve(self._easing) self._animnow.setStartValue(QPoint(pnow.x(), pnow.y())) self._animnow.setEndValue( QPoint(offsetX + pnow.x(), offsetY + pnow.y())) self._animnext.setTargetObject(w_next) self._animnext.setDuration(self._speed) self._animnext.setEasingCurve(self._easing) self._animnext.setStartValue( QPoint(-offsetX + pnext.x(), offsetY + pnext.y())) self._animnext.setEndValue(QPoint(pnext.x(), pnext.y())) self._next = _next self._now = _now self._active = 1 self._animgroup.start() def _initAnimation(self): """初始化当前页和下一页的动画变量""" # 当前页的动画 self._animnow = QPropertyAnimation(self, propertyName=b'pos', duration=self._speed, easingCurve=self._easing) # 下一页的动画 self._animnext = QPropertyAnimation(self, propertyName=b'pos', duration=self._speed, easingCurve=self._easing) # 并行动画组 self._animgroup = QParallelAnimationGroup( self, finished=self.animationDoneSlot) self._animgroup.addAnimation(self._animnow) self._animgroup.addAnimation(self._animnext) def setCurrentIndex(self, index): # 覆盖该方法实现的动画切换 # super(SlidingStackedWidget, self).setCurrentIndex(index) # 坚决不能调用上面的函数,否则动画失效 self.slideInIdx(index) def setCurrentWidget(self, widget): # 覆盖该方法实现的动画切换 super(SlidingStackedWidget, self).setCurrentWidget(widget) # 坚决不能调用上面的函数,否则动画失效 self.setCurrentIndex(self.indexOf(widget)) def mouseReleaseEvent(self, event): """ 点击事件(松开时生效) """ if event.button() == Qt.LeftButton: self.clicked_release.emit() def animationDoneSlot(self): """动画结束处理函数""" # 由于重写了setCurrentIndex方法所以这里要用父类本身的方法 # self.setCurrentIndex(self._next) QStackedWidget.setCurrentIndex(self, self._next) w = self.widget(self._now) w.hide() w.move(self._pnow) self._active = 0 def autoStop(self): """停止自动播放""" if hasattr(self, '_autoTimer'): self._autoTimer.stop() def autoStart(self, msec=3000): """自动轮播 :param time: 时间, 默认3000, 3秒 """ if not hasattr(self, '_autoTimer'): self._autoTimer = QTimer(self, timeout=self._autoStart) self._autoTimer.stop() self._autoTimer.start(msec) def _autoStart(self): if self._current == self.count(): self._current = 0 self._current += 1 self.setCurrentIndex(self._current)
class Simulation(QObject): init_move_done = pyqtSignal() make_move = pyqtSignal() drawDirection = pyqtSignal(int, int, int, int, int) def __init__(self, corrono, view, button, slider): super(QObject, self).__init__() self.scene = GraphicsScene() self.view = view self.view.setScene(self.scene) self.view.scale(.65, -.65) self.view.setRenderHint(QPainter.Antialiasing) self.view.setBackgroundBrush(QBrush(QPixmap(':/images/tufo.png'))) self.view.setCacheMode(QGraphicsView.CacheBackground) self.view.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.view.setDragMode(QGraphicsView.NoDrag) #ScrollHandDrag) self.view.setResizeAnchor(QGraphicsView.AnchorViewCenter) self.view.setTransformationAnchor(QGraphicsView.AnchorViewCenter) self.view.setWindowTitle("Tempo di Palio III") self.slider = slider self.slider.setMinimum(0) self.slider.setMaximum(50) self.button = button self.button.clicked.connect(self.button_pushed) self.human = "Lupa" self.timer = QTimer() self.timer.setInterval(25) self.timer.timeout.connect(self.moveBarberi) self.scene.move_ended.connect(self.checkVelocities) self.scene.race_ended.connect(self.fineCorsa) self.scene.setting_direction.connect(self.setHumanDirection) self.drawDirection.connect(self.scene.drawDirection) self.mortaretto = QSound("mortaretto.wav") self.iBarbero = -1 self.start = False #self.ordine = range(10) self.drawPista() # init mossa self.barberi = [] for ic, c in enumerate(corrono): #if ic > 0: #continue # FIXME posizione dx casuale (-20, 20) self.barberi.append(Barbero(c, 50 - ic * 2, 1380 + ic * 43)) if c == self.human: self.barberi[-1].isHuman = True #self.barberi.append(Barbero(corrono[2], 747.0, 1325)) #1250.0)) #360.0, 850.0)) self.scene.addItem(self.barberi[-1]) #self.barberi.append(Barbero(corrono[3], 687.0, 1300)) # 1250.0)) #360.0, 850.0)) #self.scene.addItem(self.barberi[-1]) @pyqtSlot() def button_pushed(self): self.scene.humanPlaying = True self.scene.removeLine() self.scene.removeHighlight() self.button.setEnabled(False) self.barberi[self.iBarbero].initKinematics(self.human_direction, self.slider.value()) self.timer.start() @pyqtSlot(int, int) def setHumanDirection(self, x, y): dx = x - self.barberi[self.iBarbero].pos.x() dy = y - self.barberi[self.iBarbero].pos.y() base = 5 self.human_direction = base * round(math.degrees(math.atan2(dy, dx))/5) smax, lrange, hrange = self.barberi[self.iBarbero].findTarget(self.sectors) if lrange < self.human_direction < hrange: isok = 1 self.button.setEnabled(True) else: isok = -1 self.button.setEnabled(False) self.drawDirection.emit(self.barberi[self.iBarbero].pos.x(), self.barberi[self.iBarbero].pos.y(), x, y, isok) def drawPista(self): self.sectors = [] for s in json.load(open("pista.json")): self.sectors.append(Sector(**s)) # FIXME to remove after testing self.scene.setPista(self.sectors) self.borders = [] tmp = json.load(open("bordi.json")) for b in tmp: border = Border(*b) self.scene.addItem(border) self.borders.append(border) @pyqtSlot() def run(self): if not self.start: return # FIXME aggiungere penalty casuale alle mossa # dipendente dalla prontezza del fantino # alla rincorsa niente per definizione self.iBarbero += 1 if self.iBarbero == len(self.barberi): self.iBarbero = 0 #sorted(self.barberi, key=compare) self.barberi = sorted(self.barberi, key=cmp_to_key(compare), reverse = True) print ("ordinamento ") for b in self.barberi: print (b.id, b.currentSector, b.distToTarget()) # cambia vista per centrarla # x0 = max(0, self.barberi[self.iBarbero].pos.x() - 235) # x1 = x0 + 470 # y1 = min(1800, self.barberi[self.iBarbero].pos.y() + 205) # y0 = y1 - 405 #print (x0, x1, y0, y1) # if x0 < 0: # x0 = 0 # x1 = 470 # elif x1 > self.scene.xlim: # x1 = self.scene.xlim # x0 = x1 - 470 # # if y0 < 0: # y0 = 0 # y1 = 410 # elif y1 > self.scene.ylim: # y1 = self.scene.ylim # y0 = y1 - 410 #self.scene.setSceneRect(x0, y0, x1, y1) # scegli il path migliore o definisci velocita direzione se umano if not self.barberi[self.iBarbero].isHuman: self.barberi[self.iBarbero].chooseMove(self.iBarbero, self.sectors, self.borders) self.timer.start() else: self.scene.humanPlaying = True self.scene.highlightPlayer(self.barberi[self.iBarbero].pos.x(), self.barberi[self.iBarbero].pos.y()) self.button.setEnabled(True) @pyqtSlot(str) def fineCorsa(self, vincitore): self.timer.stop() print ("Il Palio e` stato vinto da {}".format(vincitore)) #sound = QSound("mortaretto.wav") self.mortaretto.play() # raccogli informazioni necessarie e passale a chi deve aggioranre statistiche # ovvero al manager # vittorie contrada, fantino, cavallo # eventuali aggiornamenti a fantini cavalli e contrade (infortuni, miglioramenti...) # partiti fatti e mantenuti @pyqtSlot() def moveBarberi(self): for it in self.scene.items(): if it.type() == QGraphicsItem.UserType + 2: it.move(self.sectors) @pyqtSlot() def checkVelocities(self): print ("CHECK SPEED") move = False for b in self.barberi: #print ("Vel:", b.v.length()) if b.v.length() > 0.1: move = True break if not move: self.timer.stop() print (self.barberi[self.iBarbero].pos)
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ui = Ui_MainWindow() self.ui.setupUi(self) # Fix so that image shows when run from python or bundled .exe path_to_logo = os.path.abspath(os.path.join(os.path.dirname(__file__), 'images/logo.png')) self.ui.logoImg.setPixmap(QtGui.QPixmap(path_to_logo)) self.data = dict() # How long is each round in seconds self.roundTime = 3 self.progressMaxValue = 1000000 self.gameTimer = QTimer() # Ease the progress bar to give more tension to the player making a choice self.pbar_animation = QPropertyAnimation(self.ui.gameTime, b"value") self.pbar_animation.setEasingCurve(QEasingCurve.InCubic) self.pbar_animation.setDuration(self.roundTime * 1000) self.pbar_animation.setStartValue(self.progressMaxValue) self.pbar_animation.setEndValue(0) self.ui.gameTime.setMaximum(self.progressMaxValue) self.gameTimer.timeout.connect(self.handle_gameTime) self.ui.startGame.clicked.connect(lambda: self.start_game()) self.ui.btnRock.clicked.connect(lambda: self.handle_choice("r")) self.ui.btnPaper.clicked.connect(lambda: self.handle_choice("p")) self.ui.btnScissors.clicked.connect(lambda: self.handle_choice("s")) self.ui.btnLizard.clicked.connect(lambda: self.handle_choice("li")) self.ui.btnSpock.clicked.connect(lambda: self.handle_choice("sp")) # What choice beats what other choices according to the rules self.rulesDict = {"r": ['s', 'li'], "p": ['r', 'sp'], "s": ['p', 'li'], "sp": ['r', 's'], "li": ['p', 'sp']} self.choices = ['r', 'p', 's', 'li', 'sp'] def start_game(self): """ Starts game and creates/loads save file that contains the player's name, win/losses, and historical probability for "AI" choices. :return: """ if self.ui.playerName.text() != "": self.ui.statusbar.showMessage("Good Luck!", 3000) self.ui.widgetStack.setCurrentIndex(1) self.gameTimer.start(self.roundTime * 1000) self.ui.playerLabel.setText(f"<h1>{self.ui.playerName.text()}</h2>") self.pbar_animation.start() playerName = self.ui.playerName.text().lower() filename = playerName + ".dat" if os.path.exists(filename): infile = open(filename, 'rb') logger.info(f"OPEN DAT:{filename}") self.data = pickle.load(infile) infile.close() else: outfile = open(filename, 'wb') self.data = { "player": playerName, "wins": 0, "losses": 0, "probability": [0.2, 0.2, 0.2, 0.2, 0.2] } pickle.dump(self.data, outfile) outfile.close() self.update_data() self.gameMessage('<b>Welcome to RPSLiSp!!!</b>') self.gameMessage('Time is ticking, choose your option!') self.gameMessage('========================================') logger.info("START GAME") else: self.ui.statusbar.showMessage("Your name is required!", 3000) logger.info("NAMED REQUIRED") def handle_gameTime(self): """ Timer handler that awards the computer a point if the player doesnt act in time. :return: """ self.gameMessage(" ") self.gameMessage('<b style="color:red">TIME UP!</b> Next round!') self.gameMessage("<b style='color:#E76F51'>===== COMPUTER WINS BY DEFAULT =====</b>") self.data["losses"] += 1 self.update_data() self.pbar_animation.stop() self.pbar_animation.start() def reset_timer(self): self.gameTimer.stop() self.pbar_animation.stop() self.gameTimer.start(self.roundTime * 1000) self.pbar_animation.start() def handle_choice(self, playerChoice): value = choice_to_value(playerChoice) self.ui.statusbar.showMessage(f"{value}!", 3000) self.check_winner(playerChoice) def check_winner(self, playerChoice): computer_choice = choice(self.choices, 1, p=self.data["probability"]) self.gameMessage(" ") self.gameMessage( f"{self.ui.playerName.text()}: <b style='color:#E9C46A'>{choice_to_value(playerChoice)}</b> vs " f"COMPUTER: <b style='color:#E9C46A'>{choice_to_value(computer_choice[0])}</b> ") if computer_choice[0] == playerChoice: self.gameMessage("<b style='color:#264653'>===== TIE! =====</b>") elif computer_choice[0] in self.rulesDict[playerChoice]: self.gameMessage("<b style='color:#2A9D8F'>===== PLAYER WINS =====</b>") self.data["wins"] += 1 self.update_probability(playerChoice) else: self.gameMessage("<b style='color:#E76F51'>===== COMPUTER WINS =====</b>") self.data["losses"] += 1 self.reset_timer() self.update_data() def update_probability(self, playerChoice): """ The "AI" takes the choice the player made and increases the probability that it will pick a choice to beat the player next time as well as decrease the probability that it will pick a choice that would allow the player to beat it. Probability is fed into numpy.choice() for "random" selection. :param playerChoice: :return: """ choiceIndex = {"r": 0, "p": 1, "s": 2, "li": 3, "sp": 4} oppositeRules = {"r": ['p', 'sp'], "p": ['s', 'li'], "s": ['r', 'sp'], "sp": ['p', 'li'], "li": ['s', 'r']} newProbabilityArray = self.data["probability"] for rule in oppositeRules[playerChoice]: logger.info(f"Increase rule: {rule} ") newProb = format(newProbabilityArray[choiceIndex[rule]] + 0.02, '.2f') if float(newProb) <= 0.98: newProbabilityArray[choiceIndex[rule]] = float(newProb) for rule in self.rulesDict[playerChoice]: logger.info(f"Decrease rule: {rule} ") newProb = format(newProbabilityArray[choiceIndex[rule]] - 0.02, '.2f') if float(newProb) >= 0.02: newProbabilityArray[choiceIndex[rule]] = float(newProb) # To prevent crash reset array when it gets out of bounds for numpy.choice() if sum(newProbabilityArray) > 1.0: newProbabilityArray = [0.2, 0.2, 0.2, 0.2, 0.2] logger.info(sum(newProbabilityArray)) self.data["probability"] = newProbabilityArray logger.info(self.data["probability"]) def gameMessage(self, message): self.ui.gameLog.append(message) def update_data(self): """ Updates UI for player wins and losses :return: """ self.ui.playerScore.display(self.data["wins"]) self.ui.computerScore.display(self.data["losses"]) def closeEvent(self, event: QCloseEvent) -> None: """ Handle clean up after application is closed :param event: :return: """ self.gameTimer.stop() if self.ui.playerName.text() != "": filename = self.data["player"] + ".dat" outfile = open(filename, 'wb') pickle.dump(self.data, outfile) outfile.close() logger.info(f"CLOSING: {filename}")
class MineSweeperGUI(superGUI.Ui_MainWindow): def __init__(self, MainWindow): file = r'media/background.mp3' pygame.mixer.init() pygame.mixer.music.load(file) pygame.mixer.music.play(-1, 0) self.row = 9 self.column = 9 self.mineNum = 10 self.rank = 0 self.finish = False self.mainWindow = MainWindow self.mainWindow.setWindowIcon(QIcon("media/mine.jpg")) self.mainWindow.setFixedSize(self.mainWindow.minimumSize()) self.setupUi(self.mainWindow) self.mineLabel = [] self.initMineArea() self.createMine() self.label_2.leftRelease.connect(self.gameStart) pixmap = QPixmap("media/underway.png") self.label_2.setPixmap(pixmap) self.label_2.setScaledContents(True) pe = QPalette() pe.setColor(QPalette.WindowText, Qt.red) # 设置字体颜色 self.label_3.setPalette(pe) self.label_3.setFont(QFont("Roman times", 15, QFont.Bold)) self.label.setPalette(pe) self.label.setFont(QFont("Roman times", 15, QFont.Bold)) self.label.setText(str(self.mineNum)) self.timer = QTimer() self.timer.setInterval(1000) self.timer.timeout.connect(self.timeCount) self.timeStart = False # 绑定菜单栏事件 self.action.triggered.connect(self.gameStart) self.action_B.triggered.connect(self.action_BEvent) self.action_I.triggered.connect(self.action_IEvent) self.action_E.triggered.connect(self.action_Event) self.action_C.triggered.connect(self.action_CEvent) self.action_T.triggered.connect(self.action_TEvent) self.action_R.triggered.connect(self.action_REvent) self.action_S_2.triggered.connect(self.action_SEvent) self.action_L_2.triggered.connect(self.action_LEvent) self.action_X_2.triggered.connect(QCoreApplication.instance().quit) self.actionChecked('B') # 默认选择初级 def outOfBorder(self, i, j): if i < 0 or i >= self.row or j < 0 or j >= self.column: return True return False def createMine(self): num = self.mineNum while num > 0: r = random.randint(0, self.row - 1) c = random.randint(0, self.column - 1) if self.mineLabel[r][c].num != -1: self.mineLabel[r][c].num = -1 num -= 1 for i in range(r - 1, r + 2): for j in range(c - 1, c + 2): if not self.outOfBorder( i, j) and (self.mineLabel[i][j].num != -1): self.mineLabel[i][j].num += 1 def initMineArea(self): self.gridLayout.setSpacing(0) for i in range(0, self.row): self.mineLabel.append([]) for j in range(0, self.column): label = mineLabel.mineLabel(i, j, 0, "") label.setMinimumSize(18, 18) label.setFrameShape(QtWidgets.QFrame.WinPanel) label.setFrameShadow(QtWidgets.QFrame.Raised) label.setAlignment(Qt.AlignCenter) # 绑定雷区点击事件 label.leftPressed.connect(self.mineAreaLeftPressed) label.leftAndRightPressed.connect( self.mineAreaLeftAndRightPressed) label.leftAndRightRelease.connect( self.mineAreaLeftAndRightRelease) label.leftRelease.connect(self.mineAreaLeftRelease) label.rightRelease.connect(self.mineAreaRightRelease) self.mineLabel[i].append(label) self.gridLayout.addWidget(label, i, j) def timeCount(self): self.label_3.setText(str(int(self.label_3.text()) + 1)) def setFontColor(self, i, j): if self.mineLabel[i][j].num == 1: self.mineLabel[i][j].setStyleSheet("color:blue;") elif self.mineLabel[i][j].num == 2: self.mineLabel[i][j].setStyleSheet("color:brown;") elif self.mineLabel[i][j].num == 3: self.mineLabel[i][j].setStyleSheet("color:red;") elif self.mineLabel[i][j].num == 4: self.mineLabel[i][j].setStyleSheet("color:purple;") elif self.mineLabel[i][j].num == 5: self.mineLabel[i][j].setStyleSheet("color:orange;") elif self.mineLabel[i][j].num == 6: self.mineLabel[i][j].setStyleSheet("color:pink;") elif self.mineLabel[i][j].num == 7: self.mineLabel[i][j].setStyleSheet("color:#00aa00;") elif self.mineLabel[i][j].num == 8: self.mineLabel[i][j].setStyleSheet("color:black;") def DFS(self, i, j, start0): if self.mineLabel[i][j].status == 0: self.mineLabel[i][j].status = 1 self.mineLabel[i][j].setFrameShape(QtWidgets.QFrame.Panel) self.mineLabel[i][j].setFrameShadow(QtWidgets.QFrame.Sunken) if self.mineLabel[i][j].num > 0: if not self.timeStart: self.timeStart = True self.timer.start() self.setFontColor(i, j) self.mineLabel[i][j].setFont( QFont("Roman times", 15, QFont.Bold)) self.mineLabel[i][j].setText(str(self.mineLabel[i][j].num)) if self.isGameFinished(): self.gameWin() if (start0 and self.mineLabel[i][j].num == 0) or (not start0 and self.mineLabel[i][j].num == 0): for r in range(i - 1, i + 2): for c in range(j - 1, j + 2): if not self.outOfBorder(r, c) and self.mineLabel[r][ c].status == 0 and self.mineLabel[r][ c].num != -1: self.DFS(r, c, start0) def mineAreaLeftRelease(self, i, j): if not self.finish: if self.mineLabel[i][j].num >= 0: self.DFS(i, j, self.mineLabel[i][j].num == 0) if self.isGameFinished(): self.gameWin() else: self.gameFailed() def mineAreaRightRelease(self, i, j): if not self.finish: if self.mineLabel[i][j].status == 0: pixmap = QPixmap("media/flag.jpg") self.mineLabel[i][j].setPixmap(pixmap) self.mineLabel[i][j].setScaledContents(True) self.mineLabel[i][j].status = 2 self.label.setText(str(int(self.label.text()) - 1)) elif self.mineLabel[i][j].status == 2: self.mineLabel[i][j].setPixmap(QPixmap("media/question.jpg")) self.mineLabel[i][j].status = 3 self.label.setText(str(int(self.label.text()) + 1)) elif self.mineLabel[i][j].status == 3: self.mineLabel[i][j].setPixmap(QPixmap("")) self.mineLabel[i][j].status = 0 def mineAreaLeftPressed(self, i, j): if not self.finish: if self.mineLabel[i][j].status == 0: self.mineLabel[i][j].setFrameShape(QtWidgets.QFrame.Panel) self.mineLabel[i][j].setFrameShadow(QtWidgets.QFrame.Sunken) def mineAreaLeftAndRightPressed(self, i, j): if not self.finish: if self.mineLabel[i][j].status == 1: count = 0 for r in range(i - 1, i + 2): for c in range(j - 1, j + 2): if not self.outOfBorder(r, c): if self.mineLabel[r][ c].status == 0 or self.mineLabel[r][ c].status == 3: self.mineLabel[r][c].setFrameShape( QtWidgets.QFrame.Panel) self.mineLabel[r][c].setFrameShadow( QtWidgets.QFrame.Sunken) elif self.mineLabel[r][c].status == 2: count += 1 return count == self.mineLabel[i][j].num else: return False def mineAreaLeftAndRightRelease(self, i, j): if not self.finish: if self.mineLabel[i][j].status == 1: if self.mineAreaLeftAndRightPressed(i, j): Fail = False for r in range(i - 1, i + 2): for c in range(j - 1, j + 2): if not self.outOfBorder(r, c): if self.mineLabel[r][ c].status == 0 or self.mineLabel[r][ c].status == 3: if self.mineLabel[r][c].status == 3: self.mineLabel[r][c].setPixmap( QPixmap("")) self.mineLabel[r][c].setScaledContents( True) self.mineLabel[r][c].status = 0 if self.mineLabel[r][c].num >= 0: self.DFS(r, c, self.mineLabel[r][c].num == 0) else: Fail = True if Fail: self.gameFailed() else: for r in range(i - 1, i + 2): for c in range(j - 1, j + 2): if not self.outOfBorder(r, c): if self.mineLabel[r][ c].status == 0 or self.mineLabel[r][ c].status == 3: self.mineLabel[r][c].setFrameShape( QtWidgets.QFrame.WinPanel) self.mineLabel[r][c].setFrameShadow( QtWidgets.QFrame.Raised) def gameStart(self): for i in self.mineLabel: for j in i: self.gridLayout.removeWidget(j) sip.delete(j) self.label.setText(str(self.mineNum)) pixmap = QPixmap("media/underway.png") self.label_2.setPixmap(pixmap) self.label_2.setScaledContents(True) self.label_3.setText("0") self.timeStart = False self.finish = False self.timer.stop() self.mineLabel.clear() self.mineLabel = [] self.initMineArea() self.createMine() self.mainWindow.setMinimumSize(0, 0) self.mainWindow.resize(self.mainWindow.minimumSize()) def gameFinished(self): for i in self.mineLabel: for j in i: if j.num == -1 or j.status == 2: j.setFrameShape(QtWidgets.QFrame.Panel) j.setFrameShadow(QtWidgets.QFrame.Sunken) if j.num == -1 and j.status == 2: pixmap = QPixmap("media/correct.jpg") elif j.num == -1: pixmap = QPixmap("media/mine.jpg") else: pixmap = QPixmap("media/mistake.jpg") j.setPixmap(pixmap) j.setScaledContents(True) j.status = 1 self.timer.stop() self.finish = True def isGameFinished(self): for i in self.mineLabel: for j in i: if j.status == 0 and j.num != -1: return False return True def gameWin(self): pixmap = QPixmap("media/win.png") self.label_2.setPixmap(pixmap) self.label_2.setScaledContents(True) self.gameFinished() try: with open("hero.txt") as file: data = file.readlines() except FileNotFoundError: with open("hero.txt", "w") as file2: file2.write("9999\n匿名\n9999\n匿名\n9999\n匿名") with open("hero.txt") as file3: data = file3.readlines() if self.rank < 3 and int(data[self.rank * 2].rstrip()) > int( self.label_3.text()): dic = ["初级", "中级", "高级"] s, ok = QInputDialog.getText( self.mainWindow, "已破纪录", "已破" + dic[int(self.rank)] + "记录,请留尊姓大名:", QLineEdit.Normal, "匿名") if ok and s.strip(): data[self.rank * 2] = self.label_3.text() + "\n" data[self.rank * 2 + 1] = s + "\n" with open("hero.txt", "w") as file2: for i in data: file2.write(i) ui = heroDialog.Ui_Dialog() ui.Dialog.setModal(True) ui.Dialog.show() ui.Dialog.exec_() def gameFailed(self): pixmap = QPixmap("media/fail.png") self.label_2.setPixmap(pixmap) self.label_2.setScaledContents(True) self.gameFinished() def actionChecked(self, k): self.action_B.setChecked(False) self.action_I.setChecked(False) self.action_E.setChecked(False) self.action_C.setChecked(False) if k == 'B': self.action_B.setChecked(True) elif k == 'I': self.action_I.setChecked(True) elif k == 'E': self.action_E.setChecked(True) elif k == 'C': self.action_C.setChecked(True) def action_BEvent(self): self.actionChecked('B') self.row = 9 self.column = 9 self.mineNum = 10 self.rank = 0 self.gameStart() def action_IEvent(self): self.actionChecked('I') self.row = 16 self.column = 16 self.mineNum = 40 self.rank = 1 self.gameStart() def action_Event(self): self.actionChecked('E') self.row = 16 self.column = 30 self.mineNum = 99 self.rank = 2 self.gameStart() def action_CEvent(self): self.actionChecked('C') ui = selfDefinedParameter.Ui_Dialog(self.row, self.column, self.mineNum) ui.Dialog.setModal(True) ui.Dialog.show() ui.Dialog.exec_() if ui.alter: self.row = ui.row self.column = ui.column self.mineNum = ui.mineNum self.rank = 3 self.gameStart() def action_TEvent(self): ui = heroDialog.Ui_Dialog() ui.Dialog.setModal(True) ui.Dialog.show() ui.Dialog.exec_() def action_REvent(self): Dialog = QtWidgets.QDialog() ui = rule.Ui_Dialog() ui.setupUi(Dialog) Dialog.show() Dialog.exec_() def action_SEvent(self): Dialog = QtWidgets.QDialog() ui = symbolDialog.SymbolDialog(Dialog) Dialog.show() Dialog.exec_() def action_LEvent(self): Dialog = QtWidgets.QDialog() ui = limit.Ui_Dialog() ui.setupUi(Dialog) Dialog.show() Dialog.exec_()
class GameMainWindow(object): def setup_ui(self, MainWindow): global in_progress global choose_cell_phase MainWindow.setObjectName("Draughts") MainWindow.resize(800, 600) MainWindow.setMinimumSize(QtCore.QSize(800, 600)) MainWindow.setMaximumSize(QtCore.QSize(800, 600)) MainWindow.setStyleSheet("") self.centralwidget = QtWidgets.QWidget(MainWindow) self.frame = QtWidgets.QFrame(self.centralwidget) self.frame.setGeometry(QtCore.QRect(0, 0, 181, 601)) self.frame.setStyleSheet("QFrame {\n" "border-color:black;\n" "border-style:solid;\n" "border-width:5px;\n" "background-color:#FFFACD;\n" "}\n" "\n" "QPushButton {\n" "border-color:black;\n" "border-style:solid;\n" "background-color: #dcb35c;\n" "border-radius: 40px;\n" "border-width:3px;\n" "}\n" "\n" "QPushButton:hover {\n" "background-color: #ECE7BC;\n" "}\n" "\n" "QLabel{\n" "border-color:black;\n" "border-style:solid;\n" "border-width:5 5 5 5;\n" "}") self.label = QtWidgets.QLabel(self.frame) self.label.setGeometry(QtCore.QRect(0, 0, 181, 111)) font = QtGui.QFont() font.setFamily("Verdana") font.setPointSize(12) font.setBold(True) font.setWeight(75) self.label.setFont(font) self.label.setTextFormat(QtCore.Qt.PlainText) self.label.setAlignment(QtCore.Qt.AlignCenter) self.label.setWordWrap(True) self.restart = QtWidgets.QPushButton(self.frame) self.restart.setGeometry(QtCore.QRect(20, 160, 141, 81)) font = QtGui.QFont() font.setFamily("Verdana") font.setPointSize(12) font.setBold(True) font.setWeight(75) self.restart.setFont(font) self.restart.setObjectName("restart") self.restart.pressed.connect(lambda: self.restart_pressed()) self.widget = QtWidgets.QWidget(self.centralwidget) self.widget.setGeometry(QtCore.QRect(180, 0, 621, 601)) self.gridLayout = QtWidgets.QGridLayout(self.widget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) for vertical, horizontal in itertools.product(range(board.width), range(board.height)): board_cell = BoardCell(vertical, horizontal) groupBox = QGroupBoxClickable(self.widget) image_label = QtWidgets.QLabel(groupBox) image_label.setGeometry(QtCore.QRect(0, 0, 80, 80)) if (vertical + horizontal) % 2 == 0: image_label.setStyleSheet("background-color:#fdeda8;") else: image_label.setStyleSheet("background-color:#ba7b55;") event = functools.partial(self.group_clicked, board_cell) groupBox.clicked.connect(event) buttons[board_cell] = groupBox self.gridLayout.addWidget(groupBox, horizontal, vertical, 1, 1) self.dialog = UnclosableDialog(self.centralwidget) self.dialog.resize(400, 257) self.dialog.setMinimumSize(QtCore.QSize(400, 257)) self.dialog.setMaximumSize(QtCore.QSize(400, 257)) self.dialog.setModal(True) self.dialog.setWindowFlag(Qt.FramelessWindowHint) self.dialog.setStyleSheet("QPushButton {\n" "border-color:black;\n" "border-style:solid;\n" "background-color: #dcb35c;\n" "border-radius: 25px;\n" "border-width:3px;\n" "}\n" "QPushButton:hover {\n" "background-color: #ECE7BC;\n" "}\n" "QDialog {\n" "border-color:black;\n" "border-style:solid;\n" "border-width:5px;\n" "background-color:#FFFACD;\n" "}\n" "QFrame {\n" "border-color:black;\n" "border-style:solid;\n" "border-width:3px;\n" "}\n" "") self.frame = QtWidgets.QFrame(self.dialog) self.frame.setGeometry(QtCore.QRect(60, 80, 120, 80)) self.frame.setStyleSheet("background-color: #dcb35c;\n" "border-width:3px;") self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame.setFrameShadow(QtWidgets.QFrame.Raised) self.first_player_human = QtWidgets.QRadioButton(self.frame) self.first_player_human.setGeometry(QtCore.QRect(20, 20, 82, 17)) self.first_player_human.setStyleSheet("border-width:0px;") self.first_player_human.setChecked(True) self.first_player_computer = QtWidgets.QRadioButton(self.frame) self.first_player_computer.setGeometry(QtCore.QRect(20, 50, 82, 17)) self.first_player_computer.setStyleSheet("border-width:0px;") self.frame_2 = QtWidgets.QFrame(self.dialog) self.frame_2.setGeometry(QtCore.QRect(220, 80, 120, 80)) self.frame_2.setStyleSheet("background-color: #dcb35c;\n" "border-width:3px;") self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) self.second_player_human = QtWidgets.QRadioButton(self.frame_2) self.second_player_human.setGeometry(QtCore.QRect(20, 20, 82, 17)) self.second_player_human.setStyleSheet("border-width:0px;") self.second_player_human.setChecked(True) self.second_player_computer = QtWidgets.QRadioButton(self.frame_2) self.second_player_computer.setGeometry(QtCore.QRect(20, 50, 82, 17)) self.second_player_computer.setStyleSheet("border-width:0px;") self.push_button = QtWidgets.QPushButton(self.dialog) self.push_button.setGeometry(QtCore.QRect(140, 180, 121, 51)) self.push_button.pressed.connect(lambda: self.dialog_pressed()) self.dialog_first = QtWidgets.QLabel(self.dialog) self.dialog_first.setGeometry(QtCore.QRect(70, 50, 101, 21)) self.dialog_first.setStyleSheet("font: 75 12pt \"Verdana\";\n" "border-width:0px;") self.dialog_second = QtWidgets.QLabel(self.dialog) self.dialog_second.setGeometry(QtCore.QRect(220, 50, 121, 21)) self.dialog_second.setStyleSheet("font: 75 12pt \"Verdana\";\n" "border-width:0px;") self.dialog.show() self.timer = QTimer() in_progress = True MainWindow.setCentralWidget(self.centralwidget) self.retranslate_ui(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslate_ui(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("Draughts", "Draughts")) self.restart.setText(_translate("MainWindow", "Restart game")) self.first_player_human.setText(_translate("Dialog", "Human")) self.first_player_computer.setText(_translate("Dialog", "Computer")) self.second_player_human.setText(_translate("Dialog", "Human")) self.second_player_computer.setText(_translate("Dialog", "Computer")) self.push_button.setText(_translate("Dialog", "OK")) self.dialog_first.setText( _translate( "Dialog", "<html><head/><body><p><span style=\" font-size:12pt;\">First " "player</span></p></body></html>")) self.dialog_second.setText( _translate( "Dialog", "<html><head/><body><p>Second player</p></body></html>")) def group_clicked(self, board_cell: BoardCell): global choose_cell_phase global chosen_cell group = buttons[board_cell] image_label = group.children()[0] if in_progress: if choose_cell_phase: if board.cells[board_cell] is not None: if board.cells[board_cell].side == board.turn: image_label.setStyleSheet("background-color:#ac6539;") choose_cell_phase = False chosen_cell = board_cell else: turn = board.make_turn(chosen_cell, board_cell.vertical, board_cell.horizontal) if board.turn_phase != 2: if turn is None: buttons[chosen_cell].children()[0].setStyleSheet( "background-color:#ba7b55;") choose_cell_phase = True chosen_cell = None elif turn[1] is None: image_label.setStyleSheet("background-color:#ba7b55;") buttons[chosen_cell].children()[0].setStyleSheet( "background-color:#ba7b55;") self.update_board(chosen_cell) self.update_board(board_cell) choose_cell_phase = True chosen_cell = None current_turn = board.turn for cell in marked_cells: buttons[cell].children()[0].setStyleSheet( "background-color:#ba7b55;") buttons[cell].children()[0].repaint() marked_cells.clear() while board.turn == current_turn and in_progress and ( self.first_player_computer.isChecked() or self.second_player_computer.isChecked()): sleep(1) self.make_computer_turn() if turn is not None and turn[1] is not None: buttons[chosen_cell].children()[0].setStyleSheet( "background-color:#ba7b55;") self.update_board(chosen_cell) self.update_board(turn[1]) self.update_board(turn[0]) if board.cells[board_cell].side == board.turn: image_label.setStyleSheet("background-color:#ac6539;") chosen_cell = board_cell else: choose_cell_phase = True chosen_cell = None current_turn = board.turn for cell in marked_cells: buttons[cell].children()[0].setStyleSheet( "background-color:#ba7b55;") buttons[cell].children()[0].repaint() marked_cells.clear() while board.turn == current_turn and in_progress and ( self.first_player_computer.isChecked() or self.second_player_computer.isChecked()): sleep(1) self.make_computer_turn() def restart_pressed(self): if self.timer.isActive(): self.timer.stop() self.dialog.show() def dialog_pressed(self): global in_progress global chosen_cell global choose_cell_phase if chosen_cell is not None: buttons[chosen_cell].children()[0].setStyleSheet( "background-color:#ba7b55;") choose_cell_phase = True chosen_cell = None board.fill_board() for cell in board.cells: self.update_board(cell) for cell in marked_cells: buttons[cell].children()[0].setStyleSheet( "background-color:#ba7b55;") buttons[cell].children()[0].repaint() marked_cells.clear() in_progress = True self.dialog.hide() if self.first_player_computer.isChecked( ) and self.second_player_human.isChecked(): board.turn = Side.BLACK self.make_computer_turn() if self.first_player_computer.isChecked( ) and self.second_player_computer.isChecked(): self.timer.timeout.connect(lambda: self.make_computer_turn()) self.timer.start(1500) self.update_status() def make_computer_turn(self): sleep(1) if board.attr_story and board.turn != board.attr_story[ len(board.attr_story) - 1][0]: for cell in marked_cells: buttons[cell].children()[0].setStyleSheet( "background-color:#ba7b55;") buttons[cell].children()[0].repaint() marked_cells.clear() computer_turn = ComputerLogic.make_computer_turn(board) self.update_board(computer_turn[0]) self.update_board(computer_turn[1][0]) buttons[computer_turn[0]].children()[0].setStyleSheet( "background-color:#ac6539;") buttons[computer_turn[0]].children()[0].repaint() buttons[computer_turn[1][0]].children()[0].setStyleSheet( "background-color:#ac6539;") buttons[computer_turn[1][0]].children()[0].repaint() marked_cells.append(computer_turn[0]) marked_cells.append(computer_turn[1][0]) if computer_turn[1][1] is not None: self.update_board(computer_turn[1][1]) if not in_progress and self.timer.isActive(): self.timer.stop() def update_board(self, board_cell: BoardCell): checker = board.cells[board_cell] group = buttons[board_cell] is_inverted_sides = self.first_player_computer.isChecked( ) and self.second_player_human.isChecked() if len(group.children()) > 1: group.children()[len(group.children()) - 1].setParent(None) group.repaint() if checker is None: return elif (checker.side == Side.BLACK and not is_inverted_sides) or\ (checker.side == Side.WHITE and is_inverted_sides): image_label = QtWidgets.QLabel(group) image_label.setGeometry(QtCore.QRect(10, 8, 60, 60)) if checker.is_king: image_label.setPixmap( QtGui.QPixmap(resource_path("Black_king.png"))) else: image_label.setPixmap( QtGui.QPixmap(resource_path("Black_checker.png"))) group.children().append(image_label) image_label.show() elif (checker.side == Side.WHITE and not is_inverted_sides) or\ (checker.side == Side.BLACK and is_inverted_sides): image_label = QtWidgets.QLabel(group) image_label.setGeometry(QtCore.QRect(10, 8, 60, 60)) if checker.is_king: image_label.setPixmap( QtGui.QPixmap(resource_path("White_king.png"))) else: image_label.setPixmap( QtGui.QPixmap(resource_path("White_checker.png"))) group.children().append(image_label) image_label.show() self.centralwidget.repaint() self.update_status() def update_status(self): global in_progress winner = board.winner() is_inverted_sides = self.first_player_computer.isChecked( ) and self.second_player_human.isChecked() if winner == Side.BLACK: in_progress = False status_string = str( "Game status:\nBlack wins. Press restart to play again.") elif winner == Side.WHITE: in_progress = False status_string = str( "Game status:\nWhite wins. Press restart to play again.") elif (board.turn == Side.BLACK and not is_inverted_sides) or \ (board.turn == Side.WHITE and is_inverted_sides) : status_string = str("Game status:\nBlacks turn.") else: status_string = str("Game status:\nWhites turn.") self.label.setText(status_string)
class OpencvWidget(QLabel): def __init__(self, *args, **kwargs): super(OpencvWidget, self).__init__(*args, **kwargs) self.httpRequestAborted = False self.fps = 24 self.resize(800, 600) if not os.path.exists("Data/shape_predictor_68_face_landmarks.dat"): self.setText("正在下载数据文件。。。") self.outFile = QFile( "Data/shape_predictor_68_face_landmarks.dat.bz2") if not self.outFile.open(QIODevice.WriteOnly): QMessageBox.critical(self, '错误', '无法写入文件') return self.qnam = QNetworkAccessManager(self) self._reply = self.qnam.get(QNetworkRequest(QUrl(URL))) self._reply.finished.connect(self.httpFinished) self._reply.readyRead.connect(self.httpReadyRead) self._reply.downloadProgress.connect(self.updateDataReadProgress) else: self.startCapture() def httpFinished(self): self.outFile.close() if self.httpRequestAborted or self._reply.error(): self.outFile.remove() self._reply.deleteLater() del self._reply # 下载完成解压文件并加载摄像头 self.setText("正在解压数据。。。") try: bz = BZ2Decompressor() data = bz.decompress( open('Data/shape_predictor_68_face_landmarks.dat.bz2', 'rb').read()) open('Data/shape_predictor_68_face_landmarks.dat', 'wb').write(data) except Exception as e: self.setText('解压失败:' + str(e)) return self.setText('正在开启摄像头。。。') self.startCapture() def httpReadyRead(self): self.outFile.write(self._reply.readAll()) self.outFile.flush() def updateDataReadProgress(self, bytesRead, totalBytes): self.setText('已下载:{} %'.format(round(bytesRead / 64040097 * 100, 2))) def startCapture(self): self.setText("请稍候,正在初始化数据和摄像头。。。") try: # 检测相关 self.detector = dlib.get_frontal_face_detector() self.predictor = dlib.shape_predictor( "Data/shape_predictor_68_face_landmarks.dat") cascade_fn = "Data/lbpcascades/lbpcascade_frontalface.xml" self.cascade = cv2.CascadeClassifier(cascade_fn) if not self.cascade: return QMessageBox.critical(self, "错误", cascade_fn + " 无法找到") self.cap = cv2.VideoCapture(0) if not self.cap or not self.cap.isOpened(): return QMessageBox.critical(self, "错误", "打开摄像头失败") # 开启定时器定时捕获 self.timer = QTimer(self, timeout=self.onCapture) self.timer.start(1000 / self.fps) except Exception as e: QMessageBox.critical(self, "错误", str(e)) def closeEvent(self, event): if hasattr(self, "_reply") and self._reply: self.httpRequestAborted = True self._reply.abort() try: os.unlink("Data/shape_predictor_68_face_landmarks.dat.bz2") except: pass try: os.unlink("Data/shape_predictor_68_face_landmarks.dat") except: pass if hasattr(self, "timer"): self.timer.stop() self.timer.deleteLater() self.cap.release() del self.predictor, self.detector, self.cascade, self.cap super(OpencvWidget, self).closeEvent(event) self.deleteLater() def onCapture(self): _, frame = self.cap.read() minisize = (int(frame.shape[1] / DOWNSCALE), int(frame.shape[0] / DOWNSCALE)) tmpframe = cv2.resize(frame, minisize) tmpframe = cv2.cvtColor(tmpframe, cv2.COLOR_BGR2GRAY) # 做灰度处理 tmpframe = cv2.equalizeHist(tmpframe) # minNeighbors表示每一个目标至少要被检测到5次 faces = self.cascade.detectMultiScale(tmpframe, minNeighbors=5) del tmpframe if len(faces) < 1: # 没有检测到脸 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[1] * 3, QImage.Format_RGB888) del frame return self.setPixmap(QPixmap.fromImage(img)) # 特征点检测描绘 for x, y, w, h in faces: x, y, w, h = x * DOWNSCALE, y * DOWNSCALE, w * DOWNSCALE, h * DOWNSCALE # 画脸矩形 cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0)) # 截取的人脸部分 tmpframe = frame[y:y + h, x:x + w] # 进行特征点描绘 rects = self.detector(tmpframe, 1) if len(rects) > 0: landmarks = numpy.matrix( [[p.x, p.y] for p in self.predictor(tmpframe, rects[0]).parts()]) for _, point in enumerate(landmarks): pos = (point[0, 0] + x, point[0, 1] + y) # 在原来画面上画点 cv2.circle(frame, pos, 3, color=(0, 255, 0)) # 转成Qt能显示的 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[1] * 3, QImage.Format_RGB888) del frame self.setPixmap(QPixmap.fromImage(img))
class ObjectPacker: def __init__(self, physics_plugin): self._physics_plugin = physics_plugin self._running = False self._message = None self._planes = [] self._current_iteration = 0 self._max_iterations = 100 self._initial_plane_distance = -500 self._min_plane_distance = -100 self._timer = QTimer() self._timer.setInterval(20) self._timer.timeout.connect(self._update) def pack(self): if self._running: return self._message = Message("Packing Objects...", lifetime=-1, dismissable=False, progress=100) self._message.show() Application.getInstance().getController().setToolsEnabled(False) plane = ode.GeomPlane(self._physics_plugin.space, (1, 0, 0), self._initial_plane_distance) self._planes.append(plane) plane = ode.GeomPlane(self._physics_plugin.space, (-1, 0, 0), self._initial_plane_distance) self._planes.append(plane) plane = ode.GeomPlane(self._physics_plugin.space, (0, 0, 1), self._initial_plane_distance) self._planes.append(plane) plane = ode.GeomPlane(self._physics_plugin.space, (0, 0, -1), self._initial_plane_distance) self._planes.append(plane) for node in DepthFirstIterator(Application.getInstance().getController( ).getScene().getRoot()): if node.getDecorator(RigidBodyDecorator.RigidBodyDecorator): random_position = Vector( random.randrange(self._initial_plane_distance, -self._initial_plane_distance), 0, random.randrange(self._initial_plane_distance, -self._initial_plane_distance)) node.setPosition(random_position) self._timer.start() def _update(self): for plane in self._planes: params = plane.getParams() distance = max( self._min_plane_distance, self._initial_plane_distance - (self._initial_plane_distance * (self._current_iteration / self._max_iterations))) plane.setParams(params[0], distance) self._current_iteration += 1 if self._current_iteration > self._max_iterations: self._timer.stop() self._message.hide() Application.getInstance().getController().setToolsEnabled(True) for plane in self._planes: self._physics_plugin.space.remove(plane) self._planes = [] self._current_iteration = 0 else: self._message.setProgress( (self._current_iteration / self._max_iterations) * 100)
class GUIBoard(QFrame): """ """ # Class Constant BOARD_SIZE = 500 # signal turn_change = pyqtSignal(int) is_game_over = pyqtSignal(bool) def __init__(self): """盤面の初期化。完全ランダムで埋める.Playerの選択も""" super().__init__() self.init_ui() self.table_size = 4 self.init_board() def init_board(self): self.Board = CBoard.Board(table_size=self.table_size) self.Board.init_board(max_num=3) self.is_paused = True self.resize(GUIBoard.BOARD_SIZE, GUIBoard.BOARD_SIZE) self.Board_drawn = self.Board self.drop_timer = QTimer(self) self.drop_timer.setInterval(200) self.is_game_over.emit(False) self.drop_timer.timeout.connect(self.draw_dropped) def step(self): """step one turn""" if not self.is_paused: # ポーズ中でなければすすめる if not self.Board.is_game_end(): # self.timer.stop() next_c = self.player.next_cell(self.Board) # draw before_drop self.Board_drawn = self.Board.select_cell( next_c, return_board_before_drop=True) # self.timer.start() self.update() # draw after_drop self.drop_timer.start() else: self.is_paused = True self.is_game_over.emit(True) def draw_dropped(self): """Boardをおとした後の盤面を表示する""" self.drop_timer.stop() self.Board_drawn = self.Board self.update() self.turn_change.emit(self.Board.get_turn_num()) QTimer.singleShot(100, self.step) def start(self): """AIをスタートする""" if self.Board.is_game_end(): self.init_board() self.is_paused = False QTimer.singleShot(100, self.step) def pause(self): """AIをストップする""" self.is_paused = True def mousePressEvent(self, event): """when mouse clicked""" self.mouse_step(event.x(), event.y()) def mouse_step(self, x, y): """select cell which is clicked by mouse""" # get number of clicked cell cell = -1 if not self.Board.is_game_end(): cell_size = self._get_cell_size() table_size = self.Board.get_table_size() for i in range(table_size): for j in range(table_size): if j * cell_size <= x < (j + 1) * cell_size: if i * cell_size <= y < (i + 1) * cell_size: cell = i * self.Board.get_table_size() + j # if cell is selectable if cell in self.Board.selectable_list(): next_c = cell self.Board_drawn = self.Board.select_cell( next_c, return_board_before_drop=True) if not self.Board.is_game_end(): self.drop_timer.start() self.update() else: self.drop_timer.start() self.update() self.is_game_over.emit(True) def init_ui(self): """GUIBoardのUIの初期化""" self.resize(GUIBoard.BOARD_SIZE, GUIBoard.BOARD_SIZE) def _get_cell_size(self): """それぞれのマスの大きさをピクセル数で返す""" return GUIBoard.BOARD_SIZE // self.Board.get_table_size() def paintEvent(self, event): self._draw_board(self.Board_drawn) def _draw_a_cell(self, painter, i, j, value): """Draw cell(i, j), value is the number of the cell""" color_table = [ "white", "#01bfa6", "#0b9cdb", "#ff5d1a", "#ffa81b", "#f22c43", "#7b50ff", "#e33b92", "black", "green", "blue", "orange", "red", "indigo", "peru" ] color = QColor(color_table[value]) painter.fillRect(j * self._get_cell_size(), i * self._get_cell_size(), self._get_cell_size(), self._get_cell_size(), color) font = QFont("Times", 0.20 * self._get_cell_size()) painter.setFont(font) pen_color = QColor("white") painter.setPen(pen_color) painter.drawText(j * self._get_cell_size(), i * self._get_cell_size(), self._get_cell_size() - 1, self._get_cell_size() - 1, Qt.AlignCenter, str(value)) def _draw_board(self, board): painter = QPainter(self) board_drawn = board.get_board() for i in range(self.Board.get_table_size()): for j in range(self.Board.get_table_size()): self._draw_a_cell( painter, i, j, board_drawn[i * self.Board.get_table_size() + j])
class LeftMenuPlaylist(QListWidget): """ This class represents the menu with video files that is visible in the left menu. Only shows when a video is playing. """ playing_item_change = pyqtSignal(int) # file index list_loaded = pyqtSignal() item_should_play = pyqtSignal( ) # no info required, a double click always follow a click event def __init__(self, parent): QListWidget.__init__(self, parent) self.files_data = [] self.loaded_list = False self.loading_list = False self.active_index = -1 self.infohash = None self.itemClicked.connect(self.on_item_clicked) self.itemDoubleClicked.connect(self.on_item_double_clicked) self.files_request_mgr = None self.files_request_timer = None def set_loading(self): self.clear() self.addItem("Loading...") self.loaded_list = False self.loading_list = True def load_list(self, infohash): self.infohash = infohash self.set_loading() if self.files_request_timer: self.files_request_timer.stop() self.files_request_timer = QTimer() self.files_request_timer.timeout.connect( self.perform_get_files_request) self.files_request_timer.start(1000) def perform_get_files_request(self): self.files_request_mgr = TriblerRequestManager() self.files_request_mgr.perform_request("downloads/%s/files" % self.infohash, self.on_received_files, capture_errors=False) def on_received_files(self, files): if not files: return if "files" not in files or not files["files"]: return if self.files_request_timer: self.files_request_timer.stop() self.files_request_timer = None self.set_files(files["files"]) self.loaded_list = True self.loading_list = False self.list_loaded.emit() def get_largest_file(self): largest_file = None largest_index = None for index, file_info in enumerate(self.files_data): if is_video_file(file_info["name"]) and \ (largest_file is None or file_info["size"] > largest_file["size"]): largest_file = file_info largest_index = index return largest_index, largest_file def set_files(self, files): self.clear() self.files_data = [] for file_info in files: if is_video_file(file_info['name']): self.addItem(file_info['name']) self.files_data.append(file_info) def set_active_index(self, file_index): cur_ind = 0 for ind, file_info in enumerate(self.files_data): if ind == file_index: self.item(cur_ind).setSelected(True) self.setFocus() break cur_ind += 1 def get_file_info(self, menu_index): """ Get the file info, based on the menu index """ return self.files_data[menu_index] if menu_index < len( self.files_data) else None def on_item_clicked(self, item): item_ind = self.row(item) if self.loaded_list: self.playing_item_change.emit(item_ind) def on_item_double_clicked(self, item): self.item_should_play.emit()
class CuraEngineBackend(QObject, Backend): backendError = Signal() ## Starts the back-end plug-in. # # This registers all the signal listeners and prepares for communication # with the back-end in general. # CuraEngineBackend is exposed to qml as well. def __init__(self) -> None: super().__init__() # Find out where the engine is located, and how it is called. # This depends on how Cura is packaged and which OS we are running on. executable_name = "CuraEngine" if Platform.isWindows(): executable_name += ".exe" default_engine_location = executable_name search_path = [ os.path.abspath(os.path.dirname(sys.executable)), os.path.abspath( os.path.join(os.path.dirname(sys.executable), "bin")), os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")), os.path.join(CuraApplication.getInstallPrefix(), "bin"), os.path.dirname(os.path.abspath(sys.executable)), ] for path in search_path: engine_path = os.path.join(path, executable_name) if os.path.isfile(engine_path): default_engine_location = engine_path break if Platform.isLinux() and not default_engine_location: if not os.getenv("PATH"): raise OSError( "There is something wrong with your Linux installation.") for pathdir in cast(str, os.getenv("PATH")).split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break self._application = CuraApplication.getInstance( ) #type: CuraApplication self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel] self._machine_error_checker = None #type: Optional[MachineErrorChecker] if not default_engine_location: raise EnvironmentError("Could not find CuraEngine") Logger.log("i", "Found CuraEngine at: %s", default_engine_location) default_engine_location = os.path.abspath(default_engine_location) self._application.getPreferences().addPreference( "backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False #type: bool self._onActiveViewChanged() self._stored_layer_data = [] # type: List[Arcus.PythonMessage] self._stored_optimized_layer_data = { } # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._scene = self._application.getController().getScene( ) #type: Scene self._scene.sceneChanged.connect(self._onSceneChanged) # Triggers for auto-slicing. Auto-slicing is triggered as follows: # - auto-slicing is started with a timer # - whenever there is a value change, we start the timer # - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the # auto-slicing timer when that error check is finished # If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished # to start the auto-slicing timer again. # self._global_container_stack = None #type: Optional[ContainerStack] # Listeners for receiving messages from the back-end. self._message_handlers["cura.proto.Layer"] = self._onLayerMessage self._message_handlers[ "cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage self._message_handlers["cura.proto.Progress"] = self._onProgressMessage self._message_handlers[ "cura.proto.GCodeLayer"] = self._onGCodeLayerMessage self._message_handlers[ "cura.proto.GCodePrefix"] = self._onGCodePrefixMessage self._message_handlers[ "cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates self._message_handlers[ "cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._start_slice_job = None #type: Optional[StartSliceJob] self._start_slice_job_build_plate = None #type: Optional[int] self._slicing = False #type: bool # Are we currently slicing? self._restart = False #type: bool # Back-end is currently restarting? self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers. self._build_plates_to_be_sliced = [ ] #type: List[int] # what needs slicing? self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer self._error_message = None #type: Optional[Message] # Pop-up message that shows errors. self._last_num_objects = defaultdict( int ) #type: Dict[int, int] # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [ ] #type: List[SceneNode] # scene change is postponed (by a tool) self._slice_start_time = None #type: Optional[float] self._is_disabled = False #type: bool self._application.getPreferences().addPreference( "general/auto_slice", False) self._use_timer = False #type: bool # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # This timer will group them up, and only slice for the last setting changed signal. # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. self._change_timer = QTimer() #type: QTimer self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() self._application.getPreferences().preferenceChanged.connect( self._onPreferencesChanged) self._application.initializationFinished.connect(self.initialize) def initialize(self) -> None: self._multi_build_plate_model = self._application.getMultiBuildPlateModel( ) self._application.getController().activeViewChanged.connect( self._onActiveViewChanged) if self._multi_build_plate_model: self._multi_build_plate_model.activeBuildPlateChanged.connect( self._onActiveViewChanged) self._application.getMachineManager().globalContainerChanged.connect( self._onGlobalStackChanged) self._onGlobalStackChanged() # extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash ExtruderManager.getInstance().extrudersChanged.connect( self._extruderChanged) self.backendQuit.connect(self._onBackendQuit) self.backendConnected.connect(self._onBackendConnected) # When a tool operation is in progress, don't slice. So we need to listen for tool operations. self._application.getController().toolOperationStarted.connect( self._onToolOperationStarted) self._application.getController().toolOperationStopped.connect( self._onToolOperationStopped) self._machine_error_checker = self._application.getMachineErrorChecker( ) self._machine_error_checker.errorCheckFinished.connect( self._onStackErrorCheckFinished) ## Terminate the engine process. # # This function should terminate the engine process. # Called when closing the application. def close(self) -> None: # Terminate CuraEngine if it is still running at this point self._terminate() ## Get the command that is used to call the engine. # This is useful for debugging and used to actually start the engine. # \return list of commands and args / parameters. def getEngineCommand(self) -> List[str]: command = [ self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "" ] parser = argparse.ArgumentParser(prog="cura", add_help=False) parser.add_argument( "--debug", action="store_true", default=False, help="Turn on the debug mode by setting this option.") known_args = vars(parser.parse_known_args()[0]) if known_args["debug"]: command.append("-vvv") return command ## Emitted when we get a message containing print duration and material amount. # This also implies the slicing has finished. # \param time The amount of time the print will take. # \param material_amount The amount of material the print will use. printDurationMessage = Signal() ## Emitted when the slicing process starts. slicingStarted = Signal() ## Emitted when the slicing process is aborted forcefully. slicingCancelled = Signal() @pyqtSlot() def stopSlicing(self) -> None: self.setState(BackendState.NotStarted) if self._slicing: # We were already slicing. Stop the old job. self._terminate() self._createSocket() if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon. Logger.log("i", "Aborting process layers job...") self._process_layers_job.abort() self._process_layers_job = None if self._error_message: self._error_message.hide() ## Manually triggers a reslice @pyqtSlot() def forceSlice(self) -> None: self.markSliceAll() self.slice() ## Perform a slice of the scene. def slice(self) -> None: Logger.log("i", "Starting to slice...") self._slice_start_time = time() if not self._build_plates_to_be_sliced: self.processingProgress.emit(1.0) Logger.log( "w", "Slice unnecessary, nothing has changed that needs reslicing.") self.setState(BackendState.Done) return if self._process_layers_job: Logger.log("d", "Process layers job still busy, trying later.") return if not hasattr(self._scene, "gcode_dict"): self._scene.gcode_dict = { } #type: ignore #Because we are creating the missing attribute here. # see if we really have to slice active_build_plate = self._application.getMultiBuildPlateModel( ).activeBuildPlate build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0) Logger.log( "d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced) num_objects = self._numObjectsPerBuildPlate() self._stored_layer_data = [] if build_plate_to_be_sliced not in num_objects or num_objects[ build_plate_to_be_sliced] == 0: self._scene.gcode_dict[build_plate_to_be_sliced] = [ ] #type: ignore #Because we created this attribute above. Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced) if self._build_plates_to_be_sliced: self.slice() return self._stored_optimized_layer_data[build_plate_to_be_sliced] = [] if self._application.getPrintInformation( ) and build_plate_to_be_sliced == active_build_plate: self._application.getPrintInformation().setToZeroPrintInformation( build_plate_to_be_sliced) if self._process is None: # type: ignore self._createSocket() self.stopSlicing() self._engine_is_fresh = False # Yes we're going to use the engine self.processingProgress.emit(0.0) self.backendStateChange.emit(BackendState.NotStarted) self._scene.gcode_dict[build_plate_to_be_sliced] = [ ] #type: ignore #[] indexed by build plate number self._slicing = True self.slicingStarted.emit() self.determineAutoSlicing() # Switch timer on or off if appropriate slice_message = self._socket.createMessage("cura.proto.Slice") self._start_slice_job = StartSliceJob(slice_message) self._start_slice_job_build_plate = build_plate_to_be_sliced self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate) self._start_slice_job.start() self._start_slice_job.finished.connect(self._onStartSliceCompleted) ## Terminate the engine process. # Start the engine process by calling _createSocket() def _terminate(self) -> None: self._slicing = False self._stored_layer_data = [] if self._start_slice_job_build_plate in self._stored_optimized_layer_data: del self._stored_optimized_layer_data[ self._start_slice_job_build_plate] if self._start_slice_job is not None: self._start_slice_job.cancel() self.slicingCancelled.emit() self.processingProgress.emit(0) Logger.log("d", "Attempting to kill the engine process") if self._application.getUseExternalBackend(): return if self._process is not None: # type: ignore Logger.log("d", "Killing engine process") try: self._process.terminate() # type: ignore Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) # type: ignore self._process = None # type: ignore except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this. Logger.log( "d", "Exception occurred while trying to kill the engine %s", str(e)) ## Event handler to call when the job to initiate the slicing process is # completed. # # When the start slice job is successfully completed, it will be happily # slicing. This function handles any errors that may occur during the # bootstrapping of a slice job. # # \param job The start slice job that was just finished. def _onStartSliceCompleted(self, job: StartSliceJob) -> None: if self._error_message: self._error_message.hide() # Note that cancelled slice jobs can still call this method. if self._start_slice_job is job: self._start_slice_job = None if job.isCancelled() or job.getError() or job.getResult( ) == StartJobResult.Error: self.setState(BackendState.Error) self.backendError.emit(job) return if job.getResult() == StartJobResult.MaterialIncompatible: if self._application.platformActivity: self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice with the current material as it is incompatible with the selected machine or configuration." ), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.setState(BackendState.Error) self.backendError.emit(job) else: self.setState(BackendState.NotStarted) return if job.getResult() == StartJobResult.SettingError: if self._application.platformActivity: if not self._global_container_stack: Logger.log( "w", "Global container stack not assigned to CuraEngineBackend!" ) return extruders = ExtruderManager.getInstance( ).getActiveExtruderStacks() error_keys = [] #type: List[str] for extruder in extruders: error_keys.extend(extruder.getErrorKeys()) if not extruders: error_keys = self._global_container_stack.getErrorKeys() error_labels = set() for key in error_keys: for stack in [ self._global_container_stack ] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack. definitions = cast( DefinitionContainerInterface, stack.getBottom()).findDefinitions(key=key) if definitions: break #Found it! No need to continue search. else: #No stack has a definition for this setting. Logger.log( "w", "When checking settings for errors, unable to find definition for key: {key}" .format(key=key)) continue error_labels.add(definitions[0].label) self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice with the current settings. The following settings have errors: {0}" ).format(", ".join(error_labels)), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.setState(BackendState.Error) self.backendError.emit(job) else: self.setState(BackendState.NotStarted) return elif job.getResult() == StartJobResult.ObjectSettingError: errors = {} for node in DepthFirstIterator( self._application.getController().getScene().getRoot()): stack = node.callDecoration("getStack") if not stack: continue for key in stack.getErrorKeys(): if not self._global_container_stack: Logger.log( "e", "CuraEngineBackend does not have global_container_stack assigned." ) continue definition = cast(DefinitionContainerInterface, self._global_container_stack.getBottom() ).findDefinitions(key=key) if not definition: Logger.log( "e", "When checking settings for errors, unable to find definition for key {key} in per-object stack." .format(key=key)) continue errors[key] = definition[0].label self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}" ).format(error_labels=", ".join(errors.values())), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.setState(BackendState.Error) self.backendError.emit(job) return if job.getResult() == StartJobResult.BuildPlateError: if self._application.platformActivity: self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice because the prime tower or prime position(s) are invalid." ), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.setState(BackendState.Error) self.backendError.emit(job) else: self.setState(BackendState.NotStarted) if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder: self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." ) % job.getMessage(), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.setState(BackendState.Error) self.backendError.emit(job) return if job.getResult() == StartJobResult.NothingToSlice: if self._application.platformActivity: self._error_message = Message(catalog.i18nc( "@info:status", "Please review settings and check if your models:" "\n- Fit within the build volume" "\n- Are assigned to an enabled extruder" "\n- Are not all set as modifier meshes"), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.setState(BackendState.Error) self.backendError.emit(job) else: self.setState(BackendState.NotStarted) self._invokeSlice() return # Preparation completed, send it to the backend. self._socket.sendMessage(job.getSliceMessage()) # Notify the user that it's now up to the backend to do it's job self.setState(BackendState.Processing) if self._slice_start_time: Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time) ## Determine enable or disable auto slicing. Return True for enable timer and False otherwise. # It disables when # - preference auto slice is off # - decorator isBlockSlicing is found (used in g-code reader) def determineAutoSlicing(self) -> bool: enable_timer = True self._is_disabled = False if not self._application.getPreferences().getValue( "general/auto_slice"): enable_timer = False for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("isBlockSlicing"): enable_timer = False self.setState(BackendState.Disabled) self._is_disabled = True gcode_list = node.callDecoration("getGCodeList") if gcode_list is not None: self._scene.gcode_dict[node.callDecoration( "getBuildPlateNumber" )] = gcode_list #type: ignore #Because we generate this attribute dynamically. if self._use_timer == enable_timer: return self._use_timer if enable_timer: self.setState(BackendState.NotStarted) self.enableTimer() return True else: self.disableTimer() return False ## Return a dict with number of objects per build plate def _numObjectsPerBuildPlate(self) -> Dict[int, int]: num_objects = defaultdict(int) #type: Dict[int, int] for node in DepthFirstIterator(self._scene.getRoot()): # Only count sliceable objects if node.callDecoration("isSliceable"): build_plate_number = node.callDecoration("getBuildPlateNumber") if build_plate_number is not None: num_objects[build_plate_number] += 1 return num_objects ## Listener for when the scene has changed. # # This should start a slice if the scene is now ready to slice. # # \param source The scene node that was changed. def _onSceneChanged(self, source: SceneNode) -> None: if not source.callDecoration("isSliceable"): return # This case checks if the source node is a node that contains GCode. In this case the # current layer data is removed so the previous data is not rendered - CURA-4821 if source.callDecoration("isBlockSlicing") and source.callDecoration( "getLayerData"): self._stored_optimized_layer_data = {} build_plate_changed = set() source_build_plate_number = source.callDecoration( "getBuildPlateNumber") if source == self._scene.getRoot(): # we got the root node num_objects = self._numObjectsPerBuildPlate() for build_plate_number in list( self._last_num_objects.keys()) + list(num_objects.keys()): if build_plate_number not in self._last_num_objects or num_objects[ build_plate_number] != self._last_num_objects[ build_plate_number]: self._last_num_objects[build_plate_number] = num_objects[ build_plate_number] build_plate_changed.add(build_plate_number) else: # we got a single scenenode if not source.callDecoration("isGroup"): mesh_data = source.getMeshData() if mesh_data is None or mesh_data.getVertices() is None: return # There are some SceneNodes that do not have any build plate associated, then do not add to the list. if source_build_plate_number is not None: build_plate_changed.add(source_build_plate_number) if not build_plate_changed: return if self._tool_active: # do it later, each source only has to be done once if source not in self._postponed_scene_change_sources: self._postponed_scene_change_sources.append(source) return self.stopSlicing() for build_plate_number in build_plate_changed: if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) self.printDurationMessage.emit(source_build_plate_number, {}, []) self.processingProgress.emit(0.0) self._clearLayerData(build_plate_changed) self._invokeSlice() ## Called when an error occurs in the socket connection towards the engine. # # \param error The exception that occurred. def _onSocketError(self, error: Arcus.Error) -> None: if self._application.isShuttingDown(): return super()._onSocketError(error) if error.getErrorCode() == Arcus.ErrorCode.Debug: return self._terminate() self._createSocket() if error.getErrorCode() not in [ Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug ]: Logger.log("w", "A socket error caused the connection to be reset") # _terminate()' function sets the job status to 'cancel', after reconnecting to another Port the job status # needs to be updated. Otherwise backendState is "Unable To Slice" if error.getErrorCode( ) == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None: self._start_slice_job.setIsCancelled(False) # Check if there's any slicable object in the scene. def hasSlicableObject(self) -> bool: has_slicable = False for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("isSliceable"): has_slicable = True break return has_slicable ## Remove old layer data (if any) def _clearLayerData(self, build_plate_numbers: Set = None) -> None: # Clear out any old gcode self._scene.gcode_dict = {} # type: ignore for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData"): if not build_plate_numbers or node.callDecoration( "getBuildPlateNumber") in build_plate_numbers: # We can asume that all nodes have a parent as we're looping through the scene (and filter out root) cast(SceneNode, node.getParent()).removeChild(node) def markSliceAll(self) -> None: for build_plate_number in range( self._application.getMultiBuildPlateModel().maxBuildPlate + 1): if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) ## Convenient function: mark everything to slice, emit state and clear layer data def needsSlicing(self) -> None: # CURA-6604: If there's no slicable object, do not (try to) trigger slice, which will clear all the current # gcode. This can break Gcode file loading if it tries to remove it afterwards. if not self.hasSlicableObject(): return self.determineAutoSlicing() self.stopSlicing() self.markSliceAll() self.processingProgress.emit(0.0) if not self._use_timer: # With manually having to slice, we want to clear the old invalid layer data. self._clearLayerData() ## A setting has changed, so check if we must reslice. # \param instance The setting instance that has changed. # \param property The property of the setting instance that has changed. def _onSettingChanged(self, instance: SettingInstance, property: str) -> None: if property == "value": # Only reslice if the value has changed. self.needsSlicing() self._onChanged() elif property == "validationState": if self._use_timer: self._change_timer.stop() def _onStackErrorCheckFinished(self) -> None: self.determineAutoSlicing() if self._is_disabled: return if not self._slicing and self._build_plates_to_be_sliced: self.needsSlicing() self._onChanged() ## Called when a sliced layer data message is received from the engine. # # \param message The protobuf message containing sliced layer data. def _onLayerMessage(self, message: Arcus.PythonMessage) -> None: self._stored_layer_data.append(message) ## Called when an optimized sliced layer data message is received from the engine. # # \param message The protobuf message containing sliced layer data. def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None: if self._start_slice_job_build_plate is not None: if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: self._stored_optimized_layer_data[ self._start_slice_job_build_plate] = [] self._stored_optimized_layer_data[ self._start_slice_job_build_plate].append(message) ## Called when a progress message is received from the engine. # # \param message The protobuf message containing the slicing progress. def _onProgressMessage(self, message: Arcus.PythonMessage) -> None: self.processingProgress.emit(message.amount) self.setState(BackendState.Processing) def _invokeSlice(self) -> None: if self._use_timer: # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # otherwise business as usual if self._machine_error_checker is None: self._change_timer.stop() return if self._machine_error_checker.needToWaitForResult: self._change_timer.stop() else: self._change_timer.start() ## Called when the engine sends a message that slicing is finished. # # \param message The protobuf message signalling that slicing is finished. def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None: self.setState(BackendState.Done) self.processingProgress.emit(1.0) try: gcode_list = self._scene.gcode_dict[ self. _start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically. except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end. gcode_list = [] for index, line in enumerate(gcode_list): replaced = line.replace( "{print_time}", str(self._application.getPrintInformation().currentPrintTime. getDisplayString(DurationFormat.Format.ISO8601))) replaced = replaced.replace( "{filament_amount}", str(self._application.getPrintInformation().materialLengths)) replaced = replaced.replace( "{filament_weight}", str(self._application.getPrintInformation().materialWeights)) replaced = replaced.replace( "{filament_cost}", str(self._application.getPrintInformation().materialCosts)) replaced = replaced.replace( "{jobname}", str(self._application.getPrintInformation().jobName)) gcode_list[index] = replaced self._slicing = False if self._slice_start_time: Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time) Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate())) # See if we need to process the sliced layers job. active_build_plate = self._application.getMultiBuildPlateModel( ).activeBuildPlate if (self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate and active_build_plate not in self._build_plates_to_be_sliced): self._startProcessSlicedLayersJob(active_build_plate) # self._onActiveViewChanged() self._start_slice_job_build_plate = None Logger.log("d", "See if there is more to slice...") # Somehow this results in an Arcus Error # self.slice() # Call slice again using the timer, allowing the backend to restart if self._build_plates_to_be_sliced: self.enableTimer( ) # manually enable timer to be able to invoke slice, also when in manual slice mode self._invokeSlice() ## Called when a g-code message is received from the engine. # # \param message The protobuf message containing g-code, encoded as UTF-8. def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None: try: self._scene.gcode_dict[self._start_slice_job_build_plate].append( message.data.decode("utf-8", "replace") ) #type: ignore #Because we generate this attribute dynamically. except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end. pass # Throw the message away. ## Called when a g-code prefix message is received from the engine. # # \param message The protobuf message containing the g-code prefix, # encoded as UTF-8. def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None: try: self._scene.gcode_dict[self._start_slice_job_build_plate].insert( 0, message.data.decode("utf-8", "replace") ) #type: ignore #Because we generate this attribute dynamically. except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end. pass # Throw the message away. ## Creates a new socket connection. def _createSocket(self, protocol_file: str = None) -> None: if not protocol_file: plugin_path = PluginRegistry.getInstance().getPluginPath( self.getPluginId()) if not plugin_path: Logger.log("e", "Could not get plugin path!", self.getPluginId()) return protocol_file = os.path.abspath( os.path.join(plugin_path, "Cura.proto")) super()._createSocket(protocol_file) self._engine_is_fresh = True ## Called when anything has changed to the stuff that needs to be sliced. # # This indicates that we should probably re-slice soon. def _onChanged(self, *args: Any, **kwargs: Any) -> None: self.needsSlicing() if self._use_timer: # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # otherwise business as usual if self._machine_error_checker is None: self._change_timer.stop() return if self._machine_error_checker.needToWaitForResult: self._change_timer.stop() else: self._change_timer.start() ## Called when a print time message is received from the engine. # # \param message The protobuf message containing the print time per feature and # material amount per extruder def _onPrintTimeMaterialEstimates(self, message: Arcus.PythonMessage) -> None: material_amounts = [] for index in range(message.repeatedMessageCount("materialEstimates")): material_amounts.append( message.getRepeatedMessage("materialEstimates", index).material_amount) times = self._parseMessagePrintTimes(message) self.printDurationMessage.emit(self._start_slice_job_build_plate, times, material_amounts) ## Called for parsing message to retrieve estimated time per feature # # \param message The protobuf message containing the print time per feature def _parseMessagePrintTimes( self, message: Arcus.PythonMessage) -> Dict[str, float]: result = { "inset_0": message.time_inset_0, "inset_x": message.time_inset_x, "skin": message.time_skin, "infill": message.time_infill, "support_infill": message.time_support_infill, "support_interface": message.time_support_interface, "support": message.time_support, "skirt": message.time_skirt, "prime_tower": message.time_prime_tower, "travel": message.time_travel, "retract": message.time_retract, "none": message.time_none } return result ## Called when the back-end connects to the front-end. def _onBackendConnected(self) -> None: if self._restart: self._restart = False self._onChanged() ## Called when the user starts using some tool. # # When the user starts using a tool, we should pause slicing to prevent # continuously slicing while the user is dragging some tool handle. # # \param tool The tool that the user is using. def _onToolOperationStarted(self, tool: Tool) -> None: self._tool_active = True # Do not react on scene change self.disableTimer() # Restart engine as soon as possible, we know we want to slice afterwards if not self._engine_is_fresh: self._terminate() self._createSocket() ## Called when the user stops using some tool. # # This indicates that we can safely start slicing again. # # \param tool The tool that the user was using. def _onToolOperationStopped(self, tool: Tool) -> None: self._tool_active = False # React on scene change again self.determineAutoSlicing() # Switch timer on if appropriate # Process all the postponed scene changes while self._postponed_scene_change_sources: source = self._postponed_scene_change_sources.pop(0) self._onSceneChanged(source) def _startProcessSlicedLayersJob(self, build_plate_number: int) -> None: self._process_layers_job = ProcessSlicedLayersJob( self._stored_optimized_layer_data[build_plate_number]) self._process_layers_job.setBuildPlate(build_plate_number) self._process_layers_job.finished.connect( self._onProcessLayersFinished) self._process_layers_job.start() ## Called when the user changes the active view mode. def _onActiveViewChanged(self) -> None: view = self._application.getController().getActiveView() if view: active_build_plate = self._application.getMultiBuildPlateModel( ).activeBuildPlate if view.getPluginId( ) == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet. self._layer_view_active = True # There is data and we're not slicing at the moment # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. # TODO: what build plate I am slicing if (active_build_plate in self._stored_optimized_layer_data and not self._slicing and not self._process_layers_job and active_build_plate not in self._build_plates_to_be_sliced): self._startProcessSlicedLayersJob(active_build_plate) else: self._layer_view_active = False ## Called when the back-end self-terminates. # # We should reset our state and start listening for new connections. def _onBackendQuit(self) -> None: if not self._restart: if self._process: # type: ignore Logger.log( "d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait()) # type: ignore self._process = None # type: ignore ## Called when the global container stack changes def _onGlobalStackChanged(self) -> None: if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect( self._onSettingChanged) self._global_container_stack.containersChanged.disconnect( self._onChanged) for extruder in self._global_container_stack.extruderList: extruder.propertyChanged.disconnect(self._onSettingChanged) extruder.containersChanged.disconnect(self._onChanged) self._global_container_stack = self._application.getMachineManager( ).activeMachine if self._global_container_stack: self._global_container_stack.propertyChanged.connect( self._onSettingChanged ) # Note: Only starts slicing when the value changed. self._global_container_stack.containersChanged.connect( self._onChanged) for extruder in self._global_container_stack.extruderList: extruder.propertyChanged.connect(self._onSettingChanged) extruder.containersChanged.connect(self._onChanged) self._onChanged() def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None: if job.getBuildPlate() in self._stored_optimized_layer_data: del self._stored_optimized_layer_data[job.getBuildPlate()] else: Logger.log( "w", "The optimized layer data was already deleted for buildplate %s", job.getBuildPlate()) self._process_layers_job = None Logger.log("d", "See if there is more to slice(2)...") self._invokeSlice() ## Connect slice function to timer. def enableTimer(self) -> None: if not self._use_timer: self._change_timer.timeout.connect(self.slice) self._use_timer = True ## Disconnect slice function from timer. # This means that slicing will not be triggered automatically def disableTimer(self) -> None: if self._use_timer: self._use_timer = False self._change_timer.timeout.disconnect(self.slice) def _onPreferencesChanged(self, preference: str) -> None: if preference != "general/auto_slice": return auto_slice = self.determineAutoSlicing() if auto_slice: self._change_timer.start() ## Tickle the backend so in case of auto slicing, it starts the timer. def tickle(self) -> None: if self._use_timer: self._change_timer.start() def _extruderChanged(self) -> None: if not self._multi_build_plate_model: Logger.log( "w", "CuraEngineBackend does not have multi_build_plate_model assigned!" ) return for build_plate_number in range( self._multi_build_plate_model.maxBuildPlate + 1): if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) self._invokeSlice()
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self._version = __version__ self.setWindowIcon(QIcon(":/logo.png")) self.setWindowTitle("Tasmota Device Manager {}".format(self._version)) self.unknown = [] self.env = TasmotaEnvironment() self.device = None self.topics = [] self.mqtt_queue = [] self.fulltopic_queue = [] # ensure TDM directory exists in the user directory if not os.path.isdir("{}/TDM".format(QDir.homePath())): os.mkdir("{}/TDM".format(QDir.homePath())) self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()), QSettings.IniFormat) self.devices = QSettings("{}/TDM/devices.cfg".format(QDir.homePath()), QSettings.IniFormat) self.setMinimumSize(QSize(1000, 600)) # configure logging logging.basicConfig(filename="{}/TDM/tdm.log".format(QDir.homePath()), level=self.settings.value("loglevel", "INFO"), datefmt="%Y-%m-%d %H:%M:%S", format='%(asctime)s [%(levelname)s] %(message)s') logging.info("### TDM START ###") # load devices from the devices file, create TasmotaDevices and add the to the environment for mac in self.devices.childGroups(): self.devices.beginGroup(mac) device = TasmotaDevice(self.devices.value("topic"), self.devices.value("full_topic"), self.devices.value("device_name")) device.debug = self.devices.value("debug", False, bool) device.p['Mac'] = mac.replace("-", ":") device.env = self.env self.env.devices.append(device) # load device command history self.devices.beginGroup("history") for k in self.devices.childKeys(): device.history.append(self.devices.value(k)) self.devices.endGroup() self.devices.endGroup() self.device_model = TasmotaDevicesModel(self.env) self.setup_mqtt() self.setup_main_layout() self.add_devices_tab() self.build_mainmenu() # self.build_toolbars() self.setStatusBar(QStatusBar()) pbSubs = QPushButton("Show subscriptions") pbSubs.setFlat(True) pbSubs.clicked.connect(self.showSubs) self.statusBar().addPermanentWidget(pbSubs) self.queue_timer = QTimer() self.queue_timer.timeout.connect(self.mqtt_publish_queue) self.queue_timer.start(250) self.auto_timer = QTimer() self.auto_timer.timeout.connect(self.auto_telemetry) self.load_window_state() if self.settings.value("connect_on_startup", False, bool): self.actToggleConnect.trigger() self.tele_docks = {} self.consoles = [] def setup_main_layout(self): self.mdi = QMdiArea() self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder) self.mdi.setTabsClosable(True) self.setCentralWidget(self.mdi) def setup_mqtt(self): self.mqtt = MqttClient() self.mqtt.connecting.connect(self.mqtt_connecting) self.mqtt.connected.connect(self.mqtt_connected) self.mqtt.disconnected.connect(self.mqtt_disconnected) self.mqtt.connectError.connect(self.mqtt_connectError) self.mqtt.messageSignal.connect(self.mqtt_message) def add_devices_tab(self): self.devices_list = ListWidget(self) sub = self.mdi.addSubWindow(self.devices_list) sub.setWindowState(Qt.WindowMaximized) self.devices_list.deviceSelected.connect(self.selectDevice) self.devices_list.openConsole.connect(self.openConsole) self.devices_list.openRulesEditor.connect(self.openRulesEditor) self.devices_list.openTelemetry.connect(self.openTelemetry) self.devices_list.openWebUI.connect(self.openWebUI) def load_window_state(self): wndGeometry = self.settings.value('window_geometry') if wndGeometry: self.restoreGeometry(wndGeometry) def build_mainmenu(self): mMQTT = self.menuBar().addMenu("MQTT") self.actToggleConnect = QAction(QIcon(":/disconnect.png"), "Connect") self.actToggleConnect.setCheckable(True) self.actToggleConnect.toggled.connect(self.toggle_connect) mMQTT.addAction(self.actToggleConnect) mMQTT.addAction(QIcon(), "Broker", self.setup_broker) mMQTT.addAction(QIcon(), "Autodiscovery patterns", self.patterns) mMQTT.addSeparator() mMQTT.addAction(QIcon(), "Clear obsolete retained LWTs", self.clear_LWT) mMQTT.addSeparator() mMQTT.addAction(QIcon(), "Auto telemetry period", self.auto_telemetry_period) self.actToggleAutoUpdate = QAction(QIcon(":/auto_telemetry.png"), "Auto telemetry") self.actToggleAutoUpdate.setCheckable(True) self.actToggleAutoUpdate.toggled.connect(self.toggle_autoupdate) mMQTT.addAction(self.actToggleAutoUpdate) mSettings = self.menuBar().addMenu("Settings") mSettings.addAction(QIcon(), "BSSId aliases", self.bssid) mSettings.addSeparator() mSettings.addAction(QIcon(), "Preferences", self.prefs) # mExport = self.menuBar().addMenu("Export") # mExport.addAction(QIcon(), "OpenHAB", self.openhab) def build_toolbars(self): main_toolbar = Toolbar(orientation=Qt.Horizontal, iconsize=24, label_position=Qt.ToolButtonTextBesideIcon) main_toolbar.setObjectName("main_toolbar") def initial_query(self, device, queued=False): for c in initial_commands(): cmd, payload = c cmd = device.cmnd_topic(cmd) if queued: self.mqtt_queue.append([cmd, payload]) else: self.mqtt.publish(cmd, payload, 1) def setup_broker(self): brokers_dlg = BrokerDialog() if brokers_dlg.exec_( ) == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected: self.mqtt.disconnect() def toggle_autoupdate(self, state): if state == True: if self.mqtt.state == self.mqtt.Connected: for d in self.env.devices: self.mqtt.publish(d.cmnd_topic('STATUS'), payload=8) self.auto_timer.setInterval( self.settings.value("autotelemetry", 5000, int)) self.auto_timer.start() else: self.auto_timer.stop() def toggle_connect(self, state): if state and self.mqtt.state == self.mqtt.Disconnected: self.broker_hostname = self.settings.value('hostname', 'localhost') self.broker_port = self.settings.value('port', 1883, int) self.broker_username = self.settings.value('username') self.broker_password = self.settings.value('password') self.mqtt.hostname = self.broker_hostname self.mqtt.port = self.broker_port if self.broker_username: self.mqtt.setAuth(self.broker_username, self.broker_password) self.mqtt.connectToHost() elif not state and self.mqtt.state == self.mqtt.Connected: self.mqtt_disconnect() def auto_telemetry(self): if self.mqtt.state == self.mqtt.Connected: for d in self.env.devices: self.mqtt.publish(d.cmnd_topic('STATUS'), payload=8) def mqtt_connect(self): self.broker_hostname = self.settings.value('hostname', 'localhost') self.broker_port = self.settings.value('port', 1883, int) self.broker_username = self.settings.value('username') self.broker_password = self.settings.value('password') self.mqtt.hostname = self.broker_hostname self.mqtt.port = self.broker_port if self.broker_username: self.mqtt.setAuth(self.broker_username, self.broker_password) if self.mqtt.state == self.mqtt.Disconnected: self.mqtt.connectToHost() def mqtt_disconnect(self): self.mqtt.disconnectFromHost() def mqtt_connecting(self): self.statusBar().showMessage("Connecting to broker") def mqtt_connected(self): self.actToggleConnect.setIcon(QIcon(":/connect.png")) self.actToggleConnect.setText("Disconnect") self.statusBar().showMessage("Connected to {}:{} as {}".format( self.broker_hostname, self.broker_port, self.broker_username if self.broker_username else '[anonymous]')) self.mqtt_subscribe() def mqtt_subscribe(self): # clear old topics self.topics.clear() custom_patterns.clear() # load custom autodiscovery patterns self.settings.beginGroup("Patterns") for k in self.settings.childKeys(): custom_patterns.append(self.settings.value(k)) self.settings.endGroup() # expand fulltopic patterns to subscribable topics for pat in default_patterns: # tasmota default and SO19 self.topics += expand_fulltopic(pat) # check if custom patterns can be matched by default patterns for pat in custom_patterns: if pat.startswith("%prefix%") or pat.split('/')[1] == "%prefix%": continue # do nothing, default subcriptions will match this topic else: self.topics += expand_fulltopic(pat) for d in self.env.devices: # if device has a non-standard pattern, check if the pattern is found in the custom patterns if not d.is_default() and d.p['FullTopic'] not in custom_patterns: # if pattern is not found then add the device topics to subscription list. # if the pattern is found, it will be matched without implicit subscription self.topics += expand_fulltopic(d.p['FullTopic']) # passing a list of tuples as recommended by paho self.mqtt.subscribe([(topic, 0) for topic in self.topics]) @pyqtSlot(str, str) def mqtt_publish(self, t, p): self.mqtt.publish(t, p) def mqtt_publish_queue(self): for q in self.mqtt_queue: t, p = q self.mqtt.publish(t, p) self.mqtt_queue.pop(self.mqtt_queue.index(q)) def mqtt_disconnected(self): self.actToggleConnect.setIcon(QIcon(":/disconnect.png")) self.actToggleConnect.setText("Connect") self.statusBar().showMessage("Disconnected") def mqtt_connectError(self, rc): reason = { 1: "Incorrect protocol version", 2: "Invalid client identifier", 3: "Server unavailable", 4: "Bad username or password", 5: "Not authorized", } self.statusBar().showMessage("Connection error: {}".format(reason[rc])) self.actToggleConnect.setChecked(False) def mqtt_message(self, topic, msg): # try to find a device by matching known FullTopics against the MQTT topic of the message device = self.env.find_device(topic) if device: if topic.endswith("LWT"): if not msg: msg = "Offline" device.update_property("LWT", msg) if msg == 'Online': # known device came online, query initial state self.initial_query(device, True) else: # forward the message for processing device.parse_message(topic, msg) if device.debug: logging.debug("MQTT: %s %s", topic, msg) else: # unknown device, start autodiscovery process if topic.endswith("LWT"): self.env.lwts.append(topic) logging.info("DISCOVERY: LWT from an unknown device %s", topic) # STAGE 1 # load default and user-provided FullTopic patterns and for all the patterns, # try matching the LWT topic (it follows the device's FullTopic syntax for p in default_patterns + custom_patterns: match = re.fullmatch( p.replace("%topic%", "(?P<topic>.*?)").replace( "%prefix%", "(?P<prefix>.*?)") + ".*$", topic) if match: # assume that the matched topic is the one configured in device settings possible_topic = match.groupdict().get('topic') if possible_topic not in ('tele', 'stat'): # if the assumed topic is different from tele or stat, there is a chance that it's a valid topic # query the assumed device for its FullTopic. False positives won't reply. possible_topic_cmnd = p.replace( "%prefix%", "cmnd").replace( "%topic%", possible_topic) + "FullTopic" logging.debug( "DISCOVERY: Asking an unknown device for FullTopic at %s", possible_topic_cmnd) self.mqtt_queue.append([possible_topic_cmnd, ""]) elif topic.endswith("RESULT") or topic.endswith( "FULLTOPIC"): # reply from an unknown device # STAGE 2 full_topic = loads(msg).get('FullTopic') if full_topic: # the device replies with its FullTopic # here the Topic is extracted using the returned FullTopic, identifying the device parsed = parse_topic(full_topic, topic) if parsed: # got a match, we query the device's MAC address in case it's a known device that had its topic changed logging.debug( "DISCOVERY: topic %s is matched by fulltopic %s", topic, full_topic) d = self.env.find_device(topic=parsed['topic']) if d: d.update_property("FullTopic", full_topic) else: logging.info( "DISCOVERY: Discovered topic=%s with fulltopic=%s", parsed['topic'], full_topic) d = TasmotaDevice(parsed['topic'], full_topic) self.env.devices.append(d) self.device_model.addDevice(d) logging.debug( "DISCOVERY: Sending initial query to topic %s", parsed['topic']) self.initial_query(d, True) tele_topic = d.tele_topic("LWT") if tele_topic in self.env.lwts: self.env.lwts.remove(tele_topic) d.update_property("LWT", "Online") def export(self): fname, _ = QFileDialog.getSaveFileName(self, "Export device list as...", directory=QDir.homePath(), filter="CSV files (*.csv)") if fname: if not fname.endswith(".csv"): fname += ".csv" with open(fname, "w", encoding='utf8') as f: column_titles = [ 'mac', 'topic', 'friendly_name', 'full_topic', 'cmnd_topic', 'stat_topic', 'tele_topic', 'module', 'module_id', 'firmware', 'core' ] c = csv.writer(f) c.writerow(column_titles) for r in range(self.device_model.rowCount()): d = self.device_model.index(r, 0) c.writerow([ self.device_model.mac(d), self.device_model.topic(d), self.device_model.friendly_name(d), self.device_model.fullTopic(d), self.device_model.commandTopic(d), self.device_model.statTopic(d), self.device_model.teleTopic(d), # modules.get(self.device_model.module(d)), self.device_model.module(d), self.device_model.firmware(d), self.device_model.core(d) ]) def bssid(self): BSSIdDialog().exec_() def patterns(self): PatternsDialog().exec_() # def openhab(self): # OpenHABDialog(self.env).exec_() def showSubs(self): QMessageBox.information(self, "Subscriptions", "\n".join(sorted(self.topics))) def clear_LWT(self): dlg = ClearLWTDialog(self.env) if dlg.exec_() == ClearLWTDialog.Accepted: for row in range(dlg.lw.count()): itm = dlg.lw.item(row) if itm.checkState() == Qt.Checked: topic = itm.text() self.mqtt.publish(topic, retain=True) self.env.lwts.remove(topic) logging.info("MQTT: Cleared %s", topic) def prefs(self): dlg = PrefsDialog() if dlg.exec_() == QDialog.Accepted: update_devices = False devices_short_version = self.settings.value( "devices_short_version", True, bool) if devices_short_version != dlg.cbDevShortVersion.isChecked(): update_devices = True self.settings.setValue("devices_short_version", dlg.cbDevShortVersion.isChecked()) update_consoles = False console_font_size = self.settings.value("console_font_size", 9) if console_font_size != dlg.sbConsFontSize.value(): update_consoles = True self.settings.setValue("console_font_size", dlg.sbConsFontSize.value()) console_word_wrap = self.settings.value("console_word_wrap", True, bool) if console_word_wrap != dlg.cbConsWW.isChecked(): update_consoles = True self.settings.setValue("console_word_wrap", dlg.cbConsWW.isChecked()) if update_consoles: for c in self.consoles: c.console.setWordWrapMode(dlg.cbConsWW.isChecked()) new_font = QFont(c.console.font()) new_font.setPointSize(dlg.sbConsFontSize.value()) c.console.setFont(new_font) self.settings.sync() def auto_telemetry_period(self): curr_val = self.settings.value("autotelemetry", 5000, int) period, ok = QInputDialog.getInt( self, "Set AutoTelemetry period", "Values under 5000ms may cause increased ESP LoadAvg", curr_val, 1000) if ok: self.settings.setValue("autotelemetry", period) self.settings.sync() @pyqtSlot(TasmotaDevice) def selectDevice(self, d): self.device = d @pyqtSlot() def openTelemetry(self): if self.device: tele_widget = TelemetryWidget(self.device) self.addDockWidget(Qt.RightDockWidgetArea, tele_widget) self.mqtt_publish(self.device.cmnd_topic('STATUS'), "8") @pyqtSlot() def openConsole(self): if self.device: console_widget = ConsoleWidget(self.device) self.mqtt.messageSignal.connect(console_widget.consoleAppend) console_widget.sendCommand.connect(self.mqtt.publish) self.addDockWidget(Qt.BottomDockWidgetArea, console_widget) console_widget.command.setFocus() self.consoles.append(console_widget) @pyqtSlot() def openRulesEditor(self): if self.device: rules = RulesWidget(self.device) self.mqtt.messageSignal.connect(rules.parseMessage) rules.sendCommand.connect(self.mqtt_publish) self.mdi.setViewMode(QMdiArea.TabbedView) self.mdi.addSubWindow(rules) rules.setWindowState(Qt.WindowMaximized) rules.destroyed.connect(self.updateMDI) self.mqtt_queue.append((self.device.cmnd_topic("ruletimer"), "")) self.mqtt_queue.append((self.device.cmnd_topic("rule1"), "")) self.mqtt_queue.append((self.device.cmnd_topic("Var"), "")) self.mqtt_queue.append((self.device.cmnd_topic("Mem"), "")) @pyqtSlot() def openWebUI(self): if self.device and self.device.p.get('IPAddress'): url = QUrl("http://{}".format(self.device.p['IPAddress'])) try: webui = QWebEngineView() webui.load(url) frm_webui = QFrame() frm_webui.setWindowTitle("WebUI [{}]".format(self.device.name)) frm_webui.setFrameShape(QFrame.StyledPanel) frm_webui.setLayout(VLayout(0)) frm_webui.layout().addWidget(webui) frm_webui.destroyed.connect(self.updateMDI) self.mdi.addSubWindow(frm_webui) self.mdi.setViewMode(QMdiArea.TabbedView) frm_webui.setWindowState(Qt.WindowMaximized) except NameError: QDesktopServices.openUrl( QUrl("http://{}".format(self.device.p['IPAddress']))) def updateMDI(self): if len(self.mdi.subWindowList()) == 1: self.mdi.setViewMode(QMdiArea.SubWindowView) self.devices_list.setWindowState(Qt.WindowMaximized) def closeEvent(self, e): self.settings.setValue("version", self._version) self.settings.setValue("window_geometry", self.saveGeometry()) self.settings.setValue("views_order", ";".join(self.devices_list.views.keys())) self.settings.beginGroup("Views") for view, items in self.devices_list.views.items(): self.settings.setValue(view, ";".join(items[1:])) self.settings.endGroup() self.settings.sync() for d in self.env.devices: mac = d.p.get('Mac') topic = d.p['Topic'] full_topic = d.p['FullTopic'] device_name = d.name if mac: self.devices.beginGroup(mac.replace(":", "-")) self.devices.setValue("topic", topic) self.devices.setValue("full_topic", full_topic) self.devices.setValue("device_name", device_name) for i, h in enumerate(d.history): self.devices.setValue("history/{}".format(i), h) self.devices.endGroup() self.devices.sync() e.accept()
class DataRecordUI(QWidget): receiveLogSignal = pyqtSignal(str) def __init__(self): super(DataRecordUI, self).__init__() loadUi('./identity/ui/DataRecord.ui', self) self.setWindowIcon(QIcon('../identity/icons/icon.png')) self.setFixedSize(1011, 601) self.setWindowTitle('pc端个人隐私防护系统 - 人脸采集') # OpenCV self.cap = cv2.VideoCapture() self.faceCascade = cv2.CascadeClassifier( './identity/haarcascades/haarcascade_frontalface_default.xml') self.logQueue = queue.Queue() # 日志队列 # 图像捕获 self.isExternalCameraUsed = False self.useExternalCameraCheckBox.stateChanged.connect( lambda: self.useExternalCamera(self.useExternalCameraCheckBox)) self.startWebcamButton.toggled.connect(self.startWebcam) self.startWebcamButton.setCheckable(True) # 定时器 self.timer = QTimer(self) self.timer.timeout.connect(self.updateFrame) # 人脸检测 self.isFaceDetectEnabled = False self.enableFaceDetectButton.toggled.connect(self.enableFaceDetect) self.enableFaceDetectButton.setCheckable(True) # 数据库 self.database = './identity/FaceBase.db' self.datasets = './identity/datasets' self.isDbReady = False self.initDbButton.setIcon(QIcon('../identity/icons/warning.png')) self.initDbButton.clicked.connect(self.initDb) # 用户信息 self.isUserInfoReady = False self.userInfo = {'stu_id': '', 'cn_name': '', 'en_name': ''} self.addOrUpdateUserInfoButton.clicked.connect( self.addOrUpdateUserInfo) self.migrateToDbButton.clicked.connect(self.migrateToDb) # 人脸采集 self.startFaceRecordButton.clicked.connect( lambda: self.startFaceRecord(self.startFaceRecordButton)) self.faceRecordCount = 0 self.minFaceRecordCount = 100 #最低采集张数,可以根据安全需求改变 self.isFaceDataReady = False self.isFaceRecordEnabled = False self.enableFaceRecordButton.clicked.connect(self.enableFaceRecord) # 日志系统 self.receiveLogSignal.connect(lambda log: self.logOutput(log)) self.logOutputThread = threading.Thread(target=self.receiveLog, daemon=True) self.logOutputThread.start() # 是否使用外接摄像头 def useExternalCamera(self, useExternalCameraCheckBox): if useExternalCameraCheckBox.isChecked(): self.isExternalCameraUsed = True else: self.isExternalCameraUsed = False # 打开/关闭摄像头 def startWebcam(self, status): if status: if self.isExternalCameraUsed: camID = 1 else: camID = 0 self.cap.open(camID) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) ret, frame = self.cap.read() if not ret: logging.error('无法调用电脑摄像头{}'.format(camID)) self.logQueue.put('Error:初始化摄像头失败') self.cap.release() self.startWebcamButton.setIcon( QIcon('../identity/icons/error.png')) self.startWebcamButton.setChecked(False) else: self.startWebcamButton.setText('关闭摄像头') self.enableFaceDetectButton.setEnabled(True) self.timer.start(5) self.startWebcamButton.setIcon( QIcon('../identity/icons/success.png')) else: if self.cap.isOpened(): if self.timer.isActive(): self.timer.stop() self.cap.release() self.faceDetectCaptureLabel.clear() self.faceDetectCaptureLabel.setText( '<font color=red>摄像头未开启</font>') self.startWebcamButton.setText('打开摄像头') self.enableFaceDetectButton.setEnabled(False) self.startWebcamButton.setIcon(QIcon()) # 开启/关闭人脸检测 def enableFaceDetect(self, status): if self.cap.isOpened(): if status: self.enableFaceDetectButton.setText('关闭人脸检测') self.isFaceDetectEnabled = True else: self.enableFaceDetectButton.setText('开启人脸检测') self.isFaceDetectEnabled = False # 采集当前捕获帧 def enableFaceRecord(self): if not self.isFaceRecordEnabled: self.isFaceRecordEnabled = True # 开始/结束采集人脸数据 def startFaceRecord(self, startFaceRecordButton): if startFaceRecordButton.text() == '开始采集人脸数据': if self.isFaceDetectEnabled: if self.isUserInfoReady: self.addOrUpdateUserInfoButton.setEnabled(False) if not self.enableFaceRecordButton.isEnabled(): self.enableFaceRecordButton.setEnabled(True) self.enableFaceRecordButton.setIcon(QIcon()) self.startFaceRecordButton.setIcon( QIcon('../identity/icons/success.png')) self.startFaceRecordButton.setText('结束当前人脸采集') else: self.startFaceRecordButton.setIcon( QIcon('../identity/icons/error.png')) self.startFaceRecordButton.setChecked(False) self.logQueue.put('Error:操作失败,系统未检测到有效的用户信息') else: self.startFaceRecordButton.setIcon( QIcon('../identity/icons/error.png')) self.logQueue.put('Error:操作失败,请开启人脸检测') else: if self.faceRecordCount < self.minFaceRecordCount: text = '系统当前采集了 <font color=blue>{}</font> 帧图像,采集数据过少会导致较大的识别误差。'.format( self.faceRecordCount) informativeText = '<b>请至少采集 <font color=red>{}</font> 帧图像。</b>'.format( self.minFaceRecordCount) DataRecordUI.callDialog(QMessageBox.Information, text, informativeText, QMessageBox.Ok) else: text = '系统当前采集了 <font color=blue>{}</font> 帧图像,继续采集可以提高识别准确率。'.format( self.faceRecordCount) informativeText = '<b>你确定结束当前人脸采集吗?</b>' ret = DataRecordUI.callDialog(QMessageBox.Question, text, informativeText, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if ret == QMessageBox.Yes: self.isFaceDataReady = True if self.isFaceRecordEnabled: self.isFaceRecordEnabled = False self.enableFaceRecordButton.setEnabled(False) self.enableFaceRecordButton.setIcon(QIcon()) self.startFaceRecordButton.setText('开始采集人脸数据') self.startFaceRecordButton.setEnabled(False) self.startFaceRecordButton.setIcon(QIcon()) self.migrateToDbButton.setEnabled(True) # 定时器,实时更新画面 def updateFrame(self): ret, frame = self.cap.read() if ret: self.displayImage(frame) if self.isFaceDetectEnabled: detected_frame = self.detectFace(frame) self.displayImage(detected_frame) else: self.displayImage(frame) # 检测人脸 def detectFace(self, frame): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = self.faceCascade.detectMultiScale(gray, 1.3, 5, minSize=(90, 90)) stu_id = self.userInfo.get('stu_id') for (x, y, w, h) in faces: if self.isFaceRecordEnabled: try: if not os.path.exists('{}/stu_{}'.format( self.datasets, stu_id)): os.makedirs('{}/stu_{}'.format(self.datasets, stu_id)) if len(faces) > 1: raise RecordDisturbance cv2.imwrite( '{}/stu_{}/img.{}.jpg'.format( self.datasets, stu_id, self.faceRecordCount + 1), gray[y - 20:y + h + 20, x - 20:x + w + 20]) except RecordDisturbance: self.isFaceRecordEnabled = False logging.error('检测到多张人脸或环境干扰') self.logQueue.put('Warning:检测到多张人脸或环境干扰,请解决问题后继续') self.enableFaceRecordButton.setIcon( QIcon('../identity/icons/warning.png')) continue except Exception as e: logging.error('写入人脸图像文件到计算机过程中发生异常') self.enableFaceRecordButton.setIcon( QIcon('../identity/icons/error.png')) self.logQueue.put('Error:无法保存人脸图像,采集当前捕获帧失败') else: self.enableFaceRecordButton.setIcon( QIcon('../identity/icons/success.png')) self.faceRecordCount = self.faceRecordCount + 1 if self.faceRecordCount > 100: self.isFaceRecordEnabled = False self.faceRecordCountLcdNum.display(self.faceRecordCount) cv2.rectangle(frame, (x - 5, y - 10), (x + w + 5, y + h + 10), (0, 0, 255), 2) return frame # 显示图像 def displayImage(self, img): # BGR -> RGB img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # default:The image is stored using 8-bit indexes into a colormap, for example:a gray image qformat = QImage.Format_Indexed8 if len(img.shape) == 3: # rows[0], cols[1], channels[2] if img.shape[2] == 4: # The image is stored using a 32-bit byte-ordered RGBA format (8-8-8-8) # A: alpha channel,不透明度参数。如果一个像素的alpha通道数值为0%,那它就是完全透明的 qformat = QImage.Format_RGBA8888 else: qformat = QImage.Format_RGB888 outImage = QImage(img, img.shape[1], img.shape[0], img.strides[0], qformat) self.faceDetectCaptureLabel.setPixmap(QPixmap.fromImage(outImage)) self.faceDetectCaptureLabel.setScaledContents(True) # 初始化数据库 def initDb(self): conn = sqlite3.connect(self.database) cursor = conn.cursor() try: # 检测人脸数据目录是否存在,不存在则创建 if not os.path.isdir(self.datasets): os.makedirs(self.datasets) # 查询数据表是否存在,不存在则创建 cursor.execute('''CREATE TABLE IF NOT EXISTS users ( stu_id VARCHAR(12) PRIMARY KEY NOT NULL, face_id INTEGER DEFAULT -1, cn_name VARCHAR(10) NOT NULL, en_name VARCHAR(16) NOT NULL, created_time DATE DEFAULT (date('now','localtime')) ) ''') # 查询数据表记录数 cursor.execute('SELECT Count(*) FROM users') result = cursor.fetchone() dbUserCount = result[0] except Exception as e: logging.error('读取数据库异常,无法完成数据库初始化') self.isDbReady = False self.initDbButton.setIcon(QIcon('../identity/icons/error.png')) self.logQueue.put('Error:初始化数据库失败') else: self.isDbReady = True self.dbUserCountLcdNum.display(dbUserCount) self.logQueue.put('Success:数据库初始化完成') self.initDbButton.setIcon(QIcon('../identity/icons/success.png')) self.initDbButton.setEnabled(False) self.addOrUpdateUserInfoButton.setEnabled(True) finally: cursor.close() conn.commit() conn.close() # 增加/修改用户信息 def addOrUpdateUserInfo(self): self.userInfoDialog = UserInfoDialog() stu_id, cn_name, en_name = self.userInfo.get( 'stu_id'), self.userInfo.get('cn_name'), self.userInfo.get( 'en_name') self.userInfoDialog.stuIDLineEdit.setText(stu_id) self.userInfoDialog.cnNameLineEdit.setText(cn_name) self.userInfoDialog.enNameLineEdit.setText(en_name) self.userInfoDialog.okButton.clicked.connect(self.checkToApplyUserInfo) self.userInfoDialog.exec() # 校验用户信息并提交 def checkToApplyUserInfo(self): if not (self.userInfoDialog.stuIDLineEdit.hasAcceptableInput() and self.userInfoDialog.cnNameLineEdit.hasAcceptableInput() and self.userInfoDialog.enNameLineEdit.hasAcceptableInput()): self.userInfoDialog.msgLabel.setText( '<font color=red>你的输入有误,提交失败,请检查并重试!</font>') else: # 获取用户输入 self.userInfo['stu_id'] = self.userInfoDialog.stuIDLineEdit.text( ).strip() self.userInfo['cn_name'] = self.userInfoDialog.cnNameLineEdit.text( ).strip() self.userInfo['en_name'] = self.userInfoDialog.enNameLineEdit.text( ).strip() # 信息确认 stu_id, cn_name, en_name = self.userInfo.get( 'stu_id'), self.userInfo.get('cn_name'), self.userInfo.get( 'en_name') self.stuIDLineEdit.setText(stu_id) self.cnNameLineEdit.setText(cn_name) self.enNameLineEdit.setText(en_name) self.isUserInfoReady = True if not self.startFaceRecordButton.isEnabled(): self.startFaceRecordButton.setEnabled(True) self.migrateToDbButton.setIcon(QIcon()) # 关闭对话框 self.userInfoDialog.close() # 同步用户信息到数据库 def migrateToDb(self): if self.isFaceDataReady: stu_id, cn_name, en_name = self.userInfo.get( 'stu_id'), self.userInfo.get('cn_name'), self.userInfo.get( 'en_name') conn = sqlite3.connect(self.database) cursor = conn.cursor() try: cursor.execute('SELECT * FROM users WHERE stu_id=?', (stu_id, )) if cursor.fetchall(): text = '数据库已存在学号为 <font color=blue>{}</font> 的用户记录。'.format( stu_id) informativeText = '<b>是否覆盖?</b>' ret = DataRecordUI.callDialog( QMessageBox.Warning, text, informativeText, QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.Yes: # 更新已有记录 cursor.execute( 'UPDATE users SET cn_name=?, en_name=? WHERE stu_id=?', ( cn_name, en_name, stu_id, )) else: raise OperationCancel # 记录取消覆盖操作 else: # 插入新记录 cursor.execute( 'INSERT INTO users (stu_id, cn_name, en_name) VALUES (?, ?, ?)', ( stu_id, cn_name, en_name, )) cursor.execute('SELECT Count(*) FROM users') result = cursor.fetchone() dbUserCount = result[0] except OperationCancel: pass except Exception as e: logging.error('读写数据库异常,无法向数据库插入/更新记录') self.migrateToDbButton.setIcon( QIcon('../identity/icons/error.png')) self.logQueue.put('Error:读写数据库异常,同步失败') else: text = '<font color=blue>{}</font> 已添加/更新到数据库。'.format(stu_id) informativeText = '<b><font color=blue>{}</font> 的人脸数据采集已完成!</b>'.format( cn_name) DataRecordUI.callDialog(QMessageBox.Information, text, informativeText, QMessageBox.Ok) # 清空用户信息缓存 for key in self.userInfo.keys(): self.userInfo[key] = '' self.isUserInfoReady = False self.faceRecordCount = 0 self.isFaceDataReady = False self.faceRecordCountLcdNum.display(self.faceRecordCount) self.dbUserCountLcdNum.display(dbUserCount) # 清空历史输入 self.stuIDLineEdit.clear() self.cnNameLineEdit.clear() self.enNameLineEdit.clear() self.migrateToDbButton.setIcon( QIcon('../identity/icons/success.png')) # 允许继续增加新用户 self.addOrUpdateUserInfoButton.setEnabled(True) self.migrateToDbButton.setEnabled(False) finally: cursor.close() conn.commit() conn.close() else: self.logQueue.put('Error:操作失败,你尚未完成人脸数据采集') self.migrateToDbButton.setIcon( QIcon('../identity/icons/error.png')) # 系统日志服务常驻,接收并处理系统日志 def receiveLog(self): while True: data = self.logQueue.get() if data: self.receiveLogSignal.emit(data) else: continue # LOG输出 def logOutput(self, log): # 获取当前系统时间 time = datetime.now().strftime('[%Y/%m/%d %H:%M:%S]') log = time + ' ' + log + '\n' self.logTextEdit.moveCursor(QTextCursor.End) self.logTextEdit.insertPlainText(log) self.logTextEdit.ensureCursorVisible() # 自动滚屏 # 系统对话框 @staticmethod def callDialog(icon, text, informativeText, standardButtons, defaultButton=None): msg = QMessageBox() msg.setWindowIcon(QIcon('../identity/icons/icon.png')) msg.setWindowTitle('pc端个人隐私防护系统 - 人脸采集') msg.setIcon(icon) msg.setText(text) msg.setInformativeText(informativeText) msg.setStandardButtons(standardButtons) if defaultButton: msg.setDefaultButton(defaultButton) return msg.exec() # 窗口关闭事件,关闭定时器、摄像头 def closeEvent(self, event): if self.timer.isActive(): self.timer.stop() if self.cap.isOpened(): self.cap.release() event.accept()
class SphericalBackend(QObject, MultiBackend): printDurationMessage = Signal() slicingStarted = Signal() slicingCancelled = Signal() backendError = Signal() def __init__(self): super().__init__() self._application = SteSlicerApplication.getInstance() self._multi_build_plate_model = None # type: Optional[MultiBuildPlateModel] self._machine_error_checker = None # type: Optional[MachineErrorChecker] self._backend_manager = self._application.getBackendManager( ) #type: BackendManager if self._backend_manager is None: Logger.log("e", "Failed to load BackendManager!") self._states = [] # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False # type: bool self._onActiveViewChanged() self._stored_layer_data = [] self._stored_optimized_layer_data = {} self._scene = self._application.getController().getScene( ) # type: Scene self._scene.sceneChanged.connect(self._onSceneChanged) self._global_container_stack = None # type: Optional[ContainerStack] self._slice_messages = [] #type: List[Any] self._start_slice_job_build_plate = None # type: Optional[int] self._start_slice_job = None #type: Optional[StartSliceJob] self._generate_basement_job = None self._glicer_process = None self._layers_size = 0 self._classic_layers_size = 0 self._slicing = False # type: bool # Are we currently slicing? self._restart = False # type: bool # Back-end is currently restarting? self._tool_active = False # type: bool # If a tool is active, some tasks do not have to do anything self._always_restart = True # type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._build_plates_to_be_sliced = [ ] # type: List[int] # what needs slicing? self._engine_is_fresh = True # type: bool # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 # type: int # Maximum number of lines to buffer self._error_message = None # type: Optional[Message] # Pop-up message that shows errors. self._last_num_objects = defaultdict( int ) # type: Dict[int, int] # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [ ] # type: List[SceneNode] # scene change is postponed (by a tool) self._process_layers_job = None self._process_mesh_job = None self._slice_start_time = None # type: Optional[float] self._is_disabled = False # type: bool self._material_amounts = [] self._times = {} self._application.getPreferences().addPreference( "general/auto_slice", False) self._use_timer = False # type: bool # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # This timer will group them up, and only slice for the last setting changed signal. # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. self._change_timer = QTimer() # type: QTimer self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() self._application.getPreferences().preferenceChanged.connect( self._onPreferencesChanged) self._application.initializationFinished.connect(self.initialize) def initialize(self) -> None: self._loadBackends() self._multi_build_plate_model = self._application.getMultiBuildPlateModel( ) self._application.getController().activeViewChanged.connect( self._onActiveViewChanged) if self._multi_build_plate_model: self._multi_build_plate_model.activeBuildPlateChanged.connect( self._onActiveViewChanged) self._application.globalContainerStackChanged.connect( self._onGlobalStackChanged) self._onGlobalStackChanged() # extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash ExtruderManager.getInstance().extrudersChanged.connect( self._extruderChanged) # self.backendQuit.connect(self._onBackendQuit) # self.backendConnected.connect(self._onBackendConnected) # When a tool operation is in progress, don't slice. So we need to listen for tool operations. self._application.getController().toolOperationStarted.connect( self._onToolOperationStarted) self._application.getController().toolOperationStopped.connect( self._onToolOperationStopped) self._machine_error_checker = self._application.getMachineErrorChecker( ) self._machine_error_checker.errorCheckFinished.connect( self._onStackErrorCheckFinished) def _loadBackends(self) -> None: classic_backend = self._backend_manager.getBackendByType("classic") spherical_backend = self._backend_manager.getBackendByType("spherical") if classic_backend is None or spherical_backend is None: raise ModuleNotFoundError("Not all Backends found") self._states = [BackendState.NotStarted, BackendState.NotStarted] self.addBackend(classic_backend) self.addBackend(spherical_backend) def close(self) -> None: for name, backend in self.getBackends().items(): backend._terminate() @pyqtSlot() def stopSlicing(self) -> None: self.backendStateChange.emit(BackendState.NotStarted) if self._slicing: self.close() if self._generate_basement_job is not None: Logger.log("d", "Aborting generate basement job...") self._generate_basement_job.abort() self._generate_basement_job = None if self._process_mesh_job is not None: Logger.log("d", "Aborting process mesh job...") self._process_mesh_job.abort() self._process_mesh_job = None if self._process_layers_job is not None: Logger.log("d", "Aborting process layers job...") self._process_layers_job.abort() self._process_layers_job = None if self._error_message: self._error_message.hide() @pyqtSlot() def forceSlice(self) -> None: self.markSliceAll() self.slice() def slice(self) -> None: Logger.log("d", "Starting to slice...") self._material_amounts = [] self._times = {} self._slice_start_time = time() if not self._build_plates_to_be_sliced: self.processingProgress.emit(1.0) Logger.log( "w", "Slice unnecessary, nothing has changed that needs reslicing.") return if self._process_layers_job: Logger.log("d", "Process layers job still busy, trying later.") return if not hasattr(self._scene, "gcode_dict"): self._scene.gcode_dict = { } #type: ignore #Because we are creating the missing attribute here. # see if we really have to slice active_build_plate = self._application.getMultiBuildPlateModel( ).activeBuildPlate build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0) Logger.log( "d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced) num_objects = self._numObjectsPerBuildPlate() self._stored_layer_data = [] self._stored_optimized_layer_data[build_plate_to_be_sliced] = [] if build_plate_to_be_sliced not in num_objects or num_objects[ build_plate_to_be_sliced] == 0: self._scene.gcode_dict[build_plate_to_be_sliced] = [] Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced) if self._build_plates_to_be_sliced: self.slice() return if self._application.getPrintInformation( ) and build_plate_to_be_sliced == active_build_plate: self._application.getPrintInformation().setToZeroPrintInformation( build_plate_to_be_sliced) self.stopSlicing() self.processingProgress.emit(0.0) self.backendStateChange.emit(BackendState.NotStarted) self._scene.gcode_dict[build_plate_to_be_sliced] = [] self._slicing = True self.slicingStarted.emit() self.determineAutoSlicing() self._start_slice_job = StartSliceJob(self.getBackends()) self._start_slice_job_build_plate = build_plate_to_be_sliced self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate) self._start_slice_job.start() self._start_slice_job.finished.connect(self._onStartSliceCompleted) def _onStartSliceCompleted(self, job) -> None: if self._error_message: self._error_message.hide() if self._start_slice_job is job: self._start_slice_job = None if job.isCancelled() or job.getError() or job.getResult( ) == StartJobResult.Error: self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) return if job.getResult() == StartJobResult.MaterialIncompatible: if self._application.platformActivity: self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice with the current material as it is incompatible with the selected machine or configuration." ), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) else: self.backendStateChange.emit(BackendState.NotStarted) return if job.getResult() == StartJobResult.SettingError: if self._application.platformActivity: if not self._global_container_stack: Logger.log( "w", "Global container stack not assigned to GlicerBackend!" ) return if job.getResult() == StartJobResult.BuildPlateError: if self._application.platformActivity: self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice because the prime tower or prime position(s) are invalid." ), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) else: self.backendStateChange.emit(BackendState.NotStarted) if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder: self._error_message = Message(catalog.i18nc( "@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) return if job.getResult() == StartJobResult.NothingToSlice: if self._application.platformActivity: self._error_message = Message(catalog.i18nc( "@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit." ), title=catalog.i18nc( "@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) self.backendError.emit(job) else: self.backendStateChange.emit(BackendState.NotStarted) self._invokeSlice() return self.backendStateChange.emit(BackendState.Processing) self.processingProgress.emit(0.0) self._slice_messages = job.getSliceMessages() self._generate_basement_job = GenerateBasementJob() self._generate_basement_job.processingProgress.connect( self._onGenerateBasementProcessingProgress) self._generate_basement_job.finished.connect( self._onGenerateBasementJobFinished) self._generate_basement_job.start() def _onGenerateBasementProcessingProgress(self, amount): self.processingProgress.emit(amount / 10) self.backendStateChange.emit(BackendState.Processing) def _onGenerateBasementJobFinished(self, job: GenerateBasementJob): if not self._scene.gcode_dict: self._scene.gcode_dict = {0: []} if not self._scene.gcode_dict[self._start_slice_job_build_plate]: self._scene.gcode_dict[self._start_slice_job_build_plate] = [] self._scene.gcode_dict[self._start_slice_job_build_plate].extend( job.getGCodeList()) if self._start_slice_job_build_plate is not None: if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: self._stored_optimized_layer_data[ self._start_slice_job_build_plate] = [] self._stored_optimized_layer_data[ self._start_slice_job_build_plate].extend(job.getLayersData()) self._classic_layers_size = len(self._stored_optimized_layer_data[ self._start_slice_job_build_plate]) self._layers_size = self._classic_layers_size if self._generate_basement_job is job: self._generate_basement_job = None # sending to the first backend slice_message = self._slice_messages[0] if isinstance(slice_message, PythonMessage): self._backends["CuraEngineBackend"]._terminate() self._backends["CuraEngineBackend"]._createSocket() Logger.log("d", "Sending Arcus Message") # Listeners for receiving messages from the back-end. self._backends["CuraEngineBackend"]._message_handlers[ "cura.proto.Layer"] = self._onLayerMessage self._backends["CuraEngineBackend"]._message_handlers[ "cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage self._backends["CuraEngineBackend"]._message_handlers[ "cura.proto.Progress"] = self._onProgressMessage self._backends["CuraEngineBackend"]._message_handlers[ "cura.proto.GCodeLayer"] = self._onGCodeLayerMessage self._backends["CuraEngineBackend"]._message_handlers[ "cura.proto.GCodePrefix"] = self._onGCodePrefixMessage self._backends["CuraEngineBackend"]._message_handlers[ "cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates self._backends["CuraEngineBackend"]._message_handlers[ "cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._backends["CuraEngineBackend"]._socket.sendMessage( slice_message) def _sendGlicerSliceMessage(self, slice_message): if isinstance(slice_message, List): Logger.log("d", "Sending Engine Message") slice_message.append('-o') filename = next(tempfile._get_candidate_names()) output_path = [os.path.join(tempfile.tempdir, filename)] slice_message.extend(output_path) self._glicer_process = self._runGlicerEngineProcess(slice_message) self._startProcessMeshLayersJob( output_path, self._application.getMultiBuildPlateModel().activeBuildPlate, self._slice_messages[2]) if self._slice_start_time: Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time) def _onLayerMessage(self, message: Arcus.PythonMessage) -> None: self._stored_layer_data.append(message) def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None: if self._start_slice_job_build_plate is not None: if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: self._stored_optimized_layer_data[ self._start_slice_job_build_plate] = [] self._stored_optimized_layer_data[ self._start_slice_job_build_plate].append(message) def _onMeshOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None: if self._start_slice_job_build_plate is not None: if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: self._stored_optimized_layer_data[ self._start_slice_job_build_plate] = [] message.id += self._classic_layers_size self._stored_optimized_layer_data[ self._start_slice_job_build_plate].append(message) def _onProgressMessage(self, message: Arcus.PythonMessage) -> None: self.processingProgress.emit(0.1 + message.amount / 2.5) self.backendStateChange.emit(BackendState.Processing) def _onLayersProcessorProgressMessage( self, message: Arcus.PythonMessage) -> None: self.processingProgress.emit(0.6 + message.amount / 2.5) self.backendStateChange.emit(BackendState.Processing) def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None: if not self._scene.gcode_dict: self._scene.gcode_dict = {0: []} if not self._scene.gcode_dict[self._start_slice_job_build_plate]: self._scene.gcode_dict[self._start_slice_job_build_plate] = [] msg = message.data.decode("utf-8", "replace") # type: str #TODO: Remove this since new basement will have start and end gcode if msg.startswith(";Generated with Cura_SteamEngine"): self._scene.gcode_dict[self._start_slice_job_build_plate].insert( 0, msg) else: self._scene.gcode_dict[self._start_slice_job_build_plate].append( msg ) #type: ignore #Because we generate this attribute dynamically. def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None: if not self._scene.gcode_dict: self._scene.gcode_dict = {0: []} if not self._scene.gcode_dict[self._start_slice_job_build_plate]: self._scene.gcode_dict[self._start_slice_job_build_plate] = [] self._scene.gcode_dict[self._start_slice_job_build_plate].insert( 0, message.data.decode("utf-8", "replace") ) # type: ignore #Because we generate this attribute dynamically. def _onPrintTimeMaterialEstimates(self, message: Arcus.PythonMessage) -> None: material_amounts = [] for index in range(message.repeatedMessageCount("materialEstimates")): material_amounts.append( message.getRepeatedMessage("materialEstimates", index).material_amount) if len(self._material_amounts) > 0: zipped_amounts = zip(self._material_amounts, material_amounts) # sum items in two lists self._material_amounts = [x + y for (x, y) in zipped_amounts] else: self._material_amounts = material_amounts self._times = self._parseMessagePrintTimes(message) self.printDurationMessage.emit(self._start_slice_job_build_plate, self._times, self._material_amounts) def _parseMessagePrintTimes( self, message: Arcus.PythonMessage) -> Dict[str, float]: result = { "inset_0": self._times.get("inset_0", 0) + message.time_inset_0, "inset_x": self._times.get("inset_x", 0) + message.time_inset_x, "skin": self._times.get("skin", 0) + message.time_skin, "infill": self._times.get("infill", 0) + message.time_infill, "support_infill": self._times.get("support_infill", 0) + message.time_support_infill, "support_interface": self._times.get("support_interface", 0) + message.time_support_interface, "support": self._times.get("support", 0) + message.time_support, "skirt": self._times.get("skirt", 0) + message.time_skirt, "travel": self._times.get("travel", 0) + message.time_travel, "retract": self._times.get("retract", 0) + message.time_retract, "none": self._times.get("none", 0) + message.time_none } return result def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None: self._classic_layers_size = len(self._stored_optimized_layer_data[ self._start_slice_job_build_plate]) - self._classic_layers_size self._layers_size += self._classic_layers_size self.backendStateChange.emit(BackendState.Processing) self.processingProgress.emit(0.5) self._backends["CuraEngineBackend"]._message_handlers = {} gcode_list = self._scene.gcode_dict[ self. _start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically. for index, line in enumerate(gcode_list): replaced = line.replace( "{print_time}", str(self._application.getPrintInformation().currentPrintTime. getDisplayString(DurationFormat.Format.ISO8601))) replaced = replaced.replace( "{filament_amount}", str(self._application.getPrintInformation().materialLengths)) replaced = replaced.replace( "{filament_weight}", str(self._application.getPrintInformation().materialWeights)) replaced = replaced.replace( "{filament_cost}", str(self._application.getPrintInformation().materialCosts)) replaced = replaced.replace( "{jobname}", str(self._application.getPrintInformation().jobName)) gcode_list[index] = replaced # Launch GlicerBackend here slice_message = self._slice_messages[1] self._sendGlicerSliceMessage(slice_message) def _runGlicerEngineProcess(self, command_list) -> Optional[subprocess.Popen]: try: return subprocess.Popen(command_list) except PermissionError: Logger.log( "e", "Couldn't start back-end: No permission to execute process.") except FileNotFoundError: Logger.logException("e", "Unable to find backend executable: %s", command_list[0]) return None def _startProcessMeshLayersJob(self, output_path: List[str], build_plate_number: int, arcus_message: Arcus.PythonMessage) -> None: self._process_mesh_job = ProcessMeshJob(self._glicer_process, output_path, arcus_message) self._process_mesh_job.setBuildPlate(build_plate_number) self._process_mesh_job.finished.connect(self._onProcessMeshFinished) self._process_mesh_job.start() def _onTimeMaterialEstimates(self, material_amounts, times): zipped_amounts = zip(self._material_amounts, material_amounts) # sum items in two lists self._material_amounts = [x + y for (x, y) in zipped_amounts] # and sum items in two dicts self._times = { k: self._times.get(k, 0) + times.get(k, 0) for k in set(self._times) } self.printDurationMessage.emit(self._start_slice_job_build_plate, self._times, self._material_amounts) def _onProcessMeshFinished(self, job: ProcessMeshJob): # remove end gcode from Curaengine del self._scene.gcode_dict[self._start_slice_job_build_plate][-1] self._backends["LayersProcessorBackend"]._terminate() self._backends["LayersProcessorBackend"]._createSocket() Logger.log("d", "Sending Arcus Message") self._backends["LayersProcessorBackend"]._message_handlers[ "layersprocessor.proto.LayerOptimized"] = self._onMeshOptimizedLayerMessage self._backends["LayersProcessorBackend"]._message_handlers[ "layersprocessor.proto.Progress"] = self._onLayersProcessorProgressMessage self._backends["LayersProcessorBackend"]._message_handlers[ "layersprocessor.proto.GCodeLayer"] = self._onGCodeLayerMessage self._backends["LayersProcessorBackend"]._message_handlers[ "layersprocessor.proto.GCodePrefix"] = self._onGCodePrefixMessage self._backends["LayersProcessorBackend"]._message_handlers[ "layersprocessor.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates self._backends["LayersProcessorBackend"]._message_handlers[ "layersprocessor.proto.SlicingFinished"] = self._onLayersProcessorFinishedMessage # Note that cancelled slice jobs can still call this method. if self._process_mesh_job is job: self._process_mesh_job = None self._backends["LayersProcessorBackend"]._socket.sendMessage( job.getSliceMessage()) self.backendStateChange.emit(BackendState.Processing) self.processingProgress.emit(0.6) if self._slice_start_time: Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time) def _onLayersProcessorFinishedMessage( self, message: Arcus.PythonMessage) -> None: self._classic_layers_size = 0 self._layers_size = 0 self.backendStateChange.emit(BackendState.Done) self.processingProgress.emit(1.0) gcode_list = self._scene.gcode_dict[ self. _start_slice_job_build_plate] # type: ignore #Because we generate this attribute dynamically. for index, line in enumerate(gcode_list): replaced = line.replace( "{print_time}", str(self._application.getPrintInformation().currentPrintTime. getDisplayString(DurationFormat.Format.ISO8601))) replaced = replaced.replace( "{filament_amount}", str(self._application.getPrintInformation().materialLengths)) replaced = replaced.replace( "{filament_weight}", str(self._application.getPrintInformation().materialWeights)) replaced = replaced.replace( "{filament_cost}", str(self._application.getPrintInformation().materialCosts)) replaced = replaced.replace( "{jobname}", str(self._application.getPrintInformation().jobName)) gcode_list[index] = replaced if self._slice_start_time: Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time) Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate())) # See if we need to process the sliced layers job. active_build_plate = self._application.getMultiBuildPlateModel( ).activeBuildPlate if (self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate and active_build_plate not in self._build_plates_to_be_sliced): self._startProcessSlicedLayersJob(active_build_plate) # self._onActiveViewChanged() self._start_slice_job_build_plate = None Logger.log("d", "See if there is more to slice...") # Somehow this results in an Arcus Error # self.slice() # Call slice again using the timer, allowing the backend to restart if self._build_plates_to_be_sliced: self.enableTimer( ) # manually enable timer to be able to invoke slice, also when in manual slice mode self._invokeSlice() self._backends["LayersProcessorBackend"]._message_handlers = {} self._slicing = False def _startProcessSlicedLayersJob(self, build_plate_number: int) -> None: self._process_layers_job = ProcessSlicedLayersJob( self._stored_optimized_layer_data[build_plate_number]) self._process_layers_job.setBuildPlate(build_plate_number) self._process_layers_job.finished.connect( self._onProcessLayersFinished) self._process_layers_job.start() def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob): del self._stored_optimized_layer_data[job.getBuildPlate()] self._process_layers_job = None Logger.log("d", "See if there is more to slice(2)...") self._invokeSlice() def _terminate(self) -> None: self._slicing = False self._stored_layer_data = [] if self._start_slice_job_build_plate in self._stored_optimized_layer_data: del self._stored_optimized_layer_data[ self._start_slice_job_build_plate] if self._start_slice_job is not None: self._start_slice_job.cancel() if self._application.getUseExternalBackend(): return Logger.log("d", "Killing engine process") try: for backend in self.getBackends(): # type: Backend backend._terminate() except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this. Logger.log( "d", "Exception occurred while trying to kill the engine %s", str(e)) ## Called when the user changes the active view mode. def _onActiveViewChanged(self) -> None: view = self._application.getController().getActiveView() if view: active_build_plate = self._application.getMultiBuildPlateModel( ).activeBuildPlate if view.getPluginId( ) == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet. self._layer_view_active = True # There is data and we're not slicing at the moment # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. # TODO: what build plate I am slicing if (active_build_plate in self._stored_optimized_layer_data and not self._slicing and not self._process_layers_job and active_build_plate not in self._build_plates_to_be_sliced): self._startProcessSlicedLayersJob(active_build_plate) else: self._layer_view_active = False def _onSceneChanged(self, source: SceneNode) -> None: if not isinstance(source, SceneNode): return if source.callDecoration("isBlockSlicing") and source.callDecoration( "getLayerData"): self._stored_optimized_layer_data = {} build_plate_changed = set() source_build_plate_number = source.callDecoration( "getBuildPlateNumber") if source == self._scene.getRoot(): num_objects = self._numObjectsPerBuildPlate() for build_plate_number in list( self._last_num_objects.keys()) + list(num_objects.keys()): if build_plate_number not in self._last_num_objects or num_objects[build_plate_number] != \ self._last_num_objects[build_plate_number]: self._last_num_objects[build_plate_number] = num_objects[ build_plate_number] build_plate_changed.add(build_plate_number) else: if not source.callDecoration("isGroup"): mesh_data = source.getMeshData() if mesh_data is None or mesh_data.getVertices() is None: return if source_build_plate_number is not None: build_plate_changed.add(source_build_plate_number) if not build_plate_changed: return if self._tool_active: if source not in self._postponed_scene_change_sources: self._postponed_scene_change_sources.append(source) return self.stopSlicing() for build_plate_number in build_plate_changed: if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) self.printDurationMessage.emit(source_build_plate_number, {}, []) self.processingProgress.emit(0.0) self.backendStateChange.emit(BackendState.NotStarted) # if not self._use_timer: # With manually having to slice, we want to clear the old invalid layer data. self._clearLayerData(build_plate_changed) self._invokeSlice() def determineAutoSlicing(self) -> bool: enable_timer = True self._is_disabled = False if not self._application.getPreferences().getValue( "general/auto_slice"): enable_timer = False for node in DepthFirstIterator( self._scene.getRoot() ): # type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("isBlockSlicing"): enable_timer = False self.backendStateChange.emit(BackendState.Disabled) self._is_disabled = True gcode_list = node.callDecoration("getGCodeList") if gcode_list is not None: self._scene.gcode_dict[node.callDecoration( "getBuildPlateNumber" )] = gcode_list # type: ignore #Because we generate this attribute dynamically. if self._use_timer == enable_timer: return self._use_timer if enable_timer: self.backendStateChange.emit(BackendState.NotStarted) self.enableTimer() return True else: self.disableTimer() return False def _onPreferencesChanged(self, preference: str) -> None: if preference != "general/auto_slice": return auto_slice = self.determineAutoSlicing() if auto_slice: self._change_timer.start() def _extruderChanged(self) -> None: if not self._multi_build_plate_model: Logger.log( "w", "GlicerBackend does not have multi_build_plate_model assigned!" ) return for build_plate_number in range( self._multi_build_plate_model.maxBuildPlate + 1): if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) self._invokeSlice() ## Called when the global container stack changes def _onGlobalStackChanged(self) -> None: if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect( self._onSettingChanged) self._global_container_stack.containersChanged.disconnect( self._onChanged) extruders = list(self._global_container_stack.extruders.values()) for extruder in extruders: extruder.propertyChanged.disconnect(self._onSettingChanged) extruder.containersChanged.disconnect(self._onChanged) self._global_container_stack = self._application.getGlobalContainerStack( ) if self._global_container_stack: self._global_container_stack.propertyChanged.connect( self._onSettingChanged ) # Note: Only starts slicing when the value changed. self._global_container_stack.containersChanged.connect( self._onChanged) extruders = list(self._global_container_stack.extruders.values()) for extruder in extruders: extruder.propertyChanged.connect(self._onSettingChanged) extruder.containersChanged.connect(self._onChanged) self._onChanged() def _onChanged(self, *args: Any, **kwargs: Any) -> None: self.needsSlicing() if self._use_timer: # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # otherwise business as usual if self._machine_error_checker is None: self._change_timer.stop() return if self._machine_error_checker.needToWaitForResult: self._change_timer.stop() else: self._change_timer.start() ## Return a dict with number of objects per build plate def _numObjectsPerBuildPlate(self) -> Dict[int, int]: num_objects = defaultdict(int) #type: Dict[int, int] for node in DepthFirstIterator( self._scene.getRoot() ): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. # Only count sliceable objects if node.callDecoration("isSliceable"): build_plate_number = node.callDecoration("getBuildPlateNumber") if build_plate_number is not None: num_objects[build_plate_number] += 1 return num_objects def _onSettingChanged(self, instance: SettingInstance, property: str) -> None: if property == "value": # Only reslice if the value has changed. self.needsSlicing() self._onChanged() elif property == "validationState": if self._use_timer: self._change_timer.stop() def needsSlicing(self) -> None: self.stopSlicing() self.markSliceAll() self.processingProgress.emit(0.0) self.backendStateChange.emit(BackendState.NotStarted) if not self._use_timer: self._clearLayerData() def markSliceAll(self) -> None: for build_plate_number in range( self._application.getMultiBuildPlateModel().maxBuildPlate + 1): if build_plate_number not in self._build_plates_to_be_sliced: self._build_plates_to_be_sliced.append(build_plate_number) def _clearLayerData(self, build_plate_numbers: Set = None) -> None: self._scene.gcode_dict = {} for node in DepthFirstIterator( self._scene.getRoot() ): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("getLayerData"): if not build_plate_numbers or node.callDecoration( "getBuildPlateNumber") in build_plate_numbers: node.getParent().removeChild(node) def _onToolOperationStarted(self, tool: Tool) -> None: self._tool_active = True # Do not react on scene change self.disableTimer() # Restart engine as soon as possible, we know we want to slice afterwards if not self._engine_is_fresh: self._terminate() def _onToolOperationStopped(self, tool: Tool) -> None: self._tool_active = False # React on scene change again self.determineAutoSlicing() # Switch timer on if appropriate # Process all the postponed scene changes while self._postponed_scene_change_sources: source = self._postponed_scene_change_sources.pop(0) self._onSceneChanged(source) ## Connect slice function to timer. def enableTimer(self) -> None: if not self._use_timer: self._change_timer.timeout.connect(self.slice) self._use_timer = True ## Disconnect slice function from timer. # This means that slicing will not be triggered automatically def disableTimer(self) -> None: if self._use_timer: self._use_timer = False self._change_timer.timeout.disconnect(self.slice) def _onStackErrorCheckFinished(self) -> None: self.determineAutoSlicing() if self._is_disabled: return if not self._slicing and self._build_plates_to_be_sliced: self.needsSlicing() self._onChanged() def _invokeSlice(self) -> None: if self._use_timer: # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # otherwise business as usual if self._machine_error_checker is None: self._change_timer.stop() return if self._machine_error_checker.needToWaitForResult: self._change_timer.stop() else: self._change_timer.start() def tickle(self) -> None: if self._use_timer: self._change_timer.start() def _createSocket(self, protocol_file): Logger.log("d", "Create Socket") pass
class PrinterOutputDevice(QObject, OutputDevice): printersChanged = pyqtSignal() connectionStateChanged = pyqtSignal(str) acceptsCommandsChanged = pyqtSignal() # Signal to indicate that the material of the active printer on the remote changed. materialIdChanged = pyqtSignal() # # Signal to indicate that the hotend of the active printer on the remote changed. hotendIdChanged = pyqtSignal() # Signal to indicate that the info text about the connection has changed. connectionTextChanged = pyqtSignal() # Signal to indicate that the configuration of one of the printers has changed. uniqueConfigurationsChanged = pyqtSignal() def __init__(self, device_id: str, parent: QObject = None) -> None: super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance self._printers = [] # type: List[PrinterOutputModel] self._unique_configurations = [] # type: List[ConfigurationModel] self._monitor_view_qml_path = "" #type: str self._monitor_component = None #type: Optional[QObject] self._monitor_item = None #type: Optional[QObject] self._control_view_qml_path = "" #type: str self._control_component = None #type: Optional[QObject] self._control_item = None #type: Optional[QObject] self._accepts_commands = False #type: bool self._update_timer = QTimer() #type: QTimer self._update_timer.setInterval(2000) # TODO; Add preference for update interval self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) self._connection_state = ConnectionState.closed #type: ConnectionState self._firmware_updater = None #type: Optional[FirmwareUpdater] self._firmware_name = None #type: Optional[str] self._address = "" #type: str self._connection_text = "" #type: str self.printersChanged.connect(self._onPrintersChanged) QtApplication.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations) @pyqtProperty(str, notify = connectionTextChanged) def address(self) -> str: return self._address def setConnectionText(self, connection_text): if self._connection_text != connection_text: self._connection_text = connection_text self.connectionTextChanged.emit() @pyqtProperty(str, constant=True) def connectionText(self) -> str: return self._connection_text def materialHotendChangedMessage(self, callback: Callable[[int], None]) -> None: Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'") callback(QMessageBox.Yes) def isConnected(self) -> bool: return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error def setConnectionState(self, connection_state: ConnectionState) -> None: if self._connection_state != connection_state: self._connection_state = connection_state self.connectionStateChanged.emit(self._id) @pyqtProperty(str, notify = connectionStateChanged) def connectionState(self) -> ConnectionState: return self._connection_state def _update(self) -> None: pass def _getPrinterByKey(self, key: str) -> Optional["PrinterOutputModel"]: for printer in self._printers: if printer.key == key: return printer return None def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None: raise NotImplementedError("requestWrite needs to be implemented") @pyqtProperty(QObject, notify = printersChanged) def activePrinter(self) -> Optional["PrinterOutputModel"]: if len(self._printers): return self._printers[0] return None @pyqtProperty("QVariantList", notify = printersChanged) def printers(self) -> List["PrinterOutputModel"]: return self._printers @pyqtProperty(QObject, constant = True) def monitorItem(self) -> QObject: # Note that we specifically only check if the monitor component is created. # It could be that it failed to actually create the qml item! If we check if the item was created, it will try to # create the item (and fail) every time. if not self._monitor_component: self._createMonitorViewFromQML() return self._monitor_item @pyqtProperty(QObject, constant = True) def controlItem(self) -> QObject: if not self._control_component: self._createControlViewFromQML() return self._control_item def _createControlViewFromQML(self) -> None: if not self._control_view_qml_path: return if self._control_item is None: self._control_item = QtApplication.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self}) def _createMonitorViewFromQML(self) -> None: if not self._monitor_view_qml_path: return if self._monitor_item is None: self._monitor_item = QtApplication.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self}) ## Attempt to establish connection def connect(self) -> None: self.setConnectionState(ConnectionState.connecting) self._update_timer.start() ## Attempt to close the connection def close(self) -> None: self._update_timer.stop() self.setConnectionState(ConnectionState.closed) ## Ensure that close gets called when object is destroyed def __del__(self) -> None: self.close() @pyqtProperty(bool, notify = acceptsCommandsChanged) def acceptsCommands(self) -> bool: return self._accepts_commands @deprecated("Please use the protected function instead", "3.2") def setAcceptsCommands(self, accepts_commands: bool) -> None: self._setAcceptsCommands(accepts_commands) ## Set a flag to signal the UI that the printer is not (yet) ready to receive commands def _setAcceptsCommands(self, accepts_commands: bool) -> None: if self._accepts_commands != accepts_commands: self._accepts_commands = accepts_commands self.acceptsCommandsChanged.emit() # Returns the unique configurations of the printers within this output device @pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged) def uniqueConfigurations(self) -> List["ConfigurationModel"]: return self._unique_configurations def _updateUniqueConfigurations(self) -> None: self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None])) self._unique_configurations.sort(key = lambda k: k.printerType) self.uniqueConfigurationsChanged.emit() def _onPrintersChanged(self) -> None: for printer in self._printers: printer.configurationChanged.connect(self._updateUniqueConfigurations) # At this point there may be non-updated configurations self._updateUniqueConfigurations() ## Set the device firmware name # # \param name The name of the firmware. def _setFirmwareName(self, name: str) -> None: self._firmware_name = name ## Get the name of device firmware # # This name can be used to define device type def getFirmwareName(self) -> Optional[str]: return self._firmware_name def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]: return self._firmware_updater @pyqtSlot(str) def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None: if not self._firmware_updater: return self._firmware_updater.updateFirmware(firmware_file)
class RunningDialog(QDialog, Ui_RunningDialog): """ running dialog for all the algorithms # Dialog Description ## Purpose To run an AI algorithms ## Structure The dialog is composed from -# Horizontal layout that holds 2 Plot containers (viewing of a graph) and a text edit for the messages -# Grid layout with 4 rows (from top to button): -# labels that will be used to show the time elapse for the action activated by the buttons below them -# The buttons row to activate the algorithm -# labels that will be used to show the time elapse for independent actions -# The buttons row for the independent actions ## Dialog working method -# Each one of the buttons is responsible for one step in the processing of the algorithm -# The steps are : -# Load -# Normalize -# Init -# Repeating steps or Run to end -# Test -# Additionally There are independent actions: -# Activate the scipy version of the algorithm -# Activating the scikit - Learn version of the algorithm -# Each button is doing: -# Get all the inputs needed for the action -# Activate the activate method -# The activate method does: -# Start a timer -# Call the run method of the model to perform the action -# While the action is running in a separate thread in the model, the timer is active in the dialog -# The timer is running as long as the model is running an action -# When the model finished an action, it sets an event -# The timer method checks the event and if it is set it does: -# Stop the timer -# Generate a finish/error message -# Set the enable attribute of the buttons according a dictionary in the model ## Attributes -# self.model - The model of the dialog (That performs the actual operations) -# self.timer - The timer that is presenting the time and activates the end action operations -# self.action - The action to perform (set by the activate method to be sent to the model) -# self.timerLabel - Tells the timer event handler where to write the time elapsed when an action is performed -# self.timerText - A text for the time label (set by the action method and used by the timer event handler) -# self.startTime - The start time of the action to be used by the timer event -# self.stopEvent - The event that will be used by the model to signal to the timer that it finished Args: Parent : (QDialog) - The calling dialog """ def __init__(self, parent): QDialog.__init__(self, parent) self.setupUi(self) self.pushButton_load.clicked.connect(self.pushButton_load_clicked) self.pushButton_normalize.clicked.connect( self.pushButton_normalize_clicked) self.pushButton_init.clicked.connect(self.pushButton_init_clicked) self.pushButton_step.clicked.connect(self.pushButton_step_clicked) self.pushButton_runToEnd.clicked.connect( self.pushButton_runToEnd_clicked) self.pushButton_test.clicked.connect(self.pushButton_test_clicked) self.pushButton_scipy.clicked.connect(self.pushButton_scipy_clicked) self.pushButton_scikitLearn.clicked.connect( self.pushButton_scikitLearn_clicked) self.pushButton_exit.clicked.connect(self.reject) self.model = RunningDialogModel() self.model.view = self self.setWidgetsEnable() self.timer = QTimer(self) self.timer.timeout.connect(self.timerEvent) @staticmethod def prms(widget): """ This is a static method that returns the parameters that the user can set for the dialog The parameters are changed in the ParametersDialog dialog Args: widget: (QWidget) - The parametersWidget that is used to change the parameters """ parameters = [] parameters.append( StdPrm("Algorithm data file", "", False, ParameterInput.button, ["Select algorithm data file"], widget.fileInput, ["Select data file", DATA_PATH, "csv file (*.csv)"])) parameters.append( StdPrm("Test data file", "", False, ParameterInput.button, ["Select test data file"], widget.fileInput, ["Select test data file", DATA_PATH, "csv file (*.csv)"])) plotHandlers = [ plotHandler.__name__ for plotHandler in PythonUtilities.inheritors(PlotHandler) ] parameters.append( StdPrm("Left Plot Handler", plotHandlers[0], False, ParameterInput.comboBox, [plotHandlers], widget.selectionChanged, [])) parameters.append( StdPrm("Right Plot Handler", plotHandlers[0], False, ParameterInput.comboBox, [plotHandlers], widget.selectionChanged, [])) return parameters def setWidgetsEnable(self): """ Sets the widgets enable attribute according to the allowed actions by the model Each action in the model decides according to it's results what are the possible actions that can be activated after it This method enable/disable the buttons according to that decision """ self.pushButton_load.setEnabled( self.model.enable_actions[Actions.Load]) self.pushButton_normalize.setEnabled( self.model.enable_actions[Actions.Normalize]) self.pushButton_init.setEnabled( self.model.enable_actions[Actions.Init]) self.pushButton_step.setEnabled( self.model.enable_actions[Actions.Step]) self.spinBox_numSteps.setEnabled(self.model.enable_actions[Actions.Step] or \ self.model.enable_actions[Actions.Step]) self.pushButton_runToEnd.setEnabled( self.model.enable_actions[Actions.RunToEnd]) self.pushButton_test.setEnabled( self.model.enable_actions[Actions.Test]) self.pushButton_scipy.setEnabled( self.model.enable_actions[Actions.SciPy]) self.pushButton_scikitLearn.setEnabled( self.model.enable_actions[Actions.SciKitLearn]) def timerEvent(self): """ The event activated by the timer #### This method does: -# When the timer is active - Show the time elapsed from the beginning of the action -# When the event is set - Activating ending actions """ currentTime = datetime.now() elapsedTime = currentTime - self.startTime self.timerLabel.setText(self.timerText + " time: " + str(elapsedTime)) self.addMessage() if self.stopEvent.isSet(): QApplication.restoreOverrideCursor() self.setWidgetsEnable() self.generateEndMessage() self.timer.stop() def generateEndMessage(self): """ Generate the end action message The inputs of the message generation is found in the model #### The message is generated in the following way: -# If the algorithm message is not empty add the algorithm message to the model message -# Else leave the model message -# If the step succeeded produce an information message -# Else produce an error message """ if self.model.algorithm_message == "": message = self.model.end_message else: message = self.model.end_message + ":\n" + self.model.algorithm_message if self.model.success: QMessageBox.information(self, "RunningDialog Information", message) else: QMessageBox.critical(self, "RunningDialog Information", message) def initPlot(self): """ Initialize a plot """ self.myQtPlotContainer_left.init( eval(self.parameters["Left Plot Handler"]["value"] + "(self.model.algorithm_data)")) self.myQtPlotContainer_right.init( eval(self.parameters["Right Plot Handler"]["value"] + "(self.model.algorithm_data)")) def plot(self): """ Update a plot """ self.myQtPlotContainer_left.handler.drawStepResult() self.myQtPlotContainer_right.handler.drawStepResult() def addMessage(self): """ Add all the messages currently in the queue to the PlainTextEdit - This method is activated while running by the timer - Because it is activated by the timer it is not known how many messages the model inserted to it between the timer ticks. so we empty all the queue in a loog """ while True: try: msg = self.messages.get(block=False) textCharFormat = QTextCharFormat() textCharFormat.setForeground(msg.Color) self.plainTextEdit_messages.textCursor().insertText( msg.Message + "\n", textCharFormat) self.plainTextEdit_messages.ensureCursorVisible() except Empty: break def activate(self, action: Actions, label: QLabel, text: str): """ Activate an action (The timer and the appropriate action of the model) Args: action : (Actions) - The action to perform label : (QLabel) - The label to write the time result in text : (string) - The text for the time label """ self.messages = Queue() self.action = action self.timerLabel = label self.timerText = text self.startTime = datetime.now() self.stopEvent = Event() self.timer.start(100) self.model.run(action, self.stopEvent) def initRun(self): """ Clear all the time labels when a load is done """ self.label_loadTime.setText("") self.label_normalizeTime.setText("") self.label_initTime.setText("") self.label_stepTime.setText("") self.label_runToEndTime.setText("") self.stepCount = 0 def pushButton_load_clicked(self): """ Set the parameters and load the file -# Activate the Running dialog to get the: -# Parameters for the dialog -# The training method -# The parameters for the training class -# The algorithm -# The parameters for the algorithm -# Check if algorithm data file was inserted """ parametersDialog = ParametersDialog(self) if parametersDialog.exec_() == QDialog.Accepted: _, self.parameters = parametersDialog.dialogPrms() self.model.train, self.model.trainPrms = parametersDialog.trainPrms( ) self.model.algorithm, self.model.algorithmPrms = parametersDialog.algorithmPrms( ) self.model.filename = self.parameters["Algorithm data file"][ "value"] self.model.test_filename = self.parameters["Test data file"][ "value"] self.plainTextEdit_messages.clear() self.initRun() self.activate(Actions.Load, self.label_loadTime, "Load") def pushButton_normalize_clicked(self): """ Normalize data and design the algorithm data -# Start an algorithmDataDesign dialog -# Set the results of the algorithm data design in the model -# activate normalize action in the model (by calling to the activate method) """ # activate the algorithm_data design and set the model.data_matrix (data for the algorithm) algorithmDataDesign = AlgorithmDataDesign( self, self.model.data_matrix, self.parameters["Algorithm data file"]["value"]) algorithmDataDesign.exec_() self.model.normalize_data = algorithmDataDesign.normalizeData self.model.data_matrix = algorithmDataDesign.dataMatrix _, _, self.model.test_data_matrix = algorithmDataDesign.loadNormalizeData( self.model.test_data_matrix) # create the model self.activate(Actions.Normalize, self.label_normalizeTime, "Normalize") def pushButton_init_clicked(self): """ Activate the init action of the algorithm """ self.activate(Actions.Init, self.label_initTime, "Init") self.plot() def pushButton_step_clicked(self, numSteps=0): """ Activate the step action of the model """ self.model.num_steps = self.spinBox_numSteps.value() self.message = "" self.activate(Actions.Step, self.label_stepTime, "Step") def pushButton_runToEnd_clicked(self): """ Activate the run to end action of the model """ self.model.num_steps = self.spinBox_numSteps.value() self.activate(Actions.RunToEnd, self.label_runToEndTime, "RunToEnd") def pushButton_test_clicked(self): """ Activate the test action of the algorithm -# Gets the file name of the test data -# Set the test file name attribute of the model -# Activate the test action of the model """ self.activate(Actions.Test, self.label_testTime, "Test") def pushButton_scipy_clicked(self): """ Activate the scipy vertion of the algorithm -# Gets the file name of the test data -# Set the test file name attribute of the model -# Activate the test action of the model """ self.activate(Actions.SciPy, self.label_scipyTime, "Scipy") def pushButton_scikitLearn_clicked(self): """ Activate the scikit - Learn version aof the algorithm -# Gets the file name of the test data -# Set the test file name attribute of the model -# Activate the test action of the model """ self.activate(Actions.SciKitLearn, self.label_scikitLearnTime, "Scikit-Learn") self.initPlot()
class Timer(QLabel): def __init__(self, parent, toplevel): super(QLabel, self).__init__(parent) self.toplevel = toplevel self.timer = QTimer() self.timer.timeout.connect(self.countdown) self.setAlignment(Qt.AlignCenter) def start(self, gallery, time): parent = self.parent() self.gallery = iter(gallery) self.image = next(self.gallery) parent.update(self.image) self.setGeometry(int(parent.width() * .85), int(parent.height() * .85), 75, 75) self.setStyleSheet('background: white; font: 20px') self.time, self.current = time, time self.updateText() self.timer.start(1000) def pause(self): if self.timer.isActive(): self.timer.stop() else: self.timer.start(1000) def updateText(self, delta=1): self.current = (self.current - delta) % self.time self.setText('{}:{:02}'.format(*divmod(self.current, 60))) self.setStyleSheet(f''' background: white; font: 20px; color: {"red" if self.current <= 5 else "black"} ''') def countdown(self): if self.current: self.updateText() else: worker = Worker(self.toplevel.mysql.execute, UPDATE.format(f'date_used=CURDATE()'), [self.image.data(Qt.UserRole)[0]], emit=0) self.toplevel.threadpool.start(worker) self.updateText() try: self.image = next(self.gallery) self.parent().update(self.image) except StopIteration: self.timer.stop() self.parent().update() self.setText('End of session') self.setStyleSheet( 'background: black; color: white; font: 20px') self.setGeometry(int(self.parent().width() * .4), int(self.parent().height() * .1), 125, 75)
class AutoSettingsObject(QObject): gain_changed = pyqtSignal(float) exposure_time_changed = pyqtSignal(float) def __init__(self, **kwargs): super().__init__(**kwargs) self._saturation = 0 self._enabled = False self._piUi = 0 self._last_error = 0 self._gain = None self._exposure_time = None self._min_gain = None self._max_gain = None self._min_exposure_time = None self._max_exposure_time = None self._setpoint = 85. self.Kp = None self.Kd = None self.Ki = None self._interval = 200 self._timer = QTimer() self._timer.timeout.connect(self._run) @pyqtSlot(int) def set_saturation(self, sat): self._saturation = sat @pyqtSlot(float) def set_exposure_time(self, exposure): self._exposure_time = exposure @pyqtSlot(float) def set_gain(self, gain): self._gain = gain def set_exposure_range(self, rng): self._min_exposure_time, self._max_exposure_time = rng def set_gain_range(self, rng): self._min_gain, self._max_gain = rng def start(self): self._timer.start(self._interval) def stop(self): self._timer.stop() def _run(self): error = self._saturation - self._setpoint ui = self._piUi + error * self._interval / 1000 * self.Ki self._piUi = ui ud = self._last_error / self._interval * self.Kd output = - self.Kp * (error + ui + ud) self._last_error = error previous_gain = self._gain previous_exposure_time = self._exposure_time if (error > 0 and self._min_gain < self._gain) or (error < 0 and self._gain < self._max_gain): # adjust gain db_increase = output / 5 self._gain = clamp(self._gain + db_increase, self._min_gain, self._max_gain) elif (error > 0 and self._min_exposure_time < self._exposure_time) or \ (error < 0 and self._exposure_time < self._max_exposure_time): self._exposure_time = clamp(self._exposure_time + output, self._min_exposure_time, self._max_exposure_time) else: # stuck at the edge... self._piUi = 0 if self._exposure_time != previous_exposure_time: self.exposure_time_changed.emit(self._exposure_time) if self._gain != previous_gain: self.gain_changed.emit(self._gain)
class ElectrumGui(Logger): @profiler def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'): set_language(config.get('language', get_default_language())) Logger.__init__(self) # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electrum-gzro.desktop') self.gui_thread = threading.current_thread() self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] self.efilter = OpenFileEventFilter(self.windows) self.app = QElectrumApplication(sys.argv) self.app.installEventFilter(self.efilter) self.app.setWindowIcon(read_QIcon("electrum-gzro.png")) # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.network_dialog = None self.lightning_dialog = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Electrum') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.app.new_window_signal.connect(self.start_new_window) self.set_dark_theme_if_needed() run_hook('init_qt', self) def set_dark_theme_if_needed(self): use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark' if use_dark_theme: try: import qdarkstyle self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) except BaseException as e: use_dark_theme = False self.logger.warning(f'Error setting dark theme: {repr(e)}') # Apply any necessary stylesheet patches patch_qt_stylesheet(use_dark_theme=use_dark_theme) # Even if we ourselves don't set the dark theme, # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme) def build_tray_menu(self): # Avoid immediate GC of old menu when window closed via its action if self.tray.contextMenu() is None: m = QMenu() self.tray.setContextMenu(m) else: m = self.tray.contextMenu() m.clear() if self.config.get('lightning'): m.addAction(_("Lightning"), self.show_lightning_dialog) for window in self.windows: name = window.wallet.basename() submenu = m.addMenu(name) submenu.addAction(_("Show/Hide"), window.show_or_hide) submenu.addAction(_("Close"), window.close) m.addAction(_("Dark/Light"), self.toggle_tray_icon) m.addSeparator() m.addAction(_("Exit Electrum"), self.close) def tray_icon(self): if self.dark_icon: return read_QIcon('electrum_dark_icon.png') else: return read_QIcon('electrum_light_icon.png') def toggle_tray_icon(self): self.dark_icon = not self.dark_icon self.config.set_key("dark_icon", self.dark_icon, True) self.tray.setIcon(self.tray_icon()) def tray_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() else: for w in self.windows: w.hide() def close(self): for window in self.windows: window.close() if self.network_dialog: self.network_dialog.close() if self.lightning_dialog: self.lightning_dialog.close() def new_window(self, path, uri=None): # Use a signal as can be called from daemon thread self.app.new_window_signal.emit(path, uri) def show_lightning_dialog(self): if not self.lightning_dialog: self.lightning_dialog = LightningDialog(self) self.lightning_dialog.bring_to_top() def show_network_dialog(self, parent): if not self.daemon.network: parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline')) return if self.network_dialog: self.network_dialog.on_update() self.network_dialog.show() self.network_dialog.raise_() return self.network_dialog = NetworkDialog(self.daemon.network, self.config, self.network_updated_signal_obj) self.network_dialog.show() def _create_window_for_wallet(self, wallet): w = ElectrumWindow(self, wallet) self.windows.append(w) self.build_tray_menu() # FIXME: Remove in favour of the load_wallet hook run_hook('on_new_window', w) w.warn_if_testnet() w.warn_if_watching_only() return w def count_wizards_in_progress(func): def wrapper(self: 'ElectrumGui', *args, **kwargs): with self._num_wizards_lock: self._num_wizards_in_progress += 1 try: return func(self, *args, **kwargs) finally: with self._num_wizards_lock: self._num_wizards_in_progress -= 1 return wrapper @count_wizards_in_progress def start_new_window(self, path, uri, *, app_is_starting=False): '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it''' wallet = None try: wallet = self.daemon.load_wallet(path, None) except BaseException as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (1):\n' + repr(e)) # if app is starting, still let wizard to appear if not app_is_starting: return if not wallet: try: wallet = self._start_wizard_to_select_or_create_wallet(path) except (WalletFileException, BitcoinException) as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (2):\n' + repr(e)) if not wallet: return # create or raise window try: for window in self.windows: if window.wallet.storage.path == wallet.storage.path: break else: window = self._create_window_for_wallet(wallet) except BaseException as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot create window for wallet') + ':\n' + repr(e)) if app_is_starting: wallet_dir = os.path.dirname(path) path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir)) self.start_new_window(path, uri) return if uri: window.pay_to_URI(uri) window.bring_to_top() window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) window.activateWindow() return window def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]: wizard = InstallWizard(self.config, self.app, self.plugins) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist if storage is None: wizard.path = path # needed by trustedcoin plugin wizard.run('new') storage = wizard.create_storage(path) else: wizard.run_upgrades(storage) except (UserCancelled, GoBack): return except WalletAlreadyOpenInMemory as e: return e.wallet finally: wizard.terminate() # return if wallet creation is not complete if storage is None or storage.get_action(): return wallet = Wallet(storage, config=self.config) wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) return wallet def close_window(self, window: ElectrumWindow): if window in self.windows: self.windows.remove(window) self.build_tray_menu() # save wallet path of last open window if not self.windows: self.config.save_last_wallet(window.wallet) run_hook('on_close_window', window) self.daemon.stop_wallet(window.wallet.storage.path) def init_network(self): # Show network dialog if config does not exist if self.daemon.network: if self.config.get('auto_connect') is None: wizard = InstallWizard(self.config, self.app, self.plugins) wizard.init_network(self.daemon.network) wizard.terminate() def main(self): try: self.init_network() except UserCancelled: return except GoBack: return except BaseException as e: self.logger.exception('') return self.timer.start() path = self.config.get_wallet_path(use_gui_last_wallet=True) if not self.start_new_window(path, self.config.get('url'), app_is_starting=True): return signal.signal(signal.SIGINT, lambda *args: self.app.quit()) def quit_after_last_window(): # keep daemon running after close if self.config.get('daemon'): return # check if a wizard is in progress with self._num_wizards_lock: if self._num_wizards_in_progress > 0 or len(self.windows) > 0: return if self.config.get('persist_daemon'): return self.app.quit() self.app.setQuitOnLastWindowClosed(False) # so _we_ can decide whether to quit self.app.lastWindowClosed.connect(quit_after_last_window) def clean_up(): # Shut down the timer cleanly self.timer.stop() # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html event = QtCore.QEvent(QtCore.QEvent.Clipboard) self.app.sendEvent(self.app.clipboard(), event) self.tray.hide() self.app.aboutToQuit.connect(clean_up) # main loop self.app.exec_() # on some platforms the exec_ call may not return, so use clean_up() def stop(self): self.logger.info('closing GUI') self.app.quit()
class OpencvImg(QDialog): def __init__(self): super(OpencvImg, self).__init__() loadUi('demo2gui.ui', self) self.image = None self.start_button.clicked.connect(self.start_webcam) self.stop_button.clicked.connect(self.stop_webcam) self.commandLinkButton.clicked.connect(self.start_card) def start_card(self): self.timer.timeout.connect(self.find_card) self.timer.start(5) def start_webcam(self): self.capture = cv2.VideoCapture(1) # self.capture = cap self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 300) self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, 400) self.x1_slider.setValue(95) self.y1_slider.setValue(53) self.x2_slider.setValue(491) self.y2_slider.setValue(60) self.x3_slider.setValue(45) self.y3_slider.setValue(458) self.x4_slider.setValue(523) self.y4_slider.setValue(457) self.card_lh.setValue(46) self.card_ls.setValue(0) self.card_lv.setValue(127) self.card_uh.setValue(179) self.card_us.setValue(255) self.card_uv.setValue(255) self.red_lh.setValue(0) self.red_ls.setValue(30) self.red_lv.setValue(6) self.red_uh.setValue(6) self.red_us.setValue(255) self.red_uv.setValue(255) self.green_lh.setValue(41) self.green_ls.setValue(21) self.green_lv.setValue(58) self.green_uh.setValue(93) self.green_us.setValue(255) self.green_uv.setValue(246) self.blue_lh.setValue(109) self.blue_ls.setValue(22) self.blue_lv.setValue(131) self.blue_uh.setValue(139) self.blue_us.setValue(255) self.blue_uv.setValue(255) self.yellow_lh.setValue(17) self.yellow_ls.setValue(46) self.yellow_lv.setValue(0) self.yellow_uh.setValue(29) self.yellow_us.setValue(255) self.yellow_uv.setValue(242) self.black_lh.setValue(90) self.black_ls.setValue(0) self.black_lv.setValue(0) self.black_uh.setValue(106) self.black_us.setValue(83) self.black_uv.setValue(220) # 115 0 0 166 29 243 # self.black_lh.setValue(0) # self.black_ls.setValue(0) # self.black_lv.setValue(0) # self.black_uh.setValue(179) # self.black_us.setValue(30) # self.black_uv.setValue(120) self.timer = QTimer(self) self.timer.timeout.connect(self.update_frame) self.timer.start(5) def update_frame(self): ret, self.image = self.capture.read() self.notCircle = self.image.copy() cv2.circle(self.image, (self.x1_slider.value(), self.y1_slider.value()), 5, (0, 0, 255), -1) self.current_value.setText('X1 Y1 -> :' + str(self.x1_slider.value()) + ' ' + str(self.y1_slider.value())) cv2.circle(self.image, (self.x2_slider.value(), self.y2_slider.value()), 5, (0, 0, 255), -1) self.current_value2.setText('X2 Y2 -> :' + str(self.x2_slider.value()) + ' ' + str(self.y2_slider.value())) cv2.circle(self.image, (self.x3_slider.value(), self.y3_slider.value()), 5, (0, 0, 255), -1) self.current_value3.setText('X3 Y3 -> :' + str(self.x3_slider.value()) + ' ' + str(self.y3_slider.value())) cv2.circle(self.image, (self.x4_slider.value(), self.y4_slider.value()), 5, (0, 0, 255), -1) self.current_value4.setText('X4 Y4 -> :' + str(self.x4_slider.value()) + ' ' + str(self.y4_slider.value())) pts1 = np.float32([[self.x1_slider.value(), self.y1_slider.value()], [self.x2_slider.value(), self.y2_slider.value()], [self.x3_slider.value(), self.y3_slider.value()], [self.x4_slider.value(), self.y4_slider.value()]]) pts2 = np.float32([[0, 0], [500, 0], [0, 500], [500, 500]]) matrix = cv2.getPerspectiveTransform(pts1, pts2) resultWarp = cv2.warpPerspective(self.notCircle, matrix, (500, 500)) self.imageWarp = resultWarp # self.image = cv2.flip(self.image, 1) # self.imageWarp = cv2.flip(self.imageWarp, 1) # self.displayImage(self.image, self.imageWarp, 1) Mblurred = cv2.medianBlur(self.imageWarp, 5) hsv = cv2.cvtColor(Mblurred, cv2.COLOR_BGR2HSV) color_lower = np.array( [self.card_lh.value(), self.card_ls.value(), self.card_lv.value()], np.uint8) color_upper = np.array( [self.card_uh.value(), self.card_us.value(), self.card_uv.value()], np.uint8) mask = cv2.inRange(hsv, color_lower, color_upper) kernel = np.ones((5, 5), np.uint8) red_lower = np.array( [self.red_lh.value(), self.red_ls.value(), self.red_lv.value()], np.uint8) red_upper = np.array( [self.red_uh.value(), self.red_us.value(), self.red_uv.value()], np.uint8) green_lower = np.array([ self.green_lh.value(), self.green_ls.value(), self.green_lv.value() ], np.uint8) green_upper = np.array([ self.green_uh.value(), self.green_us.value(), self.green_uv.value() ], np.uint8) blue_lower = np.array( [self.blue_lh.value(), self.blue_ls.value(), self.blue_lv.value()], np.uint8) blue_upper = np.array( [self.blue_uh.value(), self.blue_us.value(), self.blue_uv.value()], np.uint8) yellow_lower = np.array([ self.yellow_lh.value(), self.yellow_ls.value(), self.yellow_lv.value() ], np.uint8) yellow_upper = np.array([ self.yellow_uh.value(), self.yellow_us.value(), self.yellow_uv.value() ], np.uint8) black_lower = np.array([ self.black_lh.value(), self.black_ls.value(), self.black_lv.value() ], np.uint8) black_upper = np.array([ self.black_uh.value(), self.black_us.value(), self.black_uv.value() ], np.uint8) self.card_value.setText('Current Value -> Min :' + str(color_lower) + ' Max: ' + str(color_upper)) self.red_value.setText('Current Value -> Min :' + str(red_lower) + ' Max: ' + str(red_upper)) self.green_value.setText('Current Value -> Min :' + str(green_lower) + ' Max: ' + str(green_upper)) self.blue_value.setText('Current Value -> Min :' + str(blue_lower) + ' Max: ' + str(blue_upper)) self.yellow_value.setText('Current Value -> Min :' + str(yellow_lower) + ' Max: ' + str(yellow_upper)) self.black_value.setText('Current Value -> Min :' + str(black_lower) + ' Max: ' + str(black_upper)) color_mask = cv2.inRange(hsv, color_lower, color_upper) card = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=color_mask) bFilterC2 = cv2.bilateralFilter(color_mask, 9, 75, 75) openingC2 = cv2.morphologyEx(bFilterC2, cv2.MORPH_OPEN, kernel) closingC2 = cv2.morphologyEx(openingC2, cv2.MORPH_CLOSE, kernel) bFilterC = cv2.bilateralFilter(card, 9, 75, 75) openingC = cv2.morphologyEx(bFilterC, cv2.MORPH_OPEN, kernel) closingC = cv2.morphologyEx(openingC, cv2.MORPH_CLOSE, kernel) mask = cv2.erode(closingC2, kernel) edged = cv2.Canny(mask, 50, 220) contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) red_mask = cv2.inRange(hsv, red_lower, red_upper) red = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=red_mask) bFilterR2 = cv2.bilateralFilter(red_mask, 9, 75, 75) dilationR2 = cv2.dilate(bFilterR2, kernel, iterations=1) openingR2 = cv2.morphologyEx(dilationR2, cv2.MORPH_OPEN, kernel) closingR2 = cv2.morphologyEx(openingR2, cv2.MORPH_CLOSE, kernel) bFilterR2 = cv2.bilateralFilter(red, 9, 75, 75) dilationR = cv2.dilate(bFilterR2, kernel, iterations=1) openingR = cv2.morphologyEx(dilationR, cv2.MORPH_OPEN, kernel) closingR = cv2.morphologyEx(openingR, cv2.MORPH_CLOSE, kernel) green_mask = cv2.inRange(hsv, green_lower, green_upper) green = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=green_mask) dilationG2 = cv2.dilate(green_mask, kernel, iterations=1) openingG2 = cv2.morphologyEx(dilationG2, cv2.MORPH_OPEN, kernel) closingG2 = cv2.morphologyEx(openingG2, cv2.MORPH_CLOSE, kernel) dilationG = cv2.dilate(green, kernel, iterations=1) openingG = cv2.morphologyEx(dilationG, cv2.MORPH_OPEN, kernel) closingG = cv2.morphologyEx(openingG, cv2.MORPH_CLOSE, kernel) blue_mask = cv2.inRange(hsv, blue_lower, blue_upper) blue = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=blue_mask) dilationB2 = cv2.dilate(blue_mask, kernel, iterations=1) openingB2 = cv2.morphologyEx(dilationB2, cv2.MORPH_OPEN, kernel) closingB2 = cv2.morphologyEx(openingB2, cv2.MORPH_CLOSE, kernel) dilationB = cv2.dilate(blue, kernel, iterations=1) openingB = cv2.morphologyEx(dilationB, cv2.MORPH_OPEN, kernel) closingB = cv2.morphologyEx(openingB, cv2.MORPH_CLOSE, kernel) yellow_mask = cv2.inRange(hsv, yellow_lower, yellow_upper) yellow = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=yellow_mask) dilationY2 = cv2.dilate(yellow_mask, kernel, iterations=1) openingY2 = cv2.morphologyEx(dilationY2, cv2.MORPH_OPEN, kernel) closingY2 = cv2.morphologyEx(openingY2, cv2.MORPH_CLOSE, kernel) dilationY = cv2.dilate(yellow, kernel, iterations=1) openingY = cv2.morphologyEx(dilationY, cv2.MORPH_OPEN, kernel) closingY = cv2.morphologyEx(openingY, cv2.MORPH_CLOSE, kernel) black_mask = cv2.inRange(hsv, black_lower, black_upper) black = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=black_mask) dilationbl2 = cv2.dilate(black_mask, kernel, iterations=1) openingBl2 = cv2.morphologyEx(dilationbl2, cv2.MORPH_OPEN, kernel) closingBl2 = cv2.morphologyEx(openingBl2, cv2.MORPH_CLOSE, kernel) # openingBl2 = cv2.morphologyEx(black, cv2.MORPH_OPEN, kernel) # closingBl2 = cv2.morphologyEx(openingBl2, cv2.MORPH_CLOSE, kernel) # dilationBl2 = cv2.dilate(closingBl2, kernel, iterations=1) dilationBl = cv2.dilate(black, kernel, iterations=1) openingBl = cv2.morphologyEx(dilationBl, cv2.MORPH_OPEN, kernel) closingBl = cv2.morphologyEx(openingBl, cv2.MORPH_CLOSE, kernel) self.displayImage(self.image, closingC, 1) self.displayImage(self.image, mask, 2) self.displayImage(self.image, edged, 13) # self.displayImage(self.image, edge, 14) self.displayImage(self.image, closingR2, 3) self.displayImage(self.image, closingR, 4) self.displayImage(self.image, self.imageWarp, 15) self.displayImage(self.image, closingG2, 5) self.displayImage(self.image, closingG, 6) self.displayImage(self.image, closingB2, 7) self.displayImage(self.image, closingB, 8) self.displayImage(self.image, closingY2, 9) self.displayImage(self.image, closingY, 10) self.displayImage(self.image, closingBl2, 11) self.displayImage(self.image, closingBl, 12) def find_card(self): stateWork = 0 print("find card ... !!!") rX = 100 rY = 100 gX = 100 gY = 100 bX = 100 bY = 100 yX = 100 yY = 100 blX = 100 blY = 100 self.debugTextBrowser.append("Home ...") ret, self.image = self.capture.read() self.notCircle = self.image.copy() cv2.circle(self.image, (self.x1_slider.value(), self.y1_slider.value()), 5, (0, 0, 255), -1) self.current_value.setText('X1 Y1 -> :' + str(self.x1_slider.value()) + ' ' + str(self.y1_slider.value())) cv2.circle(self.image, (self.x2_slider.value(), self.y2_slider.value()), 5, (0, 0, 255), -1) self.current_value2.setText('X2 Y2 -> :' + str(self.x2_slider.value()) + ' ' + str(self.y2_slider.value())) cv2.circle(self.image, (self.x3_slider.value(), self.y3_slider.value()), 5, (0, 0, 255), -1) self.current_value3.setText('X3 Y3 -> :' + str(self.x3_slider.value()) + ' ' + str(self.y3_slider.value())) cv2.circle(self.image, (self.x4_slider.value(), self.y4_slider.value()), 5, (0, 0, 255), -1) self.current_value4.setText('X4 Y4 -> :' + str(self.x4_slider.value()) + ' ' + str(self.y4_slider.value())) pts1 = np.float32([[self.x1_slider.value(), self.y1_slider.value()], [self.x2_slider.value(), self.y2_slider.value()], [self.x3_slider.value(), self.y3_slider.value()], [self.x4_slider.value(), self.y4_slider.value()]]) pts2 = np.float32([[0, 0], [500, 0], [0, 500], [500, 500]]) matrix = cv2.getPerspectiveTransform(pts1, pts2) resultWarp = cv2.warpPerspective(self.notCircle, matrix, (500, 500)) self.imageWarp = resultWarp # self.imageWarp = cv2.flip(self.imageWarp, 1) # self.displayImage(self.image, self.imageWarp, 1) Mblurred = cv2.medianBlur(self.imageWarp, 5) hsv = cv2.cvtColor(Mblurred, cv2.COLOR_BGR2HSV) color_lower = np.array( [self.card_lh.value(), self.card_ls.value(), self.card_lv.value()], np.uint8) color_upper = np.array( [self.card_uh.value(), self.card_us.value(), self.card_uv.value()], np.uint8) mask = cv2.inRange(hsv, color_lower, color_upper) kernel = np.ones((5, 5), np.uint8) red_lower = np.array( [self.red_lh.value(), self.red_ls.value(), self.red_lv.value()], np.uint8) red_upper = np.array( [self.red_uh.value(), self.red_us.value(), self.red_uv.value()], np.uint8) green_lower = np.array([ self.green_lh.value(), self.green_ls.value(), self.green_lv.value() ], np.uint8) green_upper = np.array([ self.green_uh.value(), self.green_us.value(), self.green_uv.value() ], np.uint8) blue_lower = np.array( [self.blue_lh.value(), self.blue_ls.value(), self.blue_lv.value()], np.uint8) blue_upper = np.array( [self.blue_uh.value(), self.blue_us.value(), self.blue_uv.value()], np.uint8) yellow_lower = np.array([ self.yellow_lh.value(), self.yellow_ls.value(), self.yellow_lv.value() ], np.uint8) yellow_upper = np.array([ self.yellow_uh.value(), self.yellow_us.value(), self.yellow_uv.value() ], np.uint8) black_lower = np.array([ self.black_lh.value(), self.black_ls.value(), self.black_lv.value() ], np.uint8) black_upper = np.array([ self.black_uh.value(), self.black_us.value(), self.black_uv.value() ], np.uint8) self.card_value.setText('Current Value -> Min :' + str(color_lower) + ' Max: ' + str(color_upper)) self.red_value.setText('Current Value -> Min :' + str(red_lower) + ' Max: ' + str(red_upper)) self.green_value.setText('Current Value -> Min :' + str(green_lower) + ' Max: ' + str(green_upper)) self.blue_value.setText('Current Value -> Min :' + str(blue_lower) + ' Max: ' + str(blue_upper)) self.yellow_value.setText('Current Value -> Min :' + str(yellow_lower) + ' Max: ' + str(yellow_upper)) self.black_value.setText('Current Value -> Min :' + str(black_lower) + ' Max: ' + str(black_upper)) color_mask = cv2.inRange(hsv, color_lower, color_upper) card = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=color_mask) openingC2 = cv2.morphologyEx(color_mask, cv2.MORPH_OPEN, kernel) closingC2 = cv2.morphologyEx(openingC2, cv2.MORPH_CLOSE, kernel) openingC = cv2.morphologyEx(card, cv2.MORPH_OPEN, kernel) closingC = cv2.morphologyEx(openingC, cv2.MORPH_CLOSE, kernel) mask = cv2.erode(closingC2, kernel) edged = cv2.Canny(mask, 50, 220) contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: # time.sleep(0.1) area = cv2.contourArea(cnt) approx = cv2.approxPolyDP(cnt, 0.02 * cv2.arcLength(cnt, True), True) x = approx.ravel()[0] y = approx.ravel()[1] self.debugTextBrowser.append(str(area)) if 14000 > area > 5000: # stateWork = 1 if len(approx) == 4: time.sleep(0.5) cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5] Approx = approx Outline = cv2.drawContours(self.imageWarp.copy(), [Approx], -5, (0, 0, 255), 1) ratio = Outline.shape[0] / 500 Crop_card = four_point_transform( self.imageWarp, Approx.reshape(4, 2) * ratio) Crop_card = cv2.resize(Crop_card, (int(500), int(500))) img_name = "crop_card.png" # time.sleep(0.1) cv2.imwrite(img_name, Crop_card) imgCrop = cv2.imread("crop_card.png") # ----------------- clahe = cv2.createCLAHE(clipLimit=3., tileGridSize=(8, 8)) lab = cv2.cvtColor(imgCrop, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) # split on 3 different channels l2 = clahe.apply(l) # apply CLAHE to the L-channel lab = cv2.merge((l2, a, b)) # merge channels imgCrop = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) # ----------------- # cardCount = 0 stateWork = 0 countRed = 0 countBlue = 0 countYellow = 0 countGreen = 0 countBlack = 0 cropBlur = cv2.medianBlur(imgCrop, 5) hsv = cv2.cvtColor(cropBlur, cv2.COLOR_BGR2HSV) red_mask = cv2.inRange(hsv, red_lower, red_upper) red = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=red_mask) bFilterR2 = cv2.bilateralFilter(red_mask, 9, 75, 75) dilationR2 = cv2.dilate(bFilterR2, kernel, iterations=1) openingR2 = cv2.morphologyEx(dilationR2, cv2.MORPH_OPEN, kernel) closingR2 = cv2.morphologyEx(openingR2, cv2.MORPH_CLOSE, kernel) # bFilterR2 = cv2.bilateralFilter(red_mask, 9, 75, 75) dilationR = cv2.dilate(red, kernel, iterations=1) openingR = cv2.morphologyEx(dilationR, cv2.MORPH_OPEN, kernel) closingR = cv2.morphologyEx(openingR, cv2.MORPH_CLOSE, kernel) green_mask = cv2.inRange(hsv, green_lower, green_upper) green = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=green_mask) dilationG2 = cv2.dilate(green_mask, kernel, iterations=1) openingG2 = cv2.morphologyEx(dilationG2, cv2.MORPH_OPEN, kernel) closingG2 = cv2.morphologyEx(openingG2, cv2.MORPH_CLOSE, kernel) dilationG = cv2.dilate(green, kernel, iterations=1) openingG = cv2.morphologyEx(dilationG, cv2.MORPH_OPEN, kernel) closingG = cv2.morphologyEx(openingG, cv2.MORPH_CLOSE, kernel) blue_mask = cv2.inRange(hsv, blue_lower, blue_upper) blue = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=blue_mask) dilationB2 = cv2.dilate(blue_mask, kernel, iterations=1) openingB2 = cv2.morphologyEx(dilationB2, cv2.MORPH_OPEN, kernel) closingB2 = cv2.morphologyEx(openingB2, cv2.MORPH_CLOSE, kernel) dilationB = cv2.dilate(blue, kernel, iterations=1) openingB = cv2.morphologyEx(dilationB, cv2.MORPH_OPEN, kernel) closingB = cv2.morphologyEx(openingB, cv2.MORPH_CLOSE, kernel) yellow_mask = cv2.inRange(hsv, yellow_lower, yellow_upper) yellow = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=yellow_mask) dilationY2 = cv2.dilate(yellow_mask, kernel, iterations=1) openingY2 = cv2.morphologyEx(dilationY2, cv2.MORPH_OPEN, kernel) closingY2 = cv2.morphologyEx(openingY2, cv2.MORPH_CLOSE, kernel) dilationY = cv2.dilate(yellow, kernel, iterations=1) openingY = cv2.morphologyEx(dilationY, cv2.MORPH_OPEN, kernel) closingY = cv2.morphologyEx(openingY, cv2.MORPH_CLOSE, kernel) black_mask = cv2.inRange(hsv, black_lower, black_upper) black = cv2.bitwise_and(self.imageWarp, self.imageWarp, mask=black_mask) bFilterBl2 = cv2.bilateralFilter(black_mask, 9, 75, 75) dilationbl2 = cv2.dilate(bFilterBl2, kernel, iterations=1) openingBl2 = cv2.morphologyEx(dilationbl2, cv2.MORPH_OPEN, kernel) closingBl2 = cv2.morphologyEx(openingBl2, cv2.MORPH_CLOSE, kernel) dilationBl = cv2.dilate(black, kernel, iterations=1) openingBl = cv2.morphologyEx(dilationBl, cv2.MORPH_OPEN, kernel) closingBl = cv2.morphologyEx(openingBl, cv2.MORPH_CLOSE, kernel) contoursRed, _ = cv2.findContours(closingR2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) try: biggest_contoursRed = max(contoursRed, key=cv2.contourArea) (x, y, w, h) = cv2.boundingRect(biggest_contoursRed) # cv2.rectangle(imgCrop, (x, y), # (x+w, y+h), (0, 0, 255), 2) countRed = 1 Mred = cv2.moments(biggest_contoursRed) rX = int(Mred["m10"] / Mred["m00"]) rY = int(Mred["m01"] / Mred["m00"]) cv2.circle(imgCrop, (rX, rY), 5, (0, 0, 255), -1) cv2.putText( imgCrop, 'X :' + str(rX) + " Y :" + str(rY), # bottomLeftCornerOfText (rX + 30, rY), cv2.FONT_HERSHEY_SIMPLEX, # font 0.55, # fontScale (0, 0, 255), # fontColor 1) # cv2.putText(resultWarp, ":" + rX, (rX - 25, rY - 25), # cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2) except: pass contoursBlue, _ = cv2.findContours(closingB2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) try: biggest_contoursBlue = max(contoursBlue, key=cv2.contourArea) (x, y, w, h) = cv2.boundingRect(biggest_contoursBlue) # cv2.rectangle(imgCrop, (x, y), # (x+w, y+h), (255, 0, 0), 2) countBlue = 1 Mblue = cv2.moments(biggest_contoursBlue) bX = int(Mblue["m10"] / Mblue["m00"]) bY = int(Mblue["m01"] / Mblue["m00"]) cv2.circle(imgCrop, (bX, bY), 5, (255, 0, 0), -1) cv2.putText( imgCrop, 'X :' + str(bX) + " Y :" + str(bY), # bottomLeftCornerOfText (bX + 30, bY), cv2.FONT_HERSHEY_SIMPLEX, # font 0.55, # fontScale (255, 0, 0), # fontColor 1) except: pass contoursGreen, _ = cv2.findContours( closingG2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) try: biggest_contoursGreen = max(contoursGreen, key=cv2.contourArea) (x, y, w, h) = cv2.boundingRect(biggest_contoursGreen) # cv2.rectangle(imgCrop, (x, y), # (x+w, y+h), (0, 255, 0), 2) countGreen = 1 Mgreen = cv2.moments(biggest_contoursGreen) gX = int(Mgreen["m10"] / Mgreen["m00"]) gY = int(Mgreen["m01"] / Mgreen["m00"]) cv2.circle(imgCrop, (gX, gY), 5, (0, 255, 0), -1) cv2.putText( imgCrop, 'X :' + str(gX) + " Y :" + str(gY), # bottomLeftCornerOfText (gX + 30, gY), cv2.FONT_HERSHEY_SIMPLEX, # font 0.55, # fontScale (0, 255, 0), # fontColor 1) pGx = gX * 0.8 pGy = gY * 0.8 cv2.circle(imgCrop, (pGx, pGy), 5, (0, 255, 0), -1) except: pass contoursYellow, _ = cv2.findContours( closingY2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) try: biggest_contoursYellow = max(contoursYellow, key=cv2.contourArea) (x, y, w, h) = cv2.boundingRect(biggest_contoursYellow) # cv2.rectangle(imgCrop, (x, y), # (x+w, y+h), (0, 255, 255), 2) countYellow = 1 Myellow = cv2.moments(biggest_contoursYellow) yX = int(Myellow["m10"] / Myellow["m00"]) yY = int(Myellow["m01"] / Myellow["m00"]) cv2.circle(imgCrop, (yX, yY), 5, (0, 255, 255), -1) cv2.putText( imgCrop, 'X :' + str(yX) + " Y :" + str(yY), # bottomLeftCornerOfText (yX + 30, yY), cv2.FONT_HERSHEY_SIMPLEX, # font 0.55, # fontScale (0, 255, 255), # fontColor 1) except: pass contoursBlack, _ = cv2.findContours( closingBl2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) try: biggest_contoursBlack = max(contoursBlack, key=cv2.contourArea) (x, y, w, h) = cv2.boundingRect(biggest_contoursBlack) # cv2.rectangle(imgCrop, (x, y), (x+w, y+h), (0, 0, 0), 2) countBlack = 1 Mblack = cv2.moments(biggest_contoursBlack) blX = int(Mblack["m10"] / Mblack["m00"]) blY = int(Mblack["m01"] / Mblack["m00"]) cv2.circle(imgCrop, (blX, blY), 5, (191, 191, 191), -1) cv2.putText( imgCrop, 'X :' + str(blX) + " Y :" + str(blY), # bottomLeftCornerOfText (blX + 30, blY), cv2.FONT_HERSHEY_SIMPLEX, # font 0.55, # fontScale (0, 0, 0), # fontColor 1) except: pass rX = int(250 - (rX / 2)) rY = int(250 - (rY / 2)) gX = int(250 - (gX / 2)) gY = int(250 - (gY / 2)) bX = int(250 - (bX / 2)) bY = int(250 - (bY / 2)) yX = int(250 - (yX / 2)) yY = int(250 - (yY / 2)) blX = int(250 - (blX / 2)) blY = int(250 - (blY / 2)) Send = [rY, rX, gY, gX, bY, bX, yY, yX, blY, blX] # Send = [250,250,0,250,250,0,0,0,0,0] self.debugTextBrowser.append(str(Send)) self.displayImage(Outline, imgCrop, 14) if stateWork == 0: print("stateWork = 0") for i in Send: time.sleep(0.2) c = struct.pack('B', i) serialPIC.write(c) # self.debugTextBrowser.append(str(i)) # # r g b y black sCount = 0 while sCount < 10: # print("start "+ str(sCount)) Rpic = serialPIC.read() Radr = serialAD.read() if Rpic == b'1': keep = struct.pack('B', 49) serialAD.write(keep) print("keep") elif Rpic == b'0': paste = struct.pack('B', 48) serialAD.write(paste) print("past") else: continue # print(sCount) sCount += 1 stateWork = 1 time.sleep(8) print("------FINISH------- & statework =" + str(stateWork)) subprocess.call(["afplay", "beep-06.wav"]) self.timer.stop() # ------------------ # else: # # pass # self.timer.stop() # print("else stop") # print("stop") # self.timer.stop() def stop_webcam(self): self.capture.release() self.timer.stop() # def stop_card(self): # # self.capture.release() # self.timer.stop() def displayImage(self, img, imageWarp, window=1): qformat = QImage.Format_Indexed8 if len(imageWarp.shape) == 3: # [0]=rows, [1]=cols, [2]=channels if imageWarp.shape[2] == 4: qformat = QImage.Format_RGBA8888 else: qformat = QImage.Format_RGB888 outImage = QImage(img, img.shape[1], img.shape[0], img.strides[0], qformat) outImage = outImage.rgbSwapped() outImageWarp = QImage(imageWarp, imageWarp.shape[1], imageWarp.shape[0], imageWarp.strides[0], qformat) outImageWarp = outImageWarp.rgbSwapped() if window == 1: self.frame_card1.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_card1.setScaledContents(True) self.camera_set.setPixmap(QPixmap.fromImage(outImage)) self.camera_set.setScaledContents(True) if window == 2: self.frame_card2.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_card2.setScaledContents(True) elif window == 3: self.frame_red.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_red.setScaledContents(True) elif window == 4: self.frame_red2.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_red2.setScaledContents(True) elif window == 5: self.frame_green.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_green.setScaledContents(True) elif window == 6: self.frame_green2.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_green2.setScaledContents(True) elif window == 7: self.frame_blue.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_blue.setScaledContents(True) elif window == 8: self.frame_blue2.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_blue2.setScaledContents(True) elif window == 9: self.frame_yellow.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_yellow.setScaledContents(True) elif window == 10: self.frame_yellow2.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_yellow2.setScaledContents(True) elif window == 11: self.frame_black.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_black.setScaledContents(True) elif window == 12: self.frame_black2.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_black2.setScaledContents(True) elif window == 13: self.frame_card3.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_card3.setScaledContents(True) elif window == 14: self.card_output.setPixmap(QPixmap.fromImage(outImageWarp)) self.card_output.setScaledContents(True) self.card_output2.setPixmap(QPixmap.fromImage(outImage)) self.card_output2.setScaledContents(True) self.card_output3.setPixmap(QPixmap.fromImage(outImageWarp)) self.card_output3.setScaledContents(True) elif window == 15: self.frame_red3.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_red3.setScaledContents(True) self.frame_green3.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_green3.setScaledContents(True) self.frame_blue3.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_blue3.setScaledContents(True) self.frame_yellow3.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_yellow3.setScaledContents(True) self.frame_black3.setPixmap(QPixmap.fromImage(outImageWarp)) self.frame_black3.setScaledContents(True)
class NEditor(QPlainTextEdit): """Ninja-IDE Editor""" # Editor signals fontChanged = pyqtSignal('QString') zoomChanged = pyqtSignal(int) editorFocusObtained = pyqtSignal() cursor_position_changed = pyqtSignal(int, int) keyPressed = pyqtSignal(QKeyEvent) postKeyPressed = pyqtSignal(QKeyEvent) painted = pyqtSignal() current_line_changed = pyqtSignal(int) # FIXME: cambiar esto highlight_checker_updated = pyqtSignal('PyQt_PyObject') addBackItemNavigation = pyqtSignal() @property def nfile(self): return self._neditable.nfile @property def neditable(self): return self._neditable @property def file_path(self): return self._neditable.file_path @property def is_modified(self) -> bool: return self.document().isModified() @property def visible_blocks(self) -> Tuple[int, int, QTextBlock]: return self.__visible_blocks @property def default_font(self): return self.document().defaultFont() @property def encoding(self): if self.__encoding is not None: return self.__encoding return 'utf-8' @encoding.setter def encoding(self, encoding): self.__encoding = encoding @property def cursor_position(self) -> Tuple[int, int]: """Get or set the current cursor position. :param position: The position to set. :type position: tuple(line, column). :return: Current cursor position in document. :rtype: tuple(line, colum).""" cursor = self.textCursor() return (cursor.blockNumber(), cursor.columnNumber()) @cursor_position.setter def cursor_position(self, position): line, column = position line = min(line, self.line_count() - 1) column = min(column, len(self.line_text(line))) cursor = QTextCursor(self.document().findBlockByNumber(line)) cursor.setPosition(cursor.block().position() + column, QTextCursor.MoveAnchor) self.setTextCursor(cursor) @property def background_color(self) -> QColor: """Get or set the background color. :param color: Color to set (name or hexa). :type color: QColor or str. :return: Background color. :rtype: QColor.""" return self._background_color @background_color.setter def background_color(self, color): if isinstance(color, str): color = QColor(color) self._background_color = color # Refresh stylesheet self.__apply_style() @property def foreground_color(self): """Get or set the foreground color. :param color: Color to set (name or hexa). :type color: QColor or str. :return: Foreground color. :rtype: QColor""" return self._foreground_color @foreground_color.setter def foreground_color(self, color): if isinstance(color, str): color = QColor(color) self._foreground_color = color self.__apply_style() @property def show_whitespaces(self): return self.__show_whitespaces @show_whitespaces.setter def show_whitespaces(self, show): if self.__show_whitespaces != show: self.__show_whitespaces = show self.__set_whitespaces_flags(show) @property def margins(self): return self.__margins def __init__(self, neditable): QPlainTextEdit.__init__(self) self.setFrameStyle(0) # Remove border self._neditable = neditable self.setMouseTracking(True) # Style self._background_color = QColor( resources.get_color('EditorBackground')) self._foreground_color = QColor(resources.get_color('Default')) self._selection_color = QColor( resources.get_color('EditorSelectionColor')) self._selection_background_color = QColor( resources.get_color('EditorSelectionBackground')) self.__apply_style() self._init_settings() self._highlighter = None self.__visible_blocks = [] self._last_line_position = 0 self.__encoding = None self.__show_whitespaces = settings.SHOW_TABS_AND_SPACES self.__side_widgets = [] # Extra Selections self._extra_selections = OrderedDict() self._current_line_selection = None self.__checker_extra_selections = [] self.__occurrences = [] # Load indenter based on language self._indenter = indenter.load_indenter(self, neditable.language()) # Set editor font before build lexer self.set_font(settings.FONT) self.register_syntax_for(neditable.language()) # Register all editor extension self.__extensions = {} self.initialize_extensions() # FIXME: based on lang self.enable_extension('indentation_guides', settings.SHOW_INDENTATION_GUIDE) self.enable_extension('margin_line', settings.SHOW_MARGIN_LINE) self.enable_extension('line_highlighter', True) self.enable_extension('symbol_highlighter', True) self.enable_extension('quotes', True) self.enable_extension('braces', True) # self._symbol_completer = symbol_completer.SymbolCompleter(self) # Mark occurrences timer self._highlight_word_timer = QTimer() self._highlight_word_timer.setSingleShot(True) self._highlight_word_timer.setInterval(800) self._highlight_word_timer.timeout.connect( self.highlight_selected_word) # Install custom scrollbar self._scrollbar = scrollbar.NScrollBar(self) self._scrollbar.setAttribute(Qt.WA_OpaquePaintEvent, False) self.setVerticalScrollBar(self._scrollbar) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.additional_builtins = None # Set the editor after initialization if self._neditable is not None: if self._neditable.editor: self.setDocument(self._neditable.document) else: self._neditable.set_editor(self) self._neditable.checkersUpdated.connect(self._highlight_checkers) # Widgets on side area self._line_number_area = None if settings.SHOW_LINE_NUMBERS: self._line_number_area = self.add_side_widget( line_number_area.LineNumberArea, 2) self._lint_area = None # if settings.SHOW_LINT_AREA: # self._lint_area = self.add_side_widget(lint_area.LintArea, 3) self._marker_area = None # if settings.SHOW_MARK_AREA: # self._marker_area = self.add_side_widget(marker_area.MarkerArea, 1) self._text_change_area = None if settings.SHOW_TEXT_CHANGE_AREA: self._text_change_area = self.add_side_widget( text_change_area.TextChangeArea, 0) # FIXME: we need a method to initialize self.__set_whitespaces_flags(self.__show_whitespaces) self.cursorPositionChanged.connect(self._on_cursor_position_changed) self.cursorPositionChanged.connect(self.viewport().update) def dropEvent(self, event): if event.type() == Qt.ControlModifier and self.has_selection: insertion_cursor = self.cursorForPosition(event.pos()) insertion_cursor.insertText(self.selected_text()) else: super().dropEvent(event) def set_language(self, language): self.register_syntax_for(language=language) self._indenter = indenter.load_indenter(self, lang=language) def register_syntax_for(self, language=None): if language is None: return syntax_registry = IDE.get_service("syntax_registry") syntax = syntax_registry.get_syntax_for(language) if syntax is None: syntax = syntaxhighlighter.build_highlighter_for(language) self._highlighter = syntaxhighlighter.SyntaxHighlighter( self.document(), syntax.partition_scanner, syntax.scanners, syntax.formats) def initialize_extensions(self): """Register all extensions in this Editor""" for Klass in ExtensionRegistry.extensions: self.__extensions[Klass.name] = Klass(self) def indentation_width(self): """Returns the indentation width of current indenter""" return self._indenter.width def move_up_down(self, up=False): cursor = self.textCursor() move = cursor with self: has_selection = cursor.hasSelection() start, end = cursor.selectionStart(), cursor.selectionEnd() if has_selection: move.setPosition(start) move.movePosition(QTextCursor.StartOfBlock) move.setPosition(end, QTextCursor.KeepAnchor) m = QTextCursor.EndOfBlock if move.atBlockStart(): m = QTextCursor.Left move.movePosition(m, QTextCursor.KeepAnchor) else: move.movePosition(QTextCursor.StartOfBlock) move.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) text = cursor.selectedText() move.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor) move.removeSelectedText() if up: move.movePosition(QTextCursor.PreviousBlock) move.insertBlock() move.movePosition(QTextCursor.Left) else: move.movePosition(QTextCursor.EndOfBlock) if move.atBlockStart(): move.movePosition(QTextCursor.NextBlock) move.insertBlock() move.movePosition(QTextCursor.Left) else: move.insertBlock() start = move.position() move.clearSelection() move.insertText(text) end = move.position() if has_selection: move.setPosition(end) move.setPosition(start, QTextCursor.KeepAnchor) else: move.setPosition(start) self.setTextCursor(move) def duplicate_line(self): cursor = self.textCursor() if cursor.hasSelection(): text = cursor.selectedText() start = cursor.selectionStart() end = cursor.selectionEnd() cursor_at_start = cursor.position() == start cursor.setPosition(end) cursor.insertText("\n" + text) cursor.setPosition(end if cursor_at_start else start) cursor.setPosition(start if cursor_at_start else end, QTextCursor.KeepAnchor) else: position = cursor.position() block = cursor.block() text = block.text() + "\n" cursor.setPosition(block.position()) cursor.insertText(text) cursor.setPosition(position) self.setTextCursor(cursor) @property def occurrences(self): return self.__occurrences def adjust_scrollbar_ranges(self): line_spacing = QFontMetrics(self.font()).lineSpacing() if line_spacing == 0: return offset = self.contentOffset().y() self._scrollbar.set_visible_range( (self.viewport().rect().height() - offset) / line_spacing) self._scrollbar.set_range_offset(offset / line_spacing) def _highlight_checkers(self, neditable): """Add checker selections to the Editor""" # Remove selections if they exists self.clear_extra_selections('checker') # Get checkers from neditable checkers = neditable.sorted_checkers self.highlight_checker_updated.emit(checkers) selections = [] # FIXME: generalize it with extra_selection.ExtraSelection for items in checkers: checker, color, _ = items lines = checker.checks.keys() for line in lines: msg, col = checker.checks[line] selection = QTextEdit.ExtraSelection() selection.cursor = self.textCursor() selection.cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) selection.cursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor, line) selection.cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, col - 1) selection.cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) selection.format.setUnderlineStyle( QTextCharFormat.SingleUnderline) selection.format.setUnderlineColor(QColor(color)) selections.append(selection) self.add_extra_selections('checker', selections) def extra_selections(self, selection_key): return self._extra_selections.get(selection_key, []) def add_extra_selections(self, selection_key, selections): """Adds a extra selection on a editor instance""" self._extra_selections[selection_key] = selections self.update_extra_selections() def clear_extra_selections(self, selection_key): """Removes a extra selection from the editor""" if selection_key in self._extra_selections: self._extra_selections[selection_key] = [] self.update_extra_selections() def update_extra_selections(self): extra_selections = [] for key, selection in self._extra_selections.items(): extra_selections.extend(selection) self.setExtraSelections(extra_selections) def all_extra_selections(self): return self._extra_selections def _init_settings(self): """Set some configuration flags for the Editor""" # FIXME: self.setCursorWidth(1) # Wrap Mode wrap_mode = QPlainTextEdit.NoWrap if settings.ALLOW_WORD_WRAP: wrap_mode = QPlainTextEdit.WidgetWidth self.setLineWrapMode(wrap_mode) def _update_tab_stop_width(self): """Update the tab stop width""" width = self.fontMetrics().width(' ') * self._indenter.WIDTH self.setTabStopWidth(width) def clone(self): """Returns an instance of the same class and links this instance with its original""" line, col = self.cursor_position clone = self.__class__(self.neditable) clone.cursor_position = line, col clone.setExtraSelections(self.extraSelections()) return clone def line_count(self): """Returns the number of lines""" return self.document().blockCount() def set_font(self, font): """Set font and update tab stop width""" QPlainTextEdit.setFont(self, font) self._update_tab_stop_width() def line_text(self, line): """Returns the text of the specified line. :param line: The line number of the text to return. :return: Entire lines text. :rtype: str. """ block = self.document().findBlockByNumber(line) return block.text() @property def text(self): """Get or set the plain text editor's content. The previous contents are removed. :param text: Text to set in document. :type text: string. :return: The plain text in document. :rtype: string. """ return self.toPlainText() @text.setter def text(self, text): self.setPlainText(text) @pyqtSlot() def _on_cursor_position_changed(self): self.__clear_occurrences() line, col = self.cursor_position self.cursor_position_changed.emit(line, col) if line != self._last_line_position: self._last_line_position = line self.current_line_changed.emit(line) # Create marker for scrollbar self.update_current_line_in_scrollbar(line) # Mark occurrences self._highlight_word_timer.stop() self._highlight_word_timer.start() def update_current_line_in_scrollbar(self, current_line): """Update current line highlight in scrollbar""" self._scrollbar.remove_marker('current_line') if self._scrollbar.maximum() > 0: Marker = scrollbar.marker marker = Marker(current_line, 'white', priority=2) self._scrollbar.add_marker('current_line', marker) def add_side_widget(self, Widget, order=0): obj = Widget(self) obj.order = order self.__side_widgets.append(obj) if obj.isVisible: self.update_viewport() self.__side_widgets.sort(key=lambda widget: widget.order) return obj def __apply_style(self): palette = self.palette() palette.setColor(palette.Base, self._background_color) palette.setColor(palette.Text, self._foreground_color) palette.setColor(palette.HighlightedText, self._selection_color) palette.setColor(palette.Highlight, self._selection_background_color) self.setPalette(palette) # FIXME: no funciona con qpalette # self.setStyleSheet('border: 0px solid transparent;') def update_viewport(self): """ Recalculates geometry for all the side widgets and the editor viewport """ cr = self.contentsRect() current_x = cr.left() top = cr.top() height = cr.height() total_width = 0 for margin in self.__side_widgets: margin.setGeometry(current_x, top, margin.width(), height) current_x += margin.width() total_width += margin.width() self.setViewportMargins(total_width, 0, 0, 0) def focusInEvent(self, event): super().focusInEvent(event) self.editorFocusObtained.emit() def resizeEvent(self, event): QPlainTextEdit.resizeEvent(self, event) self.update_viewport() self.adjust_scrollbar_ranges() def paintEvent(self, event): self._update_visible_blocks() # Emit signal for extensions self.painted.emit() QPlainTextEdit.paintEvent(self, event) def mouseReleaseEvent(self, event): # if event.modifiers() == Qt.ControlModifier: # self._code_completion.go_to_definition() super().mouseReleaseEvent(event) def enable_extension(self, extension_name, value): # I'm sure the object always exists ext_obj = self.__extensions.get(extension_name) if ext_obj is None: logger.error("Extension '%s' not found" % extension_name) else: ext_obj.enabled = value logger.debug("Loaded '%s' extension" % extension_name) def mouseMoveEvent(self, event): '''if event.modifiers() == Qt.ControlModifier: if self.__link_selection is not None: return cursor = self.cursorForPosition(event.pos()) # Check that the mouse was actually on the text somewhere on_text = self.cursorRect(cursor).right() >= event.x() if on_text: cursor.select(QTextCursor.WordUnderCursor) selection_start = cursor.selectionStart() selection_end = cursor.selectionEnd() self.__link_selection = extra_selection.ExtraSelection( cursor, start_pos=selection_start, end_pos=selection_end ) self.__link_selection.set_underline("red") self.__link_selection.set_full_width() self.add_extra_selection(self.__link_selection) self.viewport().setCursor(Qt.PointingHandCursor)''' super(NEditor, self).mouseMoveEvent(event) def scroll_step_up(self): self.verticalScrollBar().triggerAction( QAbstractSlider.SliderSingleStepSub) def scroll_step_down(self): self.verticalScrollBar().triggerAction( QAbstractSlider.SliderSingleStepAdd) def text_before_cursor(self, text_cursor=None): if text_cursor is None: text_cursor = self.textCursor() text_block = text_cursor.block().text() return text_block[:text_cursor.positionInBlock()] def keyReleaseEvent(self, event): # if event.key() == Qt.Key_Control: # if self.__link_selection is not None: # self.remove_extra_selection(self.__link_selection) # self.__link_selection = None # self.viewport().setCursor(Qt.IBeamCursor) super().keyReleaseEvent(event) def keyPressEvent(self, event): if self.isReadOnly(): return # Emit a signal then plugins can do something event.ignore() self.keyPressed.emit(event) if event.matches(QKeySequence.InsertParagraphSeparator): self._indenter.indent_block(self.textCursor()) return if event.key() == Qt.Key_Home: self.__manage_key_home(event) return elif event.key() == Qt.Key_Tab: if self.textCursor().hasSelection(): self._indenter.indent_selection() else: self._indenter.indent() event.accept() elif event.key() == Qt.Key_Backspace: if self.__smart_backspace(): event.accept() if not event.isAccepted(): super().keyPressEvent(event) # Post key press self.postKeyPressed.emit(event) def _auto_indent(self): cursor = self.textCursor() at_start_of_line = cursor.positionInBlock() == 0 with self: cursor.insertBlock() if not at_start_of_line: indent = self._indenter.indent_block(cursor.block()) if indent is not None: cursor.insertText(indent) def __smart_backspace(self): accepted = False cursor = self.textCursor() text_before_cursor = self.text_before_cursor(cursor) text = cursor.block().text() indentation = self._indenter.text() space_at_start_len = len(text) - len(text.lstrip()) column_number = cursor.positionInBlock() if text_before_cursor.endswith(indentation) and \ space_at_start_len == column_number and \ not cursor.hasSelection(): to_remove = len(text_before_cursor) % len(indentation) if to_remove == 0: to_remove = len(indentation) cursor.setPosition(cursor.position() - to_remove, QTextCursor.KeepAnchor) cursor.removeSelectedText() accepted = True return accepted def __manage_key_home(self, event): """Performs home key action""" cursor = self.textCursor() indent = self.line_indent() # For selection move = QTextCursor.MoveAnchor if event.modifiers() == Qt.ShiftModifier: move = QTextCursor.KeepAnchor # Operation if cursor.positionInBlock() == indent: cursor.movePosition(QTextCursor.StartOfBlock, move) elif cursor.atBlockStart(): cursor.setPosition(cursor.block().position() + indent, move) elif cursor.positionInBlock() > indent: cursor.movePosition(QTextCursor.StartOfLine, move) cursor.setPosition(cursor.block().position() + indent, move) self.setTextCursor(cursor) event.accept() def __enter__(self): self.textCursor().beginEditBlock() def __exit__(self, exc_type, exc_value, traceback): self.textCursor().endEditBlock() def selection_range(self): """Returns the start and end number of selected lines""" text_cursor = self.textCursor() start = self.document().findBlock( text_cursor.selectionStart()).blockNumber() end = self.document().findBlock( text_cursor.selectionEnd()).blockNumber() if text_cursor.columnNumber() == 0 and start != end: end -= 1 return start, end def _update_visible_blocks(self): """Updates the list of visible blocks""" self.__visible_blocks = [] block = self.firstVisibleBlock() block_number = block.blockNumber() top = self.blockBoundingGeometry(block).translated( self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() editor_height = self.height() while block.isValid(): visible = bottom <= editor_height if not visible: break if block.isVisible(): self.__visible_blocks.append((top, block_number, block)) block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() block_number += 1 def zoom(self, delta: int): font = self.default_font previous_point_size = font.pointSize() new_point_size = int(max(1, previous_point_size + delta)) if new_point_size != previous_point_size: font.setPointSize(new_point_size) self.set_font(font) # Set font in all margins for side_area in self.__side_widgets: side_area.setFont(font) # Emit signal for indicator default_point_size = settings.FONT.pointSize() percent = new_point_size / default_point_size * 100.0 self.zoomChanged.emit(percent) def reset_zoom(self): font = self.default_font default_point_size = settings.FONT.pointSize() if font.pointSize() != default_point_size: font.setPointSize(default_point_size) self.set_font(font) # Set font in all margins for margin in self.__side_widgets: margin.setFont(font) # Emit signal for indicator self.zoomChanged.emit(100) def __set_whitespaces_flags(self, show): """Sets white spaces flag""" doc = self.document() options = doc.defaultTextOption() if show: options.setFlags(options.flags() | QTextOption.ShowTabsAndSpaces) else: options.setFlags(options.flags() & ~QTextOption.ShowTabsAndSpaces) doc.setDefaultTextOption(options) def selected_text(self): """Returns the selected text""" return self.textCursor().selectedText() def has_selection(self): return self.textCursor().hasSelection() def get_right_word(self): """Gets the word on the right of the text cursor""" cursor = self.textCursor() cursor.movePosition(QTextCursor.WordRight, QTextCursor.KeepAnchor) return cursor.selectedText().strip() def get_right_character(self): """Gets the right character on the right of the text cursor""" right_word = self.get_right_word() right_char = None if right_word: right_char = right_word[0] return right_char def is_code(self, cursor): user_data = cursor.block().userData() if user_data is not None: pass # this_syntax = syntax_registry.get_syntax_for() # if syntax is not None: # self._hig = syntaxhighlighter.SyntaxHighlighter( # self.document(), # syntax.partition_scanner, # syntax.scanners, # syntax.formats # ) # syntax = syntaxhighlighter.load_syntax(language) # if syntax is not None: # partition_scanner = syntax.partition_scanner # scanners = syntax.scanners # formats = syntax.formats # self._highlighter = syntaxhighlighter.SyntaxHighlighter( # self.document(), partition_scanner, scanners, formats) def line_indent(self, line=-1): """Returns the indentation level of `line`""" if line == -1: line, _ = self.cursor_position text = self.document().findBlockByNumber(line).text() indentation = len(text) - len(text.lstrip()) return indentation def replace_match(self, word_old, word_new, cs=False, wo=False, all_words=False, selection=False): """ Find if searched text exists and replace it with new one. If there is a selection just do it inside it and exit """ cursor = self.textCursor() if selection: if not cursor.hasSelection(): return start, end = cursor.selectionStart(), cursor.selectionEnd() text = cursor.selectedText() old_len = len(text) text = text.replace(word_old, word_new) new_len = len(text) cursor.insertText(text) # Set selection cursor.setPosition(start) cursor.setPosition(end + new_len - old_len, QTextCursor.KeepAnchor) self.setTextCursor(cursor) return # Replace once cursor.insertText(word_new) if not all_words: return # Replace all flags = QTextDocument.FindFlags() if wo: flags |= QTextDocument.FindWholeWords if cs: flags |= QTextDocument.FindCaseSensitively self.moveCursor(QTextCursor.Start) cursor.beginEditBlock() while all_words: found = self.find(word_old, flags) if found: cursor = self.textCursor() if cursor.hasSelection(): cursor.insertText(word_new) else: break # Set cursor on last replace text self.setTextCursor(cursor) cursor.endEditBlock() def find_matches(self, search, case_sensitive=False, whole_word=False, backward=False, find_next=True): flags = QTextDocument.FindFlags() if case_sensitive: flags = QTextDocument.FindCaseSensitively if whole_word: flags |= QTextDocument.FindWholeWords if backward: flags |= QTextDocument.FindBackward if find_next or backward: self.moveCursor(QTextCursor.NoMove) else: self.moveCursor(QTextCursor.StartOfWord) found = self.find(search, flags) cursor = self.textCursor() if not found: # Original cursor is saved for restore later cursor = self.textCursor() # Move cursor to beginning of document to search again if backward: self.moveCursor(QTextCursor.End) else: self.moveCursor(QTextCursor.Start) found = self.find(search, flags) if not found: # self.clear_extra_selections() # Restore cursor self.setTextCursor(cursor) self.clear_extra_selections('searchs') return 0, [] index, results = self._get_find_index_results(search, case_sensitive, whole_word) # TODO: obtain line numbers for highlight in scrollbar # TODO: cambiar el 2 # FIXME: clear its ok? self.clear_extra_selections('searchs') if len(search) > 2: ss = [] append = ss.append results = results[:500] for start, end in results: s = extra_selection.ExtraSelection(self.textCursor(), start_pos=start, end_pos=end) s.set_full_width() c = QColor('yellow') c.setAlpha(40) s.set_background(c) s.set_outline('gray') append(s) self.add_extra_selections('searchs', ss) return index, results def is_comment(self, block): """Check if the block is a inline comment""" text_block = block.text().lstrip() return text_block.startswith('#') # FIXME: generalize it def _get_find_index_results(self, expr: str, cs: bool, wo: bool) -> Tuple[int, list]: text = self.text has_search = len(expr) > 0 if not has_search: return 0, 0 def find_all_iter(string, sub, flags): for i in re.finditer(sub, string, flags): yield i.span() flags = re.UNICODE search = expr # for count whole words if wo: expr = "\\b{}\\b".format(expr) if not cs: text = text.lower() expr = expr.lower() flags |= re.IGNORECASE results = list(find_all_iter(text, expr, flags)) pos = self.textCursor().position() index = text[:pos].count(search) return index, results def __clear_occurrences(self): """Clear extra selection occurrences from editor and scrollbar""" self.__occurrences.clear() self._scrollbar.remove_marker('occurrence') self.clear_extra_selections('occurrences') def highlight_selected_word(self): import keyword self.__clear_occurrences() text = self._text_under_cursor() if not text: return # Do not highlight keywords if text in keyword.kwlist or text == 'self': return result = self._get_find_index_results(text, False, True)[1] selections = [] for start, end in result: selection = extra_selection.ExtraSelection(self.textCursor(), start_pos=start, end_pos=end) selection.set_full_width() # FIXME: from theme selection.set_background(resources.get_color('SearchResult')) selections.append(selection) line = selection.cursor.blockNumber() Marker = scrollbar.marker marker = Marker(line, resources.get_color('SearchResult'), 0) self._scrollbar.add_marker('occurrence', marker) self.add_extra_selections('occurrences', selections) def line_from_position(self, position): height = self.fontMetrics().height() for top, line, block in self.__visible_blocks: if top <= position <= top + height: return line return -1 def _text_under_cursor(self): text_cursor = self.textCursor() text_cursor.select(QTextCursor.WordUnderCursor) match = re.findall(r'([^\d\W]\w*)', text_cursor.selectedText()) if match: return match[0] def go_to_line(self, lineno, column=0, center=True): """Go to an specific line :param lineno: The line number to go :param column: The column number to go :param center: If True scrolls the document in order to center the cursor vertically. :type lineno: int""" if self.line_count() >= lineno: self.cursor_position = lineno, column if center: self.centerCursor() else: self.ensureCursorVisible() self.addBackItemNavigation.emit() def set_brace_matching(self, value): pass def comment(self): pass def save_state(self): state = {} state['vscrollbar'] = self.verticalScrollBar().value() return state
class KeyhintWidget(QLabel): """Widget to display partial matches above the statusbar. The widget is shown when there are partial keybinding matches, e.g. on 'g'. It is filled with a list of possible keybindings to fulfill a command. Attributes: _show_timer: Timer used to show the widget on partial matches after a delay. _suffix_color: Color used to display the remaining keys for a match. _mainwindow_bottom: y-coordinate of the bottom of the mainwindow. """ STYLESHEET = """ QLabel { font: {statusbar.font}; color: {statusbar.fg}; background: {statusbar.bg}; padding: {keyhint.padding}; border-top-right-radius: {keyhint.border_radius}; } """ def __init__(self, parent): super().__init__(parent=parent) self._show_timer = QTimer(self) self._show_timer.setSingleShot(True) self._show_timer.setInterval(api.settings.keyhint.delay.value) self._show_timer.timeout.connect(self.show) self._suffix_color = styles.get("keyhint.suffix_color") self._mainwindow_bottom = 0 styles.apply(self) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) self.setTextFormat(Qt.RichText) partial_handler = eventhandler.EventHandlerMixin.partial_handler partial_handler.partial_matches.connect(self._on_partial_matches) partial_handler.partial_cleared.connect(self._on_partial_cleared) api.settings.keyhint.delay.changed.connect(self._on_delay_changed) self.hide() def update_geometry(self, _window_width, window_bottom): """Adapt location when main window geometry changes.""" self._mainwindow_bottom = window_bottom self._update_geometry() def _update_geometry(self): """Update geometry according to current text content and window location.""" self.adjustSize() y = self._mainwindow_bottom - self.height() self.setGeometry(0, y, self.width(), self.height()) def _on_partial_matches(self, prefix: str, matches: Iterator[Tuple[str, str]]): """Initialize widget when partial matches exist. Args: prefix: Key(s) pressed for which there are partial matches. matches: List of keybindings and commands that can complete the partial. """ self._show_timer.start() text = "" for keybinding, command in matches: suffix = keybinding[len(prefix):] prefix = utils.escape_html(prefix) suffix = utils.escape_html(suffix) text += ("<tr>" f"<td>{prefix}</td>" f"<td style='color: {self._suffix_color}'>{suffix}</td>" f"<td style='padding-left: 2ex'>{command}</td>" "</tr>") self.setText(f"<table>{text}</table>") self._update_geometry() @utils.slot def _on_partial_cleared(self): """Stop timer and hide widget when there are no partial matches to show.""" self._show_timer.stop() self.hide() def _on_delay_changed(self, value: int): """Update timer interval if the keyhint delay setting changed.""" self._show_timer.setInterval(value) def show(self): """Override show to always raise the widget in addition.""" super().show() self.raise_()
class Game(QWidget): def __init__(self): super().__init__() self.spinner = Spinner() self.tries_remaining = 9 self.game_end_fail = False self.game_end_triumph = False self.game_timeout = False self.screen_updated = False self.long_message_length = 10 self.game_time = 1500 self.default_message = 'Waiting for database key ...' self.game_failure_message = 'Database shutdown, system intrusion detected' self.secret_keys = { '9291184': False, '117193': False, '861364710132011141107': False } self.secret_password = '******' self.background_image_path = self.resource_path("Webster_Notes.png") self.font = "Times" self.font_size = 8 self.initUI() def initUI(self): myfont = QFont(self.font, self.font_size) oImage = QImage(self.background_image_path) sImage = oImage.scaled(QSize(1500, 800)) palette = QPalette() palette.setBrush(10, QBrush(sImage)) self.setPalette(palette) self.timer_label = QLabel(self) self.timer = QTimer() self.qline_edit = QLineEdit(self) self.qline_edit.setFont(myfont) self.message_label = QLabel(self.default_message, self) self.message_label.setFont(myfont) self.code_label = QLabel(self) self.code_label.setFont(myfont) self.timer_label.setFont(myfont) self.success_message_label = QLabel(self) self.success_message_label.setFont(myfont) self.qbutton = QPushButton('Submit', self) self.qbutton.setFont(myfont) self.qline_edit.move(600, 380) self.qbutton.move(800, 375) self.message_label.move(600, 280) self.success_message_label.move(600, 480) self.code_label.move(755, 480) self.timer_label.move(500, 260) self.qline_edit.returnPressed.connect(self.qbutton.click) self.qbutton.clicked.connect(self.on_qbutton_clicked) self.timer.timeout.connect(self.timerTick) self.qbutton.resize(self.qbutton.sizeHint()) self.setGeometry(300, 300, 1500, 800) self.setWindowTitle('Secure Database') self.show() def resource_path(self, relative_path): try: # PyInstaller creates a temp folder and stores path in _MEIPASS base_path = sys._MEIPASS except Exception: base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) def updateTimerDisplay(self): time = "%d:%02d" % (self.time_left / 60, self.time_left % 60) self.update_label(self.timer_label, time) def startTimer(self): self.time_left = self.game_time self.updateTimerDisplay() self.timer.start(1000) def timerTick(self): self.time_left -= 1 self.updateTimerDisplay() if self.time_left <= 0: self.timer.stop() self.game_timeout = True self.game_end_timeout(self.screen_updated) def tries_left(self): self.tries_remaining = self.tries_remaining - 1 return self.tries_remaining def update_label(self, label, text): label.setText(text) label.adjustSize() QApplication.processEvents() def check_message(self, text): if len(text) >= self.long_message_length: # If the message is too long, cut it down text = text[0:9] + "..." return 'Entering key ({0}) to the secure database {1}'.format( text, self.spinner.get_next()) def not_found_message(self): message = 'Key not accepted, you have {0} {1} remaining' if self.tries_remaining == 2: return message.format(self.tries_left(), 'try') else: return message.format(self.tries_left(), 'tries') def check_test_sleep(self, text): for i in self.spinner.calculate_rotations(): self.update_label(self.message_label, self.check_message(text)) sleep(self.spinner.speed) self.update_label(self.message_label, 'Done') self.spinner.reset() sleep(0.5) def check_text(self, text): self.check_test_sleep(text) code_text = None success_message_text = '' num_guessed = 0 for key, value in self.secret_keys.items(): if text == key: if self.secret_keys[key] == False: message_text = 'Key entered' code_text = 'Key accepted' self.secret_keys[key] = True else: message_text = 'Key already entered' code_text = '' break if code_text is None: message_text = self.not_found_message() code_text = '' for key, value in self.secret_keys.items(): if value: num_guessed += 1 if num_guessed == len(self.secret_keys.keys()): message_text = 'Top Secret access granted. Welcome Agent 69-88' success_message_text = '' code_text = self.secret_password self.game_end_triumph = True # Game over if self.tries_remaining == 0 and code_text == '': message_text = self.game_failure_message self.game_end_fail = True return message_text, code_text, success_message_text def game_end_timeout(self, screen_updated): if not screen_updated: message_text = self.game_failure_message self.update_label(self.message_label, message_text) self.qline_edit.hide() self.code_label.hide() self.success_message_label.hide() self.qbutton.hide() self.timer.stop() self.timer_label.hide() self.game_end_fail = True def game_end_failure(self, screen_updated): if not screen_updated: message_text = self.game_failure_message self.update_label(self.message_label, message_text) self.qline_edit.hide() self.code_label.hide() self.success_message_label.hide() self.qbutton.hide() self.timer.stop() self.timer_label.hide() self.game_end_fail = True def game_end_success(self): self.qline_edit.hide() self.qbutton.hide() self.timer.stop() self.game_end_triumph = True def game_end_check(self, screen_updated): if self.game_end_fail: self.game_end_failure(screen_updated) if self.game_timeout: self.game_end_timeout(screen_updated) if self.game_end_triumph: self.game_end_success() @pyqtSlot() def on_qbutton_clicked(self): input_text = self.qline_edit.text() self.qline_edit.clear() self.update_label(self.code_label, '') message_text, code_text, success_message_text = self.check_text( input_text) # When we click the button make sure we update the messages after processing self.update_label(self.message_label, message_text) self.update_label(self.success_message_label, success_message_text) self.update_label(self.code_label, code_text) self.screen_updated = True # Check for game end before returning to the main screen if not self.game_end_fail and not self.game_end_triumph and not self.game_timeout: sleep(2) self.update_label(self.message_label, self.default_message) self.update_label(self.code_label, '') # Reset the screen status self.screen_updated = False else: self.screen_updated = False self.game_end_check(self.screen_updated)
class ZhuceWindow(QMainWindow, Ui_ZhuceWindow): def __init__(self, company_list, company_name_sum, generate_name, generate_data): super(ZhuceWindow, self).__init__() self.setupUi(self) self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) self.count = 60 self.time = QTimer(self) self.time.setInterval(1000) self.url = 'https://www.tianyancha.com/' self.driver = webdriver.Chrome() self.control_init() self.generate_name = generate_name self.generate_data = generate_data self.company_list = company_list self.company_name_sum = company_name_sum self.auto_Bracket = None # 页面组件设置 def control_init(self): screen = QDesktopWidget().screenGeometry() self.move(0, screen.height() * 0.3) self.tel_line.textChanged.connect(self.check_get_func) self.verification_line.textChanged.connect(self.check_input_func) self.auto_verification.toggled.connect(self.tuokuai_verification) self.manual_verification.toggled.connect(self.tuokuai_verification) self.zc_active_button.setEnabled(False) self.get_verification_button.setEnabled(False) self.verification_line.setVisible(False) self.verification_label.setVisible(False) self.get_verification_button.clicked.connect(self.check_get_button) self.zc_active_button.clicked.connect(self.check_active_button) self.zc_active_button.clicked.connect(self.check_active_button) self.zc_active_button.clicked.connect(self.closewin) self.time.timeout.connect(self.Refresh) # 获取验证码按钮设置 def check_get_func(self): if self.tel_line.text(): self.get_verification_button.setEnabled(True) else: self.get_verification_button.setEnabled(False) # 获得验证码按钮 def check_get_button(self): self.get_verification_button.setEnabled(False) telephone = self.tel_line.text() self.driver.get(self.url) script = 'Object.defineProperty(navigator,"webdriver",{get:() => false,});' self.driver.execute_script(script) self.driver.maximize_window() time.sleep(1) self.driver.find_element_by_xpath( '/html/body/div[1]/div/div[1]/div[1]/div[1]/div/div[2]/div/div[5]/a' ).click() time.sleep(1) self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[1]/div[1]' ).click() time.sleep(0.5) try: input_tel = self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[1]/div[1]/input' ) time.sleep(1) input_tel.send_keys(telephone) except: input_tel = self.driver.find_element_by_xpath( '/html/body/div[10]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[1]/div[1]/input' ) time.sleep(1) input_tel.send_keys(input_tel) time.sleep(1) print('f**k!') try: self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[2]/div[1]' ).click() except: self.driver.find_element_by_xpath( '/html/body/div[10]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[2]/div[1]' ).click() self.toukuai_choose_run() try: self.driver.find_element_by_class_name('input-group-btn.-disabled') self.time.start() self.verification_label.setVisible(True) self.verification_line.setVisible(True) except: self.close() # 完整背景图 def before_deal_image(self): time.sleep(1) self.location = self.driver.find_element_by_class_name( 'gt_box_holder').location self.size = self.driver.find_element_by_class_name( 'gt_box_holder').size time.sleep(1) self.driver.save_screenshot('capture1.png') self.left = self.location['x'] self.top = self.location['y'] self.right = self.location['x'] + self.size['width'] self.bottom = self.location['y'] + self.size['height'] self.im = Image.open('capture1.png') self.im = self.im.crop((self.left, self.top, self.right, self.bottom)) self.im.save('ele_capture1.png') print("完整背景截取成功------") # 缺口背景图 def after_deal_image(self): time.sleep(1) slider = self.driver.find_element_by_xpath( '/html/body/div[10]/div[2]/div[2]/div[2]/div[2]') ActionChains(self.driver).click_and_hold(slider).perform() ActionChains(self.driver).move_by_offset(xoffset=198, yoffset=0).perform() time.sleep(1) self.driver.save_screenshot('capture2.png') ActionChains(self.driver).release().perform() time.sleep(1) self.left = self.location['x'] self.top = self.location['y'] self.right = self.location['x'] + self.size['width'] self.bottom = self.location['y'] + self.size['height'] self.im = Image.open('capture2.png') self.im = self.im.crop((self.left, self.top, self.right, self.bottom)) self.im.save('ele_capture2.png') print("缺口背景截取成功------") time.sleep(1) # 获得滑动距离 def slide_distance(self, image1, image2): cut_image = Image.open(image2) full_image = Image.open(image1) threshold = 85 for i in range(cut_image.size[0]): for j in range(cut_image.size[1]): pixel1 = cut_image.load()[i, j] pixel2 = full_image.load()[i, j] res_R = abs(pixel1[0] - pixel2[0]) res_G = abs(pixel1[1] - pixel2[1]) res_B = abs(pixel1[2] - pixel2[2]) if res_R >= threshold and res_G >= threshold and res_B >= threshold: print("缺口相对所截取图片位置", i) num = i - 7 - 14 - 2 - 2 num = int(num) return num # 拖动托块 def get_track_move(self, distance): track = [] current = 0 mid = distance * 3 / 4 t = random.uniform(0.5, 0.8) v = 0 while current <= distance: if current <= mid: a = 2 else: a = -3 v0 = v v = v0 + a * t move = v0 * t + 1 / 2 * a * t * t current += move track.append(round(move)) slider = self.driver.find_element_by_class_name( "gt_slider_knob.gt_show") ActionChains(self.driver).click_and_hold(slider).perform() time.sleep(0.5) track += [5, -7, 3, -1] print('移动轨迹:', track) # time.sleep(0.5) for i in track: ActionChains(self.driver).move_by_offset(xoffset=i, yoffset=0).perform() a = random.uniform(0.1, 0.8) time.sleep(a) ActionChains(self.driver).release(slider).perform() # 循环尝试拖块部分 def for_tuokuai(self): print('开始重试。。。') for i in range(5): try: time.sleep(1) self.driver.find_element_by_class_name( 'gt_refresh_button').click() time.sleep(1) self.driver.find_element_by_class_name( 'gt_popup_cross').click() time.sleep(0.5) self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[2]/div[1]' ).click() time.sleep(1) self.before_deal_image() time.sleep(1) self.after_deal_image() time.sleep(1) self.track = self.slide_distance('ele_capture1.png', 'ele_capture2.png') print('按钮移动距离:', self.track) self.get_track_move(self.track) time.sleep(1) self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[4]' ) self.j += 1 return self.track except: pass if self.j > 0: break self.driver.close() self.error_func() # 验证码刷新 def Refresh(self): self.time.start() if self.count >= 0: self.get_verification_button.setText(str(self.count) + '秒') self.count -= 1 else: self.time.stop() self.get_verification_button.setEnabled(True) self.get_verification_button.setText('重新获取验证码') self.count = 60 # 输入框设置 def check_input_func(self): if self.verification_line.text(): self.zc_active_button.setEnabled(True) else: self.zc_active_button.setEnabled(False) # 托块验证设置 def tuokuai_verification(self): radiobutton = self.sender() if radiobutton.isChecked(): if radiobutton == self.auto_verification: self.auto_Bracket = True else: self.auto_Bracket = False else: if radiobutton == self.auto_verification: self.auto_Bracket = True else: self.auto_Bracket = False # 对托块验证的选择运行 def toukuai_choose_run(self): if self.auto_Bracket == True: self.before_deal_image() self.after_deal_image() time.sleep(1) self.track = self.slide_distance('ele_capture1.png', 'ele_capture2.png') print('按钮移动距离:', self.track) self.get_track_move(self.track) try: self.driver.find_element_by_class_name('home-main-search') print('登录成功!') return self.track except: print('拖块存在问题,正在重试...') self.for_tuokuai() else: self.driver.implicitly_wait(10) try: self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[4]' ) except: self.driver.find_element_by_xpath( '/html/body/div[10]/div[2]/div[2]/div[2]/div[2]') reply = QMessageBox.question(self, '提示', '请手动进行托块验证!', QMessageBox.Yes) if reply == QMessageBox.Yes: self.driver.implicitly_wait(10) self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[4]' ) # 注册按钮操作 def check_active_button(self): verification = self.verification_line.text() input_verification = self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[2]/input' ) time.sleep(1) input_verification.send_keys(verification) try: self.driver.find_element_by_xpath( '/html/body/div[9]/div[2]/div/div[2]/div/div/div[3]/div[3]/div[3]/div[4]' ).click() self.Ui_processwindow = ProcessWindow(self.company_list, self.company_name_sum, self.driver, self.generate_name, self.generate_data) self.Ui_processwindow.show() except: self.close() def closewin(self): self.close()