Two hats

Design + technologies

illustratorでUIデザイン:スクリプトことはじめ

Adobeの製品は自分で書いたプログラムを動かすことができ、バッチのように自動で大量のタスクをこなしたりすることができます。このような機能は「スクリプト」と「プラグイン」と呼ばれ、それぞれの役割は似ていても作り方や使い方が違ってきます。
今回は「スクリプト」について書いていこうと思います。


私も幾つかスクリプトを作ってみました。
llustratorでUIデザイン:相対座標書出しスクリプト - Two hats
llustratorでUIデザイン:縦横サイズ書出しスクリプト - Two hats
illustratorでUIデザイン:ボタン量産スクリプト - Two hats


作ってみて分かったことですが、想像していたよりも敷居が低いなぁと思いました。
もちろんJavascriptなど言語をある程度知っているという条件がありますが。
やってみると意外と簡単ですよということを知ってもらうために、事始めの記事を書いてみたいと思います。


この記事では単に使い方を紹介するだけでなく、サンプルを通して座標と縦横サイズを書き出すすごく単純なツールを作ってみようと思います。
目的も分からず勉強するより、何かしら役立つものを作ったほうが楽しいですからね。
また、私はWIndowsでもMacでも使えるように言語はJavascriptを使用しています。



1.開発環境を整える

スクリプトは正式には「ExtendScript」と呼ばれ、開発環境である「ExtendScript Toolkit(略:ESTK)」を使って作成します。
Adobe製品を持っている大抵の人はそうだと思いますが、実はこのアプリはご自身のPCに既にインストールされていたりします。
こんなアイコンのアプリです。

f:id:two-hats:20140910010501j:plain
Adobe Creative Cloud
ESTKとillustratorを起動すれば準備OKです。



2.Toolkitの簡単な使い方

f:id:two-hats:20140910010957g:plain

ESTKはコンパクトな開発環境のようなUIをしています。
左側のテキストエリアにプログラムを書き、▲ボタンで実行します。
右側のコンソールエリアにはプログラムの状態を表示したりすることができます。



3.まずは「おまじない」

下記のコードを入力し保存してください。ファイル名は何でもよいです。

#target "illustrator"

(function(){
    //ここから
    //...
    //ここまでに変数や関数などを定義
    
    //main
    try {
        if (app.documents.length > 0 ) {
            // ここから
            // ...
            // ここまでにメインコードを書く
        }
        else{
            throw new Error('ドキュメントが開かれていません。');
        }
    }
    catch(e) {
        alert( e.message, "スクリプト警告", true);
    }
})()

いわゆる「おまじない」というものです。自分のプログラムはコメントアウトしている
//ここから〜
//...
//ここまで...

の場所に書きます。それ以外の場所に書くとエラーがおきてしまったりするのでご注意下さい。



4.コンソールとアラートに何か表示してみる。

メインコードの箇所に下記を入力し、実行してください。

$.writeln("コンソールに表示します");
alert("アラートダイアログを表示します");

すると下図のアラートが出て、ESTKのコンソールに下記のように表示されます。

f:id:two-hats:20140910012755g:plain

f:id:two-hats:20140910012805g:plain

alertはJavascriptの使い方と同じですね。コンソールの方に表示するには$.writeln()というコマンドを使用します。


5.現在選択しているものを検知する。

メインコードの下記のものに書き換えて保存してください。

alert(app.selection.length);

次にillustratorで長方形ツール複数図形を作り、選択した状態でESTKに戻り実行してください。すると「1」や「3」などと選択している図形の数が表示されます。
つまりapp.selectionではillustratorで選択しているオブジェクトにアクセスすることができます。



6.座標を取得する。

メインコードの下記のものに書き換えて保存してください。
そして、図形を選択したまま実行してください。

var obj = app.selection[0];
var x1 = obj.geometricBounds[0];
var y1 = obj.geometricBounds[1];
var x2 = obj.geometricBounds[2];
var y2 = obj.geometricBounds[3];
$.writeln("(x1:" + x1 + ", y1:" + y1 + ")");
$.writeln("(x2:" + x2 + ", y2:" + y2 + ")");

下図のようにコンソールに図形の左上と右下の座標が表示されます。

f:id:two-hats:20140911024347g:plain

app.selection[0];は現在選択されているものの1番目を指します。
オブジェクトにはgeometricBoundsというプロパティがあり、左上のX、左上のY、右下のX、右下のYの順の配列で座標が格納されています。


ここで「あれれ?」と思ったかもしれませんが、Y座標がマイナス値になっています。
illustratorは表面的には下方向にプラスの座標系を採用していますが、スクリプトでは下方向にマイナスになっています。
ややこしいですね。

f:id:two-hats:20140911024457g:plain



7.座標をドキュメントに書いてみる。

コンソールに表示するだけでは直感的でないので、直接ドキュメントに座標を表示してみたいと思います。

var obj = app.selection[0];
var x1 = obj.geometricBounds[0];
var y1 = obj.geometricBounds[1];
var x2 = obj.geometricBounds[2];
var y2 = obj.geometricBounds[3];
            
var coordinateText1 = app.activeDocument.activeLayer.textFrames.add();
coordinateText1.contents = "(x1:" + x1 + ", y1:" + y1 + ")";
coordinateText1.position = [x1, y1];
            
var coordinateText2 = app.activeDocument.activeLayer.textFrames.add();
coordinateText2.contents = "(x2:" + x2 + ", y2:" + y2 + ")";
coordinateText2.position = [x2, y2];

すると図形の近傍に直接座標が表示されるようになりました。

f:id:two-hats:20140911024714g:plain

「app.activeDocument.activeLayer.textFrames.add();」ですが「illustratorアプリの現在アクティブなドキュメントの現在アクティブなレイヤーのテキスト類に新しいテキストを追加しますよー」という命令になります。そのまんまですがw。
ExtendScriptjavascriptでは新しい要素は.add();の命令で追加していく様式になります。


.contentsでは表示したい文字列を格納します。
.positionでは表示する座標を配列で指定してあげます。
すると先ほどの図のように座標が直接ドキュメント上に表示されるようになります。



8.illustratorのオブジェクトツリーの構造を理解する。

先ほどの手順で一気に「わからねー!!!」となった方もいると思うので、ここでillustratorのDOMについて補足しておきます。


applicationを最上位階層とし、その傘下にドキュメントやレイヤーなどがぶら下がった階層構造をしています。
公式リファレンスには下図が載っています。

f:id:two-hats:20140910023541j:plain

http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/illustrator/scripting/cs6/Illustrator-Scripting-Guide.pdf

でも、これだけ見ると「なんじゃこりゃ」って事になりそうなので、あくまでイメージですがDOM構造が分かりやすい図を作ってみました。


f:id:two-hats:20140910023901g:plain

  • 最上位にapplication(変数はapp)がある。
  • appは複数のドキュメント(documents)を持っている。
  • それぞれのドキュメントは複数のレイヤー(layers)を持っている。
  • それぞれのレイヤーには様々な要素群(PathItems, TextFrames, GroupItems, RasterItemsなど)をもっている。
  • ドキュメントに表示されている要素はPageItemsで全てひとまとめにしてアクセスできる。


例えばapp.documents[2].layers[3].pageItems[1];であれば、「3番目のドキュメントの4番目のレイヤーの2番目の何かしら表示されている要素」を指す事になります。
普段illustratorを使っている人であればイメージがつきやすいと思います。


9.関数を作る

座標を表示する部分を関数にして分けたいと思います。
メインコードとは別のところに書くので、一旦プログラムの全容を掲載します。

#target "illustrator"

(function(){ 
    //RGB色を設定
    function setColor(r, g, b){
        var tempColor = new RGBColor();
        tempColor.red = r;
        tempColor.green = g;
        tempColor.blue = b;
        return tempColor;
    }
    
    //座標を書く
    function writeCoordinate(x, y){
        var coordinateText = app.activeDocument.activeLayer.textFrames.add();
        coordinateText.contents = "(x:" + x + ", y:" + y + ")";
        coordinateText.textRange.characterAttributes.fillColor = setColor(255, 0, 0);
        coordinateText.position = [x, -y];
    }
    
    //main
    try {
        if (app.documents.length > 0 ) {
            var obj = app.selection[0];
            var x1 = obj.geometricBounds[0];
            var y1 = obj.geometricBounds[1];
            var x2 = obj.geometricBounds[2];
            var y2 = obj.geometricBounds[3];
            
            //座標の引数はマイナス反転してあげる
            writeCoordinate(x1, -y1);
            writeCoordinate(x2, -y2);
            
        }
        else{
            throw new Error('ドキュメントが開かれていません。');
        }
    }
    catch(e) {
        alert( e.message, "スクリプト警告", true);
    }
})()

座標を書くだけではなく、文字色を赤にするコードも追加しました。
基本的には色を示す属性に値を反映するだけなのですが、RGBColorという要素を作って引き渡す必要があります。


また、この関数に渡す引数のY座標のものはあらかじめ符号を反転しています。
関数内のプログラムを書くときに、Y座標がマイナスのままだと計算式を考えるのが大変になるので。
表示される数値もプラスの値が出るようになりました。
但し、描画するときには更に反転しマイナス座標に合わせるようにします。

f:id:two-hats:20140911025942g:plain



10.幅を示す線を引く

次にオブジェクトの幅を描画してみたいと思います。
下図の関数を追加し、メインコードをちょこっといじってください。

    //....
    //下記の関数を追加する
    //サイズを書く
    function writeSize(x1, y1, x2, y2){
        //幅
        var widthLine = app.activeDocument.activeLayer.pathItems.add();
        widthLine.setEntirePath ([[x1 , -y1], [x1 , -(y1 - 15)], [x2 , -(y1 - 15)], [x2 , -y1]]);
        widthLine.filled = false;
        widthLine.stroked = true;
        widthLine.strokeWidth = 1;
        widthLine.strokeColor = setColor(255, 0, 0);
    }
    //...
    //メインコード
        var obj = app.selection[0];
        var x1 = obj.geometricBounds[0];
        var y1 = obj.geometricBounds[1];
        var x2 = obj.geometricBounds[2];
        var y2 = obj.geometricBounds[3];
            
        writeCoordinate(x1, -y1);
        writeSize(x1, -y1, x2, -y2);
    //....

f:id:two-hats:20140911030746g:plain

アクティブなレイヤーにpathItemを追加し、コの字型になるよう座標を配列で指定してあげます。
スタイルは線太さ1px、色が赤、塗り無しになるよう属性を指定します。



11.幅の数値も表示する。

単に幅の数値を表示するだけならテキストを追加するだけで良いのですが、要素がバラバラになってしまうとあとでillustrator上で編集したいときに面倒くさいことになります。
そこで先ほど描いた線とあらかじめグループになるようにテキストを追加してみたいと思います。


サイズを描く部分のコードを下記に書き換えてください。

    //サイズを書く
    function writeSize(x1, y1, x2, y2){
        //幅グループのオブジェクト
        var widthGroup = app.activeDocument.activeLayer.groupItems.add();
        
        //幅グループに線を追加
        var widthLine = widthGroup.pathItems.add();
        widthLine.setEntirePath ([[x1 , -y1], [x1 , -(y1 - 15)], [x2 , -(y1 - 15)], [x2 , -y1]]);
        widthLine.filled = false;
        widthLine.stroked = true;
        widthLine.strokeWidth = 1;
        widthLine.strokeColor = setColor(255, 0, 0);
        
        //幅グループに数値を表示するテキストを追加
        var widthText = widthGroup.textFrames.add();
        widthText.contents = x2 - x1;
        widthText.textRange.characterAttributes.fillColor = setColor(255, 0, 0);
        widthText.position = [x1 + (x2 - x1) / 2, -(y1 - 30)];
    }


先ほどと違うのは、あらかじめGropuItemを作成し、それにパスやテキストを追加しているところです。
このようにすると要素をグループとしてまとめることが出来ます。

f:id:two-hats:20140911031739g:plain

線のそばに数値が表示されるようになりました。


12.仕上げ

ここまできたらほぼ完成です。
仕上げとして縦方向の座標を表示し、座標の数値の位置をオブジェクトに被らないようにずらしてあげます。
さらにメインコードを変更し、複数選択したもの全てに対して書出しを行うように書き換えてあげます。

    //...
    //サイズを描く関数を下記に書き換える
    //サイズを書く
    function writeSize(x1, y1, x2, y2){
        //幅グループのオブジェクト
        var widthGroup = app.activeDocument.activeLayer.groupItems.add();
        
        //幅グループに線を追加
        var widthLine = widthGroup.pathItems.add();
        widthLine.setEntirePath ([[x1 , -y1], [x1 , -(y1 - 15)], [x2 , -(y1 - 15)], [x2 , -y1]]);
        widthLine.filled = false;
        widthLine.stroked = true;
        widthLine.strokeWidth = 1;
        widthLine.strokeColor = setColor(255, 0, 0);
        
        //幅グループに数値を表示するテキストを追加
        var widthText = widthGroup.textFrames.add();
        widthText.contents = x2 - x1;
        widthText.textRange.characterAttributes.fillColor = setColor(255, 0, 0);
        widthText.position = [x1 + (x2 - x1) / 2, -(y1 - 30)];
        
        //高さグループのオブジェクト
        var heightGroup = app.activeDocument.activeLayer.groupItems.add();
        
        //高さグループに線を追加
        var heightLine = heightGroup.pathItems.add();
        heightLine.setEntirePath ([[x1 , -y1], [x1 - 15 , -y1], [x1 - 15 , -y2], [x1 , -y2]]);
        heightLine.filled = false;
        heightLine.stroked = true;
        heightLine.strokeWidth = 1;
        heightLine.strokeColor = setColor(255, 0, 0);

        //高さグループに数値を表示するテキストを追加
        var heightText = heightGroup.textFrames.add();
        heightText.contents = x2 - x1;
        heightText.textRange.characterAttributes.fillColor = setColor(255, 0, 0);
        heightText.position = [x1 - 35, -(y1 + (y2 - y1) / 2)];
    }
    //...
    //メインコードを下記に書き換える
            var sl = app.selection.length;
            if(sl > 0){
                for(i = 0; i < sl; i++){
                    var x1 = app.selection[i].geometricBounds[0];
                    var y1 = app.selection[i].geometricBounds[1];
                    var x2 = app.selection[i].geometricBounds[2];
                    var y2 = app.selection[i].geometricBounds[3];
                    
                    writeCoordinate(x1, -y1);
                    writeSize(x1, -y1, x2, -y2);
                }
            } else {
                alert( "オブジェクトを選んでから実行してください。", "スクリプト警告", true);
            }
    //...

複数要素を選択してから実行してあげると下図のように表示されます。

f:id:two-hats:20140911032404g:plain

見栄えの問題はありますが座標と縦横サイズを書き出すスクリプトができあがりました。
こんな感じでillustratorスクリプトを作成していくことが出来ます。

作成したプログラムの完成形は下記からダウンロードしてください。
illustratorスクリプトことはじめ.jsx



ドキュメント

公式ドキュメントは下記にあります。お使いのバージョンのものを参照下さい。
Illustrator Scripting | Adobe Developer Connection


ただ、英語はなぁ・・・という方は、古旗さんの書籍を参考にすると良いと思います。
(私はまだ読んでいないのですが ^^;)

ExtendScript Toolkit(ESTK)基本編 (Adobe JavaScriptシリーズ(NextPublishing))

ExtendScript Toolkit(ESTK)基本編 (Adobe JavaScriptシリーズ(NextPublishing))

Adobe JavaScriptリファレンス (NextPublishing)

Adobe JavaScriptリファレンス (NextPublishing)

2015/8/18 新しい本が発売されていたので追記します。

ペーパーバック板
Illustrator自動化基本編 (Adobe JavaScriptシリーズ(NextPublishing))


ペーパーバック板
Photoshop自動化基本編 (Adobe JavaScriptシリーズ(NextPublishing))


プラグインについて

つい先日知ったのですが、今年の6月に発表があったようですがプラグインHTML5の技術で作成出来るようになったそうです。
対応しているバージョンはCCからのようです。

Introducing HTML5 extensions | Adobe Developer Connection
A Short Guide to HTML5 Extensions | Adobe Developer Connection


AdobeエバンジェリストのAndyさんが日本語で記事を書いていますのでそちらも参考にしてください。
CEPスーパー メガ ガイド: HTML5+Node.jsでAdobeのツールを拡張する | // andy hall


スクリプトだと毎度毎度「ファイル>スクリプト>該当のスクリプト」と選ばなければなりませんでしたが、プラグイン形式にできるとillustratorのパネルとして常駐させておくことができます。


そのパネル自体もHTMLで作成できるようになるので、Javascriptを勉強していたひとにとっては朗報ですね。
私はCS6の環境なので、そのうちCCにアップグレードしたら挑戦してみようかと思います。


この記事を読んで、スクリプトプラグイン作ってみようかな〜と思っていただけたら幸いです!


2015/8/18
スクリプトだけで常駐パネルを作る方法をブログに書きました。extension化は結構面倒なので、個人で使う分にはこちらの方法で十分だと思います。

2-hats.hateblo.jp