
Illustratorには,手動では使えるもののスクリプトだとコントロールできない機能がたくさんあります。例えば「ブレンド」「段組設定」などですね。
それらを自由に操作するため先人たちは,アクションのソースコードをスクリプトで即時生成・編集して実行する,という技を編み出しました。
でもその技術を使うにはそれなりの知識を持っている必要があります。また,相応の知識を持っていても実際に使おうとすると結構面倒そうです。「これなら諦めて手作業にしたほうが速いかな」なんて予感がよぎり,憂鬱になってしまいますよね。
そこで今回はアクションの即時生成と実行をサポートするJavaScriptのfunctionを紹介します。コピー&ペーストで使えることを目標に書いていますので,読んだ後はある程度簡単に即時生成をコントロールできるようになると思いますよ。
なおスクリプトを書かない方は,読んでもまったく理解できないと思いますのでご注意ください。
まずは参考サイトです。即時生成のことを初めて知った方は手抜きLabさんの記事を読めば雰囲気がつかめると思います。
ではスクリプトの紹介に入ります。functionのソースコードは最後に載せることにして,使用方法を中心にお伝えします。
アクションのソースコードの生成・編集
まずアクションを文字列として記述するわけですが,複数行の文字列を書くには文字を+演算子で何回も連結するなど,何らかの工夫が必要です。とても大変ですね。そこでtemplateというfunctionを作りました。以下のように記述することで,改行を含む文字列を見た目通りに表現できるようになります。
1 2 3 4 5 |
var txt = template(function() {/* 1行目ここに改行→ 2行目ここに改行→ 3行目 */}) ; |
このfunctionでは${ }という形式で式展開ができるようにしてあります。例えば以下のようにして使います。
1 2 3 4 5 |
var myName = 'したたか企画' ; var txt = template(function() {/* 私の名前は ${myName} です。 */}) ; // 私の名前は したたか企画 です。 |
さて,Illustratorアクションのソースコードではアクション名などを特殊な暗号のようなもので表しています。これらを編集するためustringというfunctionを作りました。以下のようにして使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/* 元のテキスト。セット名「セット1」の場合 /name [ 10 e382bbe38383e3838831 ] */ var newName = ustring('アクションセット001') ; var actionCode = template(function() {/* /name [ ${newName.length} ${newName.hex} ] */}) ; /* 結果。セット名「アクションセット001」の場合 /name [ 27 E382A2E382AFE382B7E383A7E383B3E382BBE38383E38388303031 ] */ |
実際にアクションのソースコードを作ってみると次のようになります。パスオブジェクトの中心点を非表示にするアクションです。例では特に必要なかったのでustringは使っていません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
var showCenterPoint = 0 ; // 表示しない場合は0,するなら1 var actionCode = template(function() {/* /version 3 /name [ 7 456c656d656e74 ] /isOpen 1 /actionCount 1 /action-1 { /name [ 15 6869646543656e746572506f696e74 ] /keyIndex 0 /colorIndex 0 /isOpen 1 /eventCount 1 /event-1 { /useRulersIn1stQuadrant 0 /internalName (adobe_attributePalette) /localizedName [ 12 e5b19ee680a7e8a8ade5ae9a ] /isOpen 1 /isOn 1 /hasDialog 0 /parameterCount 1 /parameter-1 { /key 1668183154 /showInPalette 4294967295 /type (boolean) /value ${showCenterPoint} } } } */}) ; |
アクションの読み込みと実行
次に,作ったソースコードをファイルとして書き出します。それをapp.loadActionでアクションとして読み込み,実行することになります。
ここで問題になるのはエラー処理です。テキストを書き出したりアクションを実行する過程でエラーが起きた際には,書きかけのファイルを安全に閉じたり,いらなくなったファイルをゴミ箱に入れたりといった雑務が山ほど出てきます。エラーなしで正常に終了する場合もまた,同じような雑務に追われます。
今回作ったtempActionというfunctionではそれらを全自動でやります。以下のように使ってください。actionCodeにアクションのソースコードが記録されている前提です。
1 2 3 |
tempAction(actionCode, function(actionItems) { actionItems[0].exec() ; }) ; |
これは一見して何をやっているかわからないと思いますが,ソースコードを解析してIllustratorに読み込み,アクションセット内の1つ目のアクションを実行しているところです。
セット名やアクション名はfunction内で自動取得されるため,スクリプトを書く人間はそれらを覚えておく必要はありません。また,ループの中で何度もアクション生成と破棄を繰り返すような無駄を避けられるよう,ブロック構文にしてあります。
アクション実行は次のように表すこともできます。
1 2 3 4 5 |
tempAction(actionCode, function(actionItems) { actionItems.index(-1).exec() ; // 後ろから1つ目のアクション actionItems.getByName('アクション名').exec() ; // アクション名を指定 actionItems['アクション名'].exec() ; // アクション名を指定 }) ; |
それでは実際に使えるスクリプトを用意しました。選択したオブジェクトの中心点を非表示にするスクリプトです。
これでまた少し仕事が速くなりました。今日もさっさと仕事を切り上げて好きなことをしましょう!
コードはこちら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
/** * @fileOverview 選択しているオブジェクトの中心点を非表示にする * @author sttk3.com */ #target 'illustrator' var showCenterPoint = 0 ; (function() { if(app.documents.length <= 0) {return ;} var doc = app.documents[0] ; var sel = doc.selection ; if(sel.length <= 0) {return ;} var actionCode = template(function() {/* /version 3 /name [ 7 456c656d656e74 ] /isOpen 1 /actionCount 1 /action-1 { /name [ 15 6869646543656e746572506f696e74 ] /keyIndex 0 /colorIndex 0 /isOpen 1 /eventCount 1 /event-1 { /useRulersIn1stQuadrant 0 /internalName (adobe_attributePalette) /localizedName [ 12 e5b19ee680a7e8a8ade5ae9a ] /isOpen 1 /isOn 1 /hasDialog 0 /parameterCount 1 /parameter-1 { /key 1668183154 /showInPalette 4294967295 /type (boolean) /value ${showCenterPoint} } } } */}) ; tempAction(actionCode, function(actionItems) { actionItems[0].exec(false) ; }) ; })() ; /** * アクションのソースコード内のNameやustringを作る機能 * @param {String} str 変換したい文字列 * @return {Object} { * source : strそのまま, * hex : utf8の16進数文字コード, * length : 文字数 * } */ function ustring(str) { var tempStr = str.replace(/[0-9A-Za-z!'()*._~-]/g, function(c) { return c.charCodeAt(0).toString(16) ; }) ; tempStr = encodeURIComponent(tempStr).replace(/%/g, '') ; var len = tempStr.length / 2 ; var res = {source : str, hex : tempStr, length : len} ; return res ; } /** * 複数行の文字を簡単に記述するための機能。func内のコメントで囲まれた部分の文字を取り出す<br /> * ${ 式 } という形で式展開できる。\} とエスケープすれば } という文字も使える * @param {Function} func テキストを記入するFunction * @return {String} */ function template(func) { var interpolate = function(str) { // 正規表現のループ展開で,式展開の中身をキャプチャするモデル // Prefix Start Space ( Normal* (?: Escape Normal* )* ) Space End // 例えば ${ expression } の場合 // /\$\{\s*([^\}\\]*(?:\\}[^\}\\]*)*)\s*\}/g // 変更可能な設定。#{ } でも %r| | でも好きなのを書けば良し。 // 目的の文字が正規表現のメタキャラクタの場合はエスケープが必要 var strPrefix = '\\$' ; var strStart = '\\{' ; // 1文字限定 var strEnd = '\\}' ; // 1文字限定 // 変更しない設定 var strBS = String.fromCharCode(92) ; // backslash var strSpace = strBS + 's*' ; var strNormal = '[^' + strBS + strEnd + strBS + strBS + ']' ; var strEscape = strBS + strBS + strEnd ; var strWhole = strPrefix + strStart + strSpace + '(' + strNormal + '*(?:' + strEscape + strNormal + '*)*)' + strSpace + strEnd ; var reg = new RegExp(strWhole, 'g') ; return str.replace(reg, function(m0, m1) { var v = '' ; try { v = eval(m1) ; } catch(e) { v = e ; } return v ; }) ; } ; var outgoingStr = func.toString().match(/\/\*\s*([^]+?)\s*\*\//)[1] ; outgoingStr = interpolate(outgoingStr) ; return outgoingStr ; } /** * アクションを文字列から生成し実行するブロック構文。終了時・エラー発生時の後片付けは自動 * @param {String} actionCode アクションのソースコード * @param {Function} func ブロック内処理をここに記述する * @return なし */ function tempAction(actionCode, func) { // utf8の16進数文字コードをJavaScript内部で扱える文字列に変換する var hexToString = function(hex) { var res = decodeURIComponent(hex.replace(/(.{2})/g, '%$1')) ; return res ; } ; // ActionItemのconstructor。ActionItem.exec()を使えばわざわざ名前を直接指定しなくても実行できる var ActionItem = function ActionItem(index, name, parent) { this.index = index ; this.name = name ; // actionName this.parent = parent ; // setName } ; ActionItem.prototype.exec = function(showDialog) { doScript(this.name, this.parent, showDialog) ; } ; // ActionItemsのconstructor。 // ActionItems['actionName'], ActionItems.getByName('actionName'), // ActionItems[0], ActionItems.index(-1) // などの形式で中身のアクションを取得できる var ActionItems = function ActionItems() { this.length = 0 ; } ; ActionItems.prototype.getByName = function(nameStr) { for(var i = 0, len = this.length ; i < len ; i++) { if(this[i].name == nameStr) {return this[i] ;} } } ; ActionItems.prototype.index = function(keyNumber) { var res ; if(keyNumber >= 0) { res = this[keyNumber] ; } else { res = this[this.length + keyNumber] ; } return res ; } ; // アクションセット名を取得 var regExpSetName = /^\/name\s+\[\s+\d+\s+([^\]]+?)\s+\]/m ; var setName = hexToString(actionCode.match(regExpSetName)[1].replace(/\s+/g, '')) ; // セット内のアクションを取得 var regExpActionNames = /^\/action-\d+\s+\{\s+\/name\s+\[\s+\d+\s+([^\]]+?)\s+\]/mg ; var actionItemsObj = new ActionItems() ; var i = 0 ; var matchObj ; while(matchObj = regExpActionNames.exec(actionCode)) { var actionName = hexToString(matchObj[1].replace(/\s+/g, '')) ; var actionObj = new ActionItem(i, actionName, setName) ; actionItemsObj[actionName] = actionObj ; actionItemsObj[i] = actionObj ; i++ ; if(i > 1000) {break ;} // limiter } actionItemsObj.length = i ; // aiaファイルとして書き出し var failed = false ; var aiaFileObj = new File(Folder.temp + '/tempActionSet.aia') ; try { aiaFileObj.open('w') ; aiaFileObj.write(actionCode) ; } catch(e) { failed = true ; alert(e) ; return ; } finally { aiaFileObj.close() ; if(failed) {try {aiaFileObj.remove() ;} catch(e) {}} } // 同名アクションセットがあったらunloadする。これは余計なお世話かもしれない try {app.unloadAction(setName, '') ;} catch(e) {} // アクションを読み込み実行する var actionLoaded = false ; try { app.loadAction(aiaFileObj) ; actionLoaded = true ; func.call(func, actionItemsObj) ; } catch(e) { alert(e) ; } finally { // 読み込んだアクションと,そのaiaファイルを削除 if(actionLoaded) {app.unloadAction(setName, '') ;} aiaFileObj.remove() ; } } |
2016.07.21 追記 ここから
あるふぁ(仮)さんのアドバイスに基づき追記します。
templateの式展開は,templateを呼び出したスコープでなくtemplateを定義したスコープで実行されるため,ローカル変数が展開できないという問題があるようです。
以下のように変えるとローカル変数を呼び出せるようになる,と教えていただきました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
(function() { var myName = 'したたか企画' ; // (expr),return eval(expr) ; の部分を追加 var txt = template(function(expr) {/* 私の名前は ${myName} です。 */ return eval(expr) ;}) ; })() ; /** * 複数行の文字を簡単に記述するための機能。func内のコメントで囲まれた部分の文字を取り出す<br /> * ${ 式 } という形で式展開できる。\} とエスケープすれば } という文字も使える * @param {Function} func テキストを記入するFunction * @return {String} */ function template(func) { var interpolate = function(str) { // 正規表現のループ展開で,式展開の中身をキャプチャするモデル // Prefix Start Space ( Normal* (?: Escape Normal* )* ) Space End // 例えば ${ expression } の場合 // /\$\{\s*([^\}\\]*(?:\\}[^\}\\]*)*)\s*\}/g // 変更可能な設定。#{ } でも %r| | でも好きなのを書けば良し。 // 目的の文字が正規表現のメタキャラクタの場合はエスケープが必要 var strPrefix = '\\$' ; var strStart = '\\{' ; // 1文字限定 var strEnd = '\\}' ; // 1文字限定 // 変更しない設定 var strBS = String.fromCharCode(92) ; // backslash var strSpace = strBS + 's*' ; var strNormal = '[^' + strBS + strEnd + strBS + strBS + ']' ; var strEscape = strBS + strBS + strEnd ; var strWhole = strPrefix + strStart + strSpace + '(' + strNormal + '*(?:' + strEscape + strNormal + '*)*)' + strSpace + strEnd ; var reg = new RegExp(strWhole, 'g') ; return str.replace(reg, function(m0, m1) { var v = '' ; try { v = func(m1) ; // eval(m1)から変更 } catch(e) { v = e ; } return v ; }) ; } ; var outgoingStr = func.toString().match(/\/\*\s*([^]+?)\s*\*\//)[1] ; outgoingStr = interpolate(outgoingStr) ; return outgoingStr ; } |
また,この機能はjsxbinで保存すると使えなくなってしまいます。ご注意ください。
2016.07.21 追記 ここまで
2017.12.21 追記 ここから
すっかり書くのを忘れていたのですが,ExtendScriptでは '''シングルクォート3つ''' や """ダブルクォート3つ""" で囲むと複数行の文字列を記述できます。
変数の展開は下のような形式で表現します。
1 2 3 4 5 6 |
var newName = ustring('アクションセット001') ; var actionCode = ''' /name [ ''' + newName.length + ''' ''' + newName.hex + ''' ] ''' ; |
せっかく作ったtemplate機能も必要なくなってしまいました……
2017.12.21 追記 ここまで
このスクリプトは無断で転載・配布・編集してもかまいませんが,無断で販売することは禁じます。
使用により生じたあらゆる損害について,私どもは責任を負いません。
スクリプトやファイルのダウンロードを行った時点で,上記の規定に同意したとみなします。