Маски в Engee
Маска — это настраиваемый пользовательский интерфейс для создания собственных блоков. Блоки могут быть созданы на основе любых блоков библиотеки Engee и подсистем.
Маска упрощает использование и переиспользования блоков. У маскированного блока (с наложенной маской) есть собственное окно настроек, в котором можно менять параметры блока. Маски позволяют передать параметры не только в блок или подсистему, но и в исходный код блоков Engee Function и C Function.
Маска позволяет:
-
Создавать собственное диалоговое окно параметров для быстрой настройки блока/подсистемы;
-
Изменять внешний вид блока/подсистемы;
-
Скрывать содержимое блока/подсистемы.
Редактор масок — это инструмент для настройки маски. Для открытия редактора нажмите на блок правой кнопкой мыши и выберите Маска → Добавить маску:
Редактор открывается в новом окне браузера:
После сохранения маски в настройках блока показываются параметры маски. Чтобы увидеть параметры самого блока, нажмите Посмотреть под маску. Чтобы вернуться, нажмите Посмотреть маску:
Для подсистем опция Посмотреть под маску переносит внутрь подсистемы, а не показывает параметры блока. Используйте Панель навигации по моделям для выхода из подсистемы. |
Для редактирования или удаления маски нажмите правой кнопкой мыши по иконке блока с уже созданной маской и выберите Редактировать маску или Удалить маску:
Маска легко настраивается так, чтобы при изменении значений ее параметров автоматически менялись значения параметров блока. Однако, обратное влияние невозможно: значения параметров блока не могут менять значения параметров маски. Это ограничение обеспечивает контроль над настройками блока через маску и предотвращает случайные изменения в самой маске. |
Интерфейс редактора масок
Редактор масок содержит два набора вкладок:
-
Редактор интерфейса
-
Редактор кода
Редактор интерфейса
Редактор интерфейса — раздел добавления в окно настроек маски элементов управления, структурных элементов и вкладок.
-
Действия — элементы управления, с помощью которых выполняются действия по заданным условиям.
-
1.1 Кнопка
— элемент управления, выполняющий заданные действия по нажатию.
-
-
Структурные элементы — контейнеры для размещения и скрытия элементов управления:
-
2.1. Секция для скрытия
— контейнер, который можно скрыть или раскрыть по нажатию, позволяя управлять отображением нескольких элементов одновременно.
Секция для скрытия без добавленных элементов управления не будет показана в маске. Секцию по умолчанию можно переименовать.
-
-
Элементы управления — основные компоненты интерфейса из которых формируется маска блока. Каждый элемент управления имеет три параметра:
-
Название параметра — должно совпадать с именем переменной в маске, которую нужно настроить;
-
Название поля — будет отображаться в окне настроек;
-
Значение или элемент списка — определяет значение по умолчанию.
Помимо параметров можно настроить атрибуты:
-
Скрыт — скрывает элемент управления;
-
Вычислять — анализирует введенное значение, интерпретирует его и сохраняет соответствующий тип данных (например, число, строку или массив). При вводе несоответствующего типа данных система выдаст ошибку.
-
-
3.1. Поле для ввода
— добавляет параметр маски с вводимым текстовым или числовым значением. Тип данных: Any;
-
3.2. Чекбокс
— область для проставления флажка. Тип данных: Bool;
-
3.3. Выпадающий список
— отображает список доступных опций, из которых выбирается одна. Чтобы добавить элементы в выпадающий список, вводите их по одному в поле Элементы списка и нажимайте Enter после каждого значения. Тип данных: String;
Выпадающий список без параметров выдает ошибку и не позволяет сохранить маску.
-
Пространство маски — область для создания маски с помощью элементов управления и структурных элементов. Все добавленные в область элементы будут в точности перенесены в маску после сохранения. Для добавления элементов перетащите их с помощью мыши:
Для удаления элемента выделите его и нажмите клавишу Delete.
Для добавления новой вкладки нажмите + в правом углу пространства маски:
-
Кнопка сохранения — сохраняет маску. Альтернативно можно использовать горячие клавиши Ctrl+S (Win/Linux) и ⌘+S (macOS).
-
Кнопка редактора обратных вызовов — открывает раздел Редактор кода.
-
Скрыть левую панель/показать левую панель.
-
Скрыть правую панель/показать правую панель.
Редактор кода
Редактор кода — раздел настроек элементов управления с помощью функций обратного вызова (callbacks). Для каждого элемента управления задаются собственные уникальные обратные вызовы.
-
Названия параметров маски (можно изменить в редакторе интерфейса).
-
Название поля — указывается метка или тип поля параметра (можно изменить в редакторе интерфейса). Оно дает более подробное описание назначения или функции параметра.
-
Пространство кода обратных вызовов — область для настройки обратных вызовов у конкретного параметра маски.
-
Кнопка сохранения — сохраняет маску. Альтернативно можно использовать горячие клавиши Ctrl+S (Win/Linux) и ⌘+S (macOS).
-
Скрыть левую панель/показать левую панель.
-
Скрыть правую панель/показать правую панель.
Обратные вызовы
Обратные вызовы — функции, которые вызываются автоматически в ответ на определенные действия. Обратные вызовы в Engee пишутся на языке Julia, не исполняются во время симуляции и используются для управления как отдельными параметрами, так и всей маской в целом. Для управления отдельными параметрами используются локальные обратные вызовы, а для всей маски в целом — глобальные:
-
Глобальные обратные вызовы (Global) — связаны со всей маской и выполняются независимо от конкретных параметров. Глобальными обратными вызовами являются iconDrawCallback и blockChangedCallback. Глобальные вызовы имеют доступ ко всем локальным вызовам и могут управлять ими. Например, можно передать данные из локального вызова в глобальный blockChangedCallback. Это позволит использовать глобальный вызов для настройки действий локального.
-
Локальные обратные вызовы — привязаны к конкретным параметрам маски и срабатывают только при изменении этих параметров. Все обратные вызовы, кроме iconDrawCallback и blockChangedCallback, являются локальными. Локальные обратные вызовы могут работать только с параметрами, к которым они привязаны в редакторе интерфейса, и не могут напрямую изменять параметры других обратных вызовов (Чекбокс не может управлять выпадающим списком, для этого используются глобальные обратные вызовы).
Чтобы работать с параметром маски, его сначала нужно получить. Для этого используется специальный объект параметра, который хранится в переменной mask
. Например, чтобы получить параметр Поле для ввода с именем
text_input_1
, можно использовать следующий код:
mask.parameters.text_input_1
У объекта параметра есть три основных свойства:
-
name::String
— имя поля (доступно только для чтения); -
value::Any
— значение параметра (доступно для чтения и записи); -
hidden::Bool
— видимость параметра (доступно для чтения и записи).
Чтобы изменить параметр, всегда присваивайте новое значение атрибуту
|
Для получения пути до блока с маской используется функция gcb()
. Путь возвращается в виде строки. Это позволяет управлять блоком и его внутренними компонентами с помощью кода. Допустим, нужно передать значение из параметра Выпадающий список (
dropdown_1
) во внутренний блок LDL Factorization:
LDLPath = gcb() * "/LDL Factorization"
engee.set_param!(LDLPath, "NonPositive" => mask.parameters.dropdown_1.value)
Здесь gcb()
получает путь к текущему блоку. Путь используется, чтобы найти блок LDL Factorization, после значение из dropdown_1
передается в параметр NonPositive
блока. Таким образом, функция gcb()
помогает связать параметры маски с другими частями модели.
В обратных вызовах внутри программного управления нельзя использовать методы engee.gcb() и engee.gcm() .
|
Обзор обратных вызовов
Порядок запуска обратных вызовов маски:
-
Для каждого измененного параметра вызывается validateCallback. Происходит первым, чтобы гарантировать, что параметры имеют допустимые значения до выполнения любых дальнейших операций;
-
Для каждого измененного параметра вызывается valueChangedCallback. После каждого обратного вызова вызывается validateCallback для каждого вновь измененного параметра, так как изменение значения может повлиять на другие параметры;
-
Запускается blockChangedCallback. Для каждого вновь измененного параметра вызывается validateCallback.
-
Запускается iconDrawCallback.
Знание порядка запуска обратных вызовов важно для правильной работы маски блока, так как изменение одного параметра может повлиять на другие. Неправильный порядок может привести к ошибкам в проверке значений или неправильному обновлению иконки. Соблюдение правильного порядка гарантирует, что все зависимости учтены и блок работает корректно.
iconDrawCallback — формирование внешнего вида блока
Details
Для работы с iconDrawCallback обязательно используется функция engee.show()
. Иконка с неправильным iconDrawCallback выглядит так:
iconDrawCallback может отображать текст, число, график, картинку или формулу (LaTeX). Например:
-
Вывод числа (аналогично для текста):
engee.show(text_input_1)
→
-
Вывод SVG:
engee.show( svg""" <svg xmlns:ns0="http://www.w3.org/2000/svg" width="78%" height="80%" viewBox="0 0 26 27" fill="none"> <path vector-effect="non-scaling-stroke" d="M13 0.5L13 25.7282" stroke="#DDDDDD" stroke-linecap="round" /> <path vector-effect="non-scaling-stroke" d="M0.5 13H25.5" stroke="#DDDDDD" stroke-linecap="round" /> <path vector-effect="non-scaling-stroke" d="M24.1894 8.46273L13 8.46273L13 17.4628L2.18942 17.4627" stroke="#212121" stroke-linecap="round" /> </svg> """ )
Рекомендуется использовать SVG-иконки для блоков, так как они занимают меньше места и автоматически подстраиваются под размер блока без потерь качества. -
Вывод LaTeX-формулы. Для этого используйте заглавную букву и кавычки
""
. Формула записывается в кавычки по классическому синтаксису LaTeX:engee.show(L"\lvert u \rvert")
-
Вывод графика:
x = range(0, 2*pi, 1000); y = sin.(x); engee.show(plot(x, y))
-
Вывод картинки:
img = "...base64-text..." engee.show(Images.load(IOBuffer(base64decode(img))))
Получить base64
представления изображения можно разными способами:
-
Через Julia-скрипт:
image_data = read("path_to_file") base64_encoded = base64encode(image_data) println(base64_encoded)
-
Через командную строку
(предварительное перейдите в режим оболочки shell, нажав ;):
base64 --wrap 0 "path_to_file"
-
Через внешние сервисы, например base64decode.org.
Можно изменить подпись порта блока с помощью функции engee.port_label()
через механизм обратного вызова. Пример ниже показывает, как выводить текст на иконку блока и задавать подписи для разных портов:
engee.show("Some text") # Вывод текста на иконку блока
engee.port_label("input", 1, "foo_1") # Подпись 'foo_1' для первого входного порта
engee.port_label("input", 2, "foo_2") # Подпись 'foo_2' для второго входного порта
engee.port_label("output", 1, "bar") # Подпись 'bar' для выходного порта
Подписи можно задавать и для ненаправленных портов. Можно задавать пустое имя:
engee.port_label("acausal", 1, "") # Пустое имя для ненаправленного порта
В подписи портов можно выводить SVG-файлы и формулы LaTeX:
engee.port_label("input", 1, svg="...")
engee.port_label("input", 1, L="...")
Нумерация портов совпадает с программным управлением. Если указать неверный номер или тип порта, функция будет проигнорирована.
blockChangedCallback — выполняется после изменения любого параметра маски
Details
Запускается при изменении любого параметра маски, но после исполнения всех остальных обратных вызовов. Будучи глобальным обратным вызовом, имеет доступ как к переменным, так и к объекту маски.
Примеры:
-
Изменение параметра в подсистеме:
LDLPath = gcb() * "/LDL Factorization" mode = dropdown_1 if mode == "Ignore" engee.set_param!(LDLPath, "NonPositive" => "Ignore") elseif mode == "Warning" engee.set_param!(LDLPath, "NonPositive" => "Warning") elseif mode == "Error" engee.set_param!(LDLPath, "NonPositive" => "Error") end
здесь значение параметра маски синхронизировано с параметром блока LDL Factorization.
mode
— переменная, которая хранит текущее значение параметраdropdown_1
. Например, если в выпадающем списке поменять значение параметра сWarning
наError
, то этот же параметр аналогично поменяется у блока LDL Factorization в подсистеме.
valueChangedCallback — выполняется при изменении значения параметра
Details
valueChangedCallback нужен для скрытия параметров или для изменения состояний подсистемы с помощью функции gcb()
. Обратный вызов запускается при изменении значения связанного параметра. Параметры связаны в том случае, если название параметра соответствующего элемента управления совпадает с кодом обратного вызова, например:
→
Примеры:
-
Скрытие параметров:
mask.parameters.text_input_1.hidden = checkbox_1
здесь параметр скрыт (hidden) когда нажат Чекбокс и виден, когда Чекбокс отжат.
-
Изменение состояния подсистемы (использование функций программного управления):
if checkbox mask.parameters.checkbox.hidden = false engee.add_block("/Basic/Ports & Subsystems/Model", gcb() * "/Model") else mask.parameters.checkbox.hidden = true engee.delete_block(gcb() * "/Model") end
здесь задана связь с Чекбокс (
checkbox_1
), при включении (флажок активен) которого в подсистему добавляется блок Model и удаляется при выключении (флажок убран).
validateCallback — проверяет корректность значения (валидацию) параметра
Details
Обратный вызов проверяет корректность значения validateCallback и, в случае некорректного значения, показывает ошибку. Все сообщения об ошибках кроме AssertionError будут считаться ошибкой самого обратного вызова, а не ошибкой введенных данных:
Корректность значения параметра проверяется с помощью переменной value
. validateCallback всегда начинается с макроса @assert.
validateCallback доступен только для элемента управления Поле для ввода и привязан к его обратному вызову valueChangedCallback.
Примеры:
-
Проверка корректности значения:
@assert value > 0 "Значение должно быть больше нуля"
-
Проверка введенного типа:
@assert value isa Number "Значение должно быть числом"
Примеры
При использовании маскированных блоков (например, блока Плоское продольное движение) важно учитывать особенности взаимодействия параметров маски и переменных рабочей области:
Если используется неизвестная переменная или параметр содержит недопустимое значение (например, необъявленная переменная или неподходящий тип данных), то интерфейс сообщает об ошибке — параметр выделяется красной рамкой и сопровождается пояснением: |
Пример передачи параметра маски в исходный код блока C Function
-
Поставьте блок C Function в рабочее пространство Engee. Нажмите по блоку правой кнопкой мыши, выберите Маска → Добавить маску.
-
В редакторе интерфейса масок добавьте Поле для ввода
.
-
Перейдите в редактор кода масок и в левом меню параметров выберите параметр Поле для ввода (по умолчанию это
text_input_1
) и перенесите его на пространство маски. Сделайте так два раза, чтобы для каждого параметра блока C Function было свое поле для ввода: -
В обратном вызове blockChangedCallback используйте следующий код:
# Установка пути к текущему блоку CFunctionPath = gcb() # Получение значений параметров масок param1 = text_input_1 param2 = text_input_2 # Формирование кода на Cи в зависимости от значений параметров c_code = """ int add_numbers(int param1, int param2) { return param1 + param2; } int result = add_numbers($param1, $param2); """ # Установка параметра "OutputCode" в блоке C Function engee.set_param!(CFunctionPath, "OutputCode" => c_code)
В данном коде устанавливается путь к текущему блоку C Function с помощью функции
gcb()
, после чего считываются значения изtext_input_1
иtext_input_2
, которые соответствуют параметрам блока param1 и param2. Затем создается строка кода на языке Cи, которая определяет функциюadd_numbers
, складывающую два целых числа, и использует введенные значения для вычисления результата. С помощьюengee.set_param!
обновляется параметр "OutputCode" блока C Function, устанавливая сформированный код. -
Теперь при редактировании параметров маски меняются и параметры блока C Function, а их измененные значения попадают в исходный код во вкладку OutputCode:
Аналогичный подход реализуется и для других вкладок исходного кода блока C Function - StartCode и TerminalCode , а также для вкладок блока Engee Function — ExeCode и InhMethodsCode .
|
Можно использовать не только числовые значения, например:
engee.set_param!(gcb(), "OutputCode"=>"print($text_input_1)")
Для этого в редакторе интерфейса маски при создании или редактировании параметра необходимо указать значение, тип данных которого впоследствии будет изменяться. Если тип данных не совпадет, то система отобразит ошибку:
Важно убедиться, что установлен флажок «Вычислять», так как это позволит маске сохранить тип данных параметра после сохранения:
Пример передачи параметра маски в исходный код блока Engee Function
-
Соберите модель из блоков Генератор синусоиды, Engee Function и Заглушка и включите запись
сигнала как показано на рисунке:
-
В исходном коде блока Engee Function добавьте следующий код:
struct Block <: AbstractCausalComponent end function (c::Block)(t::Real, x) return gain .* x end
-
Откройте редактор масок для блока Engee Function, для этого нажмите ПКМ по блоку, выберите Маска → Добавить маску. В редакторе масок добавьте Поле для ввода
, назовите параметр Gain и задайте ему значение, например 3:
-
В параметрах блока Engee Function задайте значение параметру gain как показано на рисунке:
Этот подход позволяет использовать значение параметра маски из настроек блока, добавляя его в исходный код и применяя имя параметра для получения указанного значения:
-
Проверим такой подход с помощью графика, запустив симуляцию модели:
-
Значение параметра gain действительно равняется 3, а значит маска прекрасно работает с исходным кодом блока Engee Function.
Пример настраиваемой подсистемы (блок Subsystem)
Маски могут быть наложены поверх блоков Подсистема. Рассмотрим случай, в котором требуется управлять параметрами блоков подсистемы. Например, параметрами блока Генератор синусоиды:
-
По умолчанию подсистема не имеет никаких параметров кроме Treat as atomic unit:
Зайдите в подсистему и добавьте в нее блок Sine Wave.
-
Нажмите правой кнопкой мыши по иконке подсистемы, выберите Маска → Добавить маску.
-
В редакторе интерфейса масок добавьте Выпадающий список
. В список добавьте параметры Sample based и Time based:
-
В редакторе кода масок перейдите во вкладку Global и в обратном вызове iconDrawCallback добавьте следующий код:
engee.show(dropdown_1)
Этот код будет отображать на иконке подсистемы текущее значение параметра dropdown_1 (параметр выпадающего списка).
-
В обратном вызове blockChangedCallback используйте следующий код:
SinePath = gcb() * "/Sine Wave" mode = dropdown_1 if mode == "Time based" engee.set_param!(SinePath, "SineType" => "Time based") elseif mode == "Sample based" engee.set_param!(SinePath, "SineType" => "Sample based") end
Этот код изменяет параметр
"SineType"
блока Sine Wave в подсистеме в зависимости от значения параметра маски dropdown_1: если выбрано"Time based"
, устанавливается"SineType"
⇒"Time based"
, если"Sample based"
, то"SineType"
⇒"Sample based"
.
Изменяя значение параметра Sine type в маскированной подсистеме, этот параметр автоматически изменяется и в блоке Sine Wave.
Выбран Time based |
Выбран Sample based |
Пример передачи параметра маски в блок Chart
Параметры маски можно передать в блок Chart. Для примера создайте модель из блоков Константа, Подсистема и В CSV-файл как показано на рисунке:
Добавьте блок Chart в подсистему. Чтобы подключить его к входу (Вход1) и выходу (Выход1) подсистемы, откройте блок Chart и создайте входной) и (выходной порты через окно настроек . Также добавьте две локальные переменные:
-
local_input со значением
input
; -
local_c со значением
c
.
Эти переменные будут получать значения из маски подсистемы.
После создайте состояние , задайте ему имя и напишите выражение, используя локальные переменные, входы и выходы. Убедитесь, что имена переменных в состоянии совпадают с теми, что указаны в настройках блока Chart. В результате получится модель конечного автомата с настроенными входами, выходами и переменными:
Теперь модель внутри подсистемы будет выглядеть так:
Используя маски, создайте следующее Поле для ввода для блока Chart:
Перейдите на верхний уровень модели с помощью панели навигации и для блока Subsystem создайте маску со следующим Поле для ввода :
Запустите модель . По завершении симуляции в файловом браузере
будет создан CSV файл, в котором будут указаны результаты симуляции по времени:
time,1
0.0,22.0
0.01,22.0
0.02,22.0
0.03,22.0
0.04,22.0
0.05,22.0
0.06,22.0
0.07,22.0
0.08,22.0
...
9.96,22.0
9.97,22.0
9.98,22.0
9.99,22.0
10.0,22.0
Результат равен 22
, что подтверждает корректность вычисления выражения в состоянии блока Chart: local_c = c = 6
(из маски Subsystem), local_input = input = 15
(из маски блока Chart), их сумма равна 21
, а блок Constant прибавляет еще 1
, получая итоговое значение 22
.