QtVCP — це інфраструктура для створення користувацьких екранів CNC або панелей керування для LinuxCNC.

Він відображає файл .ui, створений за допомогою редактора екранів Qt Designer, та поєднує його з _програмуванням на Python для створення екрана графічного інтерфейсу для роботи з верстатом з ЧПК.

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

1. Вітрина

Кілька прикладів екранів та віртуальних панелей керування, вбудованих у QtVCP:

Маршрутизатор QtDragon
Figure 1. QtDragon - Зразок для 3/4 осі
Qtscreen Mill
Figure 2. QtDefault - 3-осьовий приклад
Qtscreen QtAxis
Figure 3. QtAxis - Приклад самоналаштування осі
Qtscreen Blender
Figure 4. Blender - 4-осьовий зразок
Qtscreen x1mill
Figure 5. X1mill - 4-осьовий зразок
Qtscreen x1mill
Figure 6. cam_align - Вирівнювання камери VCP
Тестова панель
Figure 7. test_panel - Тестова панель VCP

2. Огляд

Для додавання налаштувань використовуються два файли, окремо або в поєднанні:

  • Файл інтерфейсу користувача, який є XML файлом, створеним за допомогою графічного редактора Qt Designer.

  • Файл обробника, який є текстовим файлом коду Python.

Зазвичай QtVCP використовує стандартний файл інтерфейсу користувача та обробника, але ви можете вказати QtVCP використовувати «локальні» файли інтерфейсу користувача та обробника.
«Локальний» файл — це файл, який знаходиться в папці configuration і визначає решту вимог до комп’ютера.

Додавання власної панелі праворуч або вкладки не обмежується лише додаванням власної панелі, оскільки QtVCP використовує Qt Designer (редактор) та PyQt5 (набір інструментів для віджетів).

QtVCP має деякі додаткові спеціальні віджети та дії LinuxCNC.
Існують спеціальні віджети для з’єднання віджетів сторонніх розробників з контактами HAL.
Можна створювати відповіді віджетів, підключаючи сигнали до коду Python у файлі обробника.

2.1. Віджети QtVCP

QtVCP використовує віджети інструментарію PyQt5 для інтеграції з LinuxCNC.

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

Ви можете вільно використовувати будь-які доступні віджети за замовчуванням у редакторі Qt Designer.

Також існують спеціальні віджети, створені для LinuxCNC, які спрощують інтеграцію.
Вони розділені на три заголовки в лівій частині редактора:

  • Один призначений для віджетів лише HAL;

  • Один призначений для _віджетів керування _CNC;

  • Один призначений для віджетів діалогу.

Ви можете вільно змішувати їх будь-яким чином на своїй панелі.

Дуже важливим віджетом для управління CNC є віджет ScreenOptions: він не додає нічого візуально на екран, але дозволяє вибирати важливі деталі, замість того, щоб кодувати їх у файлі обробника.

2.2. Налаштування INI

Якщо ви використовуєте QtVCP для створення екрана керування рухом CNC (а не панелі на основі HAL), у файлі INI, в розділі [DISPLAY], додайте рядок із таким шаблоном:

DISPLAY = qtvcp <options> <screen_name>
Note
Усі <options> мають стояти перед <screen_name>.
Опції
  • -d Налагодження триває.

  • -i Увімкнути вивід інформації.

  • -v Увімкнути детальний вивід налагодження.

  • -q Увімкнути лише вивід налагодження помилок.

  • -a Встановити вікно завжди зверху.

  • -c NAME Назва компонента HAL. За замовчуванням використовується назва файлу інтерфейсу користувача.

  • -g GEOMETRY Встановити геометрію WIDTHxHEIGHT+XOFFSET+YOFFSET. Значення вказані в пікселях, XOFFSET/YOFFSET відлічуються від верхнього лівого кута екрану. Використовуйте -g WIDTHxHEIGHT для встановлення лише розміру або -g +XOFFSET+YOFFSET для встановлення лише положення. Приклад: -g 200x400+0+100

  • -H FILE Виконайте оператори hal з ФАЙЛУ за допомогою halcmd після того, як компонент буде налаштовано та готовий до роботи.

  • -m Максимізувати вікно.

  • -f Повноекранне вікно.

  • -t THEME За замовчуванням використовується системна тема

  • -x XID Вбудувати у вікно X11, яке не підтримує вбудовування.

  • --push_xid Надіслати ідентифікатор вікна X11 QtVCP на стандартний вивід; для вбудовування.

  • -u USERMOD Шлях до файлу обробника-замінника.

  • -o USEROPTS Передати рядок до файлу обробника QtVCP у змінній списку self.w.USEROPTIONS_. Може бути декілька -o.

<screen_name>

<screen_name> – це базова назва файлів .ui та _handler.py. Якщо <screen_name> відсутнє, буде завантажено екран за замовчуванням.

QtVCP припускає, що файл інтерфейсу користувача та файл обробника використовують однакове базове ім’я. QtVCP спочатку шукатиме файли у каталозі конфігурації LinuxCNC, який було запущено, а потім у системній папці шкір, що містить стандартні екрани.

Час циклу
[DISPLAY]
CYCLE_TIME = 100
GRAPHICS_CYCLE_TIME = 100
HALPIN_CYCLE = 100

Налаштовує швидкість реагування оновлень графічного інтерфейсу в мілісекундах. За замовчуванням 100, доступний діапазон 50–200.

Віджети, графіку та оновлення PIN-кодів HAL можна налаштувати окремо.

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

2.3. Файл інтерфейсу Qt Designer

Файл Qt Designer — це текстовий файл, організований у стандарті XML, який описує макет та віджети екрана.

PyQt5 використовує цей файл для створення дисплея та реагування на ці віджети.

Редактор Qt Designer значно спрощує створення та редагування цього файлу.

2.4. Файли обробників

Файл-обробник — це файл, що містить код Python, який додається до стандартних процедур QtVCP.

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

Якщо файл обробника присутній, буде завантажено його. Дозволено лише один файл.

2.5. Модулі бібліотек

QtVCP, у вбудованому вигляді, виконує лише відображення екрана та реагує на віджети. Для отримання додаткових готових налаштувань поведінки доступні бібліотеки (знаходяться в lib/python/qtvcp/lib у файлі встановлення RIP LinuxCNC).

Бібліотеки — це попередньо створені модулі Python, які додають функції до QtVCP. Таким чином, ви можете вибрати потрібні функції, але вам не доведеться самостійно створювати поширені.

Такі бібліотеки включають:

  • audio_player

  • aux_program_loader

  • keybindings

  • message

  • preferences

  • notify

  • virtual_keyboard

  • machine_log

2.6. Теми

Теми – це спосіб змінити зовнішній вигляд віджетів на екрані.

Наприклад, колір або розмір кнопок і повзунків можна змінити за допомогою тем.

Тема Windows використовується за замовчуванням для екранів.
Тема System використовується за замовчуванням для панелей.

Щоб переглянути доступні теми, їх можна завантажити, виконавши таку команду в терміналі:

qtvcp -d -t <theme_name>

QtVCP також можна налаштувати за допомогою Qt-таблиць стилів (QSS) за допомогою CSS.

2.7. Локальні файли

Якщо такі є, то замість стандартних файлів інтерфейсу користувача будуть завантажені локальні файли UI/QSS/Python у папці конфігурації.

Локальні файли UI/QSS/Python дозволяють використовувати власні налаштовані дизайни, а не екрани за замовчуванням.

QtVCP шукатиме папку з назвою <screen_name> (у запущеній папці конфігурації, яка містить INI-файл).

У цій папці QtVCP завантажить будь-який з доступних наступних файлів:

  • <screen_name>.ui,

  • <screen_name>_handler.py, і

  • <screen_name>.qss.

2.8. Зміна стокових екранів

Існує три способи налаштування екрана/панелі.

2.8.1. Незначні зміни таблиці стилів

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

Приклад віджета з відповідними налаштуваннями таблиці стилів.
State_LED #name_of_led{
  qproperty-color: red;
  qproperty-diameter: 20;
  qproperty-flashRate: 150;
  }

2.8.2. Патчування обробника - створення підкласів вбудованих екранів

Ми можемо змусити QtVCP завантажити підкласову версію стандартного файлу обробника. У цьому файлі ми можемо маніпулювати оригінальними функціями або додавати нові.
Підкласування означає, що наш файл обробника спочатку завантажує оригінальний файл обробника і додає до нього наш новий код - фактично патч змін.
Це корисно для зміни/додавання поведінки, зберігаючи при цьому стандартні оновлення обробника з репозиторіїв LinuxCNC.

Можливо, вам все одно доведеться скористатися діалоговим вікном копіювання обробника, щоб скопіювати оригінальний файл обробника та вирішити, як його виправити. Див. розділ «користувацький файл обробника».

У папці config повинна бути папка для екранів з назвою «<CONFIG FOLDER>/qtvcp/screens/<SCREEN NAME>/».
Додайте туди файл патча з назвою <ORIGINAL SCREEN NAME>_handler.py,
наприклад, для QtDragon файл буде називатися «qtdragon_handler.py».

Ось приклад додавання штифтів повороту осі X на екран, такий як QtDragon:

import sys
import importlib
from qtvcp.core import Path, Qhal, Action
PATH = Path()
QHAL = Qhal()
ACTION = Action()

# отримати посилання на оригінальний файл обробника, щоб ми могли зробити його підкласом
sys.path.insert(0, PATH.SCREENDIR)
module = "{}.{}_handler".format(PATH.BASEPATH,PATH.BASEPATH)
mod = importlib.import_module(module, PATH.SCREENDIR)
sys.path.remove(PATH.SCREENDIR)
HandlerClass = mod.HandlerClass

# повернути наш об'єкт-обробник підкласу до QtVCP
def get_handlers(halcomp, widgets, paths):
    return [UserHandlerClass(halcomp, widgets, paths)]

# підклас HandlerClass, який було імпортовано вище
class UserHandlerClass(HandlerClass):
    # додати повідомлення терміналу, щоб ми знали, що це завантажилося
    print('\nCustom subclassed handler patch loaded.\n')

    def init_pins(self):
        # викликати функцію init_pins оригінального обробника
        super().init_pins()

        # додати штифти поворотного механізму по осі X
        pin = QHAL.newpin("jog.axis.jog-x-plus", QHAL.HAL_BIT, QHAL.HAL_IN)
        pin.value_changed.connect(lambda s: self.kb_jog(s, 0, 1, fast = False, linear = True))

        pin = QHAL.newpin("jog.axis.jog-x-minus", QHAL.HAL_BIT, QHAL.HAL_IN)
        pin.value_changed.connect(lambda s: self.kb_jog(s, 0, -1, fast = False, linear = True))

2.8.3. Незначні зміни коду Python

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

Note
Патчування обробників — це кращий спосіб додавання змін, патчування екземплярів — це вуду чорної магії, і це тут з міркувань застарілих кодів.

У файлі INI під заголовком [DISPLAY] додайте USER_COMMAND_FILE = _PATH_
PATH може бути будь-яким дійсним шляхом. Можна використовувати ~ для домашнього каталогу або WORKINGFOLDER чи CONFIGFOLDER, щоб позначити ці каталоги в QtVCP, наприклад:

[DISPLAY]
USER_COMMAND_FILE = CONFIGFOLDER/<screen_name_added_commands>

Якщо запис не знайдено в INI, QtVCP шукатиме в шляху за замовчуванням. Шлях за замовчуванням знаходиться в каталозі конфігурації як прихований файл із використанням базового імені екрана та rc, наприклад, CONFIGURATION DIRECTORY/.<screen_name>rc.

Цей файл буде зчитано та виконано як код Python у контексті файлу обробника.

Можна посилатися тільки на локальні функції та локальні атрибути.
На глобальні бібліотеки, визначені у файлі обробника екрана, можна посилатися шляхом імпортування файлу обробника.
Зазвичай вони позначаються великими літерами без попереднього self.
self посилається на функції класу вікна.
self.w зазвичай посилається на віджети.

Те, що можна використовувати, може відрізнятися залежно від екрана та циклу розробки.

Простий приклад

Зверніться до головного вікна, щоб змінити заголовок (не відображатиметься, якщо для зміни заголовка використовуються записи INI).

self.w.setWindowTitle('Мій тест на титул')
Приклад розширеного виправлення екземпляра

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

# Потрібно створити екземпляр патча.
# посилання: https://ruivieira.dev/python-monkey-patching-for-readability.html
import types

# імпортуйте файл обробника, щоб отримати посилання на його бібліотеки.
# використовувати <screenname>_handler
імпортувати qtdragon_handler як hdlr

# Це насправді необмежена функція з параметром «obj».
# Ви викликаєте цю функцію без звичайного попереднього «self.».
# Це тому, що ми не будемо вставляти її в оригінальний екземпляр класу обробника.
# Вона буде викликатися тільки з коду в цьому файлі.
def test_function(obj):
    print(dir(obj))

# Це нова функція, яку ми додамо до існуючого екземпляру класу обробника.
# Зверніть увагу, що вона викликає необмежену функцію з 'self' як параметром. 'self' є єдиним доступним глобальним посиланням.
# Воно посилається на екземпляр вікна.
def on_keycall_F10(self,event,state,shift,cntrl):
    if state:
        print ('F10')
        test_function(self)

# Це буде використовуватися для заміни існуючої функції в існуючому екземплярі класу обробника.
# Зверніть увагу, що ми також викликаємо копію оригінальної функції.
# Це показує, як розширити існуючу функцію для виконання додаткових функцій.
def on_keycall_F11(self,event,state,shift,cntrl):
    if state:
        self.on_keycall_F11_super(event,state,shift,cntrl)
        print ('Hello')

# Ми посилаємося на бібліотеку KEYBIND, яка була інстанційована в оригінальному екземплярі класу обробника,
# додавши до неї 'hdlr.' (з imp).
# Ця функція повідомляє KEYBIND викликати 'on_keycall_F10' при натисканні F10.
hdlr.KEYBIND.add_call('Key_F10','on_keycall_F10')

# Тут ми виправляємо оригінальний файл обробника, щоб додати нову функцію,
# яка викликає нашу нову функцію (з такою ж назвою), визначену в цьому файлі.
self.on_keycall_F10 = types.MethodType(on_keycall_F10, self)

# Тут ми визначаємо копію оригінальної функції 'on_keycall_F11',
# щоб ми могли викликати її пізніше. Ми можемо використовувати будь-яку дійсну, невикористану назву функції.
# Нам потрібно зробити це перед перевизначенням оригінальної функції.
self.on_keycall_F11_super = self.on_keycall_F11

# Тут ми створюємо приклад патчу для оригінального файлу обробника, щоб перезаписати існуючу функцію
# щоб вказати на нашу нову функцію (з такою ж назвою), визначену в цьому файлі.
self.on_keycall_F11 = types.MethodType(on_keycall_F11, self)


# додати новий пін-код на екран:

# зворотний виклик pin для виведення стану
def new_pin_changed(data):
    print(data)

# Спеціальна функція, яка викликається до того, як компонент HAL буде готовий.
# Тут ми використали функцію для додавання бітового вхідного виводу з callback.
def after_override__(self):
    try:
        pin = hdlr.QHAL.newpin("new_pin", hdlr.QHAL.HAL_BIT, hdlr.QHAL.HAL_IN)
        pin.value_changed.connect(new_pin_changed)
    except Exception as e:
        print(e)

# Тут ми виправляємо оригінальний файл обробника, щоб додати нову функцію,
# яка викликає нашу нову функцію (з такою ж назвою), визначену в цьому файлі.
self.after_override__ = types.MethodType(after_override__, self)

2.8.4. Повний творчий контроль за допомогою користувацьких файлів обробників/інтерфейсу користувача

Якщо ви бажаєте змінити стандартний екран з повним контролем, скопіюйте його інтерфейс користувача та файл обробника до папки конфігурації.

Для цього є панель QtVCP:

  • Відкрийте термінал і виконайте таку команду:

    qtvcp copy
  • Виберіть екран і папку призначення в діалоговому вікні

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

  • У папці config повинна бути папка; для екранів: з назвою «<CONFIG FOLDER>/qtvcp/screens/»; для панелей: з назвою «<CONFIG FOLDER>/qtvcp/panels/». Додайте папки, якщо їх немає, і скопіюйте туди свою папку/файли.

  • Перевірте, щоб скопіювати всі файли

  • Видаліть файли, які не хочете змінювати, щоб використовувати оригінальні файли.

3. Панелі VCP

QtVCP можна використовувати для створення панелей керування, які взаємодіють з HAL.

3.1. Вбудовані панелі

Доступно кілька вбудованих панелей HAL.

У терміналі введіть qtvcp <return>, щоб побачити список:

test_panel

Колекція корисних віджетів для тестування компонентів HAL, включаючи озвучування стану світлодіодів.

Вбудована панель тестування HAL QtVCP
Figure 8. Вбудована панель тестування HAL QtVCP
cam_align

Віджет відображення камери для вирівнювання повороту.

Qtscreen x1mill
Figure 9. cam_align - Вирівнювання камери VCP
sim_panel

Невелика панель керування для імітації керування бігом MPG тощо.
Для імітації конфігурацій.

Вбудована панель QtVCP Sim
Figure 10. Вбудована панель QtVCP Sim
vismach_mill_xyz

3D OpenGL-вигляд 3-осьового фрезерного верстата.

QtVismach - Вбудована панель 3-осьового фрезерного верстата
Figure 11. QtVismach - Вбудована панель 3-осьового фрезерного верстата

Ви можете завантажити їх з терміналу або з HAL-файлу за допомогою цієї базової команди:

loadusr qtvcp test_panel

Але частіше ось так:

loadusr -Wn test_panel qtvcp test_panel

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

3.2. Спеціальні панелі

Звісно, ви можете створити власну панель та завантажити її.

Якщо ви створили файл інтерфейсу користувача з назвою my_panel.ui та файл HAL з назвою my_panel.hal, ви завантажили б їх з терміналу за допомогою:

halrun -I -f my_panel.hal
Приклад HAL-файлу, що завантажує панель QtVCP
# завантаження компонентів реального часу
loadrt threads
loadrt classicladder_rt

# завантажувати програми, що не працюють у реальному часі
loadusr classicladder
loadusr -Wn my_panel qtvcp my_panel.ui  # <1>

# додати компоненти до потоку
addf classicladder.0.refresh thread1


# з'єднати контакти
net bit-input1     test_panel.checkbox_1        classicladder.0.in-00
net bit-hide       test_panel.checkbox_4        classicladder.0.hide_gui

net bit-output1    test_panel.led_1             classicladder.0.out-00

net s32-in1        test_panel.doublescale_1-s   classicladder.0.s32in-00

# почати тему
start
  1. У цьому випадку ми завантажуємо qtvcp за допомогою -Wn, що очікує завершення завантаження панелі перед продовженням виконання наступної команди HAL.
    Це робиться для того, щоб переконатися, що створені панеллю контакти HAL дійсно готові, якщо вони використовуються в решті файлу.

4. Створіть простий користувацький екран з чистого аркуша

Потворний користувацький екран QtVCP
Figure 12. Потворний користувацький екран QtVCP

4.1. Огляд

Щоб створити панель або екран:

  • Використовуйте Qt Designer, щоб створити дизайн, який вам подобається, та збережіть його у папці конфігурації з вибраним ім’ям, що закінчується на .ui

  • Змініть INI-файл конфігурації, щоб завантажити QtVCP за допомогою вашого нового файлу .ui.

  • Потім підключіть усі необхідні контакти HAL у файлі HAL.

4.2. Включити віджети LinuxCNC у Qt Designer

Встановлення Qt Designer

Спочатку у вас має бути встановлено Qt Designer.
Наступні команди мають додати його до вашої системи або скористатися вашим менеджером пакетів для цього:

sudo apt-get install qttools5-dev-tools qttools5-dev libpython3-dev
Додати посилання qtvcp_plugin.py до шляху пошуку Qt Designer

Потім вам потрібно додати посилання на qtvcp_plugin.py в одній з папок, у яких Qt Designer шукатиме.

У RIP версії LinuxCNC qtvcp_plugin.py буде:

'~/LINUXCNC_PROJECT_NAME/lib/python/qtvcp/plugins/qtvcp_plugin.py'

Для версії з встановленим пакетом має бути:

'usr/lib/python2.7/qtvcp/plugins/qtvcp_plugin.py' або
'usr/lib/python2.7/dist-packages/qtvcp/plugins/qtvcp_plugin.py'

Створіть символічне посилання на вищезгаданий файл і перемістіть його в одне з місць, де Qt Designer виконує пошук.

Qt Designer шукає посилання в цих двох місцях (виберіть одне):

'/usr/lib/x86_64-linux-gnu/qt5/plugins/designer/python' або
'~/.designer/plugins/python'

Можливо, вам знадобиться створити папку plugins/python.

Запустіть Qt Designer:
  • Для RIP-встановлення:
    Відкрийте термінал, встановіть середовище для LinuxCNC <1>, потім завантажте Qt Designer <2> за допомогою:

    . scripts/rip-environment   <1>
    designer -qt=5              <2>
  • Для встановлення пакета:
    Відкрийте термінал і введіть:

    designer -qt=5

Якщо все пройде добре, Qt Designer запуститься, і ви побачите доступні для вибору віджети LinuxCNC ліворуч.

4.3. Зберіть екранний файл .ui

Створити віджет MainWindow

Під час першого запуску Qt Designer відображається діалогове вікно
«Нова форма». Виберіть _«Головне вікно» та натисніть кнопку _«Створити».
Відображається віджет _«Головне вікно».

Ми збираємося зробити це вікно певного розміру, який не можна змінювати:

Встановити мінімальний та максимальний розмір MainWindow
  • Візьміть кут вікна та змініть його розмір до відповідного розміру, скажімо, 1000x600.

  • Клацніть правою кнопкою миші на вікні та виберіть «Встановити мінімальний розмір».

  • Зробіть це ще раз і встановіть максимальний розмір.

Розмір нашого зразка віджета тепер не можна буде змінювати.

Додайте віджет ScreenOptions

Перетягніть віджет ScreenOptions будь-де в головне вікно.

Цей віджет не додає нічого візуального, але налаштовує деякі загальні опції.

Рекомендується завжди додавати цей віджет перед будь-яким іншим.

Клацніть правою кнопкою миші на головному вікні, не на віджеті ScreenOptions, та встановіть вертикальне розташування layout, щоб зробити ScreenOptions повнорозмірним.

Додати вміст панелі

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

В інспекторі об’єктів натисніть на ScreenOptions.
Потім перейдіть до редактора властивостей і під заголовком ScreenOptions увімкніть filedialog_option.

Перетягніть віджет GCodeGraphics widget та GcodeEditor widget.
Розмістіть та змініть їх розмір на свій розсуд, залишивши місце для кнопок.

Додати кнопки дій

Додайте 7 кнопок дій до головного вікна.

Якщо двічі клацнути кнопку, можна додати текст.
Відредагуйте підписи кнопок для «Стоп», «Увімкнення машини», «Додому», «Завантаження», «Виконати», «Пауза» та «Стоп».

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

  • безпосередньо в редакторі властивостей у правій частині Qt Designer, або

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

Спочатку ми опишемо зручний спосіб:

  • Клацніть правою кнопкою миші на кнопці «Увімкнення машини» та виберіть Встановити дії.

  • Коли відобразиться діалогове вікно, скористайтеся випадаючим списком, щоб перейти до пункту КЕРУВАННЯ МАШИНОЮ - Машина ввімкнена.

  • У цьому випадку для цієї дії немає опції, тому виберіть «ОК».

Тепер кнопка вмикатиме машину при натисканні.

А тепер прямий шлях за допомогою редактора властивостей Qt Designer:

  • Виберіть кнопку «Увімкнути машину».

  • Перейдіть до Редактора властивостей у правій частині Qt Designer.

  • Прокрутіть униз, доки не знайдете заголовок ActionButton.

  • У списку властивостей та значень ви побачите прапорець дії machine_on.

Тепер кнопка керуватиме вмиканням/вимиканням машини.

Зробіть те саме для всіх інших кнопок, додавши:

  • За допомогою кнопки «Головна» нам також потрібно змінити властивість joint_number на -1.
    Це вказує контролеру перевести в початкове положення всі осі, а не певну вісь.

  • За допомогою кнопки «Пауза»:

    • Під заголовком Indicated_PushButton перевірте indicator_option.

    • Під заголовком QAbstactButton поставте галочку навпроти checkable.

Qt Designer: Вибір властивостей кнопки паузи
Figure 13. Qt Designer: Вибір властивостей кнопки паузи
Збережіть файл .ui

Потім нам потрібно зберегти цей дизайн як tester.ui у папці sim/qtvcp.

Ми зберігаємо його як tester, оскільки це ім’я файлу, яке розпізнає QtVCP і використовуватиме вбудований файл-обробник для його відображення.

4.4. Файл обробника

Файл обробника обов’язковий.

Це дозволяє писати налаштування на Python.

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

У цьому прикладі вбудований файл tester_handler.py використовується автоматично: він виконує мінімум необхідних дій для відображення екрана, визначеного tester.ui, та базової роботи з клавіатурою.

4.5. Конфігурація INI

[DISPLAY] Розділ

Якщо ви використовуєте QtVCP для створення екрана керування CNC, у файлі INI під заголовком [DISPLAY] встановіть:

DISPLAY = qtvcp <screen_name>

<screen_name> – це базова назва файлів .ui та _handler.py.

У нашому прикладі вже є конфігурація симулятора під назвою tester, яку ми використовуватимемо для відображення нашого тестового екрана.

[HAL] Розділ

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

QtVCP шукає у файлі INI, під заголовком [HAL], записи нижче:

POSTGUI_HALFILE=<filename>

Зазвичай <filename> буде +<screen_name>_postgui.hal+, але може бути будь-яким допустимим іменем файлу.
Ви можете мати кілька рядків POSTGUI_HALFILE в INI: кожен з них буде виконуватися один за одним у порядку їх появи.
Ці команди виконуються після побудови екрану, що гарантує доступність контактів HAL віджета.

POSTGUI_HALCMD=<command>

<command> може бути будь-якою дійсною командою HAL.
У файлі INI може бути кілька рядків POSTGUI_HALCMD: кожна з них буде виконана послідовно в порядку їх появи.
Щоб гарантувати доступність контактів HAL віджета, виконуються такі команди:

  • після того, як екран буде побудовано,

  • після виконання всіх POSTGUI_HALFILE.

У нашому прикладі немає контактів HAL для підключення.

5. Детальний опис файлу обробника

Файли обробників використовуються для створення користувацьких елементів керування за допомогою Python.

5.1. Огляд

Ось приклад файлу обробника.

Для зручності обговорення його розбито на розділи.

############################
# **** РОЗДІЛ ІМПОРТ **** #
############################
import sys
import os
import linuxcnc

з PyQt5 імпорт QtCore, QtWidgets

from qtvcp.widgets.mdi_line import MDILine as MDI_WIDGET
from qtvcp.widgets.gcode_editor import GcodeEditor as GCODE
from qtvcp.lib.keybindings import Keylookup
from qtvcp.core import Status, Action

# Налаштування ведення журналу
from qtvcp import logger
LOG = logger.getLogger(__name__)

# Встановити рівень журналу для цього модуля
#LOG.setLevel(logger.INFO) # Один з наступних пунктів: НАЛАГОДЖЕННЯ, ІНФОРМАЦІЯ, ПОПЕРЕДЖЕННЯ, ПОМИЛКА, КРИТИЧНИЙ

###########################################
# **** РОЗДІЛ «МІСЦЕВІ БІБЛІОТЕКИ» **** #
###########################################

KEYBIND = Keylookup()
STATUS = Status()
ACTION = Action()
###################################
# **** СЕКЦІЯ КЛАСУ ХЕНДЛЕР **** #
###################################

class HandlerClass:

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

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

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

    def processed_key_event__(self,receiver,event,is_pressed,key,code,shift,cntrl):
        # під час набору тексту в MDI ми не хочемо, щоб комбінації клавіш викликали функції,
        # тому ми безпосередньо фіксуємо та обробляємо події.
        # Однак ми хочемо, щоб ESC, F1 та F2 викликали функції комбінацій клавіш
        if code not in(QtCore.Qt.Key_Escape,QtCore.Qt.Key_F1 ,QtCore.Qt.Key_F2,
                    QtCore.Qt.Key_F3,QtCore.Qt.Key_F5,QtCore.Qt.Key_F5):

            # пошук верхнього віджета з усіх віджетів, які отримали подію
            # потім перевірка, чи це той віджет, до якого ми хочемо направити події натискання клавіш
            flag = False
            receiver2 = receiver
            while receiver2 is not None and not flag:
                if isinstance(receiver2, QtWidgets.QDialog):
                    flag = True
                    break
                if isinstance(receiver2, MDI_WIDGET):
                    flag = True
                    break
                if isinstance(receiver2, GCODE):
                    flag = True
                    break
                receiver2 = receiver2.parent()

            if flag:
                if isinstance(receiver2, GCODE):
                    # якщо в ручному режимі виконуємо наші комбінації клавіш - інакше
                    # надсилати події до віджета G-коду
                    if STATUS.is_man_mode() == False:
                        if is_pressed:
                            receiver.keyPressEvent(event)
                            event.accept()
                        return True
                elif is_pressed:
                    receiver.keyPressEvent(event)
                    event.accept()
                    return True
                else:
                    event.accept()
                    return True

        if event.isAutoRepeat():return True

        # добре, якщо ми сюди дійшли, то спробуйте комбінації клавіш
        try:
            return KEYBIND.call(self,event,is_pressed,shift,cntrl)
        except NameError as e:
            LOG.debug('Exception in KEYBINDING: {}'.format (e))
        except Exception as e:
            LOG.debug('Exception in KEYBINDING:', exc_info=e)
            print('Error in, or no function for: %s in handler file for-%s'%(KEYBIND.convert(event),key))
            return False

    ########################
    # ЗВОРОТНІ ВИКЛИКИ ЗІ СТАТУСУ #
    ########################

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

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

    # перемикання між клавішами з викликами прив'язки клавіш
    # подвоїти швидкість, якщо швидкість є правдою
    def kb_jog(self, state, joint, direction, fast = False, linear = True):
        if not STATUS.is_man_mode() or not STATUS.machine_is_on():
            return
        if linear:
            distance = STATUS.get_jog_increment()
            rate = STATUS.get_jograte()/60
        else:
            distance = STATUS.get_jog_increment_angular()
            rate = STATUS.get_jograte_angular()/60
        if state:
            if fast:
                rate = rate * 2
            ACTION.JOG(joint, direction, rate, distance)
        else:
            ACTION.JOG(joint, 0, 0, 0)

    #####################
    # ПРИВ’ЯЗКА КЛАВІШ ВИКЛИКІВ #
    #####################

    # Управління машиною
    def on_keycall_ESTOP(self,event,state,shift,cntrl):
        if state:
            ACTION.SET_ESTOP_STATE(STATUS.estop_is_clear())
    def on_keycall_POWER(self,event,state,shift,cntrl):
        if state:
            ACTION.SET_MACHINE_STATE(not STATUS.machine_is_on())
    def on_keycall_HOME(self,event,state,shift,cntrl):
        if state:
            if STATUS.is_all_homed():
                ACTION.SET_MACHINE_UNHOMED(-1)
            else:
                ACTION.SET_MACHINE_HOMING(-1)
    def on_keycall_ABORT(self,event,state,shift,cntrl):
        if state:
            if STATUS.stat.interp_state == linuxcnc.INTERP_IDLE:
                self.w.close()
            else:
                self.cmnd.abort()

    # Лінійний біг підтюпцем
    def on_keycall_XPOS(self,event,state,shift,cntrl):
        self.kb_jog(state, 0, 1, shift)

    def on_keycall_XNEG(self,event,state,shift,cntrl):
        self.kb_jog(state, 0, -1, shift)

    def on_keycall_YPOS(self,event,state,shift,cntrl):
        self.kb_jog(state, 1, 1, shift)

    def on_keycall_YNEG(self,event,state,shift,cntrl):
        self.kb_jog(state, 1, -1, shift)

    def on_keycall_ZPOS(self,event,state,shift,cntrl):
        self.kb_jog(state, 2, 1, shift)

    def on_keycall_ZNEG(self,event,state,shift,cntrl):
        self.kb_jog(state, 2, -1, shift)

    def on_keycall_APOS(self,event,state,shift,cntrl):
        pass
        #self.kb_jog(state, 3, 1, shift, False)

    def on_keycall_ANEG(self,event,state,shift,cntrl):
        pass
        #self.kb_jog(state, 3, -1, shift, linear=False)

    ###########################
    # **** заключний захід **** #
    ###########################

    ##############################
    # код необхідного класу котла #
    ##############################

    def __getitem__(self, item):
        return getattr(self, item)
    def __setitem__(self, item, value):
        return setattr(self, item, value)

################################
# необхідний код котла-обробника #
################################

def get_handlers(halcomp,widgets,paths):
     return [HandlerClass(halcomp,widgets,paths)]

5.2. Розділ ІМПОРТ

Цей розділ призначений для імпорту необхідних модулів бібліотеки для вашого екрана.

Типовим було б імпортувати бібліотеки QtVCP keybinding, Status та Action.

5.3. Розділ «СТВОРЕННЯ ЕКЗЕМПЛЯРІВ БІБЛІОТЕК»

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

Ви можете помітити це за командами, які не мають перед собою self.

За домовленістю ми пишемо з великої літери назви бібліотек, на які посилаються в усьому світі.

5.4. Розділ класу обробника

Користувацький код розміщено в класі, щоб QtVCP міг його використовувати.

Це визначення класу обробника.

5.5. Розділ INICIALIZE

Як і всі бібліотеки Python, функція +__init__+ викликається під час першого створення екземпляра бібліотеки.

Тут ви налаштуєте defaults, а також reference variables та global variables.

Посилання на віджети на даний момент недоступні.

Змінні halcomp, widgets та paths надають доступ до компонента HAL, віджетів та інформації про шляхи QtVCP відповідно.

5.6. Розділ СПЕЦІАЛЬНИХ ФУНКЦІЙ

Існує кілька спеціальних функцій, які QtVCP шукає у файлі обробника. Якщо QtVCP їх знаходить, він їх викликає, якщо ні, то тихо проігнорує.

class_patch__(self):

Класове патчування, також відоме як мавпяче патчування, дозволяє перекривати виклики функцій в імпортованому модулі.
Класове патчування повинно бути виконано до інстанціювання модуля, і воно модифікує всі інстанції, створені після цього.
Прикладом може бути патчування викликів кнопок з редактора G-коду для виклику функцій у файлі обробника.
Функція класового патчування, переозначена тут, буде викликатися з екземпляром HandlerClass як «self», а не з екземпляром патчованого класу. Це може ускладнити доступ до функцій/змінних патчованого класу.
При класовому патчуванні поза класом HandlerClass виклик функції буде використовувати екземпляр патчованого класу як «self».

initialized__(self):

Ця функція викликається після того, як віджети та виводи HAL будуть побудовані.
Ви можете маніпулювати віджетами та виводами HAL або додавати більше виводів HAL тут.
Зазвичай може бути

  • перевірені та встановлені налаштування,

  • стилі, застосовані до віджетів,

  • Стан підключеного до функцій LinuxCNC.

  • будуть додані комбінації клавіш.

pre_hal_init__(self):

Ця функція викликається перед викликом функції hal_init_ для HAL-іфікованих віджетів.
Деякі зміни властивостей необхідно внести перед викликом HAL_init для віджета.

after_override__(self):

Ця функція викликається після завантаження додаткового файлу перевизначення,
але до завантаження додаткового файлу HAL або встановлення компонента HAL у стан готовності.

processed_key_event__(self,receiver,event,is_pressed,key,code,shift,cntrl):

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

keypress_event__(self,receiver, event):

Ця функція повертає необроблені події натискання клавіш.
Вона має пріоритет над_ обробленою_подією_клавіші.

keyrelease_event__(receiver, event):

Ця функція видає необроблені події звільнення ключів.
Вона має пріоритет над_ обробленою_подією_ключів.

before_loop__(self):

Ця функція викликається безпосередньо перед входом у цикл подій Qt. У цей момент усі віджети/бібліотеки/код ініціалізації завершилися, і екран вже відображається.

system_shutdown_request__(self):

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

  • + Система Linux не вимкнеться, якщо використовується ця функція, ви повинні зробити це самостійно.
    QtVCP/LinuxCNC завершить роботу без попередження, як тільки ця функція повернеться.

closing_cleanup__(self):

Ця функція викликається безпосередньо перед закриттям екрана. Її можна використовувати для очищення перед закриттям.

5.7. Розділ зворотних викликів стану

За домовленістю, саме тут слід розміщувати функції, які є зворотними викликами з визначень STATUS.

5.8. ЗВОРОТНІ ВИКЛИКИ З РОЗДІЛУ ФОРМИ

За домовленістю, саме тут слід розміщувати функції, які є зворотними викликами віджетів, підключених до MainWindow у редакторі Qt Designer.

5.9. Розділ ЗАГАЛЬНИХ ФУНКЦІЙ

За домовленістю, саме тут ви розміщуєте свої загальні функції.

5.10. Розділ ПРИВ’ЯЗКИ КЛАВІШ

Якщо ви використовуєте бібліотеку keybinding, саме тут ви розміщуєте свої користувацькі процедури виклику клавіш.

Сигнатура функції:

def on_keycall_KEY(self,event,state,shift,cntrl):
    if state:
        self.do_something_function()

KEY код (з бібліотеки клавіатурних скорочень) для потрібної клавіші.

5.11. Розділ ЗАКРИТТЯ ПОДІЇ

Розміщення тут функції closeEvent перехоплюватиме події закриття.

Це замінює будь-яку попередньо визначену функцію closeEvent з QtVCP.

def closeEvent(self, event):
    self.do_something()
    event.accept()
Note
Зазвичай краще використовувати спеціальну функцію closing_cleanup__.

6. Підключення віджетів до коду Python

Можна підключати віджети до коду Python за допомогою сигналів та слотів.

Таким чином ви можете:

  • Надайте нові функції віджетам LinuxCNC, або

  • Використовуйте стандартні віджети Qt для керування LinuxCNC.

6.1. Огляд

У редакторі Qt Designer:

  • Ви створюєте слоти функцій користувача

  • Ви підключаєте слоти до віджетів за допомогою сигналів.

У файлі обробника:

  • Ви створюєте функції слота, визначені в Qt Designer.

6.2. Використання Qt Designer для додавання слотів

Коли ви завантажите екран у Qt Designer, додайте на екран просту кнопку PushButton.
Ви можете змінити назву кнопки на щось цікаве, наприклад, test_button.

Існує два способи редагування з’єднань - це графічний спосіб.

  • У верхній панелі інструментів Qt Designer є кнопка для редагування сигналів. Після натискання на неї, якщо натиснути і утримувати кнопку, з’явиться стрілка (схожа на сигнал заземлення з електричної схеми).

  • Пересуньте цю стрілку до частини головного вікна, на якій немає віджетів.

  • З’явиться діалогове вікно «Налаштування з’єднань».

    • Список ліворуч містить доступні сигнали з віджета.

    • Список праворуч містить доступні слоти в головному вікні, і ви можете до них додавати нові.

  • Виберіть сигнал clicked() — це зробить сторону слотів доступною.

  • Натисніть «Редагувати» у списку слотів.

  • З’явиться діалогове вікно «Слоти/сигнали головного вікна».

  • У списку слотів зверху є значок «+» – натисніть на нього.

  • Тепер ви можете редагувати нову назву слота.

  • Видаліть назву за замовчуванням slot() та змініть її на test_button().

  • Натисніть кнопку «ОК».

  • Ви повернетеся до діалогового вікна «Налаштування підключень».

  • Тепер ви можете вибрати новий слот у списку слотів.

  • Потім натисніть «ОК» та збережіть файл.

Вибір сигналу/слота в Qt Designer
Figure 14. Вибір сигналу/слота в Qt Designer

6.3. Зміни обробника Python

Тепер вам потрібно додати функцію до файлу обробника.

Сигнатура функції — def slot_name(self):.

Для нашого прикладу ми додамо код для виведення назви віджета:

def test_button(self):
    name = self.w.sender().text()
    print(name)

Додайте цей код під розділом з назвою:

#######################
# зворотні виклики з форми №
#######################

Насправді не має значення, де в класі обробника ви розміщуєте команди, але за домовленістю це саме те місце, де їх слід розміщувати.

Збережіть файл обробника.

Тепер, коли ви завантажуєте екран і натискаєте кнопку, у терміналі має вивести назву кнопки.

7. Більше інформації