Как начать программировать в Adobe Illustrator. Часть первая

Сразу хочу предупредить, что эта серия постов не для матёрых программистов и даже не для программистов вообще. Понимаю, что это звучит крайне вызывающе, учитывая IT-тематику ресурса, и все же позвольте объяснить… В качестве аудитории, я вижу обычных дизайнеров, которые хотели бы начать программировать в среде Adobe, но по каким-то причинам (из-за страха перед неизвестным, неуверенности в своих возможностях или незнания языка) не могут сделать первые шаги в данном направлении. Свою скромную задачу вижу в том, чтобы помочь им понять, что "не боги горшки обжигают" и любой, достаточно мотивированный человек, может научится писать работающий программный код. Вполне возможно, некоторые из них так увлекутся этой игрой, что решат стать настоящими разработчиками. Чем код не шутит?


В этом посте будет рассказано о том, как посредством написания небольшой программы (скрипта на JavaScript) создать свой уникальный инструмент в Adobe Illustrator, который позволит не только сократить ваше время, но и улучшить взаимодействие с этим замечательным графическим редактором. Сначала я сформулирую задачу, затем покажу код, который ее решает и, далее, подробно расскажу о том, как он создавался. Здесь не будут обсуждаться основы Javascript, особенности объектной модели Illustrator или различные редакторы для написания/отладки кода. Эту информацию вы сможете при желании найти сами. Главное, на мой взгляд, это понимание базовых принципов написания программ, на что и делается основной упор в этой статье. Если вы готовы прыгнуть чуть выше своей головы, добро пожаловать под кат!


В Adobe Illustrator есть инструмент Clipping Mask, который работает с обтравочными масками. Clipping Mask содержит три команды: Make, Release и Edit Mask. Первая создает маску, вторая — разбирает, третья — позволяет редактировать. Нас интересует вторая команда, которая разбивает объект Clipping Mask на контур и содержимое маски. Очень часто бывает нужно не просто разобрать маску, а еще и избавиться от самого контура маски, оставив только содержимое. Штатная команда Release Clipping Mask этого не делает, поэтому после ее применения, необходимо выполнить еще три действия:


  1. Убрать выделение с объектов
  2. Выделить только контур маски
  3. Удалить контур маски

Если эту последовательной операций приходится производить достаточно часто в течении дня, то возникает вопрос: а нельзя как-то уменьшить количество этих действий для получения того же результата? И дело здесь вовсе не в лени, а в отсутствии необходимого инструмента. А теперь, представьте на секунду, что такой инструмент у вас есть.


Тут вы ненадолго отвлекаетесь от работы и погружаетесь в мысли о том, как было бы здорово, если в арсенале 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), с которого и начнется наш алгоритм. Повторю их здесь, чтобы освежить контекст.


  1. Выполнить команду Release Clipping Mask
  2. Убрать выделение с объектов
  3. Выделить только контур маски
  4. Удалить контур маски

Если реализовывать данную последовательность действий строго по списку, то первые два пункта можно без труда решить вызовом метода 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. О том, как доработать скрипт, чтобы он стал действительно полноценным инструментом, читайте во второй части.

  • +13
  • 3,1k
  • 1
Поделиться публикацией

Комментарии 1

    0
    Респект.
    У меня был скрипт, который позволял верстать в Adobe Illustrator экраны для игр.
    Для этого я группировал все объекты, которые должны были выводиться спрайтом и называл их, как нужно. Скрипт проходил по структуре документа, по всем слоям, брал с них верхние группы (не заходя в их иерархию), вычислял их размеры, копировал их в отдельный новый документ того же размера и уже из него выводил PNG с прозрачностью.

    после вывода всех PNG — скрипт же выводил JSON-файл c именами и координатами, где нужно на сцене располагать соответствующие спрайты. Ну и по этому JSON-файлу построить сцену в игре было уже элементарным делом

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

    Самое читаемое