Illustratorのスクリプトで,条件に当てはまるオブジェクトをすべて選択するという処理をしたいことがよくあります。でも,普通にやるとIllustratorがクラッシュしたり,動作が遅すぎて固まることがしばしば。撃墜王の称号をつけたくなりますね。
そこで今回は,Illustratorスクリプトでオブジェクトの選択を速くする方法を紹介します。スクリプトを書かない人には何の役にも立たないのでご注意ください。
例として,縦線だけを選択するスクリプトを挙げます。pathItemが1000個あるうち,以下のように縦の線だけを選択します。
selectedをtrueにする➡8482ms
普通に書いたらこのように,条件にあてはまる線のselectedをtrueに変える処理を書くのではないでしょうか。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
alert((function() { var doc = app.documents[0] ; var startTime = new Date().getTime() ; var targetItems = doc.pathItems ; var currentItem ; // selectedを切り替えるパターン for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {currentItem.selected = true ;} } return (new Date().getTime() - startTime) ; })()) ; |
このコードを私の環境(Mac OS 10.10,メモリ8GB,Illustrator CS6)で試したところ,8482msかかりました。単位はミリ秒で,1000分の1秒を表します。数値が小さいほど速いことを意味します。
selectionに配列を渡す➡8579ms
selectedを1つひとつ切り替えるのでなく,selectionに丸ごと配列を渡します。私はselectedをいちいち切り替えるより速いと思っていたのですが,測ってみたら大して変わらないどころかちょっと遅くなってしまいました。書くのは楽です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
alert((function() { var doc = app.documents[0] ; var startTime = new Date().getTime() ; var targetItems = doc.pathItems ; var currentItem ; // selectionに配列を渡すパターン var newSelection = [] ; for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {newSelection.push(currentItem) ;} } doc.selection = newSelection ; return (new Date().getTime() - startTime) ; })()) ; |
「オブジェクトを選択」アクションを利用➡119ms
アクションにはメモ欄に同じ文字が入っているオブジェクトを選択する機能があり,CS6以降ではJavaScriptからでもそれを利用できます。メモは属性パネルの下のほうにあるテキストフィールドで付与でき,スクリプトからはPageItem.noteにアクセスすることで編集可能です。選択はアクションパネルメニューの「オブジェクトを選択…」アクションで実現します。
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 |
alert((function() { var doc = app.documents[0] ; var startTime = new Date().getTime() ; var targetItems = doc.pathItems ; var currentItem ; // アクションで選択するパターン var selectKeyword = 'a' ; for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {currentItem.note = selectKeyword ;} } var ust = ustring(selectKeyword) ; var actionCode = '''/version 3 /name [ 6 73656c656374 ] /isOpen 1 /actionCount 1 /action-1 { /name [ 6 62794e6f7465 ] /keyIndex 0 /colorIndex 0 /isOpen 1 /eventCount 2 /event-1 { /useRulersIn1stQuadrant 0 /internalName (adobe_setSelection) /localizedName [ 27 e382aae38396e382b8e382a7e382afe38388e38292e981b8e68a9e ] /isOpen 0 /isOn 1 /hasDialog 0 /parameterCount 3 /parameter-1 { /key 1952807028 /showInPalette 4294967295 /type (ustring) /value [ ''' + ust.length + ''' ''' + ust.hex + ''' ] } /parameter-2 { /key 2003792484 /showInPalette 4294967295 /type (boolean) /value 1 } /parameter-3 { /key 1667330917 /showInPalette 4294967295 /type (boolean) /value 1 } } /event-2 { /useRulersIn1stQuadrant 0 /internalName (adobe_attributePalette) /localizedName [ 12 e5b19ee680a7e8a8ade5ae9a ] /isOpen 0 /isOn 1 /hasDialog 0 /parameterCount 1 /parameter-1 { /key 1852798053 /showInPalette 4294967295 /type (ustring) /value [ 0 ] } } }''' ; tempAction(actionCode, function(actionItems) { actionItems[0].exec(false) ; }) ; return (new Date().getTime() - startTime) ; })()) ; // ここにスクリプトでアクションを実行するfunctionが入るが,今は省略 |
これは速さが桁違いです。selectionに配列を渡す方法に比べて70〜80倍くらい高速です。
ちなみに,昔のIllustratorには選択メニューに[共通:メモ]があった気がするのですが,今は見当たりません。まあ誰も使ってなかったのでしょうね。
ロックして全ロック解除➡79ms
「すべてをロック解除」を実行したとき,ロックされていたオブジェクトすべてが選択されます。これを利用し,一旦スクリプトで条件に当てはまるオブジェクトをロックしてから全ロック解除することで,選択範囲を変更するという作戦です。
selectionに配列を渡す方法に比べて100倍くらい速くなりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
alert((function() { var doc = app.documents[0] ; var startTime = new Date().getTime() ; var targetItems = doc.pathItems ; var currentItem ; // ロックして全ロック解除するパターン for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {currentItem.locked = true ;} } app.executeMenuCommand('unlockAll') ; return (new Date().getTime() - startTime) ; })()) ; |
ただし,スクリプトとは関係なくすでにロックされていたオブジェクトがある場合,選択範囲がおかしくなります。ロックされているオブジェクトが完全に把握できているなら使える方法ですね。
ちなみにロックでなく隠すを利用した場合,84msくらいでした。ほとんど変わりないですが,ややロックのほうが速い印象です。見た目を変えなくていい分ロックのほうが速いのかもしれません。
結論
使っているバージョンが対応している限り,なるべくスクリプトに地道な選択をさせず,doScript()やexecuteMenuCommand()を使ったほうが良い。
これでまた少し仕事が速くなりました。今日もさっさと仕事を切り上げて好きなことをしましょう!
コードはこちら
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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
alert((function() { var doc = app.documents[0] ; var startTime = new Date().getTime() ; var targetItems = doc.pathItems ; var currentItem ; // selectionに配列を渡すパターン /* var newSelection = [] ; for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {newSelection.push(currentItem) ;} } doc.selection = newSelection ; */ // selectedを切り替えるパターン /* for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {currentItem.selected = true ;} } */ // ロックして全ロック解除するパターン /* for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {currentItem.locked = true ;} } app.executeMenuCommand('unlockAll') ; */ // 非表示にして全表示するパターン /* for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {currentItem.hidden = true ;} } app.executeMenuCommand('showAll') ; */ // アクションで選択するパターン ///* var selectKeyword = 'a' ; for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; if(currentItem.width == 0 && currentItem.height > 0) {currentItem.note = selectKeyword ;} } var ust = ustring(selectKeyword) ; var actionCode = '''/version 3 /name [ 6 73656c656374 ] /isOpen 1 /actionCount 1 /action-1 { /name [ 6 62794e6f7465 ] /keyIndex 0 /colorIndex 0 /isOpen 1 /eventCount 2 /event-1 { /useRulersIn1stQuadrant 0 /internalName (adobe_setSelection) /localizedName [ 27 e382aae38396e382b8e382a7e382afe38388e38292e981b8e68a9e ] /isOpen 0 /isOn 1 /hasDialog 0 /parameterCount 3 /parameter-1 { /key 1952807028 /showInPalette 4294967295 /type (ustring) /value [ ''' + ust.length + ''' ''' + ust.hex + ''' ] } /parameter-2 { /key 2003792484 /showInPalette 4294967295 /type (boolean) /value 1 } /parameter-3 { /key 1667330917 /showInPalette 4294967295 /type (boolean) /value 1 } } /event-2 { /useRulersIn1stQuadrant 0 /internalName (adobe_attributePalette) /localizedName [ 12 e5b19ee680a7e8a8ade5ae9a ] /isOpen 0 /isOn 1 /hasDialog 0 /parameterCount 1 /parameter-1 { /key 1852798053 /showInPalette 4294967295 /type (ustring) /value [ 0 ] } } }''' ; tempAction(actionCode, function(actionItems) { actionItems[0].exec(false) ; }) ; //*/ return (new Date().getTime() - startTime) ; })()) ; /** * アクションのソースコード内の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 ; } /** * アクションを文字列から生成し実行するブロック構文。終了時・エラー発生時の後片付けは自動 * @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() ; } } |
このサイトで配布しているスクリプトやその他のファイルを,無断で転載・配布・販売することを禁じます。
それらの使用により生じたあらゆる損害について,私どもは責任を負いません。
スクリプトやファイルのダウンロードを行った時点で,上記の規定に同意したとみなします。