Illustratorの[線幅ツール]は線を部分的に太くまたは細くし,強弱をつける機能です。線幅ツールで作成した強弱の設定は[線幅プロファイル]として保存したり,それを呼び出したりできます。
[線幅プロファイル]は自分で作らなくても,最初からいつくか用意されています。中でも両端が細くなった[線幅プロファイル 1]や片側に向かってだんだん細くなる[線幅プロファイル 4]は汎用性が高いため,使う機会が多いのではないでしょうか。
しかしこの設定,適用するのに[線パネル][アプリケーションバー][プロパティパネル]などの該当部分を押すしかありません。
アクションやグラフィックスタイルに登録しようにも[線幅プロファイル]だけを記録することはできないようです。線の色や太さなど色々な情報が一緒についてきてしまいます。うまくいきませんね。
そこで今回はファイル名を利用して指定した線幅プロファイルを適用するJavaScriptを紹介します。スクリプトにショートカットを割り当てるソフトとの組み合わせで,キー呼び出しを実現する作戦です。
こちらのスクリプトをダウンロードし,SPAiやKeyboard Maestroなどショートカット設定ソフトに登録してください。
仕様の都合上,Illustrator CS6以降で使うことができます。
sttk3-setWidthProfile.zip
設定方法
次のどちらかの方法でスタイルにキーボードショートカットを割り当てます。
ファイル名を利用して設定する
- 線幅プロファイルを適用するJavaScriptのファイル名を widthProfile=プロファイル名.jsx のような特定の形式にすることで,適用するプロファイル名を指定(ファイル名の再編集により,それらの設定を変えることができる)
- スクリプトファイルをキーボードショートカット設定ソフトに登録し,自分の好きなキーを割り当てる
- 割り当てたいプロファイルの数だけ前のステップを繰り返す
例えばSPAiではこのようになります。
こちらの方式は,以前の記事
【解決】スタイルにショートカットを割り当てたい!が参考になります。
スクリプト実行時の引数を利用して設定する
- 線幅プロファイルを適用するJavaScriptのファイル名を widthProfile=argv.jsx に固定する
- キーボードショートカット設定ソフトにjsxファイル実行コードを登録,引数に適用するプロファイル名を指定し,自分の好きなキーを割り当てる
1tell application "Adobe Illustrator" to do javascript file "Macintosh HD:Users:[name]:Desktop:widthProfile=argv.jsx" with arguments "プロファイル名"
- 割り当てたいプロファイルの数だけ前のステップを繰り返す
こちらの方式は,macOSであれば別の記事で書いたヘルパーを使うと楽に設定できます。
使いかた
線幅プロファイルを適用したいアイテムを選択し,スクリプトを実行するだけです。
注意点
こちらのスクリプトでは内部でアクションを利用しているのですが,アクションを使うと線幅プロファイルだけ指定してもなぜか線端と角の形状(Illustrator 2020では破線も)が変わってしまいます。対策として,あらかじめそれらを記録しておき,線幅プロファイル指定後もとの状態を再設定しています。
加えて,残念ながら現状ではスクリプトを使って線幅プロファイルを均等に戻す方法がわかりません。面倒ですが,戻したいときは手動で戻してください。
これでまた少し仕事が速くなりました。今日もさっさと仕事を切り上げて好きなことをしましょう!
コードはこちら
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 |
/** * @file ファイル名,またはargumentsで指定した線幅プロファイルを適用する<br /> * widthProfile=profileName.jsx のprofileNameがプロファイル名を表す。widthProfile=は識別コードとして必要。<br /> * profileNameにargvを指定するとargumentsからプロファイル名を取得する * @version 1.0.1 * @author sttk3.com * @copyright (c) 2020 sttk3.com */ //#target 'illustrator' (function(argv) { $.localize = true ; // Illustrator CS6以前は終了する。アクション読み込み必須なので var aiVersion = appVersion()[0] ; if(aiVersion < 16) { alert({en : 'This script requires Illustrator CS6 or later', ja : 'このスクリプトは Illustrator CS6 以降のみに対応しています。'}) ; return ; } if(app.documents.length <= 0) {return ;} var doc = app.documents[0] ; var sel = allPageItemOfSelection(doc.selection) ; if(sel.length <= 0) {return ;} var targetItems = filterItems(function(aItem) {return /^PathItem$/.test(aItem.constructor.name)}, sel) ; if(targetItems.length <= 0) {return ;} // ファイル名から引数(線幅プロファイル名)を生成 var tempArgv = parseArguments(decodeURIComponent(new File($.fileName).name), argv) ; if(tempArgv == null) { alert('ファイル名から動作を読み取れませんでした。') ; return ; } var profileName = decodeURIComponent(combineDakuten(encodeURIComponent(tempArgv.option))) ; // 線端と角の形状(2020では破線も)が変わってしまうので,もとの状態を記録しておく var db = [] ; var currentItem ; for(var i = 0, len = targetItems.length ; i < len ; i++) { currentItem = targetItems[i] ; db.push({ 'item' : currentItem, 'strokeJoin' : currentItem.strokeJoin, 'strokeCap' : currentItem.strokeCap, 'strokeDashes' : currentItem.strokeDashes }) ; } // 可変線幅プロファイルを適用する doc.selection = targetItems ; tempAction(actionCode(profileName), function(actionItems) { actionItems[0].exec(false) ; }) ; // 線端と角を元に戻す for(var i = 0, len = db.length ; i < len ; i++) { currentItem = db[i] ; if(currentItem.item.strokeJoin != currentItem.strokeJoin) {currentItem.item.strokeJoin = currentItem.strokeJoin ;} if(currentItem.item.strokeCap != currentItem.strokeCap) {currentItem.item.strokeCap = currentItem.strokeCap ;} if(aiVersion >= 24) { // 破線のON|OFF・間隔を元に戻す if(currentItem.item.strokeDashes != currentItem.strokeDashes) {currentItem.item.strokeDashes = currentItem.strokeDashes ;} } } })(arguments) ; /** * widthProfile=profileName.jsxのような文字列を解析する * @param {String} str 対象の文字列 * @param {Arguments} argv ファイル実行引数 * @return {Object} {mode: String, option: String} */ function parseArguments(str, argv) { var res ; var matchObj = str.match(/^(widthProfile)[\s ]*[==][\s ]*(.+?)[\s ]*(?:\.js(?:x|xbin)?)?$/i) ; if(!matchObj) {return res ;} var mode = matchObj[1] ; mode = toHankaku(mode) ; mode = mode.toLowerCase() ; var opt = matchObj[2] ; // 単一ファイルで引数を受け取るパターンに対応する if(opt == 'argv') { var tempStr = $.getenv('sttk3_arguments') ; if(tempStr == '') { if(argv.length <= 0) {return res ;} tempStr = argv[0].toString() ; } res = {mode: mode, option: tempStr} ; } else { res = {mode: mode, option: opt} ; } return res ; } /** * 全角英数を半角にする * @param {String} str 対象の文字 * @return {String} */ function toHankaku(str) { var res = str.replace(/[A-Za-z0-9_]/g, function(s) {return String.fromCharCode(s.charCodeAt(0) - 0xFEE0)}) ; return res ; } /** * スクリプト実行元アプリケーションのバージョンを取得して数値の配列にする。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 ; } /** * selectionからgroupItemの中身を含めたすべてのpageItemを返す * @param {Array} sel selection * @return {Array} */ function allPageItemOfSelection(sel) { var res = [] ; for(var i = 0, len = sel.length ; i < len ; i++) { var currentItem = sel[i] ; switch(currentItem.constructor.name) { case 'GroupItem' : res.push(currentItem) ; res = res.concat(arguments.callee(currentItem.pageItems)) ; break ; default : res.push(currentItem) ; break ; } } return res ; } /** * Array.filterみたいなもの * @param {Function} func 条件式 * @param {Array} targetItems 対象のArrayかcollection。lengthとindexがあれば何でもいい * @return {Array} */ function filterItems(func, targetItems) { var res = [] ; for(var i = 0, len = targetItems.length ; i < len ; i++) { if(i in targetItems) { var val = targetItems[i] ; if(func.call(targetItems, val, i)) {res.push(val) ;} } } return res ; } /** * 分割された濁点・半濁点を結合された文字にする。ウムラウトなどは無視する * @param {String} str 対象の文字列。URIエンコードされたもの * @return {String} 変換されたテキスト */ function combineDakuten(str) { // 置換対象の文字とその順番 // ゔがぎぐげござじずぜぞだぢづでどばぱびぴぶぷべぺぼぽヴガギグゲゴザジズゼゾダヂヅデドバパビピブプベペボポ // 分割された文字 var nfd = ['%E3%81%86%E3%82%99', '%E3%81%8B%E3%82%99', '%E3%81%8D%E3%82%99', '%E3%81%8F%E3%82%99', '%E3%81%91%E3%82%99', '%E3%81%93%E3%82%99', '%E3%81%95%E3%82%99', '%E3%81%97%E3%82%99', '%E3%81%99%E3%82%99', '%E3%81%9B%E3%82%99', '%E3%81%9D%E3%82%99', '%E3%81%9F%E3%82%99', '%E3%81%A1%E3%82%99', '%E3%81%A4%E3%82%99', '%E3%81%A6%E3%82%99', '%E3%81%A8%E3%82%99', '%E3%81%AF%E3%82%99', '%E3%81%AF%E3%82%9A', '%E3%81%B2%E3%82%99', '%E3%81%B2%E3%82%9A', '%E3%81%B5%E3%82%99', '%E3%81%B5%E3%82%9A', '%E3%81%B8%E3%82%99', '%E3%81%B8%E3%82%9A', '%E3%81%BB%E3%82%99', '%E3%81%BB%E3%82%9A', '%E3%82%A6%E3%82%99', '%E3%82%AB%E3%82%99', '%E3%82%AD%E3%82%99', '%E3%82%AF%E3%82%99', '%E3%82%B1%E3%82%99', '%E3%82%B3%E3%82%99', '%E3%82%B5%E3%82%99', '%E3%82%B7%E3%82%99', '%E3%82%B9%E3%82%99', '%E3%82%BB%E3%82%99', '%E3%82%BD%E3%82%99', '%E3%82%BF%E3%82%99', '%E3%83%81%E3%82%99', '%E3%83%84%E3%82%99', '%E3%83%86%E3%82%99', '%E3%83%88%E3%82%99', '%E3%83%8F%E3%82%99', '%E3%83%8F%E3%82%9A', '%E3%83%92%E3%82%99', '%E3%83%92%E3%82%9A', '%E3%83%95%E3%82%99', '%E3%83%95%E3%82%9A', '%E3%83%98%E3%82%99', '%E3%83%98%E3%82%9A', '%E3%83%9B%E3%82%99', '%E3%83%9B%E3%82%9A'] ; // 結合された文字 var nfc = ['%E3%82%94', '%E3%81%8C', '%E3%81%8E', '%E3%81%90', '%E3%81%92', '%E3%81%94', '%E3%81%96', '%E3%81%98', '%E3%81%9A', '%E3%81%9C', '%E3%81%9E', '%E3%81%A0', '%E3%81%A2', '%E3%81%A5', '%E3%81%A7', '%E3%81%A9', '%E3%81%B0', '%E3%81%B1', '%E3%81%B3', '%E3%81%B4', '%E3%81%B6', '%E3%81%B7', '%E3%81%B9', '%E3%81%BA', '%E3%81%BC', '%E3%81%BD', '%E3%83%B4', '%E3%82%AC', '%E3%82%AE', '%E3%82%B0', '%E3%82%B2', '%E3%82%B4', '%E3%82%B6', '%E3%82%B8', '%E3%82%BA', '%E3%82%BC', '%E3%82%BE', '%E3%83%80', '%E3%83%82', '%E3%83%85', '%E3%83%87', '%E3%83%89', '%E3%83%90', '%E3%83%91', '%E3%83%93', '%E3%83%94', '%E3%83%96', '%E3%83%97', '%E3%83%99', '%E3%83%9A', '%E3%83%9C', '%E3%83%9D'] ; for(var i = 0, len = nfd.length ; i < len ; i++) { str = str.replace(new RegExp(nfd[i], 'ig'), nfc[i]) ; } return str ; } /** * 可変線幅プロファイルを適用するアクションのコードを生成する * @param {String} str 対象のprofileの名前 * @return {String} */ function actionCode(str) { var ust = ustring(str) ; return '''/version 3 /name [ 23 5f5f7374746b335f77696474685f70726f66696c655f5f ] /isOpen 1 /actionCount 1 /action-1 { /name [ 5 6170706c79 ] /keyIndex 0 /colorIndex 0 /isOpen 1 /eventCount 1 /event-1 { /useRulersIn1stQuadrant 0 /internalName (ai_plugin_setStroke) /localizedName [ 12 e7b79ae38292e8a8ade5ae9a ] /isOpen 0 /isOn 1 /hasDialog 0 /parameterCount 1 /parameter-1 { /key 2003858022 /showInPalette 4294967295 /type (ustring) /value [ ''' + ust.length + ''' ''' + ust.hex + ''' ] } } } ''' ; } /** * アクションのソースコード内の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, '').toLowerCase() ; 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() ; } hexToString = ActionItem = ActionItems = null ; } |
このサイトで配布しているスクリプトやその他のファイルを,無断で転載・配布・販売することを禁じます。
それらの使用により生じたあらゆる損害について,私どもは責任を負いません。
スクリプトやファイルのダウンロードを行った時点で,上記の規定に同意したとみなします。