[English version]

Правило хорошего тона и важные нюансы скриптостроения

Первые скрипты для Иллюстратора мы с Вами уже написали. Теперь давайте делать – это правильно :)

В Иллюстраторе при запуске скриптов иногда вылетает такая ошибка:

An Illustrator error occurred: 1346458189 (“PARM”)

И очень сложно понять причину, откуда она взялась. Код написан правильный, все корректно. А ошибка появляется. При повторном запуске скрипта или перезапуске Иллюстратора ошибка может не появиться..

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

Чтобы избежать этой проблемы разработчики Illustrator рекомендуют все переменные объявлять внутри функции. Тогда после завершения функции все переменные автоматически обнулятся. На примере Hello World это должно выглядеть так:

 //Создаем функции, внутри которой уже проводим все работы с переменными.
 function main () {
   //Hello World!
   var myDocument = app.documents.add();
   //Создаем новый текстовый фрейм и присваиваем его переменной  "myTextFrame"
   var myTextFrame = myDocument.textFrames.add();
   // Устанавливаем текст во фрейме и его местоположение
   myTextFrame.position = [200,200];
   myTextFrame.contents = "Hello World!"
   }
 //Вызываем эту функция, чтобы она запустилась и выполнила всю работу
 main();

Я рекомендую сразу привыкать к такой практике, чтобы потом не переписывать большие скрипты, которые вдруг начали глючить. Дальнейшие скрипты в уроках для упрощения я буду писать без этой обертки в функцию main().

Создание объектов типа PathItem

Основным объектом в Иллюстраторе можно считать контур или путь, по английски Path, а в терминологии скрипта PathItem. Пути хранятся в массиве pathItems внутри документа, слоя или группы. Чтобы создать путь, нужно обратиться к этому массиву. Давайте рассмотрим следующий пример, демонстрирующий работу с путями:

var doc = app.activeDocument;
//толстая обводка и заливка при создании новых объектов
doc.defaultStroked = true;
doc.defaultFilled = true;
doc.defaultStrokeWidth = 10;

//Создаем прямоугольник (Y верхнего левого угла,X, ширина, высота, reversed)
//Если reversed = false, то порядок точек по часовой стрелке, если true - то против часовой
var rect = doc.pathItems.rectangle( 762.5, 87.5, 425.0, 75.0 ,false );

//Прямоугольник со скругленными углами (Y верхнего левого угла,X, ширина, высота, горизонтальный радиус, вертикальный радиус)
var rndRect = doc.pathItems.roundedRectangle(637.5, 87.5, 425.0, 75.0, 20.0, 10.0 );

// Эллипс (Y верхнего левого угла,X, ширина, высота, reversed, inscribed)
// Если inscribed - false , то  эллипс будет описывать прямоугольник, описанный первыми четырьмя координатами, 
// Если inscribed - true , то эллипс будет вписан в этот же прямоугольник
var ellipse = doc.pathItems.ellipse(512.5, 87.5, 425.0, 75.0, false, false );

// Создать многоульник (centerX, centerY, радиус, количество сторон)
var octagon = doc.pathItems.polygon( 300.0, 325.0, 75.0, 8 );

// Создать звезду (centerX, centerY, внешний радиус, внутренний радиус, количество концов)
var star = doc.pathItems.star( 300.0, 125.0, 100.0, 20.0, 5 );

// Теперь создадим линию из двух точек
var line = doc.pathItems.add();
line.setEntirePath( Array( Array(20, 475), Array(175, 300) ) );
// Добавим еще одну точку к линии 
var newPoint = line.pathPoints.add();
newPoint.anchor = Array(20, 300);
//Установим координаты левой ручки, чтобы получился отрезок дуги
newPoint.leftDirection = Array(10,200);
newPoint.rightDirection = newPoint.anchor;

Запустите скрипт и проведите эксперименты. Разберитесь, зачем нужна каждая координата, как она влияет на поведение скрипта.

Работа с координатами

В Иллюстраторе принята двухмерная система координат. У каждого объекта есть свойство position. В нем записана пара координата x, y , которые указывают на левый верхний угол границ объекта. Координаты можно посмотреть в панели Info, когда объект выделен в Иллюстраторе.

Для каждого объекта есть три прямоугольника, которые описывают размеры содержимого объекта.

  • Геометрические границы (geometricBounds) – это прямоугольник, в котором содержится весь объект, за исключением его обводок
  • Видимые границы (visibleBounds) – содержит объект с учетом обводок
  • Контрольные границы (controlBounds) – содержит все точки объекта с учетом точек, направляющих дуги. Левый верхний угол этих границ соответствует точке position


03-границы объекта

Работа с артбордами

Артборды (монтажные области) находятся в массиве Document.artboards

Номер текущего артборда можно получить, вызвав функцию в Document.artboards.getActiveArtboardIndex()

Данные по расположению и размеру артборда – в Artboard.artboardRect. Данные представлены в виде массива из четырех координат. Первые две координаты указывают на левый верхний угол артборда, вторые две – на правый нижний.

Есть важный нюанс работы с координатами артбордов. Когда мы задаем координаты через Illustrator, то координата Y будет увеличиваться сверху вниз. А когда обратимся к созданному артборду через скрипт, то координата будет расти снизу вверх.

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

x: 320
y: 300
width: 200
height: 100
(координаты x и y указаны для верхнего левого угла)

В скрипте он будет обозначен массивом координат:

x1 : 320
y1 : -300
x2 : 520
y2 : -400

Практика – скрипт для паттернов

Хватит теории, давайте напишем что-нибудь интересное :) Сделаем полезный скрипт, который выделенные объекты, расположенные на границах активного артборда дублирует на другую сторону. Это позволит потом сделать бесшовный паттерн.

Нужно понять при каких условиях необходимо дублировать объект и в какую сторону. Нарисуем случаи по горизонтали.

lesson 2

По вертикали случаи аналогичные. А теперь пишем код :)

function main () {
 var doc = app.activeDocument;
 //Получаем размеры артборда
 var aIndex = doc.artboards.getActiveArtboardIndex();
 var artboard = doc.artboards[aIndex];
 var aRect = artboard.artboardRect;
 //Сохраняем в отдельные переменные для удобства
 var aLeft = aRect[0];
 var aRight = aRect[2];
 var aTop = aRect[1];
 var aBottom = aRect[3];
 var aWidth = aRight - aLeft;
 var aHeight = aTop - aBottom;
 //Выделенные объекты находятся в массиве doc.selection их необходимо скопировать в отдельный массив, иначе при копировании объектов массив с выделением нарушится
 var sel = [];
 for (var i = 0 ; i < doc.selection.length; i++) {
   sel.push(doc.selection[i]);
   }
 for (var i = 0 ; i < sel.length; i++) {
   var item = sel[i];
   //Работаем с видимыми границами, чтобы в случае пересечения обводкой границы артборда - объект тоже копировался
   var iBounds = item.visibleBounds;
   var iLeft = iBounds[0];
   var iRight = iBounds[2];
   var iTop = iBounds[1];
   var iBottom = iBounds[3];
   var copyItem = undefined;
   var copyItem2 = undefined;
  
   //Если объект пересекает только левую границу артборда
   if ((iLeft < aLeft) && (iRight > aLeft) && (iRight < aRight)) {
     copyItem = item.duplicate(item, ElementPlacement.PLACEAFTER);
     copyItem.translate(aWidth,0);
     }

   //Если объект пересекает только правую границу артборда
   if ((iLeft > aLeft) && (iLeft < aRight) && (iRight > aRight)) {
     copyItem = item.duplicate(item, ElementPlacement.PLACEAFTER);
     copyItem.translate(-aWidth,0);
     }

   //Если объект пересекает только нижнюю границу артборда
   if ((iBottom < aBottom) && (iTop > aBottom) && (iTop < aTop)) {
     copyItem2 = item.duplicate(item, ElementPlacement.PLACEAFTER);
     copyItem2.translate(0,aHeight);
     //Если мы делали копию по горизонтали, ее тоже нужно дублировать по вертикали
     if (copyItem !== undefined) {
       copyItem3 = copyItem.duplicate(copyItem, ElementPlacement.PLACEAFTER);
       copyItem3.translate(0,aHeight);
       }
     }

   //Если объект пересекает только верхнюю границу артборда
   if ((iBottom > aBottom) && (iBottom < aTop) && (iTop > aTop)) {
     copyItem2 = item.duplicate(item, ElementPlacement.PLACEAFTER);
     copyItem2.translate(0,-aHeight);
     if (copyItem !== undefined) {
       copyItem3 = copyItem.duplicate(copyItem, ElementPlacement.PLACEAFTER);
       copyItem3.translate(0,-aHeight);
       }
     }
   }
 }

main ();

Попробовали? Все работает? А теперь домашнее задание: написать скрипт, который копирует выделенные объекты, при этом копирует каждый объект 10 раз и ставит в случайное место на артборде. Должен получиться своеобразный генератор, размножающий объекты. Маленькая подсказка – Вам понадобится функция Math.random()

Есть вопросы? Идеи и предложения? Пиши в группе ВК: https://vk.com/topic-159903417_38752810