あまり有名ではない……というか、ある程度触ったことがある人ならみんな存在は知っているけれども、使ったことがあんまりない機能が、 Illustrator に限らず、 Adobe の Photoshop などの Creative Cloud 群の自動化スクリプトです。ファイルメニューの中にあり、サンプルのスクリプトが標準であるので、「Office みたいなマクロなんだろうな~」とは認識されているようです。
実際には、Webのフロントエンドで最も使われる「Javascript」による実装なので、使える人が多いはずです。
にも関わらず、そんなに普及していません。何故か?
アプリケーションの機能にアクセスする API に関するドキュメントが少なく、また公式がわかりにくいために非常に取っつきが悪いからだと言えます。というか楽介がそうでした。
じゃあ、ChatGPTにベースを書かせてデバッグすれば動くんじゃない? ということで実験してみた記録です。
- Web系エンジニアでバックエンドからフロントエンド、Webデザイナーと何でもやっていた
- Javascriptはnode.jsでサーバーサイド、Vue.jsやReactを駆使してフロントと「全部俺がやる」で業務システムを構築した経験あり
- Adobe系スクリプトは、動画作業が面倒でPremiereで勉強したものの、APIが全然揃っていなくてやりたいことができなかった経験あり
- Illustrator 用のAPIは完全未経験
- (超)上流工程に移っているのでできるだけ個別にプログラムを書きたくない
想定するスクリプト
プレゼン資料や製品資料にある「CONFIDENCIAL」とか、「写真の製品は開発中です」とか、あるいは単にコピーライトとかを色々なファイルに一括で入れるようなスクリプトを作ってみようと思います。
スクリプト内の処理はできるだけ簡単にした方が、ChatGPTに対する指示も簡素にできデバッグが楽できそうです。従って、追加する内容は別のファイルからコピーしてきて貼り付ける形式にします。
Adobe Illustrator では、「配置」という形式がありますが、「埋め込み」か「リンク」や、埋め込みであってもリンクであっても、PDF互換形式での読み込みとなるので、新しい編集形式に対応していないなどの問題が発生しがちなので、今回はコピー&ペーストさせることにしました。追加した内容を後から一括編集したい場合などは、「リンク」での配置になるでしょう。
さらに今回はテストスクリプトなので、内容が追加されるファイルを上書きはせず、別フォルダに保存するようにします。
プロンプト(深津式プロンプトを知る前の実験です)
Illustrator上で以下の動作をするスクリプトを記述してください。コメントは日本語で記述します。
c:\ターゲット\ フォルダにある、全ての .ai ファイルに以下の処理を行います。処理対象のファイルをターゲット.aiとします。
ターゲット.aiを開き新規レイヤーを作成します。作成したレイヤーの左上に、左.aiファイルの内容をコピーして貼り付けます。このとき、ターゲット.aiの他の要素と重ならないようにします。
続いて、右.aiファイルの内容をコピーして、右側に貼り付けます。このとき、ターゲット.ai の他の要素と重ならないように、2cm間隔をとります。
最後に、c:\完了\ フォルダに、ターゲット.aiファイルと同名で処理を終えたファイルを保存します。
これを、c:\ターゲット\ フォルダにある、全ての .ai ファイルに適用されるまで繰り返します。
Here’s a script in JavaScript that performs the actions described:
// Target folder path
var targetFolder = "c:\\ターゲット\\";
// Completed folder path
var completedFolder = "c:\\完了\\";
// File extension to look for
var fileExtension = ".ai";
// Get all files in the target folder with specified extension
var targetFiles = Folder(targetFolder).getFiles("*" + fileExtension);
// Loop through all files
for (var i = 0; i < targetFiles.length; i++) {
var targetFile = targetFiles[i];
// Open the target file
var doc = app.open(targetFile);
// Create a new layer
var newLayer = doc.layers.add();
// Left .ai file path
var leftFile = File("左.ai");
// Right .ai file path
var rightFile = File("右.ai");
// Open left .ai file
var leftDoc = app.open(leftFile);
// Copy contents of the left .ai file
leftDoc.selection = leftDoc.pageItems;
leftDoc.selection.copy();
// Close left .ai file
leftDoc.close(SaveOptions.DONOTSAVECHANGES);
// Paste contents of the left .ai file in the new layer at the top left corner
var leftPastePoint = [0, doc.height];
doc.pasteInPlace(leftPastePoint, newLayer);
// Open right .ai file
var rightDoc = app.open(rightFile);
// Copy contents of the right .ai file
rightDoc.selection = rightDoc.pageItems;
rightDoc.selection.copy();
// Close right .ai file
rightDoc.close(SaveOptions.DONOTSAVECHANGES);
// Paste contents of the right .ai file in the new layer with 2cm gap from the left content
var rightPastePoint = [leftPastePoint[0] + 72, leftPastePoint[1]];
doc.pasteInPlace(rightPastePoint, newLayer);
// Save the processed file with the same name in the completed folder
doc.saveAs(File(completedFolder + "/" + targetFile.name), new IllustratorSaveOptions());
// Close the target file
doc.close(SaveOptions.DONOTSAVECHANGES);
}
一見動きそうなコードが帰ってきました。「左.ai」や「右.ai」もファイルの先頭で変数に代入してくれていたらとても助かるのですが……(もちろん、後で動作環境用にパスは書き換えます)。上手いことプロンプトで指示できたらいいんですが……。
とりあえず、日本語でコメントを書いてくれた方が読みやすいので、指示してみます。
コメントを日本語に直してください。
以下がIllustrator上でのスクリプトです:
// c:\ターゲット\ フォルダ内のすべての.aiファイルを処理対象とします。
var targetFolder = new Folder("c:\\ターゲット\\");
var targetFiles = targetFolder.getFiles("*.ai");
// 各.aiファイルに対して処理を実行します。
for (var i = 0; i < targetFiles.length; i++) {
var targetFile = targetFiles[i];
// ターゲット.aiを開きます。
var targetDoc = app.open(targetFile);
// 新しいレイヤーを作成します。
var newLayer = targetDoc.layers.add();
newLayer.name = "Merged Layer";
// 左.aiの内容をコピーして、新しいレイヤーの左上に貼り付けます。
var leftFile = new File("左.ai");
var leftDoc = app.open(leftFile);
var leftBounds = leftDoc.artboards[0].artboardRect;
var leftTopLeft = [leftBounds[0], leftBounds[1]];
leftDoc.selection = null;
leftDoc.selectObjectsOnActiveArtboard();
leftDoc.copy();
targetDoc.activate();
targetDoc.paste();
var leftTop = targetDoc.activeLayer.pageItems[0];
leftTop.left = leftTopLeft[0];
leftTop.top = leftTopLeft[1];
leftDoc.close(SaveOptions.DONOTSAVECHANGES);
// 右.aiの内容をコピーして、新しいレイヤーの右に貼り付けます。
var rightFile = new File("右.ai");
var rightDoc = app.open(rightFile);
var rightBounds = rightDoc.artboards[0].artboardRect;
var rightTopLeft = [rightBounds[0], rightBounds[1]];
rightDoc.selection = null;
rightDoc.selectObjectsOnActiveArtboard();
rightDoc.copy();
targetDoc.activate();
targetDoc.paste();
var rightTop = targetDoc.activeLayer.pageItems[1];
rightTop.left = leftTop.left + leftTop.width + 72;
rightTop.top = rightTopLeft[1];
rightDoc.close(SaveOptions.DONOTSAVECHANGES);
// c:\完了\ フォルダに、ターゲット.aiファイルと同名で保存します。
var saveFolder = new Folder("c:\\完了\\");
var saveFile = new File(saveFolder + "/" + targetFile.name);
targetDoc.saveAs(saveFile, new IllustratorSaveOptions
日本語コメントの方は、終わりの方が切れている上に、内容が英語版と変わっています……。
実際に動作させてみると(デバッグしてみると)、どちらのスクリプトも動作しません(日本語の方は、もちろん末尾は英語版からコピペして補いました)。
主な問題点
- copyなんてメソッドはない
- 同じくpasteなんてメソッドはない
- 選択については、日本語コメントスクリプトが正しい(意外な結果)
- 座標指定方法が違う
- 要素が追加されるドキュメントに元々あるデータに重ならないように移動してくれない
この辺りは、オブジェクト指向が非常に英語的であるのと、同じJavascriptの別の実装(Web向けの実装やjQueryなど、あるいは同じAdobe の ActionScript, AIR)から学習してしまっているために、間違ったメソッドを使っているように思います。
日本語にしろ英語にしろ、元々情報の少ない Adobe ツールの自動化スクリプトについては学習が充分ではないということでしょう。
便利な検索に Perplexity
コピーとペーストについては、Google で検索すれば割と簡単にメソッドが判明します。
app.executeMenuCommand("copy");
オブジェクト指向かどうか、微妙に疑いたくなりますが、「アプリケーションオブジェクトのコマンドを実行する」といった形式のようです(実行したいコマンドを文字列で渡す実装は、ActionScript2 の時代に多かった気がします……多分その頃の実装のまま)。
問題は、座標を指定しての移動方法と、元々ファイルに存在しているオブジェクトに対して相対的に配置するという部分が動作していません。バウンディングボックスのサイズを取得する必要がありそうです。
ということで、Google で検索しますが、スクリプトで動作させる結果と、Illustrator の GUI から操作する結果が混在して出て来てしまいます。これも、スクリプトに関する情報が少ないための現象と考えられます。
ということで、Preplexity に問い合わせて見ます。
バウンディングボックスのサイズについては、[1]で参照されているqiita の記事を参考にさせていただきました。
関数をそのまま使わせていただきました。
次は移動について問い合わせてみます。
Perplexity でも Google と同様に(Javascriptと指定があることが指示から抜けて落ちていないにも関わらず)GUIの操作方法が大半を占めます。
しかし、[3][4]は明確に「スクリプトで」と記載されています。
加えて、利便性が高いのが記事のタイトルによらず内容を要約して提示してくれているので、検索結果を上から順に見ていく手間が省けます。従来型の検索エンジンでは、タイトルと概要をスクロールしながら読まないといけなかったですからね。
とはいえ、そのものずばりの解は得られませんでした。というか、そういう記事がないんでしょうね……。
ちなみに、正しい指定方法は、
object.position = [x, y];
という記法だそうです。オブジェクト指向ってなんでしたっけ……。これもActionScript2時代のプロパティ設計がこんな感じだった気が、遠い記憶の中にあるのでその時代のままなんでしょうね。
なお、ペーストされたオブジェクトの選択については、Illustratorの動作として、「貼り付けられたオブジェクトが選択される」という挙動がそのまま適用されるみたいなので、特別処理を行いませんでした。
AIの力を借りて作成したスクリプトがこちら
// c:\ターゲット\ フォルダ内のすべての.aiファイルを処理対象とします。
var targetFolder = new Folder("C:\\ターゲット\\");
var targetFiles = targetFolder.getFiles("*.ai");
var leftFile = new File("C:\\素材\\左.ai");
var rightFile = new File(C:\\素材\\右.ai");
var completedFolder = new Folder("C:\\完了\\");
// 各.aiファイルに対して処理を実行します。
function getBoundsRect(target) {
/* この関数は https://qiita.com/keiji_ogura/items/f62676b275753ad69f21 を参照*/
}
// Loop through all files
for (var i = 0; i < targetFiles.length; i++) {
var targetFile = targetFiles[i];
// Open the target file
var doc = app.open(targetFile);
doc.selection = null;
doc.selectObjectsOnActiveArtboard();
var rect = getBoundsRect(doc.selection);
doc.selection = null;
// Create a new layer
var newLayer = doc.layers.add();
// Open left .ai file
var leftDoc = app.open(leftFile);
// Copy contents of the left .ai file
var leftBounds = leftDoc.artboards[0].artboardRect;
var leftTopLeft = [leftBounds[0], leftBounds[1]];
leftDoc.selection = null;
leftDoc.selectObjectsOnActiveArtboard();
app.executeMenuCommand("copy");
doc.activate();
app.executeMenuCommand("paste");
var leftTop = doc.selection[0];
leftTop.position = [0, rect[1] + 72];
leftDoc.close(SaveOptions.DONOTSAVECHANGES);
// Close left .ai file
// Open right .ai file
var rightDoc = app.open(rightFile);
var rightBounds = rightDoc.artboards[0].artboardRect;
var rightTopLeft = [rightBounds[0], rightBounds[1]];
rightDoc.selection = null;
rightDoc.selectObjectsOnActiveArtboard();
app.executeMenuCommand("copy");
doc.activate();
app.executeMenuCommand("paste");
var rightTop = doc.selection[0];
rightTop.position = [rect[2] + 72, rect[1]];
rightDoc.close(SaveOptions.DONOTSAVECHANGES);
// Save the processed file with the same name in the completed folder
doc.saveAs(File(completedFolder + "/" + targetFile.name), new IllustratorSaveOptions());
// Close the target file
doc.close(SaveOptions.DONOTSAVECHANGES);
}
AI まかせで一発! とはいきませんでしたが、APIを一切知らない状態からスタートしたとは思えないくらい、短時間で動作する状態まで持って行くことができました。
課題として、
- 選択
- サイズ取得
- 移動
がありましたが、「ファイル操作各種(一覧、開く、保存して閉じる、保存しないで閉じる)」のAPIは文句なく動作しましたし、オブジェクトの選択も ChatGPT任せで動作するものができあがっています。
これらの API を知っていたとしても、2つのファイルを開いてコピーして閉じて……という似たような処理を人間が記述するのは無駄が多すぎます。
人間がこの部分を書くとしたら、関数化するのが妥当でしょうが、それでも大した省力化にはなりません。というか、同じ処理を他のスクリプトで流用しないでこのスクリプト内だけで評価するなら、リファクタリングで余計に時間がかかるかもしれません。
また、ループについても意外とタイプ量が多かったりするので、日本語で思いついたまま文章を記述してループの原型が出力されるのはスクリプトのスケルトンとして有用であると思いました。
スクリプト完成までの時間は、この記事を記述している時間の方が長かったので、相当短時間で終わらせられたと思います(なんならテスト素材を用意するのが一番面倒でした)。
終わりに:深津式プロンプトしてみた
微妙に詐欺っぽい気もするのですが、一番嘘は言っていないな……という最後の案を採用してみました。期待と違う内容だった場合は、ChatGPTのせいです。