Плагины редактора Пазлов — Софт Эйт Софт
Description: Пользовательские плагины для пазлов Вердж3Д.

# Плагины редактора Пазлов

Редактор пазлов «Вердж3Д» предоставляет возможность загрузки собственных пользовательских пазлов, что позволяет расширить функциональность редактора.

## Содержание

- Установка плагинов
- Обзор файлов плагинов
  - Формат файла init.plug
    - Category
      - Имя панели инструментов
      - Цвет панели инструментов
      - Доступные пазлы
      - Порядок пазлов
      - Текстовые метки
      - Разделители
      - Блоки пазла, доступные на вкладке "init"
      - Значения ввода и полей по умолчанию
      - Подкатегории панели инструментов
    - Скрипт
  - Формат файла .block
    - Шаблон блока
      - Цвет блока
      - Всплывающая подсказка блока
      - Справочная ссылка блока
      - Добавление входа
      - Расположение входов
      - Добавление полей
      - Выравнивание полей
      - Блочные соединения
      - Проверка типов входов и выходов
    - Функция code
      - Генерация базового кода
      - Борьба с разрастанием кода
      - Доступ к PzLib API
      - Доступ к входам и полям
- Ошибки плагинов и отдельных блоков
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "This page contains the following errors:error on line ..."
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "ReferenceError (SyntaxError, TypeError, etc...) ..."
  - BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Child block does not have output or previous statement."
  - BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "Error: Connection checks failed. Input "PARENT_INPUT_NAME" connection on "PLUGIN_NAME/BLOCK_NAME" block (id="BLOCK_ID") expected TYPE_PARENT, found TYPE_CHILD"
  - BlockError(PLUGIN_NAME/null): validation error - "TypeError: Unknown block type: PLUGIN_NAME/null"
  - BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Unknown block type: PLUGIN_NAME/BLOCK_NAME"
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function ...
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error calling code() function ...
  - PluginError(PLUGIN_NAME): error parsing init.plug file - "This page contains the following errors:error on line ..."
  - PluginError(PLUGIN_NAME): error parsing init.plug file - "ReferenceError (SyntaxError, TypeError, etc...) ..."
  - PluginError(PLUGIN_NAME): error calling code() function ...
  - Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.
- Как распространять плагины

#

## Установка плагинов

Плагин — это обычная папка с файлами. Чтобы быть распознанной редактором пазлов она должна быть помещена в подпапку puzzles/plugins установочной папки «Вердж3Д», например C:\Program Files\Verge3D_for_Blender\puzzles\plugins. И это всё что нужно сделать! После перезагрузки страницы редактора все установленные плагины должны появиться в нижней части панели инструментов редактора сразу после всех стандартных категорий пазлов.

Категории плагинов на панели инструментов.

Для разработчиков пазлов
Этот полезный плагин содержит примеры некоторых типичных блоков пазлов: ExamplePlugin.zip. Просто распакуйте его в папку puzzles/plugins, а затем ознакомьтесь с новой категорией пазлов "Example Plugin".

#

## Обзор файлов плагинов

Типичный плагин представляет собой каталог с файлом init.plug, содержащим общие настройки с большим количеством файлов .block, каждый из которых определяет один блок пазла. Если ваш текстовый редактор поддерживает подсветку синтаксиса, то режим HTML должен хорошо работать для обоих форматов файлов.

#

### Формат файла init.plug

init.plug — это обязательный файл плагина, который используется для задания общих настроек. В этом файле вы можете определить, как выглядит запись панели инструментов. Также вы можете добавить туда некоторый предварительный код «Ява Скрипт» для ваших пазлов. Вот простой пример того, что вы можете увидеть в файле init.plug:

`<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0"></label> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> </category> <script> function code() { return `console.log('Powered by My Awesome Plugin!');`; } </script>`

#

#### Category

Часть category представляет собой XML-дерево, которое определяет, как плагин и его блоки пазлов будут отображаться в панели инструментов редактора пазлов. Хотя она и необязательна, но если вы не добавите ее в файл init.plug, то плагин вообще не будет загружен.

Существует несколько вариантов, которые можно настроить через category:

#

##### Имя панели инструментов

Указывается через атрибут name:

`<category name="My Awesome Plugin"></category>`

#

##### Цвет панели инструментов

Указывается с помощью атрибута color:

`<category name="My Awesome Plugin" color="green"></category>`

Цвет может быть задан в одном из следующих форматов:

- hex triplet, например #f61 или #f0562f
- значение оттенка в диапазоне 0°-360° (в рамках цветовой модели HSV с S и V, установленными на 45% и 65% соответственно), например, 140
- цветовые ключевые слова, такие как aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white и yellow

#

##### Доступные пазлы

Чтобы сделать блок пазла доступным в категории инструментов плагина, он должен быть указан через элемент block и его атрибут type. Атрибут type может ссылаться на такие пазлы, как:

- пазлы, определяемые самим плагином - в этом случае type должен быть установлен в соответствии с именем соответствующего .block файла внутри каталога вашего плагина (например, myPuzzle для файла блока my_awesome_plugin/myPuzzle.block) `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> </category>`
- блоки стоковых пазлов - type должен быть равен типу стокового пазла(например, math_number). Это позволяет добавлять стоковые пазлы непосредственно в категорию инструментов вашего плагина `<category name="My Awesome Plugin" color="green"> <block type="math_number"></block> </category>`
- пазлы, определенные другими плагинами - type должен быть в форме PLUGIN_DIRECTORY_NAME/PUZZLE_TYPE. Например, на пазлы из стандартного плагина E-Commerce можно ссылаться следующим образом: `<category name="My Awesome Plugin" color="green"> <block type="E-Commerce/placeOrder"></block> </category>`

Чтобы узнать тип определенного блока пазла, можно воспользоваться опцией "Print Puzzle XML Tree" из контекстного меню пазла:

Этот пункт меню выводит дерево XML пазла в консоль браузера. В нем можно найти тип пазла, а также всю структуру XML, что может пригодиться при установке значений по умолчанию для полей ввода.

Опция меню "Print Puzzle XML Tree" обеспечивает простой способ добавления в ваш плагин сразу целой группы блоков пазлов. Это полезно для создания своего рода "snippets", состоящих из связанных блоков пазлов, и помещения их в ваш плагин, что делает его немного похожим на Библиотеку пазлов.

Следующий пример объясняет, как это сделать:

- создайте несколько блоков пазлов в Редакторе Пазлов:

- щелкните правой кнопкой мыши на самом верхнем/самом крайнем блоке и выберите "Print Puzzle XML Tree":

- перейдите в консоль браузера и скопируйте напечатанный элемент (вот как это выглядит в Гугл Хром):

- вставьте его в файл init.plug внутри элемента <category> и сохраните файл:

- затем после перезагрузки редактора пазлов скопированный блок должен появиться в категории плагина

#

##### Порядок пазлов

Блоки пазлов появляются на панели инструментов в том порядке, который вы определили в файле init.plug:

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> <!-- <block type="testPuzzle"></block> --> <block type="anotherPuzzle"></block> </category>`

Обратите внимание, что закомментированный блок пазла "testPuzzle" не отображается в панели инструментов.

#

##### Текстовые метки

Вы можете добавить текстовые метки в категорию инструментов с помощью элемента label:

`<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0"></label> <label text="Main Puzzles:"></label> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> <label text="Other:"></label> <block type="anotherPuzzle"></block> </category>`

label элементы не подходят для отображения многострочного текста, т.е. без переносов строк.

Элементы label также могут быть в некоторой степени стилизованы. Они поддерживают атрибут web-class, который предназначен для назначения пользовательского CSS-класса элементу label. Правила CSS для этого класса описаны в разделе init.plug's script. Этот подход проиллюстрирован в следующем примере:

`<category name="Example Plugin" color="#a52a2a"> <label text="Example Plugin v1.0 by Soft8Soft" web-class="example-plugin__label"></label> </category> <script> const styleElem = document.createElement('style'); styleElem.innerHTML = ` .example-plugin__label .blocklyFlyoutLabelText { fill: #a52a2a; font-style: italic; font-weight: bold; text-decoration: underline; } `; document.head.appendChild(styleElem); </script>`

А вот результат применения пользовательских настроек CSS:

При выборе имени CSS-класса для атрибута web-class рекомендуется учитывать особенную CSS-подстановку, например, использовать префикс, уникальный для вашего плагина пазлов, т.е. в примере выше это "example-plugin" часть класса "example-plugin__label". Таким образом, вы с меньшей вероятностью случайно нарушите какие-либо классы CSS, уже используемые на странице.

#

##### Разделители

Разделители можно использовать для изменения расстояния между блоками пазла. Вы можете добавить разделители в категорию инструментов с помощью элемента sep. Атрибут gap задает ширину расстояния в пикселях. По умолчанию расстояние между блоками (если не используется sep) равно 24 пикселям.

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <sep gap="0"></sep> <block type="doSomethingCool"></block> <sep gap="80"></sep> <block type="anotherPuzzle"></block> </category>`

#

##### Блоки пазла, доступные на вкладке "init"

По умолчанию блоки пазла доступны только на вкладках main и на созданных пользователем, но не на init. Это связано с тем, что код, генерируемый вкладкой init, выполняется до загрузки и полной инициализации приложения «Вердж3Д». Это означает, что пазлы, предназначенные для работы с 3d-сценой, 3d-объектами, материалами и т.д., не подходят для использования внутри init и могут вызвать сбой приложения. Однако пазлы, которые не требуют наличия 3d-сцены (например, те, которые предварительно загружают ресурсы или настраивают пользовательский интерфейс) не должны иметь таких проблем и могут быть разрешены в init.

Для того чтобы пазл появился на панели инструментов вкладки init, вам необходимо установить для атрибута allow-init значение true в элементе block пазлов (это также работает с элементами label и sep):

`<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0" allow-init="true"></label> <label text="Main Puzzles:"></label> <block type="myPuzzle" allow-init="true"></block> <block type="doSomethingCool"></block> <label text="Other:"></label> <block type="anotherPuzzle"></block> </category>`

Обратите внимание, что элементы block и label без allow-init не отображаются на панели инструментов.

#

##### Значения ввода и полей по умолчанию

Если блок пазла имеет входы для блока (слоты для подключения других блоков пазла) и/или поля ввода (неблочные элементы пользовательского интерфейса, такие как селекторы, галочки, текстовые поля и т.д.), то вы можете указать их блоки-заполнители и / или значения по умолчанию. Эта функция служит двум целям: она даёт пользователям подсказку о том, что можно вставить во входной слот, а также делает использование пазла немного более удобным.

Допустим, у вашего блока пазла есть вход с именем "myNumber". Вот как вы можете добавить блок-заполнитель типа math_number подключенный к этому слоту:

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <value name="myNumber"> <block type="math_number"></block> </value> </block> </category>`

А вот как это будет выглядеть:

Блок-заполнитель, вставленный во входной слот, также может быть shadow блоком. Блоки Shadow в основном такие же, как и обычные блоки, за исключением того, что они автоматически заменяются блоком, который вы вставляете в соответствующий входной слот, и они автоматически появляются обратно, когда вы удаляете этот блок из слота. Это делает блоки shadow немного проще в использовании, чем обычные блоки-заполнители.

Блоки Shadow определяются почти так же, как и обычные блоки-заполнители, с той лишь разницей, что элемент block заменяется аналогичным элементом shadow:

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <value name="myNumber"> <shadow type="math_number"></shadow> </value> </block> </category>`

И это будет выглядеть следующим образом:

Блоки пазла могут иметь входные данные команд, которые обычно заключают в себе серию дочерних блоков пазла. Предположим, что в вашем блоке пазла есть ввод команд под названием "myStatement". Затем вы можете добавить к этому входу пару блоков-заполнителей следующим образом:

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <statement name="myStatement"> <block type="addHTMLElement"> <next> <block type="setHTMLElemAttribute"></block> </next> </block> </statement> </block> </category>`

Используемый здесь элемент statement ссылается на вход "myStatement" через атрибут name attribute. Кроме того, в него добавлены некоторые блоки-заполнители. Также здесь используется элемент next для цепочки блоков-заполнителей. Результат этой настройки показан ниже:

Если в вашем блоке пазле есть поле для галочки с именем "myCheckbox", то вы можете задать его состояние по умолчанию (true - включен, false - выключен) следующим образом:

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <field name="myCheckbox">true</field> </block> </category>`

And here's the result:

Используя блоки-заполнители и значения полей по умолчанию, вы можете создавать сложные составные пазлы аналогичные тем, которые можно добавить в библиотеку пазлов:

Код в init.plug для сложного пазла на картинке выше может выглядеть следующим образом:

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <statement name="STATEMENT_0"> <block type="whenClicked"> <value name="VALUE"> <block type="objectList"> <field name="FIELDNAME">Cube</field> </block> </value> </block> </statement> <statement name="STATEMENT_1"> <block type="loadScene"> <value name="URL"> <block type="text"> <field name="TEXT">my_scene.gltf</field> </block> </value> </block> </statement> <statement name="STATEMENT_2"> <block type="show"> <value name="VALUE"> <block type="objectList"> <field name="FIELDNAME">something</field> </block> </value> </block> </statement> </block> </category>`

Проверьте параметр контекстного меню "Print Puzzle XML Tree". Это поможет найти XML структуру (конфигурацию входов и полей) интересующего вас блока пазла.

#

##### Подкатегории панели инструментов

Категория в панели инструментов редактора может иметь подкатегории, которые, в свою очередь, также могут иметь подкатегории, и так далее... Эта функция полезна, если вы хотите организовать пазлы вашего плагина в древовидную структуру.

Этого можно достичь с помощью встроенного элемента category:

`<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <category name="1" color="red"> <category name="1.1" color="silver"> <block type="anotherPuzzle"></block> </category> </category> <category name="2" color="blue"> <block type="doSomethingCool"></block> </category> </category>`

Любая category может содержать одновременно элемент category и block (хотя это не обязательно). Таким образом, она действует как родитель для подкатегорий и также предоставляет выбор блоков пазла.

#

#### Скрипт

Элемент <script> является необязательной частью init.plug. Его можно использовать для добавления кода инициализации для ваших пазлов. Иногда вам может потребоваться выполнить тяжелые вычисления и кэшировать некоторые данные, прежде чем можно будет использовать какой-либо из ваших пазлов, вот где на помощь приходит <script>

Если вы определите функцию code() внутри <script> она будет использоваться для генерации кода, который выполняется один раз перед любым пазлом. Функция code() должна возвращать строку, содержащую код «Ява Скрипт».

`<script> function code() { // this line will be executed before any puzzles return `console.log('Powered by My Awesome Plugin!');`; } </script>`

Код инициализации, возвращаемый функцией code(), добавляется в сгенерированный файл файл логики только в том случае, если пазлы плагина действительно используются в приложении (добавлены на рабочее пространство и не отключены).

#

### Формат файла .block

Файлы плагина с расширением .block используются для описания отдельных блоков пазла, а именно: как может выглядеть блок и какой код он должен генерировать при добавлении в рабочую область. Плагин может вообще не иметь файлов .block. Это может быть полезно, если вы хотите создать панель инструментов только с stock блоками пазла (даже включая более сложные настройки блоков).

Имя файла .block используется для указания того, какие блоки пазла должны быть включены в категорию инструментов плагина.

Вот минимальный пример файла .block:

`<template color="green"> <dummy> <label>myPuzzle</label> </dummy> </template> <script> function code(block) { return `console.log('This is my first puzzle!');`; } </script>`

Здесь у нас есть элемент <template>, который определяет внешний вид блока пазла. А также есть элемент <script>, внутри которого находится функция code(). Функция code() возвращает строку, содержащую код, который будет сгенерирован на месте этого пазла.

Итак, исходя из приведенного выше примера, мы можем полагать, что наш простой блок пазла будет зеленого цвета и будет иметь текстовую метку"myPuzzle":

И если добавить его в рабочую область, он должен вывести следующее сообщение в консоль браузера:

#

#### Шаблон блока

Внешний вид блока пазла можно задать двумя способами: через <template> XML элемент и через функцию template(). Первый вариант более прост и удобен в использовании. Например, вот как может выглядеть <template> типичного пазла:

`<template color="green" inline="true" output="Dictionary" tooltip="This is my first puzzle!" help="https://soft8soft.com" > <dummy name="myDummyInput"> <label>enable</label> <checkbox name="myCheckbox">true</checkbox> </dummy> <value name="myValueInput"> <label>input value</label> </value> </template> <script> function code(block) { return `console.log('This is my first puzzle!');`; } </script>`

Другой подход заключается в использовании функции template(). Это функция, которую вы можете определить внутри элемента <script>. Её также можно использовать для настройки внешнего вида пазла, но на этот раз через Blockly JavaScript API, а не через элементы и атрибуты XML. Он получает параметр block, который является копией Blockly.BlockSvg.

Тот же блок, что и в примере выше, можно переписать с помощью функции template() следующим образом:

`<script> function template(block) { block.setColor('green'); block.setInputsInline(true); block.setOutput(true, 'Dictionary'); block.setTooltip('This is a test puzzle!'); block.setHelpUrl('https://soft8soft.com'); block.appendDummyInput('myDummyInput') .appendField('enable') .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); block.appendValueInput('myValueInput') .appendField('input value'); } function code(block) { return `console.log('This is my first puzzle!');`; } </script>`

Этот подход более гибкий, но требует знания соответствующих APIs. Он особенно полезен, если вам нужно выполнить какую-то нетривиальную настройку, которую невозможно осуществить с помощью элемента <template>. Более того, вы можете использовать и <template> и template(). одновременно.

В этом разделе приведены примеры как для <template> (XML) так и для template() (JS).

Обратите внимание, что этот раздел - лишь краткий обзор того, как создать пользовательский блок пазла. Для получения более подробной информации об общей настройке, ознакомьтесь с документацией Google Blockly о настраиваемых блоках и полях.

#

##### Цвет блока

Вы можете установить цвет блока, чтобы придать пазлам особый вид:

XML
Может быть задан с помощью атрибута color: `<template color="green"></template>`
JS
Можно настроить с помощью block.setColor: `<script> function template(block) { block.setColor('green'); } </script>`

Цвета должны быть в одном из описанных здесь форматов: цветовой формат.

#

##### Всплывающая подсказка блока

Можно добавить всплывающую подсказку, которая появляется при наведении курсора на блок. Подсказка полезна для предоставления пользователю простого описания того, для чего предназначен пазл, как он работает, какие есть советы по использованию и т.д...

XML
Можно установить с помощью атрибута tooltip: `<template tooltip="This is my first puzzle!"></template>`
JS
Можно настроить с помощью block.setTooltip: `<script> function template(block) { block.setTooltip('This is my first puzzle!'); } </script>`

#

##### Справочная ссылка блока

Если tooltip недостаточно для описания вашего пазла, вы также можете добавить ссылку на сайт с более подробной документацией. Эта ссылка будет использоваться для пункта Help в контекстном меню пазла (щелкните правой кнопкой мыши на пазле):

XML
Может быть задан с помощью атрибута help: `<template help="https://www.soft8soft.com/"></template>`
JS
Можно настроить с помощью block.setHelpUrl: `<script> function template(block) { block.setHelpUrl('https://www.soft8soft.com/'); } </script>`

#

##### Добавление входа

Блоки пазла могут содержать слоты ввода для подключения других блоков. Также они могут содержать неблочные элементы пользовательского интерфейса, такие как галочки или текстовые поля. Существует 3 различных типа входов: value inputs,statement inputs и dummy inputs.

- value inputs - входные слоты, которые можно использовать для подключения блоков, имеющих обратное значение (выходное соединение), например, вход с какими-либо математическими блоками. Внутри математического блока вычисляют и получают определенное значение, к которому может получить доступ родительский блок (блок, содержащий входной слот).
- statement inputs - это входы, которые могут содержать группу блоков, представляющих серию последовательных действий, например, пазл if-else имеет именно такой тип ввода для группы блоков пазла по условиям "true" или "false".
- dummy inputs - это входы, подходящие только для добавления неблочных элементов пользовательского интерфейса, таких как галочки, текстовые поля, изображения и т.д.. Смотрите добавление полей для получения дополнительной информации.

Разницу между этими типами входов можно увидеть на рисунке ниже:

XML
Используйте элементы <value>, <statement> и <dummy> для добавления входов. Входы Value и statement должны иметь имя (для этого используйте их атрибут name). Входам Dummy обычно не нужны имена, они являются просто областями для полей. fields. `<template> <value name="myInput"></value> <statement name="myStatement"></statement> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template>`
JS
Для добавления входов используйте способы block.appendValueInput, block.appendStatementInput и block.appendDummyInput. Входы Value и statement должны иметь имя (через атрибут name). Входы Dummy обычно не нуждаются в именах, они являются просто контейнерами для fields. `<script> function template(block) { block.appendValueInput('myValue'); block.appendStatementInput('myStatement'); block.appendDummyInput() .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); } </script>`

#

##### Расположение входов

Входы блоков могут быть расположены вертикально (по умолчанию) или горизонтально.

XML
Может быть задан с помощью атрибута inline, который может быть либо true либо false. falseдля вертикального расположения, а true - для горизонтального. `<template inline="true"></template>`
JS
Может быть установлен через block.setInputsInline метод. Метод получает параметр, который может быть либо true либо false. false - для вертикального расположения и true - для горизонтального варианта. `<script> function template(block) { block.setInputsInline(true); } </script>`

#

##### Добавление полей

Вы можете добавить такие элементы пользовательского интерфейса, как текстовые метки, галочки, выпадающие списки, текстовые вводы и многое другое в ваших пазлах. Эти элементы пользовательского интерфейса называются "поля". Их можно добавлять к входам любого типа, но если вы не хотите дополнительно создавать слоты ввода для блоков пазла, вам следует придерживаться следующих правил использования dummy inputs.

Есть несколько особенностей, которые объединяют все поля:

- Возможность указать дополнительное параметр name, который часто используется для ссылки на поле в функции code() для получения значения поля.
- Возможность предоставить дополнительное значение по умолчанию. Тем не менее, значение по умолчанию, указанное в init.plug's category, всегда имеет более высокий приоритет, чем значение, указанное в шаблоне пазла.

Давайте посмотрим, как добавить различные поля в пазл.

- label - нередактируемое текстовое поле

  XML
  Может быть добавлен с помощью элемента <label>, вложенного в элемент заглушка, значение или оператор. `<template> <dummy> <label>a text label</label> </dummy> </template>`
  JS
  Может быть добавлен через метод appendField и Blockly.FieldLabel к заглушка, значение или оператор. `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldLabel('a text label')); } </script>`

- text - редактируемое текстовое поле

  XML
  Может быть добавлен с помощью элемента <text> вложенного в элемент заглушка, значение или оператор. `<template> <dummy> <text name="myText">default text</text> </dummy> </template>`
  JS
  Может быть добавлен через метод appendField и Blockly.FieldTextInput к заглушка, значение или оператор. `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldTextInput('default text'), 'myText'); } </script>`

- multiline text - редактируемое многострочное текстовое поле

  XML
  Может быть добавлен с помощью элемента <multiline> вложенного в элемент заглушка, значение или оператор. `<template> <dummy> <multiline name="myTextMultiline">This is a \n multiline \n text</multiline> </dummy> </template>`
  JS
  Может быть добавлен через метод appendField и Blockly.FieldMultilineInput к заглушка, значение или оператор. `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldMultilineInput('This is a \n multiline \n text'), 'myTextMultiline'); } </script>`

- number - редактируемое поле для числовых значений

  XML
  Может быть добавлено через <number> элемент, вложенный внутрь dummy, value или statement. `<template> <dummy> <number name="myNumber">3</number> </dummy> </template>`
  JS
  Может быть добавлено через appendField метод и Blockly.FieldNumber к dummy, value или statement. `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldNumber(3), 'myNumber'); } </script>`

- angle - редактируемое поле для числовых значений, представляющих углы (в градусах)

  XML
  Может быть добавлено через <angle> элемент, вложенный внутрь dummy, value или statement. `<template> <dummy> <angle name="myAngle">15</angle> </dummy> </template>`
  JS
  Может быть добавлено через appendField метод и Blockly.FieldAngle к dummy, value или statement. `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldAngle(15), 'myAngle'); } </script>`

- checkbox - типичное поле с галочкой

  XML
  Может быть добавлено через <checkbox> элемент, вложенный внутрь dummy, value или statement. Состояние флажка по умолчанию должно быть установлено в true или false. `<template> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template>`
  JS
  Может быть добавлено через appendField метод и Blockly.FieldCheckbox к dummy, value или statement. Состояние флажка по умолчанию передается в качестве параметра в Blockly.FieldCheckbox и может быть либо true, либо false. `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); } </script>`

- dropdown - список значений для выбора

  XML

  Может быть добавлено через <dropdown> элемент, вложенный внутрь dummy, value и statement.

  Для создания списка опций добавьте элементы <option> внутри <dropdown>. У всех элементов <option> атрибут name должен быть установлен на уникальный идентификатор (что-то, что будет более подходящим wдля использования в коде, например COLOR_GREEN). Текст, содержащийся между начальным и конечным тегами <option>, используется в качестве метки, отображаемой в выпадающем виджете для данной опции.

  Выбранный по умолчанию параметр можно установить, задав атрибуту default параметра значение true.

  `<template> <dummy> <dropdown name="myDropdown"> <option name="COLOR_GREEN">green</option> <option name="COLOR_YELLOW" default="true">yellow</option> <option name="COLOR_RED">red</option> </dropdown> </dummy> </template>`
  JS

  Может быть добавлено через appendField метод и Blockly.FieldDropdown к dummy, value или statement.

  Первым параметром Blockly.FieldDropdown может быть массив доступных опций. Каждая опция, в свою очередь, также является массивом из 2 элементов: текст для отображения в UI интерфейсе in the UI (человеческий дружественный текст, обозначающий, о чем данная опция) и уникальный идентификатор (то, что было бы более подходящим для использования в коде, например, code, e.g. COLOR_GREEN).

  Выбранную по умолчанию опцию можно установить, выбрав метод setValue. Чтобы указать, какая опция должна быть выбрана по умолчанию, просто введите уникальный идентификатор опции в качестве параметра в setValue.

  `<script> function template(block) { let field = new Blockly.FieldDropdown([ // [ displayed text, option name ] ['green', 'COLOR_GREEN'], ['yellow', 'COLOR_YELLOW'], ['red', 'COLOR_RED'], ]); // setting default option field.setValue('COLOR_YELLOW'); block.appendDummyInput() .appendField(field, 'myDropdown'); } </script>`

- color picker - виджет для выбора цвета

  XML
  Может быть добавлено через <color> элемент, вложенный внутрь dummy, value или statement. `<template> <dummy> <color name="myColor">#350af5</color> </dummy> </template>`
  JS
  Может быть добавлено через appendField метод и Blockly.FieldColor к dummy, value или statement. `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldColor('#350af5'), 'myColor'); } </script>`

#

##### Выравнивание полей

Элементы поля в блоке пазла всегда принадлежат определенному слоту входа. Будь то одно поле или несколько полей на вход, они всегда отображаются в соответствии с определенным расположением. Одно, что можно изменить - это расположение полей внутри входа, в частности, с какой стороны они выравниваются. Их можно выровнять слева (по умолчанию), справа и по центру.

XML
Чтобы изменить выравнивание поля для определенного ввода, используйте атрибут align у соответствующего input element. Действующими значениями этого атрибута являются: left, center и right. `<template> <dummy align="left"></dummy> <value align="center" name="myValueInput"></value> <statement align="right" name="myStatementInput"></statement> </template>`
JS
Чтобы изменить выравнивание поля для определенного ввода, используйте метод setAlign. Его первый параметр должен быть Blockly.ALIGN_LEFT, Blockly.ALIGN_CENTER или Blockly.ALIGN_RIGHT. `<script> function template(block) { block.appendDummyInput() .setAlign(Blockly.ALIGN_LEFT); block.appendValueInput('myValueInput') .setAlign(Blockly.ALIGN_CENTER); block.appendStatementInput('myStatementInput') .setAlign(Blockly.ALIGN_RIGHT); } </script>`

#

##### Блочные соединения

Блоки пазлов могут иметь входные, утверждающие и выходные соединения. Входные соединения добавляются автоматически для каждого созданного входного слота и служат для подключения дочерних блоков. Более подробную информацию о создании входов можно найти здесь: adding inputs. Соединения определений (предыдущих и следующих) и выходов используются для соединения блоков с родительским блоком или блоками-потомками.

Previous и next добавляют соединения сверху и снизу блока пазла соответственно, так что блок может быть соединен снизу/сверху с другими блоками, имеющими соответствующее соединение.

Выход добавляет соединение в левой части блока - это позволяет подключить блок к входному разъему родительского блока. Выходное соединение обычно используется для блоков пазла, которые возвращают значение, например, результат некоторых математических вычислений.

По умолчанию блок пазла не имеет никаких связей. В блок пазла можно добавить одно соединение любого типа. Вы можете добавить в блок даже 2 связи определения/вывода, но разрешены только следующие комбинации: предыдущий + следующий или следующий + вывод.

Вот как все эти связи могут быть добавлены в блок:

XML
Связь previous оператора можно включить, установив атрибут prev в значение true: `<template prev="true"></template>`
JS
Связь с previous оператором может быть включена вызовом block.setPreviousStatement с true в качестве первого параметра: `<script> function template(block) { block.setPreviousStatement(true); } </script>`
XML
Связь с оператором next можно включить, установив атрибут next в значение true: `<template next="true"></template>`
JS
Подключение next оператора можно включить, вызвав block.setNextStatement с true в качестве первого параметра: `<script> function template(block) { block.setNextStatement(true); } </script>`
XML
Соединение output можно включить, установив атрибут output в пустую строку: `<template output=""></template>`
JS
Соединение output может быть включено вызовом block.setOutput с true в качестве первого параметра: `<script> function template(block) { block.setOutput(true); } </script>`

#

##### Проверка типов входов и выходов

По умолчанию все блоки пазла, имеющие подходящие входы/выходы, могут быть соединены друг с другом. Однако это не означает, что все блоки должны быть совместимы. Допустим, у нас есть блок пазла, возвращающего массив координат, а у другого блока есть вход, который ожидает имя анимации. Если мы попытаемся подключить первый блок ко второму, то все может работать не так, как ожидалось. Код, сгенерированный из этих пазлов, может быть некорректным и даже привести к сбою.

К счастью, есть способ разрешить эту ситуацию. Каждому входу и выходу можно присвоить тип, и только те блоки, которые имеют совпадающие типы, могут быть соединены друг с другом.

Для получения общей информации о выходных и других соединениях см. раздел соединения блоков.

XML
Тип вывода может быть указан через атрибут output шаблона <template>. Типы, которые может принимать конкретный вход, определяются атрибутом type входа. `<!-- the block's output type is 'String' --> <template output="String"> <!-- this input accepts only blocks of type 'Number' --> <value name="myInput" type="Number"></value> </template>`
JS
Тип вывода может быть назначен блоку вызовом метода block.setOutput передавая в качестве первого параметра true, а в качестве второго - желаемый тип. Типы, которые может принимать определенный вход, устанавливаются вызовом метода setCheck. `<script> function template(block) { // этот вход принимает только блоки типа 'Number' block.appendValueInput('myInput') .setCheck('Number'); // тип вывода блока - 'String' block.setOutput(true, 'String'); } </script>`

В приведенном примере блок имеет вход, который может принимать блоки только типа "Number" или неопределенного типа (если тип не был установлен через setOutput). Блок также имеет тип выхода "String", что означает, что он может быть подключен только к входу, имеющему тип "String" или неопределенный тип (если тип не был установлен через setCheck).

Входы и выходы также могут иметь более одного типа:

XML
Для того чтобы иметь несколько типов ввода/вывода, заполните соответствующие атрибуты type и output значениями нескольких типов, разделенными пробелом: `<!-- тип вывода этого блока - 'String' или 'Animation' --> <template output="String Animation"> <!-- этот вход принимает только блоки типа 'Number' или 'Object3D' --> <value name="myInput" type="Number Object3D"></value> </template>`
JS
Чтобы иметь несколько типов ввода/вывода, предоставьте массив типов в setCheck и/или setOutput. `<script> function template(block) { // этот вход принимает только блоки типа 'Number' или 'Object3D' block.appendValueInput('myInput') .setCheck(['Number', 'Object3D']); // тип вывода этого блока - 'String' или 'Animation' block.setOutput(true, ['String', 'Animation']); } </script>`

В стандартных пазлах «Вердж3Д» используется несколько определенных типов ввода/вывода, которые вы можете заимствовать и для своих пазлов:

- для типов «Ява Скрипт» и встроенных объектов есть: Number, String, Boolean, Dictionary (для js объектов), Array, Promise.
- для сущностей сцены: Object3D, Material, Animation.
- другие типы: Canvas, Sound, Video

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

#

#### Функция code

Функция code() используется для предоставления кода «Ява Скрипт», который должен быть сгенерирован для пазла, если он добавлен в рабочее пространство. Как правило, это место, где вы определяете логику пазла и реализуете большинство ее функций.

Ожидается, что функция вернет строку, содержащую js-код. Принцип ее работы похож на работу init.plug's code function, за исключением того, что в данном случае код будет добавлен и использован столько раз, сколько блоков пазла будет добавлено в рабочее пространство. Функция code() получает параметр block - это тот же экземпляр Blockly.BlockSvg который используется в template().

Давайте посмотрим, что можно сделать с помощью функции code().

#

##### Генерация базового кода

Очень простая вещь, которую может сделать code() - это вернуть строку с парой строк кода «Ява Скрипт», который будет добавлен в результирующий файл visual_logic.js.

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

`function code(block) { return `alert('Test');`; }`

И здесь мы просто возвращаем значение 1:

`function code(block) { return `1`; }`

- этот пример не имеет особого смысла, если только блок не имеет выходного соединения. В этом случае доступ к возвращаемому значению можно получить из родительского блока, у которого этот блок подключен к одному из входов.

А теперь более сложный пример:

`function code(block) { const fun = function() { app.scene.traverse(function(obj) { obj.material = new v3d.MeshBasicMaterial({ color: new v3d.Color(Math.random(), Math.random(), Math.random()) }); }); } return `(${fun})();`; }`

- здесь все объекты получают новый материал со случайно сгенерированным цветом.

#

##### Борьба с разрастанием кода

По умолчанию код пазла копируется в создаваемый файл visual_logic.js каждый раз, когда пазл используется в рабочем пространстве. Это не проблема, если у вас всего пара строк кода. Но если код громоздкий, сложный и разбит на несколько функций, которые вы, возможно, захотите объявить только один раз, то подход по умолчанию становится неэффективным и приводит к раздуванию результирующего файла visual_logic.js.

Для решения этой проблемы можно использовать специальный метод, доступный внутри функции code(). Он называется Plug.provide(). Давайте продемонстрируем его использование на следующем примере:

`function code(block) { const fun = Plug.provide('myFunction', function(a, b, c) { console.log(a, b, c); }); return `${fun}(1, 2, 3);`; }`

Здесь у нас есть функция "myFunction", определенная через Plug.provide(), что означает, что независимо от того, сколько раз пазл используется в рабочем пространстве, "myFunction" будет скопирована в visual_logic.js только один раз. Кроме того, значение, которое фактически возвращается из code() - это ${fun}(1, 2, 3);, что, по сути, является вызовом функции "myFunction(1, 2, 3);", которая будет вставлена в visual_logic.js для каждого такого пазла, используемого на рабочем пространстве. И это именно то, чего мы хотим от нашего пазла, потому что "myFunction" нужно объявить только один раз, и после этого ее можно вызывать много раз.

Первым параметром в Plug.provide() должен быть уникальный идентификатор функции. Возвращаемая переменная fun - это имя предоставленной функции (обычно оно почти совпадает со значением, переданным в первом параметре, но может быть и другим, так как редактору пазлов необходимо убедиться, что имя корректно и не имеет коллизий с другими функциями/переменными, используемыми на рабочем пространстве). Это имя (вместо исходного "myFunction") должно быть использовано для вызова предоставленной функции - именно так это делается в части под оператором возврата.

#

##### Доступ к PzLib API

«Вердж3Д» поставляется API PzLib. Это API используется следующим образом:

`function code(block) { // сначала определяем требуемые методы так Plug.pzlib('getAllObjectNames'); Plug.pzlib('getObjectByName'); Plug.pzlib('generateUniqueName'); // или так Plug.pzlib('getAllObjectNames', 'getObjectByName', 'generateUniqueName'); // ... // затем используем их так const fun = Plug.provide('myFunction', function() { const objNames = PzLib.getAllObjectNames(); const cubeObj = PzLib.getObjectByName('Cube'); const uniqueName = PzLib.generateUniqueName('Cube'); }); return `${fun}();`; }`

Смотрите примеры и полное описание API здесь.

#

##### Доступ к входам и полям

Если блок пазла имеет поля или слоты ввода определенные в функции template(), то вы, скорее всего, захотите, чтобы они влияли на то, что генерируется в функции code() function. Например, флажок может включать или выключать одну из функций пазла.

Методы API, используемые для доступа к входам значений, входам операторов и полям are namely: Blockly.JavaScript.valueToCode, Blockly.JavaScript.statementToCode и block.getFieldValue.

Давайте создадим блок пазла, в котором есть и входы, и поля. Вот полное содержимое файла .block:

`<template color="green"> <dummy> <label>myPuzzle</label> </dummy> <value name="myValue"></value> <statement name="myStatement"></statement> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template> <script> function wrapFn(contents) { return `function() {${contents}}`; } function code(block) { const myInput = Blockly.JavaScript.valueToCode(block, 'myValue', Blockly.JavaScript.ORDER_NONE) || `''`; const myStatement = wrapFn(Blockly.JavaScript.statementToCode(block, 'myStatement')); const myCheckbox = block.getFieldValue('myCheckbox') === 'TRUE'; const fun = Plug.provide('myFunction', function(input, statements, checkbox) { console.log('input value:', input); statements(); // execute puzzles from the myStatement input console.log('checkbox state:', checkbox); }); return `${fun}(${myInput}, ${myStatement}, ${myCheckbox});`; } </script>`

В этом примере блок определяет ввод значения под названием "myValue", ввод утверждения "myStatement" и поле флажка "myCheckbox". Мы получаем их значения с помощью API, описанного выше, но прежде чем передать их в "myFunction", они претерпевают некоторые заметные изменения:

`var myInput = Blockly.JavaScript.valueToCode(block, 'myValue', Blockly.JavaScript.ORDER_NONE) || `''`;`

- Входной слот может не иметь блоков, подключенных к нему, поэтому мы просто гарантируем, что в этом случае мы получим пустую строку, добавив часть *|| `''`* в конце.

`function wrapFn(contents) { return `function() {${contents}}`; } ... var myStatement = wrapFn(Blockly.JavaScript.statementToCode(block, 'myStatement'));`

- слот ввода оператора обычно содержит группу операторов. Удобно обернуть их в функцию (см., что делает wrapFn), чтобы передать объект функции в качестве параметра и затем рассматривать его как обратный вызов.

`var myCheckbox = block.getFieldValue('myCheckbox') === 'TRUE';`

- здесь значение флажка просто сравнивается с "TRUE" для получения булевого результата.

В итоге все значения можно передать в "myFunction" следующим образом:

`return `${fun}(${myInput}, ${myStatement}, ${myCheckbox});`;`

Теперь вы можете использовать их по своему усмотрению:

`const fun = Plug.provide('myFunction', function(input, statements, checkbox) { console.log('input value:', input); statements(); // execute puzzles from the myStatement input console.log('checkbox state:', checkbox); });`

#

## Ошибки плагинов и отдельных блоков

При разработке или использовании плагинов вы можете столкнуться с различными ошибками, связанными с определенным блоком пазла или даже со всем плагином. В этом разделе описаны типичные ошибки плагинов и блоков и способы их устранения.

Если при загрузке плагина или инициализации его блоков пазлов что-то идет не так, то редактор пазлов выводит соответствующее сообщение об ошибке в консоль браузера. Обычно такая ошибка выглядит следующим образом:

PluginError(PLUGIN_NAME) ...
BlockError(PLUGIN_NAME/BLOCK_NAME) ...
Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.

- они ссылаются на конкретный плагин и конкретный блок, который вызвал ошибку.

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

Недопустимый пазл на панели инструментов и на рабочей области.

Вот список наиболее распространенных ошибок плагинов и блоков:

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "This page contains the following errors:error on line ..."

Это означает, что в соответствующем файле .block присутствуют ошибки XML, препятствующие его разбору. Например, отсутствие завершающего тега <script> приводит к такой ошибке:

`<script> function template(block) {} function code(block) {}`

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "ReferenceError (SyntaxError, TypeError, etc...) ..."

Ошибка возникает в том случае, если код в элементе <script> соответствующего файла .block содержит ошибку «Ява Скрипт» вида, указанного в сообщении об ошибке.

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Child block does not have output or previous statement."

Блок, на который ссылается эта ошибка, имеет дочерний блок, подключенный к нему. Однако дочерний блок не имеет ни выхода, ни предыдущего соединения, и поэтому не может быть использован таким образом. Такая ситуация может произойти как с блоками пазлами внутри категории инструментов плагина из-за того, как они настроены в init.plug, так и с блоками, реально используемыми на рабочем пространстве.

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "Error: Connection checks failed. Input "PARENT_INPUT_NAME" connection on "PLUGIN_NAME/BLOCK_NAME" block (id="BLOCK_ID") expected TYPE_PARENT, found TYPE_CHILD"

Блок, на который ссылается эта ошибка, имеет подключенный к нему дочерний блок, но входной слот родителя и выходное соединение дочернего блока имеют несовместимые типы. Такая ситуация может произойти как с блоками пазлами внутри категории инструментов плагина из-за того, как они настроены в init.plug, так и с блоками, реально используемыми на рабочем пространстве.

#

##### BlockError(PLUGIN_NAME/null): validation error - "TypeError: Unknown block type: PLUGIN_NAME/null"

Эта ошибка означает, что файл init.plug плагина ссылается на блок, в котором не указан атрибут типа что недопустимо. Например, это не сработает: `<category name="My Awesome Plugin" color="green"> <block></block> </category>`

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Unknown block type: PLUGIN_NAME/BLOCK_NAME"

Это сообщение об ошибке обычно появляется после одной из ошибок "error parsing .block file" и просто указывает на то, что упомянутый блок пазла не был правильно загружен и инициализирован из-за первоначальной ошибки.

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function ...

Соответствующий файл .block либо имеет неправильно определенный элемент <template> либо содержит ошибки «Ява Скрипт» внутри своей функции template().

- BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function - "TypeError: Found invalid FieldDropdown options."
  Эта конкретная ошибка означает, что одно из выпадающих полей пазла имеет неименованный параметр. Дополнительные сведения см. в разделе выпадающее поле.

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error calling code() function ...

Соответствующий файл .block содержит ошибки «Ява Скрипт» внутри своей функции code().

#

##### PluginError(PLUGIN_NAME): error parsing init.plug file - "This page contains the following errors:error on line ..."

Это означает, что в файле init.plug плагина присутствуют XML-ошибки, препятствующие его разбору. Например, отсутствие завершающего тега <category> приводит к такой ошибке:

`<category name="MyAwesomePlugin" color="green"> <block type="myPuzzle"></block>`

#

##### PluginError(PLUGIN_NAME): error parsing init.plug file - "ReferenceError (SyntaxError, TypeError, etc...) ..."

Ошибка возникает в том случае, если код в элементе <script> соответствующего файла init.plug содержит ошибку «Ява Скрипт» вида, указанного в сообщении об ошибке.

#

##### PluginError(PLUGIN_NAME): error calling code() function ...

Файл init.plug плагина содержит ошибки «Ява Скрипт» в функции code().

#

##### Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.

Это сообщение об ошибке обычно появляется после одного из сообщений BlockError и/или PluginError и просто указывает на то, что упомянутый блок пазла не был правильно загружен и инициализирован из-за первоначальных ошибок. Чтобы сохранить возможность загрузки пазлов и в какой-то степени сохранить их работоспособность, такие блоки (как в категории инструментов плагина, так и на рабочем пространстве) заменяются специальными фиктивными блоками. Пример того, как выглядит фиктивный блок, можно увидеть на этом рисунке.

#

## Как распространять плагины

Как только плагин будет разработан и протестирован, не стесняйтесь поделиться им:

- Разместив ссылку на форуме «Вердж3Д».
- Написав статью в «Вердж3Д Вики» и/или добавив ссылку в статью со списком плагинов.
- В социальных сетях, используя хэштег #verge3d.
- Поделившись в Reddit. Имеется 3 субреддита, которые модерируются нами, поэтому ваши посты не будут забанены: r/RealVerge3D, r/3dcommerce и 3dconfigurators.
- Запустив продажи на «Гумроад» или аналогичных ресурсах.

## Проблемы с пазлами?

Обратитесь за помощью на форуме!