Документация Engee

Маски в Engee

Маска — это настраиваемый пользовательский интерфейс для создания собственных блоков. Блоки могут быть созданы на основе любых блоков библиотеки Engee и подсистем.

Маска упрощает использование и переиспользования блоков. У маскированного блока есть собственное окно настроек, в котором можно менять параметры блока. Маски позволяют передать параметры не только в блок или подсистему, но и в исходный код блоков Engee Function и C Function.

Маска позволяет:

  • Создавать собственное диалоговое окно параметров для быстрой настройки блока/подсистемы;

  • Изменять внешний вид блока/подсистемы;

  • Скрывать содержимое блока/подсистемы.

Например, есть модель генератора сигналов из блоков Switch, Sine Wave, Pulse Generator, Constant и Outport:

mask subsystem example 2

С помощью маски из подсистемы создан пользовательский блок, позволяющий переключать тип сигнала через управляющий параметр. Маска передает значение в параметр Constant Value блока Constant, что изменяет тип принимаемого сигнала в блоке Switch. В результате модель переключается между сигналами Sine Wave и Pulse Generator в зависимости от выбранного значения маски (Прямоугольный импульсный сигнал или Синусоидальный). Для удобства на иконке подсистемы отображается тип выбранного сигнала.

mask subsystem 1

Маска легко настраивается так, чтобы при изменении значений ее параметров автоматически менялись значения параметров блока. Однако, обратное влияние невозможно: значения параметров блока не могут менять значения параметров маски. Это ограничение обеспечивает контроль над настройками блока через маску и предотвращает случайные изменения в самой маске.

Редактор масок — это инструмент для настройки маски. Для открытия редактора нажмите на блок правой кнопкой мыши и выберите МаскаДобавить маску:

masks 1

Редактор открывается в новом окне браузера:

mask editor 1

После применения маски в настройках блока показываются параметры маски. Чтобы увидеть параметры самого блока, нажмите Посмотреть под маску. Чтобы вернуться, нажмите Посмотреть маску:

masks switching 1

Для подсистем опция «Посмотреть под маску» переносит внутрь подсистемы, а не показывает параметры блока. Используйте панель навигации для выхода из подсистемы.

Для редактирования или удаления маски нажмите правой кнопкой мыши по иконке блока с уже созданной маской и выберите Редактировать маску или Удалить маску:

masks main 1

Интерфейс редактора масок

Редактор масок содержит два набора вкладок:

  • Редактор интерфейса

  • Редактор кода

Редактор интерфейса

Редактор интерфейса — раздел добавления в окно настроек маски элементов управления, структурных элементов и вкладок.

mask editor 1 2

  1. Структурные элементы — контейнеры для размещения и скрытия элементов управления:

    1. 1.1. Скрываемая секция hidden section masks — контейнер, который можно скрыть или раскрыть по нажатию, позволяя управлять отображением нескольких элементов одновременно.

      Скрываемая секция без добавленных элементов управления не будет показана в маске. Секцию по умолчанию можно переименовать.
  2. Элементы управления — основные компоненты интерфейса из которых формируется маска блока. Каждый элемент управления имеет три параметра:

    • Название параметра — должно совпадать с именем переменной в маске, которую нужно настроить;

    • Название поля — будет отображаться в окне настроек;

    • Значение или элемент списка — определяет значение по умолчанию.

    Помимо параметров можно настроить атрибуты:

    • Скрыт — скрывает элемент управления;

    • Вычислять — анализирует введенное значение, интерпретирует его и сохраняет соответствующий тип данных (например, число, строку или массив). При вводе несоответствующего типа данных система выдаст ошибку.

  1. 2.1. Поле для ввода text input masks — добавляет параметр маски с вводимым текстовым или числовым значением. Тип данных: Any;

  2. 2.2. Чекбокс checkbox masks — область для проставления флажка. Тип данных: Bool;

  3. 2.3. Выпадающий список dropdown masks — отображает список доступных опций, из которых выбирается одна. Чтобы добавить элементы в выпадающий список, вводите их по одному в поле Элементы списка и нажимайте Enter после каждого значения. Тип данных: String;

    Выпадающий список без параметров выдает ошибку и не позволяет сохранить маску.
  1. Пространство маски — область для создания маски с помощью элементов управления и структурных элементов. Все добавленные в область элементы будут в точности перенесены в маску после сохранения. Для добавления элементов перетащите их с помощью мыши:

    masks drag and drop

    Для удаления элемента выделите его и нажмите клавишу Delete.

    Для добавления новой вкладки нажмите + в правом углу пространства маски:

    new tab mask

  2. Кнопка сохранения — сохраняет маску. Альтернативно можно использовать горячие клавиши Ctrl+S (Win/Linux) и +S (macOS).

  3. Кнопка редактора обратных вызовов — открывает раздел Редактор кода.

  4. Скрыть левую панель/показать левую панель.

  5. Скрыть правую панель/показать правую панель.

Редактор кода

Редактор кода — раздел настроек элементов управления с помощью функций обратного вызова (callbacks). Для каждого элемента управления задаются собственные уникальные обратные вызовы.

mask editor 1 3

  1. Названия параметров маски (можно изменить в редакторе интерфейса).

  2. Название поля — указывается метка или тип поля параметра (можно изменить в редакторе интерфейса). Оно дает более подробное описание назначения или функции параметра.

  3. Пространство кода обратных вызовов — область для настройки обратных вызовов у конкретного параметра маски.

  4. Кнопка сохранения — сохраняет маску. Альтернативно можно использовать горячие клавиши Ctrl+S (Win/Linux) и +S (macOS).

  5. Скрыть левую панель/показать левую панель.

  6. Скрыть правую панель/показать правую панель.

Обратные вызовы

Обратные вызовы — функции, которые вызываются автоматически в ответ на определенные действия. Обратные вызовы в Engee пишутся на языке Julia, не исполняются во время симуляции и используются для управления как отдельными параметрами, так и всей маской в целом. Для управления отдельными параметрами используются локальные обратные вызовы, а для всей маски в целом — глобальные:

  • Глобальные обратные вызовы (Global) — связаны со всей маской и выполняются независимо от конкретных параметров. Глобальными обратными вызовами являются iconDrawCallback и blockChangedCallback. Глобальные вызовы имеют доступ ко всем локальным вызовам и могут управлять ими. Например, можно передать данные из локального вызова в глобальный blockChangedCallback. Это позволит использовать глобальный вызов для настройки действий локального.

  • Локальные обратные вызовы — привязаны к конкретным параметрам маски и срабатывают только при изменении этих параметров. Все обратные вызовы, кроме iconDrawCallback и blockChangedCallback, являются локальными. Локальные обратные вызовы могут работать только с параметрами, к которым они привязаны в редакторе интерфейса, и не могут напрямую изменять параметры других обратных вызовов (Чекбокс не может управлять выпадающим списком, для этого используются глобальные обратные вызовы).

Чтобы работать с параметром маски, его сначала нужно получить. Для этого используется специальный объект параметра, который хранится в переменной mask. Например, чтобы получить параметр текстового поля с именем text_input_1, можно использовать следующий код:

mask.parameters.text_input_1

У объекта параметра есть три основных свойства:

  • name::String — имя поля (доступно только для чтения);

  • value::Any — значение параметра (доступно для чтения и записи);

  • hidden::Bool — видимость параметра (доступно для чтения и записи).

Чтобы изменить параметр, всегда присваивайте новое значение атрибуту value. Нельзя изменять существующее значение напрямую, так как это не будет считаться изменением. Например:

  • Корректно:

    mask.parameters.text_input_1.value = [1, 2, 3]
  • Некорректно:

    append!(mask.parameters.text_input_1.value, 4)

Для получения пути до блока с маской используется функция 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().

Обзор обратных вызовов

Порядок запуска обратных вызовов маски:

  1. Для каждого измененного параметра вызывается validateCallback. Происходит первым, чтобы гарантировать, что параметры имеют допустимые значения до выполнения любых дальнейших операций;

  2. Для каждого измененного параметра вызывается valueChangedCallback. После каждого обратного вызова вызывается validateCallback для каждого вновь измененного параметра, так как изменение значения может повлиять на другие параметры;

  3. Запускается blockChangedCallback. Для каждого вновь измененного параметра вызывается validateCallback.

  4. Запускается iconDrawCallback.

Знание порядка запуска обратных вызовов важно для правильной работы маски блока, так как изменение одного параметра может повлиять на другие. Неправильный порядок может привести к ошибкам в проверке значений или неправильному обновлению иконки. Соблюдение правильного порядка гарантирует, что все зависимости учтены и блок работает корректно.


iconDrawCallback — формирование внешнего вида блока

Details

Для работы с iconDrawCallback обязательно используется функция engee.show(). Иконка с неправильным iconDrawCallback выглядит так:

display callback 5

iconDrawCallback может отображать текст, число, график, картинку или формулу (LaTeX). Например:

  • Вывод числа (аналогично для текста):

    engee.show(text_input_1)

    display callback 1display callback 2

  • Вывод 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>
        """
    )

    custom block 1

    Рекомендуется использовать SVG-иконки для блоков, так как они занимают меньше места и автоматически подстраиваются под размер блока без потерь качества.
  • Вывод LaTeX-формулы. Для этого используйте заглавную букву и кавычки "". Формула записывается в кавычки по классическому синтаксису LaTeX:

    engee.show(L"\lvert u \rvert")

    custom block 2

  • Вывод графика:

    x = range(0, 2*pi, 1000);
    y = sin.(x);
    engee.show(plot(x, y))

    display callback 3

  • Вывод картинки:

    using Base64
    
    img = "...base64-text..."
    engee.show(Images.load(IOBuffer(base64decode(img))))

Получить base64 представления изображения можно разными способами:

  • Через Julia-скрипт:

    using Base64
    image_data = read("path_to_file")
    base64_encoded = base64encode(image_data)
    println(base64_encoded)
  • Через командную строку img 41 1 2 (предварительное перейдите в режим оболочки 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' для выходного порта

sum mask 1

Подписи можно задавать и для ненаправленных портов. Можно задавать пустое имя:

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(). Обратный вызов запускается при изменении значения связанного параметра. Параметры связаны в том случае, если название параметра соответствующего элемента управления совпадает с кодом обратного вызова, например:

valuechangedfcn callback 1valuechangedfcn callback 2

Примеры:

  • Скрытие параметров:

    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

Обратный вызов проверяет корректность значения valueChangedCallback и, в случае некорректного значения, показывает ошибку. Все сообщения об ошибках кроме AssertionError будут считаться ошибкой самого обратного вызова, а не ошибкой введенных данных:

validatorcallback 1 1

Корректность значения параметра проверяется с помощью переменной value. validateCallback всегда начинается с макроса @assert.

validateCallback доступен только для элемента управления Поле для ввода text input masks и привязан к его обратному вызову valueChangedCallback.

Примеры:

  • Проверка корректности значения:

    @assert value > 0 "Значение должно быть больше нуля"
  • Проверка введенного типа:

    @assert value isa Number "Значение должно быть числом"

Примеры

Пример передачи параметра маски в исходный код C Function
  1. Поставьте блок C Function в рабочее пространство Engee. Нажмите по блоку правой кнопкой мыши, выберите Маска/Добавить маску.

  2. В редакторе интерфейса масок добавьте Поле для ввода text input masks.

  3. Перейдите в редактор кода масок и в левом меню параметров выберите параметр поля для ввода (по умолчанию это text_input_1) и перенесите его на пространство маски. Сделайте так два раза, чтобы для каждого параметра блока C Function было свое поле для ввода:

    blockchanged c function 4

  4. В обратном вызове 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)

    blockchanged c function

    В данном коде устанавливается путь к текущему блоку C Function с помощью функции gcb(), после чего считываются значения из text_input_1 и text_input_2, которые соответствуют параметрам блока param1 и param2. Затем создается строка кода на языке Cи, которая определяет функцию add_numbers, складывающую два целых числа, и использует введенные значения для вычисления результата. С помощью engee.set_param! обновляется параметр "OutputCode" блока C Function, устанавливая сформированный код.

  5. Теперь при редактировании параметров маски меняются и параметры блока C Function, а их измененные значения попадают в исходный код во вкладку OutputCode:

blockchanged c function 1

blockchanged c function 3

Аналогичный подход реализуется и для других вкладок исходного кода блока C Function - StartCode и TerminalCode, а также для вкладок блока Engee Function - ExeCode и InhMethodsCode.

Можно использовать не только числовые значения, например:

engee.set_param!(gcb(), "OutputCode"=>"print($text_input_1)")

Для этого в редакторе интерфейса маски при создании или редактировании параметра необходимо указать значение, тип данных которого впоследствии будет изменяться. Если тип данных не совпадет, то система отобразит ошибку:

error mask 1

Важно убедиться, что установлен флажок «Вычислять», так как это позволит маске сохранить тип данных параметра после сохранения:

mask param save

Пример настраиваемой подсистемы

Маски могут быть наложены поверх блоков Subsystem. Рассмотрим случай, в котором требуется управлять параметрами блоков подсистемы. Например, параметрами блока Sine Wave:

  • По умолчанию подсистема не имеет никаких параметров кроме Treat as atomic unit:

    without mask 1

    Зайдите в подсистему и добавьте в нее блок Sine Wave.

  • Нажмите правой кнопкой мыши по иконке подсистемы, выберите Маска → Добавить маску.

  • В редакторе интерфейса масок добавьте Выпадающий список dropdown masks. В список добавьте параметры Sample based и Time based:

    sine wave mask example 2

  • В редакторе кода масок перейдите во вкладку 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

sine wave mask example 1

sine wave mask example 3 1

sine wave mask example 3

sine wave mask example 1 1