1. Завантаження/збереження файлу налаштувань

Ось як завантажити та зберегти налаштування під час запуску та закриття.

Передумови
  • Параметр файлу налаштувань має бути встановлений у віджеті ScreenOptions.

  • Шлях до файлу налаштувань має бути встановлений у конфігурації INI.

Параметри читання під час запуску

У функції def initialized__(self): додайте:

if self.w.PREFS_:
    # ім'я змінної (ім'я запису, значення за замовчуванням, тип, ім'я розділу)
    self.int_value = self.w.PREFS_.getpref('Integer_value', 75, int, 'CUSTOM_FORM_ENTRIES')
    self.string_value = self.w.PREFS_.getpref('String_value', 'on', str, 'CUSTOM_FORM_ENTRIES')
Уподобання щодо письма наприкінці терміну

У функції closing_cleanup__() додайте:

if self.w.PREFS_:
    # ім'я змінної (ім'я запису, ім'я змінної, тип, ім'я розділу)
    self.w.PREFS_.putpref('Integer_value', self.integer_value, int, 'CUSTOM_FORM_ENTRIES')
    self.w.PREFS_.putpref('String_value', self.string_value, str, 'CUSTOM_FORM_ENTRIES')

2. Використання QSettings для читання/збереження змінних

Ось як завантажувати та зберігати змінні за допомогою функцій QSettings у PyQt:

Гарні практики
  • Використовуйте Група для упорядкування та унікальності імен.

  • _Враховувати значення none, яке повертається під час читання параметра, який не має запису.

  • Встановіть значення за замовчуванням для першого запуску за допомогою синтаксису або _<значення_за_замовчуванням>_.

Note
Файл фактично зберігається в ~/.config/QtVcp
Приклад

У цьому прикладі:

  • Ми додаємо or 20 та or 2.5 як значення за замовчуванням.

  • Назви MyGroupName, int_value, float_value, myInteger та myFloat визначаються користувачем.

  • У функції def initialized__(self): додайте:

    # встановити параметри сортування записаних стовпців
    self.SETTINGS_.beginGroup("MyGroupName")
    self.int_value = self.SETTINGS_.value('myInteger', type = int) or 20
    self.float_value = self.SETTINGS_.value('myFloat', type = float) or 2.5
    self.SETTINGS_.endGroup()
  • У функції def closing_cleanup__(self): додайте:

    # збереження значень за допомогою QSettings
    self.SETTINGS_.beginGroup("MyGroupName")
    self.SETTINGS_.setValue('myInteger', self.int_value)
    self.SETTINGS_.setValue('myFloat', self.float_value)
    self.SETTINGS_.endGroup()

3. Додати базовий редактор стилів

Можливість редагувати стиль на екрані, що працює, зручна.

Імпортуйте модуль StyleSheetEditor у РОЗДІЛ ІМПОРТ:
з qtvcp.widgets.stylesheeteditor імпортувати StyleSheetEditor як SSE
Створіть екземпляр модуля StyleSheetEditor у розділі INSTANTIATE SECTION:
STYLEEDITOR = SSE()
Створіть прив'язку клавіш у СЕКЦІЇ ІНІЦІАЛІЗАЦІЇ:

У функції +__init__.(self, halcomp, widgets, paths):+ додано:

KEYBIND.add_call('Key_F12','on_keycall_F12')
Створіть функцію прив’язки клавіш у розділі KEYBINDING SECTION:
def on_keycall_F12(self,event,state,shift,cntrl):
    if state:
        STYLEEDITOR.load_dialog()

4. Запит на запис у діалоговому вікні

QtVCP використовує повідомлення STATUS для спливаючого відображення та повернення інформації з діалогових вікон.

Попередньо створені діалогові вікна відстежують свою останню позицію та включають опції затінення фокуса та звуку.

Щоб отримати інформацію з діалогового вікна, потрібно використовувати повідомлення STATUS general.

Імпортуйте та створіть екземпляр модуля Status у IMPORT SECTION
з імпорту qtvcp.core Стан
STATUS = Status()

Це завантажує та ініціалізує бібліотеку Status.

Реєстрація функції для повідомлень STATUS general у розділі INITIALIZE SECTION

У функції +__init__.(self, halcomp, widgets, paths)+:

STATUS.connect('general',self.return_value)

Це реєструє STATUS для виклику функції self.return_value, коли надсилається загальне повідомлення.

Додати функцію запиту діалогового вікна введення в розділі ЗАГАЛЬНІ ФУНКЦІЇ
def request_number(self):
    mess = {'NAME':'ENTRY','ID':'FORM__NUMBER', 'TITLE':'Set Tool Offset'}
    STATUS.emit('dialog-request', mess)

Функція

  • створює словник Python за допомогою:

    • NAME – потрібно встановити унікальну назву запуску dialogs.
      NAME встановлює, який діалог запитувати.
      ENTRY або CALCULATOR дозволяє вводити числа.

    • ID - має бути встановлено на унікальне ім’я, яке надає функція. ID має бути унікальним ключем.

    • TITLE встановлює заголовок діалогового вікна.

    • До dict можна додавати довільні дані. Діалогове вікно проігнорує їх, але поверне їх до коду повернення.

  • Надсилає dict як повідомлення dialog-request STATUS

Додати функцію обробки даних повідомлень у розділі CALLBACKS FROM STATUS.
# Обробити повідомлення STATUS від set-tool-offset
def return_value(self, w, message):
    num = message.get('RETURN')
    id_code = bool(message.get('ID') == 'FORM__NUMBER')
    name = bool(message.get('NAME') == 'ENTRY')
    if id_code and name and num is not None:
        print('The {} number from {} was: {}'.format(name, id_code, num))

Це захоплює всі загальні повідомлення, тому необхідно перевірити тип діалогового вікна та ідентифікаційний код, щоб підтвердити, що це наше діалогове вікно. У цьому випадку ми запросили діалогове вікно ENTRY, а наш унікальний ідентифікатор був FORM_NUMBER, тому тепер ми знаємо, що це повідомлення для нас. Діалогові вікна ENTRY або CALCULATOR повертають число з плаваючою комою.

5. Промовте привітання стартапу

Для цього потрібна бібліотека espeak, встановлена в системі.

Імпортуйте та створіть екземпляр Status у розділі IMPORT
з імпорту qtvcp.core Стан
STATUS = Status()
Передавати голосове повідомлення в розділі «ІНІЦІАЛІЗАЦІЯ»

У функції init.(self, halcomp, widgets, paths):

STATUS.emit('play-alert','ГОВОРИТИ Будь ласка, не забудь змастити шляхи.')

SPEAK – це ключове слово: все після нього буде вимовлятися.

6. Функції панелі інструментів

Кнопки панелі інструментів та підменю додано в Qt Designer, але код, який змушує їх виконувати певні дії, додано до файлу обробника. Щоб додати підменю в Qt Designer:

  • Додайте Qaction, ввівши його в стовпці панелі інструментів, а потім натиснувши значок + праворуч.

  • Це додасть підстовпець, у який потрібно ввести ім’я.

  • Тепер оригінальна Qaction буде Qmenu.

  • Тепер видаліть Qaction, який ви додали до Qmenu, меню залишиться як меню.

У цьому прикладі ми припускаємо, що ви додали панель інструментів з одним підменю та трьома діями. Ці дії будуть налаштовані для створення:

  • меню вибору нещодавніх файлів,

  • дія спливаючого діалогового вікна «Про нас»,

  • дія виходу з програми та

  • дія функції, визначеної користувачем.

objectName кнопки панелі інструментів використовується для ідентифікації кнопки під час її налаштування - допомога з описовими назвами.

Використовуючи меню редактора дій, клацніть правою кнопкою миші та виберіть «Редагувати».
Відредагуйте назву об’єкта, текст і тип кнопки для відповідної дії.

У цьому прикладі:

  • назва підменю має бути menuRecent,

  • назви дій мають бути actionAbout, actionQuit, actionMyFunction

Завантажує бібліотеку toolbar_actions у РОЗДІЛ ІМПОРТУ
from qtvcp.lib.toolbar_actions import ToolBarActions
Створити екземпляр модуля ToolBarActions у INSTANTIATE LIBRARY SECTION
TOOLBAR = ToolBarActions()
Налаштуйте підменю та дії в РОЗДІЛІ СПЕЦІАЛЬНИХ ФУНКЦІЙ

У функції def initialized__(self) додайте:

TOOLBAR.configure_submenu(self.w.menuRecent, 'recent_submenu')
TOOLBAR.configure_action(self.w.actionAbout, 'about')
TOOLBAR.configure_action(self.w.actionQuit, 'Quit', lambda d:self.w.close())
TOOLBAR.configure_action(self.w.actionMyFunction, 'My Function', self.my_function)
Визначте функцію користувача в ЗАГАЛЬНОМУ РОЗДІЛІ ФУНКЦІЙ
def my_function(self, widget, state):
    print('My function State = ()'.format(state))

Функція, яка викликається, якщо натиснуто кнопку дії «Моя функція».

7. Додати HAL-виводи, що викликають функції

Таким чином, вам не потрібно опитувати стан вхідних контактів.

Завантажує бібліотеку Qhal у РОЗДІЛ ІМПОРТУ
from qtvcp.core import Qhal

Це дозволяє доступ до HAL-компонента QtVCP. Додаткова інформація: Qhal
Функція newPin Qhal повертає об’єкт QPin. Додаткова інформація: QPin

Створити екземпляр Qhal у РОЗДІЛІ `INSTANTIATE LIBRARY
QHAL = Qhal()
Додати функцію, яка викликається при зміні стану виводу

У функції initialized__ переконайтеся, що є запис, подібний до цього:

##########################################
# Спеціальні функції, що викликаються з QtVCP
##########################################

# на цьому етапі:
# віджети інстанціюються.
# контакти HAL побудовані, але HAL не готовий до роботи
def initialized__(self):
    self.pin_cycle_start_in = QHAL.newPin('cycle-start-in',QHAL.HAL_BIT, QHAL.HAL_IN)
    self.pin_cycle_start_in.pinValueChanged.connect(lambda o,s: self.cycleStart(s))
Визначте функцію, яка викликається зміною стану виводу, у ЗАГАЛЬНОМУ РОЗДІЛІ ФУНКЦІЙ
#####################
# загальні функції #
#####################

def cycleStart(self, state):
    if state:
        tab = self.w.mainTab.currentWidget()
        if  tab in( self.w.tab_auto,  self.w.tab_graphics):
            ACTION.RUN(line=0)
        elif tab == self.w.tab_files:
                self.w.filemanager.load()
        elif tab == self.w.tab_mdi:
            self.w.mditouchy.run_command()

Ця функція припускає, що існує віджет вкладок з назвою mainTab, який містить вкладки з назвами tab_auto, tab_graphics, tab_filemanager та tab_mdi.

Таким чином, кнопка запуску циклу працює по-різному залежно від того, яка вкладка відображається.

Це спрощено — перевірка стану та перехоплення помилок можуть бути корисними.

8. Безпосереднє читання/запис системних HAL-виводів

Іноді потрібно зчитати системний пін, а створення HAL-піна та підключення до нього вимагає більше роботи, ніж потрібно. Ви можете зчитати його безпосередньо, не підключаючись до нього.

Ось як зчитати вивід, параметр або сигнал:

self.h.hal.get_value('spindle.0.at-speed')

# або використовуйте бібліотеку Qhal ось так:
QHAL.getValue('spindle.0.at-speed')

Ось як записати на непідключений контакт або некерований сигнал:

self.h.hal.set_p('componentName.pinName','10')
self.h.hal.set_s('componentName.signalName','10')

# або використовуйте бібліотеку Qhal ось так:
QHAL.setPin('componentName.pinName', '10')
QHAL.setSignal('componentName.signalName', '10')

Використання self.h.hal або QHAL.hal дозволяє отримати доступ до функцій модуля HAL Python: Інтерфейс Python HAL

9. Додати спеціальний повзунок максимальної швидкості на основі відсотків

Іноді потрібно створити віджет для виконання чогось, чого немає вбудованого. Вбудований повзунок максимальної швидкості діє на одиниці за хвилину, тут ми покажемо, як це зробити на відсотках.

Команда STATUS гарантує, що повзунок змінюватиметься, якщо LinuxCNC змінює поточну максимальну швидкість.

valueChanged.connect() викликає функцію, коли повзунок переміщується.

У Qt Designer додайте віджет QSlider під назвою mvPercent, а потім додайте наступний код до файлу обробника:

#############################
# РОЗДІЛ СПЕЦІАЛЬНИХ ФУНКЦІЙ #
#############################

def initialized__(self):
    self.w.mvPercent.setMaximum(100)
    STATUS.connect('max-velocity-override-changed', \
        lambda w, data: self.w.mvPercent.setValue( \
            (data / INFO.MAX_TRAJ_VELOCITY)*100 \
            )
        )
    self.w.mvPercent.valueChanged.connect(self.setMVPercentValue)

#####################
# ЗАГАЛЬНІ ФУНКЦІЇ #
#####################

def setMVPercentValue(self, value):
    ACTION.SET_MAX_VELOCITY_RATE(INFO.MAX_TRAJ_VELOCITY * (value/100.0))

10. Увімкнення та вимкнення безперервного поштовху

Зазвичай вибір безперервного поштовху – це тимчасова кнопка, яка вимагає від вас вибору попереднього кроку поштовху.

Ми створимо кнопку, яка перемикатиметься між безперервним поштовхом та будь-яким уже вибраним приростом.

У дизайнері Qt:

  • Додати ActionButton без жодної дії

  • Назвіть це btn_toggle_continuous.

  • Встановіть властивість checkable AbstractButton у значення True.

  • Встановіть властивості incr_imperial_number та incr_mm_number об’єкта ActionButton на 0.

  • Використовуйте редактор слотів Qt Designer, щоб за допомогою сигналу кнопки clicked(bool) викликати функцію обробки форми toggle_continuous_clicked().
    Докладнішу інформацію див. у розділі Використання Qt Designer для додавання слотів.

Потім додайте цей фрагмент коду до файлу обробника у функції initialized__:

# на цьому етапі:
# віджети інстанціюються.
# контакти HAL побудовані, але HAL не готовий до роботи.
def initialized__(self):
    STATUS.connect('jogincrement-changed', \
        lambda w, d, t: self.record_jog_incr(d,t) \
        )
    # встановити приріст за замовчуванням для повернення назад
    self.L_incr = 0.01
    self.L_text = "0.01in"

У розділі «ЗАГАЛЬНІ ФУНКЦІЇ» додайте:

#####################
# ЗАГАЛЬНІ ФУНКЦІЇ #
#####################

# якщо це не безперервний рух, запишіть останній крок поштовху
# та вимкніть кнопку безперервного руху
def record_jog_incr(self,d, t):
    if d != 0:
        self.L_incr = d
        self.L_text = t
        self.w.btn_toggle_continuous.safecheck(False)

У розділі ЗВОРОТНІ ВИКЛИКИ З ФОРМИ додайте:

#######################
# ЗВОРОТНІ ВИКЛИКИ З ФОРМИ #
#######################

def toggle_continuous_clicked(self, state):
    if state:
        # встановити безперервність (виклик функції кнопки дії)
        self.w.btn_toggle_continuous.incr_action()
    else:
        # скинути раніше записаний приріст
        ACTION.SET_JOG_INCR(self.L_incr, self.L_text)

11. Патч класу Віджет файлового менеджера

Note
Класове патчування (monkey patching) трохи нагадує «чорну магію», тому використовуйте його тільки в разі потреби. Основна проблема полягає в тому, що якщо функції бібліотеки віджетів змінюються під час розробки, вони можуть перестати працювати.
Віджет «Файловий менеджер» призначений для завантаження вибраної програми в LinuxCNC.
Але, можливо, ви хочете спочатку надрукувати ім’я файлу.

Ми можемо «класовий патч» бібліотеки, щоб перенаправити виклик функції.
Ви можете зробити цей класовий патч всередині або зовні екземпляра HandlerClass.
Це змінить значення, яке представляє «self» у функції.
За межами HanderClass «self» буде виправленим екземпляром класу.
Всередині HanderClass «self» буде екземпляром HandlerClass.
Це змінить функції/змінні, до яких ви можете отримати доступ у функції.
Тут ми показуємо приклад всередині HandlerClass:
У розділі IMPORT SECTION додайте:

з qtvcp.widgets.file_manager імпортувати FileManager як FM

Ось що ми збираємося зробити:

  1. Зберегти посилання на оригінальну функцію (1), щоб ми все ще могли її викликати

  2. Перенаправте клас на виклик нашої користувацької функції (2) у файлі обробника.

    ##########################################
    # Спеціальні функції, що викликаються з QtVCP    #
    ##########################################
    
    # Для зміни функцій у віджетах ми можемо використовувати «патч класу».
    # Патч класу має бути виконаний до створення екземпляра класу.
    def class_patch__(self):
        self.old_load = FM.load # зберегти посилання на стару функцію <1>
        FM.load = self.our_load # функція перенаправлення до нашої функції файлу дескрипторів <2>
  3. Напишіть власну функцію, щоб замінити оригінальну:
    Ця функція повинна мати таку саму сигнатуру, як і оригінальна функція.
    self є екземпляром HandlerClass, а не екземпляром виправленого класу.
    У цьому прикладі ми все ще будемо викликати оригінальну функцію, використовуючи посилання на неї, яке ми записали раніше.
    Вона вимагає, щоб першим аргументом був екземпляр віджета, який у цьому випадку є self.w.filemanager (ім’я, надане в редакторі Qt Designer).

    #####################
    # ЗАГАЛЬНІ ФУНКЦІЇ #
    #####################
    
    def our_load(self,fname):
        print(fname)
        self.old_load(self.w.filemanager,fname)

Тепер наша користувацька функція виведе шлях до файлу в термінал перед завантаженням файлу. Звісно, нудно, але принцип показано.

Note

Є ще один, дещо інший спосіб, який може мати свої переваги: ви можете зберегти посилання на оригінальну функцію в оригінальному класі.
Тут головне — переконатися, що ім’я функції, яке ви використовуєте для збереження, ще не використовується в класі.
Хорошим вибором буде додавання super__ до імені функції.
Ми не будемо використовувати це у вбудованих віджетах QtVCP.

##########################################
# Спеціальні функції, що викликаються з QtVCP
##########################################

# Для зміни функцій у віджетах ми можемо використовувати «класовий патч».
# Класовий патч повинен бути виконаний до інстанціювання класу.
def class_patch__(self):
    FM.super__load = FM.load # зберегти посилання на стару функцію в оригінальному класі
    FM.load = self.our_load # перенаправити функцію на нашу функцію обробки файлів

#####################
# ЗАГАЛЬНІ ФУНКЦІЇ #
#####################

def our_load(self,fname):
    print(fname)
    self.w.filemanager.super__load(fname)

12. Додавання віджетів програмним способом

У деяких ситуаціях можливо додавати віджети лише за допомогою коду Python, а не використовувати редактор Qt Designer.

Під час програмного додавання віджетів QtVCP іноді потрібно виконати додаткові кроки.

Тут ми додамо індикатор швидкості шпинделя та світлодіодний індикатор швидкості до кута віджета вкладки. Qt Designer не підтримує додавання кутових віджетів до вкладок, але PyQt підтримує.

Це скорочений приклад з файлу обробника екрана QtAxis.

Імпортувати необхідні бібліотеки

Спочатку нам потрібно імпортувати потрібні бібліотеки, якщо вони ще не імпортовані у файлі обробника:

  • QtWidgets надає нам доступ до QProgressBar,

  • QColor для LED color,

  • StateLED бібліотека QtVCP, яка використовується для створення світлодіода шпинделя на швидкості,

  • Status використовується для отримання інформації про стан LinuxCNC,

  • Info надає нам інформацію про конфігурацію машини.

############################
# **** РОЗДІЛ ІМПОРТ **** #
############################

from PyQt5 import QtWidgets
from PyQt5.QtGui import QColor
from qtvcp.widgets.state_led import StateLED as LED
from qtvcp.core import Status, Info
Створення екземплярів каналів Status та Info

STATUS та INFO ініціалізуються поза класом обробника, щоб бути глобальними посиланнями (без self. попереду):

##########################################
# **** розділ створення екземплярів бібліотек **** #
###########################################

STATUS = Status()
INFO = Info()
Зареєструйте функцію моніторингу STATUS

Для індикатора швидкості обертання шпинделя нам потрібно знати поточну швидкість шпинделя. Для цього ми реєструємося за допомогою STATUS, щоб:

  • Зловити сигнал фактичної-зміни-швидкості-шпинделя

  • Виклик функції self.update_spindle()

########################
# **** ІНІЦІАЛІЗАЦІЯ **** #
########################
# Віджети дозволяють отримати доступ до віджетів із файлів QtVCP.
# На цьому етапі віджети та контакти HAL не інстанціюються.
def __init__(self,halcomp,widgets,paths):
    self.hal = halcomp
    self.w = widgets
    self.PATHS = paths

    STATUS.connect('actual-spindle-speed-changed', \
        lambda w,speed: self.update_spindle(speed))
Додайте віджети до вкладки

Перед тим, як додавати до них нові елементи, потрібно переконатися, що віджети Qt Designer вже створені. Для цього додаємо виклик функції self.make_corner_widgets(), щоб створити додаткові віджети в потрібний момент, тобто в функції initialized__():

##########################################
# Спеціальні функції, що викликаються з QtScreen #
##########################################

# на цьому етапі:
# екземпляри віджетів створено.
# піни HAL зібрано, але HAL ще не готовий
def initialized__(self):
    self.make_corner_widgets()
Створення функцій побудови віджетів

Добре, давайте напишемо функцію для створення віджетів та додамо їх до віджета вкладок. Ми припускаємо, що існує віджет вкладок, створений за допомогою Designer, під назвою rightTab.

Ми припускаємо, що існує віджет вкладок, створений за допомогою Qt Designer, під назвою rightTab.

#####################
# загальні функції #
#####################

def make_corner_widgets(self):
    # зробити зелений світлодіод, що показує швидкість шпинделя
    self.w.led = LED()                                        # <1>
    self.w.led.setProperty('is_spindle_at_speed_status',True) # <2>
    self.w.led.setProperty('color',QColor(0,255,0,255))       # <3>
    self.w.led.hal_init(HAL_NAME = 'spindle_is_at_speed')     # <4>

    # зробити планку швидкості шпинделя
    self.w.rpm_bar = QtWidgets.QProgressBar()                 # <5>
    self.w.rpm_bar.setRange(0, INFO.MAX_SPINDLE_SPEED)        # <6>

    # контейнер
    w = QtWidgets.QWidget()                                   # <7>
    w.setContentsMargins(0,0,0,6)
    w.setMinimumHeight(40)

    # макет
    hbox = QtWidgets.QHBoxLayout()                            # <8>
    hbox.addWidget(self.w.rpm_bar)                            # <9>
    hbox.addWidget(self.w.led)                                # <9>
    w.setLayout(hbox)

    # додати контейнер у кут віджета правої вкладки
    self.w.rightTab.setCornerWidget(w)                        # <10>
  1. Це ініціалізує базовий віджет StateLed та використовує self.w.led як посилання з цього моменту.

  2. Оскільки світлодіод стану може використовуватися для багатьох індикацій, ми повинні встановити властивість, яка позначає його як світлодіод, що вказує на швидкість шпинделя.

  3. Це робить його зеленим, коли він увімкнений.

  4. Це додатковий виклик функції, необхідний для деяких віджетів QtVCP.
    Якщо HAL_NAME пропущено, буде використано objectName віджета, якщо такий є.
    Це надає спеціальним віджетам посилання на:

    self.HAL_GCOMP

    екземпляр компонента HAL

    self.HAL_NAME

    Назва цього віджета у вигляді рядка

    self.QT_OBJECT_

    Екземпляр об’єкта PyQt цього віджета

    self.QTVCP_INSTANCE_

    Найвищий рівень батьківського елемента екрана

    self.PATHS_

    Екземпляр бібліотеки path у QtVCP

    self.PREFS_

    екземпляр додаткового файлу налаштувань

    self.SETTINGS_

    об’єкт Qsettings

  5. Ініціалізує PyQt5 QProgressBar.

  6. Встановлює максимальний діапазон індикатора прогресу на значення, вказане в INI.

  7. Ми створюємо QWidget
    Оскільки ви можете додати лише один віджет у кут вкладки, а нам потрібно два, ми повинні додати обидва в контейнер.

  8. додати QHBoxLayout до QWidget.

  9. Потім ми додаємо наш індикатор QProgress та світлодіод до макета.

  10. Нарешті, ми додаємо QWidget (з нашою індикатором QProgress та світлодіодом у ньому) до кута віджета вкладки.

Створіть функцію моніторингу STATUS

Тепер ми створюємо функцію, яка фактично оновлюватиме QProgressBar, коли STATUS оновлює швидкість шпинделя:

########################
# зворотні виклики від STATUS #
########################
def update_spindle(self, data):
    self.w.rpm_bar.setInvertedAppearance(bool(data<0))       # <1>
    self.w.rpm_bar.setFormat('{0:d} RPM'.format(int(data)))  # <2>
    self.w.rpm_bar.setValue(abs(data))                       # <3>
  1. У цьому випадку ми вибрали відображення зліва направо або справа наліво, залежно від того, чи повертаємося ми за годинниковою стрілкою чи проти неї.

  2. Це форматує текст у смузі.

  3. Це встановлює довжину кольорової смуги.

13. Періодично оновлювати/читати об’єкти

Іноді потрібно оновлювати віджет або регулярно зчитувати значення, яке не охоплюється звичайними бібліотеками.

Тут ми оновлюємо світлодіод на основі спостережуваного виводу HAL кожні 100 мс.

Ми припускаємо, що у файлі інтерфейсу Qt Designer є світлодіод з назвою led.

Завантажте бібліотеку Qhal для доступу до компонента HAL QtVCP

У розділі «IMPORT SECTION» додайте:

from qtvcp.core import Qhal
Створення екземпляра Qhal

У розділі «INSTANTIATE LIBRARY» додайте:

QHAL = Qhal()

Тепер додайте/змініть ці розділи, щоб включити код, подібний до цього:

Зареєструвати функцію, яка буде викликатися в період CYCLE_TIME

Зазвичай це кожен 100 мс.

########################
# **** ІНІЦІАЛІЗАЦІЯ **** #
########################
# widgets дозволяє доступ до віджетів з файлів QtVCP
# на цьому етапі віджети та піни hal не створюютьсяd
def __init__(self,halcomp,widgets,paths):
    self.hal = halcomp
    self.w = widgets
    self.PATHS = paths

    # зареєструвати функцію, яка буде викликатися через період CYCLE_TIME (зазвичай кожні 100 мс)
    STATUS.connect('periodic', lambda w: self.update_periodic())
Створіть власну функцію, яка буде викликатися періодично
#####################
# загальні функції #
#####################
def update_periodic(self):
    data = QHAL.getvalue('spindle.0.is-oriented')
    self.w.led.setState(data)

14. Зовнішнє керування за допомогою ZMQ

QtVCP може автоматично налаштувати обмін повідомленнями ZMQ для надсилання та/або отримання віддалених повідомлень від зовнішніх програм.

Він використовує шаблон обміну повідомленнями публікація/підписка від ZMQ.

Як завжди, подумайте про безпеку, перш ніж дозволяти програмам взаємодіяти через повідомлення.

14.1. Читання повідомлень ZMQ

Іноді хочеться керувати екраном за допомогою окремої програми.

Увімкнути отримання повідомлень ZMQ

У віджеті ScreenOptions ви можете вибрати властивість use_receive_zmq_option.
Ви також можете встановити цю властивість безпосередньо у файлі обробника, як у цьому прикладі.

Ми припускаємо, що віджет ScreenOptions називається screen_options у Qt Designer:

########################
# **** ІНІЦІАЛІЗАЦІЯ **** #
########################
# widgets дозволяє доступ до віджетів з файлів QtVCP
# на цьому етапі віджети та піни hal не створені
def __init__(self,halcomp,widgets,paths):
    # безпосередньо вибрати отримання повідомлень ZMQ
    self.w.screen_options.setProperty('use_receive_zmq_option',True)

Це дозволяє зовнішній програмі викликати функції у файлі обробника.

Додано функцію, яка буде викликатися під час отримання повідомлення ZMQ

Давайте додамо спеціальну функцію для тестування. Вам потрібно буде запустити LinuxCNC з терміналу, щоб побачити надрукований текст.

#####################
# загальні функції #
#####################
def test_zmq_function(self, arg1, arg2):
    print('zmq_test_function called: ', arg1, arg2)
Створіть зовнішню програму, яка надсилатиме ZMQ-повідомлення, що ініціюватимуть виклик функцій

Ось приклад зовнішньої програми для виклику функції. Вона щосекунди чергує два набори даних. Запустіть її в окремому терміналі від LinuxCNC, щоб переглянути надіслані повідомлення.

#!/usr/bin/env python3
з імпорту часу сну

import zmq
import json

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://127.0.0.1:5690")
topic = b'QtVCP'

# попередньо створене повідомлення 1
# створює словник функції для виклику плюс будь-які аргументи
x = {                               # <1>
  "FUNCTION": "test_zmq_function",
  "ARGS": [True,200]
}
# конвертувати в об'єкт JSON
m1 = json.dumps(x)

# повідомлення попередньої збірки 2
x = {                               # <1>
  "FUNCTION": "test_zmq_function",
  "ARGS": [False,0],
}
# конвертувати в об'єкт JSON
m2 = json.dumps(x)

if __name__ == '__main__':
    while True:
        print('send message 1')
        socket.send_multipart([topic, bytes((m1).encode('utf-8'))])
        sleep(ms(1000))

        print('send message 2')
        socket.send_multipart([topic, bytes((m2).encode('utf-8'))])
        sleep(ms(1000))
  1. Встановіть функцію для виклику та аргументи для надсилання цій функції.

Вам потрібно знати сигнатуру функції, яку ви хочете викликати. Також зверніть увагу, що повідомлення перетворюється на об’єкт JSON. Це пов’язано з тим, що ZMQ надсилає байтові повідомлення, а не об’єкти Python. json перетворює об’єкти Python на байти, які будуть перетворені назад після отримання.

14.2. Написання повідомлень ZMQ

Ви також можете спілкуватися із зовнішньою програмою з екрана.

У віджеті ScreenOptions ви можете вибрати властивість use_send_zmq_message. Ви також можете встановити цю властивість безпосередньо у файлі обробника, як у цьому прикладі.

Ми припускаємо, що віджет ScreenOptions називається screen_options у Qt Designer:

Увімкнути надсилання повідомлень ZMQ
########################
# **** ІНІЦІАЛІЗАЦІЯ **** #
########################
# «widgets» дозволяє доступ до віджетів з файлів QtVCP
# на цьому етапі віджети та піни hal ще не створені
def __init__(self, halcomp,widgets,paths):
    # безпосередньо вибрати надсилання повідомлення ZMQ
    self.w.screen_options.setProperty('use_send_zmq_option',True)

Це дозволяє надсилати повідомлення до окремої програми.
Надіслане повідомлення залежатиме від того, чого очікує зовнішня програма.

Створіть функцію для надсилання ZMQ-повідомлень

Додамо спеціальну функцію для тестування.
Щоб побачити надрукований текст, вам потрібно буде запустити LinuxCNC з терміналу.
Також потрібно додати щось для виклику цієї функції, наприклад, клацання кнопки.

#####################
# загальні функції #
#####################
def send_zmq_message(self):
    # Це може бути будь-який об'єкт Python, який JSON може конвертувати
    message = {"name": "John", "age": 30}
    self.w.screen_options.send_zmq_message(message)
Використайте або створіть програму, яка отримуватиме повідомлення ZMQ

Ось приклад програми, яка отримає повідомлення та виведе його на термінал:

import zmq
import json

# ZeroMQ Context
context = zmq.Context()

# Визначте сокет за допомогою "Контексту"
sock = context.socket(zmq.SUB)

# Визначте підписку та повідомлення з темою для прийняття.
topic = "" # всі теми
sock.setsockopt_string(zmq.SUBSCRIBE, topic)
sock.connect("tcp://127.0.0.1:5690")

поки True:
    topic, message = sock.recv_multipart()
    print('{} sent message:{}'.format(topic,json.loads(message)))

15. Надсилання повідомлень у рядок стану або діалогові вікна сповіщень на робочому столі

Існує кілька способів повідомити інформацію користувачеві.

Рядок стану використовується для короткої інформації, яка відображається користувачеві.

Note
Не всі екрани мають рядок стану.
Приклад використання рядка стану
self.w.statusbar.showMessage(message, timeout * 1000)

timeout вказується в секундах, і ми припускаємо, що statusbar — це ім’я віджета в наборі Qt Designer.

Ви також можете використовувати бібліотеку Status для надсилання повідомлення до бібліотеки notify, якщо вона увімкнена (зазвичай це встановлюється у віджеті ScreenOptions): Це надішле повідомлення до панелі стану та діалогового вікна сповіщень на робочому столі.

Повідомлення також записуються, доки користувач не видалить їх за допомогою елементів керування. Користувачі можуть відновити будь-які записані повідомлення.

Є кілька варіантів:

STATUS.TEMPORARY_MESSAGE

Показувати повідомлення лише короткий час.

STATUS.OPERATOR_ERROR
STATUS.OPERATOR_TEXT
STATUS.NML_ERROR
STATUS.NML_TEXT

Приклад надсилання повідомлення оператору:
STATUS.emit('error', STATUS.OPERATOR_ERROR, 'message')

Ви можете надсилати повідомлення через функції операторських повідомлень LinuxCNC. Зазвичай вони перехоплюються системою сповіщень, тому еквівалентні вищезазначеному. Вони також будуть виведені на термінал.

ACTION.SET_DISPLAY_MESSAGE('MESSAGE')
ACTION.SET_ERROR_MESSAGE('MESSAGE')

16. Зміни фокусу

Фокус використовується для спрямування дій користувача, таких як введення з клавіатури, до відповідного віджета.

Отримати віджет, що відображає поточний фокус
fwidget = QtWidgets.QApplication.focusWidget()
if fwidget is not None:
    print("focus widget class: {} name: {} ".format(fwidget, fwidget.objectName()))
Отримувати віджет з фокусом, коли фокус змінюється
# на цьому етапі:
# екземпляри віджетів створено.
# піни HAL зібрано, але HAL ще не готовий
def initialized__(self):
    QtWidgets.QApplication.instance().event_filter.focusIn.connect(self.focusInChanged)

#####################
# загальні функції #
#####################

def focusInChanged(self, widget):
    if isinstance(widget.parent(),type(self.w.gcode_editor.editor)):
        print('G-code Editor')
    elif isinstance(widget,type(self.w.gcodegraphics)):
        print('G-code Display')
    elif isinstance(widget.parent(),type(self.w.mdihistory) ):
        print('MDI History')

Зверніть увагу, що ми іноді порівнюємо з widget, іноді з widget.parent().

Це тому, що деякі віджети QtVCP побудовані з кількох підвіджетів, і останні фактично отримують фокус; тому нам потрібно перевірити батьківський елемент цих підвіджетів.

В інших випадках фокус отримує головний віджет, наприклад, віджет відображення G-коду можна налаштувати так, щоб він приймав фокус. У цьому випадку в ньому немає підвіджетів, тому порівняння з widget.parent() дасть вам контейнер, що містить віджет G-коду.

17. Параметри часу завантаження командного рядка читання

Деякі панелі потребують інформації під час завантаження для налаштування/опцій. QtVCP задовольняє цю вимогу за допомогою опцій «-o».
Аргумент «-o» підходить для декількох відносно коротких опцій, які можна додати до командного рядка завантаження.
Для більш детальної інформації, ймовірно, краще прочитати файл INI або файл налаштувань.

У командному рядку можна використовувати кілька опцій «-o», тому їх необхідно декодувати.
«self.w.USEROPTIONS_» зберігатиме всі знайдені опції «-o» у вигляді списку рядків. Ви повинні проаналізувати та визначити, що приймається і що з цим робити.

Приклад коду для отримання опцій -o для номера камери та розміру вікна
    def initialized__(self):

        # встановити номер камери за замовчуванням
        number = 0

        # перевірити, чи є взагалі якісь опції -o
        if self.w.USEROPTIONS_ is not None:

            # якщо в режимі налагодження вивести параметри в термінал
            LOG.debug('cam_align user options: {}'.format(self.w.USEROPTIONS_))

            # go через знайдені варіанти один за одним
            for num, i in enumerate(self.w.USEROPTIONS_):

                # якщо параметр -o містить 'size=', припускати, що це ширина та висота вікна
                # змінити ширину та висоту вікна за замовчуванням
                if 'size=' in self.w.USEROPTIONS_[num]:
                    try:
                        strg = self.w.USEROPTIONS_[num].strip('size=')
                        arg = strg.split(',')
                        self.w.resize(int(arg[0]),int(arg[1]))
                    except Exception as e:
                        print('Error with cam_align size setting:',self.w.USEROPTIONS_[num])

                #  # якщо параметр -o містить 'camnumber=', припускати, що це номер камери, який потрібно використовувати
                elif 'camnumber=' in self.w.USEROPTIONS_[num]:
                    try:
                        number = int(self.w.USEROPTIONS_[num].strip('camnumber='))
                    except Exception as e:
                        print('Error with cam_align camera selection - not a number - using 0')

        # встановити номер камери або за замовчуванням, або, якщо опція -o змінила змінну 'number', на цей номер.
        self.w.camview._camNum = number

18. G-код для зчитування налаштувань Qt

Ось як створити програму O-Word, щоб прочитати запис файлу переваг QtDragon і додавати його як параметр G-коду.
Зкликання цього O-слову оновлює параметр toolToLoad
Це використовує "горячий коментар Python" для спілкування з вбудованим прикладом python.
Перегляньте розділ переробки документів для опису.

(filename myofile.ngc)
o<myofile> sub

;py,from interpreter import *
;py,import os
;py,from qtvcp.lib.preferences import Access

; find and print the preference file path
;py,CONFPATH = os.environ.get('CONFIG_DIR', '/dev/null')
; adjust for your preference file name
;py,PREFFILE = os.path.join(CONFPATH,'qtdragon.pref')
;py,print(PREFFILE)

; get an preference instance
;py,Pref = Access(PREFFILE)

; load a preference and print it
;py,this.params['toolToLoad']=Pref.getpref('Tool to load', 0, int,'CUSTOM_FORM_ENTRIES')
;py,print('Tool to load->:',this.params['toolToLoad'])

; return the value
o<myofile> endsub [#<toolToLoad>]
M2