Сразу хочу предупредить, что эта серия постов не для матёрых программистов и даже не для программистов вообще. Понимаю, что это звучит крайне вызывающе, учитывая IT-тематику ресурса, и все же позвольте объяснить… В качестве аудитории, я вижу обычных дизайнеров, которые хотели бы начать программировать в среде Adobe, но по каким-то причинам (из-за страха перед неизвестным, неуверенности в своих возможностях или незнания языка) не могут сделать первые шаги в данном направлении. Свою скромную задачу вижу в том, чтобы помочь им понять, что "не боги горшки обжигают" и любой, достаточно мотивированный человек, может научится писать работающий программный код. Вполне возможно, некоторые из них так увлекутся этой игрой, что решат стать настоящими разработчиками. Чем код не шутит?
В этом посте будет рассказано о том, как посредством написания небольшой программы (скрипта на JavaScript) создать свой уникальный инструмент в Adobe Illustrator, который позволит не только сократить ваше время, но и улучшить взаимодействие с этим замечательным графическим редактором. Сначала я сформулирую задачу, затем покажу код, который ее решает и, далее, подробно расскажу о том, как он создавался. Здесь не будут обсуждаться основы Javascript, особенности объектной модели Illustrator или различные редакторы для написания/отладки кода. Эту информацию вы сможете при желании найти сами. Главное, на мой взгляд, это понимание базовых принципов написания программ, на что и делается основной упор в этой статье. Если вы готовы прыгнуть чуть выше своей головы, добро пожаловать под кат!
В Adobe Illustrator есть инструмент Clipping Mask, который работает с обтравочными масками. Clipping Mask содержит три команды: Make, Release и Edit Mask. Первая создает маску, вторая — разбирает, третья — позволяет редактировать. Нас интересует вторая команда, которая разбивает объект Clipping Mask на контур и содержимое маски. Очень часто бывает нужно не просто разобрать маску, а еще и избавиться от самого контура маски, оставив только содержимое. Штатная команда Release Clipping Mask этого не делает, поэтому после ее применения, необходимо выполнить еще три действия:
- Убрать выделение с объектов
- Выделить только контур маски
- Удалить контур маски
Если эту последовательной операций приходится производить достаточно часто в течении дня, то возникает вопрос: а нельзя как-то уменьшить количество этих действий для получения того же результата? И дело здесь вовсе не в лени, а в отсутствии необходимого инструмента. А теперь, представьте на секунду, что такой инструмент у вас есть.
Тут вы ненадолго отвлекаетесь от работы и погружаетесь в мысли о том, как было бы здорово, если в арсенале Adobe Illustrator была такая команда, как Expand Clipping Mask, которая выполняла за вас все эти действия. Классная идея! Надо написать в техподдержку Adobe, думаете вы. Следующая мысль: вдруг им про это уже много раз писали? А если когда-нибудь и может быть они добавят такой инструмент, то вам-то что от этого? Эта команда нужна здесь и сейчас!
И тут наступает момент истины — можно самому написать скрипт!
Сказано — сделано!
1 #target illustrator
2 if (app.documents.length > 0) {
3 var doc = app.activeDocument;
4 var sel = doc.selection;
5 var clipPath;
6 if (sel.length > 0) {
7 if (sel[0].typename == 'GroupItem' && sel[0].clipped == true) {
8 var clipGroup = sel[0].pageItems.length;
9 for (var i = 0; i < clipGroup; i++) {
10 if (sel[0].pageItems[i].typename == 'PathItem' &&
sel[0].pageItems[i].clipping == true) {
11 clipPath = sel[0].pageItems[i];
12 break;
13 };
14 };
15 app.executeMenuCommand('releaseMask');
16 clipPath.remove();
17 }
18 else {
19 alert ('Выделение не является объектом-маской!');
20 };
21 }
22 else {
23 alert ('Нет выделенных объектов!');
24 };
25 }
26 else {
27 alert ('Нет открытых документов!');
28 };
Вот такой небольшой по объему код поможет решить эту задачу. Вместо нескольких, уже порядком надоевших действий, вам потребуется выполнить только одно — запустить скрипт Expand Clipping Mask. Теперь у вас есть удобный инструмент для работы с масками, сделанный к тому же своими руками.
Конечно, тут я немного лукавлю. Во-первых, скрипт писали не вы, а во-вторых — он не такой универсальный, как хотелось бы. Однако, если вам интересно, как он работает, и самое главное, как научиться самому писать подобные программы, тогда буду рад рассказать вам об этом по порядку и с подробными комментариями.
Начнем с того, любой скрипт (сценарий/программа/код на JavaScript) состоит из нескольких основных блоков кода: объявления (инициализации) переменных, базовых проверок (условий) и, скажем так, "движка программы" — кода, который реализует основной рабочий функционал скрипта. Конечно, это разделение весьма условное, так как в функциональной части тоже есть проверки, но структурный принцип такой. Естественно, чем больше программа, тем сложнее будет ее разделить на подобные блоки. Но в нашем случае, это возможно. Строки с 8 по 16 — это "движок" скрипта, остальные строки — это объявления переменных и различные базовые проверки с их обработкой. Если вы подсчитаете, то получится, что количество строк блока проверок больше, чем количество строк функционального блока. Неужели так важны эти проверки?
Зачем нужны проверки?
Серьезные программисты меня поймут, а начинающим разработчикам будет полезно про это узнать. Проверки нужны для того, чтобы обеспечить нормальную работу функциональной части программы. Если их не писать, то любая ошибка при выполнении скрипта, будет приводить к программному сбою. А это не есть good.
Конечно, упомянутые выше, серьезные товарищи используют для таких целей конструкцию try/catch, но я решил, что обычный if/else будет лаконичной и более понятной конструкцией. Особенно, для начинающих скрипто-писателей.
Рассмотрим детально, что делают эти строки. Первая строка отвечает за то, что даже если скрипт будет запущен не из Adobe Illustrator, он будет выполнен именно в нем. Соответственно, если вы запускаете скрипт из Illustrator, эту строку можно опустить.
#target illustrator
Далее проверяется наличие открытых документов в Adobe Illustrator на момент запуска скрипта. Читать эти строки следует так: если (if
) в приложении (app
) количество документов (documents.length
) больше нуля (> 0), то следует выполнить код, заключенный в {...}. В противном случае (else
), вывести сообщение (alert
) 'Нет открытых документов!' и завершить работу скрипта.
if (app.documents.length > 0) {
...
...
}
else {
alert ('Нет открытых документов!');
};
Следующий блок кода проверяет наличие выделения в документе.
if (sel.length > 0) {
...
...
}
else {
alert ('Нет выделенных объектов!');
};
Следует отметить, что если в предыдущих примерах мы использовали зарезервированные имена (такие как, app
или documents
), то здесь мы используем переменную sel, которую определили сами в строках 3 и 4,
var doc = app.activeDocument;
var sel = doc.selection;
где doc
— это ссылка на активный документ Illustrator, а sel
— ссылка на выделенный объект/объекты в активном документе.
Ссылка (или reference) — это указатель на определенный объект. Конечно же, я прекрасно отдаю себе отчет в том, что вполне себе безобидное русское слово "указатель" способно ввести в ступор любого человека, не знакомого с ООП (объектно-ориентированным программированием). Но поверьте на слово, все не так сложно, как кажется. Ссылки сохраняются в переменных и используются для обращения к объектам. В переменнойdoc
мы сохраняем (присваиваем ей значение посредством оператора присвоения=
) указатель на активный документ (activeDocument) приложения (app), а в переменнойsel
— сохраняем указатель на выделение (selection) в активном документе (activeDocument) приложения (app). Просто чтобы не писать снова app.activeDocument мы используем вместо этого переменнуюdoc
, в которой этот код уже содержится. Вот поэтому ссылка будет выглядеть такsel = doc.selection
. Надеюсь, я понятно объяснил.
Таким образом, в этом условии if (sel.length > 0)
проверяется есть ли выделенные объекты в активном документе, и если нет, то выводится сообщение: 'Нет выделенных объектов!'
Следующие строки проверяют правдивость, пардон за каламбур, сразу двух условий. Первое, что выделенный объект является группой (GroupItem
) и (&&) второе, что эта группа действительно является clipping-маской (свойство clipped
данного объекта равно true
).
if (sel[0].typename == 'GroupItem' && sel[0].clipped == true) {
...
...
}
else {
alert ('Выделение не является объектом-маской!');
};
Тут нужны небольшие пояснения.
Что представляет из себя объект-маска? Это группа, но не обычная. Обычная группа — это набор различных объектов, который подчинен, скажем так, "главному" или "родительскому" объекту. Что касается объекта-маски, то это тоже группа, но в отличии от обычной, она состоит из двух частей — контура маски и ее содержимого. Так вот, определить из скрипта, что перед тобой — обычная группа или группа-маска позволяет ее свойствоclipped
. Если значение свойства clipped равноfalse
(ложь), то это обычная группа, еслиtrue
(правда) — то это clipping-группа.
Пытливые умы заметят, что вместо переменной sel
, которую мы определили ранее, используется конструкция sel[0]
. Объясняется это тем, что выделение, с точки зрения скрипта — это набор (коллекция) элементов, а не конкретный объект (даже если выделен всего один объект). И чтобы проверить тип (typename
) этого объекта на соответствие типу именно выделенного элемента коллекции используется конструкция sel[0]
, которая обращается к первому [0]
элементу коллекции, то есть, в нашем случае, к выделенной группе.
В результате, если выделенный объект является и группой и маской, то дальнейший код выполняется, в противном случае — выводится сообщение: 'Выделение не является объектом-маской!'
С проверками — все. Идем дальше.
Как создавался основной код?
В этой части статьи я попробую не просто прокомментировать как работает код, а описать процесс его создания. Если не весь процесс, то хотя бы некоторые ключевые моменты. Начнем!
Ранее были описаны три действия, которые необходимо выполнить для решения задачи по "разбиранию" Clipping Mask с последующим удалением контура маски. К ним добавится еще одно действие (команда Release), с которого и начнется наш алгоритм. Повторю их здесь, чтобы освежить контекст.
- Выполнить команду Release Clipping Mask
- Убрать выделение с объектов
- Выделить только контур маски
- Удалить контур маски
Если реализовывать данную последовательность действий строго по списку, то первые два пункта можно без труда решить вызовом метода executeMenuCommand()
. Но тогда, на третьем пункте мы столкнемся с нерешаемой проблемой. Как получить ссылку на контур маски, если после первого действия (Release Clipping Mask) никакой маски уже нет, а есть только набор выделенных объектов? Да и он уже структурно не тот, что перед выполнением этой операции.
В общем, логика подсказывает, что для начала надо создать ссылку (reference) на объект контура маски. Подумав над тем, чем же так уникален контур маски по сравнению с обычным контуром, мы обнаружим свойство clipping
у класса PathItem
. Теперь нам остается только перебрать в цикле (for) все объекты группы-маски и найти PathItem
со свойством clipping = true
. Это и будет искомый контур. В результате выполнения этого кода, мы получим ссылку на объект контура маски и сохраним ее в переменной clipPath
.
for (var i = 0; i < clipGroup; i++) {
if (sel[0].pageItems[i].typename == 'PathItem' &&
sel[0].pageItems[i].clipping == true) {
clipPath = sel[0].pageItems[i];
break;
};
};
Что дальше? Вернемся к алгоритму и напишем код для пункта 1. Эта строка выполняет команду Release Clipping Mask, но не через пользовательский интерфейс, а из скрипта. Да, так просто!
app.executeMenuCommand('releaseMask');
Пункты 2 и 3 пропускаем (потому что выделение контура маски, а точнее ссылка на объект clipPath у нас уже есть) и сразу переходим к пункту 4. Здесь мы вызываем метод remove()
объекта clipPath
. Этот метод удаляет контур маски.
clipPath.remove();
На этом, собственно, пока все. Спасибо за внимание!
Надеюсь, вы теперь понимаете, что начать программировать в Adobe Illustrator не так сложно, как кажется на первый взгляд.
P.S. Конечно, получившийся скрипт далеко не идеален. Он не работает с масками, контуры которых представлены объектами CompoundPath
, CompoundShape
или TextFrame
. О том, как доработать скрипт, чтобы он стал действительно полноценным инструментом, читайте во второй части.
Zoolander
Респект.
У меня был скрипт, который позволял верстать в Adobe Illustrator экраны для игр.
Для этого я группировал все объекты, которые должны были выводиться спрайтом и называл их, как нужно. Скрипт проходил по структуре документа, по всем слоям, брал с них верхние группы (не заходя в их иерархию), вычислял их размеры, копировал их в отдельный новый документ того же размера и уже из него выводил PNG с прозрачностью.
после вывода всех PNG — скрипт же выводил JSON-файл c именами и координатами, где нужно на сцене располагать соответствующие спрайты. Ну и по этому JSON-файлу построить сцену в игре было уже элементарным делом