ストックフォトサービスのPIXTAは,EPSベクターイラストのリジェクト項目を検索して選択するIllustrator用スクリプトを公開しています(【ベクター素材制作用】リジェクト項目の自動チェックスクリプトを公開しました | PIXTA Channel)。
リジェクト項目とは,例えば孤立点や塗りだけのオープンパス・非表示のオブジェクトなどです。どれも目視で見つけるのは難しいので,内部データを使って探してくれるのはありがたいですね。
ところがこのスクリプトは,複合パスの中にあるリジェクト項目を検出できないことがあります。これは複合パスの配下にグループが存在するのに気づかないのが原因のようです。現状では複合パスを解除してからスクリプトを実行する決まりになっていますが,できればそんなことはせずに検出してほしいですよね。
そこで今回は複合パスのままPIXTA用ベクターイラストのNGチェックをするIllustrator用JavaScriptを紹介します。ついでなので,レイヤーロック解除を自動にしたり高速化したりもっと便利にします。
こちらのスクリプトをダウンロードしてください。仕様の都合上,Illustrator CS6以降で使えます。
vectorValidate.jsx
無料で公開していますが,気に入ったらしたたか企画|noteで公開している有料スクリプトを買っていただくとか,SNSで宣伝していただけると嬉しいです。
あらましを教えて
複合パスのままPIXTA用ベクターイラストのNGチェックをするIllustrator用JavaScriptです。次の項目をリジェクト項目とみなし,選択します。
- 塗りだけのオープンパス
- 孤立点
- テキストフレーム
- ラスター画像・埋め込み画像・リンク画像
- 非表示のオブジェクト
おすすめポイントはこちらです。
- 複合パスの解除なしで検出可能
- レイヤーのロックを自動で解除,非表示は表示にする
- 全オブジェクトのロックを自動で解除する
- 非表示オブジェクトは表示して選択する
書類上の全オブジェクトの数が多いと時間がかかりますが,私の環境ではオブジェクト7000個で9秒くらいでした。そこまで多くない通常の範囲なら一瞬で終わります。
使いかた
書類を開いた状態でスクリプトを実行するだけです。NG項目があったら選択します。NG項目の有無に関係なく,確認が終わったらダイアログで通知します。
これでまた少し仕事が速くなりました。今日もさっさと仕事を切り上げて好きなことをしましょう!
コードはこちら
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 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
/** * @file PIXTAのベクター素材NGチェック * @version 1.0.1 * @author sttk3.com * @copyright (c) 2019 sttk3.com */ //#target 'illustrator' (function() { $.localize = true ; // Illustrator CS6以前は終了する。高速化のため var aiVersion = appVersion()[0] ; if(aiVersion < 16) { alert({en : 'This script requires Illustrator CS6 or later', ja : 'このスクリプトは Illustrator CS6 以降のみに対応しています。'}) ; return ; } // 時間計測用 //var startDate = new Date() ; if(app.documents.length <= 0) {return ;} var doc = app.documents[0] ; doc.pageOrigin = doc.rulerOrigin = [0, 0] ; // documentがもつcollectionはグループや複合パスの中身も拾うが,各レイヤーのcollectionをたどるより遅い var rasterImages = doc.rasterItems ; var lenRaster = rasterImages.length ; var linkImages = doc.placedItems ; var lenLink = linkImages.length ; var pathList = doc.pathItems ; var lenPath = pathList.length ; // すべてのレイヤーを編集可能にする tempAction(actionCode(), function(actionItems) { actionItems[0].exec(false) ; }) ; // ロックされたアイテムをロック解除 unlockAll() ; // すべての選択を解除 unselectAll() ; // 非表示のオブジェクトをロック=記録 findLock('showAll') ; // 孤立点をロック=記録 findLock('Stray Points menu item') ; // テキストフレームをロック=記録 findLock('Text Objects menu item') ; // ラスター画像をロック=記録 for(var i = 0 ; i < lenRaster ; i++) { lockItem(rasterImages[i]) ; } // リンク画像をロック=記録 for(var i = 0 ; i < lenLink ; i++) { lockItem(linkImages[i]) ; } var currentItem ; for(var i = 0 ; i < lenPath ; i++) { currentItem = pathList[i] ; if(!currentItem.closed) { if(currentItem.filled && !currentItem.guides) { // 塗りのあるオープンパスを記録 lockItem(currentItem) ; } else if(currentItem.pathPoints.length <= 1) { // 孤立点を記録 lockItem(currentItem) ; } } } // ロック解除=対象をすべて選択 unlockAll() ; // 時間計測用 //alert(new Date() - startDate) ; // 結果を言葉でも知らせる var ngLen = doc.selection.length ; var msg, title ; if(ngLen <= 0) { title = {en : 'Safely', ja : '正常'} ; msg = {en : 'Invalid items were not found.', ja : 'NG項目は見つかりませんでした。'} ; } else { title = {en : 'Nomaly Detection', ja : '異常検出'} ; ngLen = ngLen.toString() ; msg = {en : 'Invalid item count : ' + ngLen, ja : ngLen + ' 個のNG項目を検出しました。'} ; } alert(msg, title) ; return true ; })() ; /** * スクリプト実行元アプリケーションのバージョンを取得して数値の配列にする。16.0.4の場合[16, 0, 4] * @return {Array of numbers} */ function appVersion() { var tmp = app.version.toString().split('.') ; var res = [] ; for(var i = 0, len = tmp.length ; i < len ; i++) { res.push(Number(tmp[i])) ; } return res ; } /** * すべてのロックを解除する * @return なし */ function unlockAll() { app.executeMenuCommand('unlockAll') ; } /** * すべての選択を解除する * @return なし */ function unselectAll() { app.selection = [] ; } /** * 検索コマンドを実行し,選択されたアイテムをロックする(後でまとめて選択できる) * @param {String} keyStr 検索メニューコマンド * @return なし */ function findLock(keyStr) { app.executeMenuCommand(keyStr) ; app.redraw() ; if(app.selection.length > 0) {app.executeMenuCommand('lock') ;} } /** * アイテムをロックする * @param {PageItem} aItem 対象のページアイテム * @return なし */ function lockItem(aItem) { try { aItem.locked = true ; } catch(e) { // skip } } /** * アイテムを選択する。数が多いとき,app.selection = Arrayより諦めずに続けてくれる * @param {PageItem} aItem 対象のページアイテム * @return なし */ function selectItem(aItem) { aItem.selected = true ; } /** * アクションを文字列から生成し実行するブロック構文。終了時・エラー発生時の後片付けは自動 * @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() ; } hexToString = ActionItem = ActionItems = null ; } /** * レイヤーをすべて表示し,ロックを解除するアクションのソースコード * @return {String} */ function actionCode() { return '''/version 3 /name [ 15 5f5f7374746b335f6c617965725f5f ] /isOpen 1 /actionCount 1 /action-1 { /name [ 10 746f4564697461626c65 ] /keyIndex 0 /colorIndex 0 /isOpen 1 /eventCount 2 /event-1 { /useRulersIn1stQuadrant 0 /internalName (ai_plugin_Layer) /localizedName [ 12 e383ace382a4e383a4e383bc ] /isOpen 1 /isOn 1 /hasDialog 0 /parameterCount 3 /parameter-1 { /key 1836411236 /showInPalette 4294967295 /type (integer) /value 9 } /parameter-2 { /key 1937008996 /showInPalette 4294967295 /type (integer) /value 22 } /parameter-3 { /key 1851878757 /showInPalette 4294967295 /type (ustring) /value [ 42 e38199e381b9e381a6e381aee383ace382a4e383a4e383bce38292e383ade383 83e382afe8a7a3e999a4 ] } } /event-2 { /useRulersIn1stQuadrant 0 /internalName (ai_plugin_Layer) /localizedName [ 12 e383ace382a4e383a4e383bc ] /isOpen 1 /isOn 1 /hasDialog 0 /parameterCount 3 /parameter-1 { /key 1836411236 /showInPalette 4294967295 /type (integer) /value 7 } /parameter-2 { /key 1937008996 /showInPalette 4294967295 /type (integer) /value 17 } /parameter-3 { /key 1851878757 /showInPalette 4294967295 /type (ustring) /value [ 33 e38199e381b9e381a6e381aee383ace382a4e383a4e383bce38292e8a1a8e7a4 ba ] } } }''' ; } |
このサイトで配布しているスクリプトやその他のファイルを,無断で転載・配布・販売することを禁じます。
それらの使用により生じたあらゆる損害について,私どもは責任を負いません。
スクリプトやファイルのダウンロードを行った時点で,上記の規定に同意したとみなします。
したたか企画様
素晴らしいスクリプトありがとうございます。
とても重宝しております。
一つご提案なのですが、
チェック終了時にもしNG項目があった場合に
「NGあり」「NGなし」のアラートを数秒表示してもらあえと嬉しいのですが、
難しいでしょうか。
孤立点が一個だけあったときなどに見逃しを減らせそうですので
ぜひご検討のほどよろしくお願いいたします。
ご感想ありがとうございます。
NGあり/なしでメッセージの文言を変えるのは簡単にできます。違ったほうが親切ですね。アラートの秒数を長くするみたいな意味なら難しいです。
とりあえずメッセージの文言は変えてみます。
もう修正してくださったんですね。
しかもNG項目の残数も表示も!
このスクリプトのおかげで作業中に定期的にチェックする回数が増え
効率がUPしております。
本当にありがとうございます!
役に立っているようで良かったです。
周りのかたにもどんどん布教してください。