kick the base

Houdiniと、CG技術と、日々のこと。

Photoshop/Illustrator/JSX: 指定したディレクトリをサブディレクトリごと作成する

さて、本日はAdobe ExtendScriptで指定したディレクトリをサブディレクトリごと作成するスクリプトについてご紹介します。Linuxコマンドで言うところのmkdir -pに相当します。

こんな誰でもやりそうなもの、絶対ネットに転がってるだろうと思ったんですが、見つからなかったので作りました。シンプルな内容ゆえスクリプトに慣れていない方に良い題材だと思いましたので、例のごとく書いた順にコードの解説をしていきます。

また今回はよく使うユーティリティの作成になりますので、外部ファイル化して呼び出す方法もご紹介します。紹介され尽くしたTipsかと思いますが、スクリプトの作成とまとめて記事にしておけば、いつか誰かの役に立つかと思い書いておきます。

注意点

紹介する外部ファイルからの関数呼び出しをする際、本来ならば名前空間の衝突を避ける仕組みを用意する必要があるのですが、記事が長くなるという点とJSXで開発するのは比較的小さなツールであり、複数のモジュールを併用することが少ないため今回は割愛します。

いつか記事としてまとめるかもしれませんが、簡単に言うと読み込んだ複数のファイルで同じ名前のものがあったらどうするんじゃい問題の解決策です。

また以前も触れましたが、ぼくはJSXを作成する際はモダンな実装方法をしないスタンスなので、単一varパターンなども取り入れていません。誰でも読めることを重視しています。

コード解説

最終的なコード

たったの12行です。スクリプトが苦手な方も追える分量かと思います。

さて、コードを書いた順番に見ていきましょう。

関数名を定義する

難しいことは全くありません。関数名mkdirpを宣言し、引数にpathを取ります。引数の型については後ほどご紹介します。

function mkdirp(path) {
}

ディレクトリ作成用の変数を定義する

    var fullPath = "";

ディレクトリを作成する時に使用します。また、最後にこいつを戻り値として返します。 戻り値を返すのか返さないのか、何を戻すのかはコードを書く上でとても大切になりますので、使い方によって考えてみるといいでしょう。

後になぜこの値を返したのかを解説します。

引数を明示的に文字列にする

    path = path.toString();

これはちょっと分かりにくいかもしれません。この関数は引数pathに文字列型を期待していますが、app.activeDocument.path(現在アクティブなドキュメントのパス)なんかを放り込みたくなる時が多々あります。*1

app.activeDocument.pathは文字列ではなくオブジェクトなので、その文字列表現を取得するためにtoString()を実行してあげる必要があるのです。

pathに平打ちの文字列を入れた場合などはこの行がなくても動作しますが、オブジェクトが渡ってきた際にこの行がないと、次の処理でこのオブジェクトにはsplitメソッドがないよ!って怒られてしまいます。

設計として引数をちゃんと文字列にしてから関数を使うこととしてあげれば内部にこの処理はいりませんが、ズボラな僕はなにも考えず関数を呼び出して動くようにしたかったので、関数内で型を整備しています。

関数が職場だとして、引数の型がユニフォームだとしたら、家で着替えてから職場に行く(関数外で適正な型に変換する)か、職場に行ってから着替えるか(関数内部で変換する)というのがイメージに近いでしょうか。

パスをスラッシュで分割、配列化する

    var arr = path.split("/");

文字列が持っているメソッド、splitを使用して配列にします。ひとつ前のtoStringがここで効いてきます。これで階層構造を除いたフォルダ名ひとつひとつが配列の要素として格納されます。

サブフォルダをひとつづつ追加しながら、ディレクトリを作っていく

    for (var i = 0; i < arr.length; i++) {
        var folderName = arr[i];
        fullPath += folderName + "/";
        var folder = new Folder(fullPath);
        if (!folder.exists) folder.create();
    }

やってることは簡単で、下記手順をfor文で繰り返しているだけです。

  1. 配列からひとつづつ要素(フォルダ名)を取り出す
  2. パスのお尻にフォルダ名/を足す
  3. フォルダが無かったらつくる。すでに存在していたらなにもせず1に戻り、新たなフォルダ名をお尻に付けていく

重要なのはif (!folder.exists) folder.create();の部分。

フォルダが存在しない場合だけフォルダを作成します。こうしないとおかしなことになっちゃいますね。

最後に戻り値を返す

    return fullPath;

PhotoshopやIllustratorでディレクトリを作る際、ディレクトリを作ることが目的のことは少なく、大抵書き出しやドキュメントの保存などが絡んできます。なので、最後にフルパスを戻り値にもらい、そのパスを利用して処理を続けることになります。

サブディレクトリのパスをすべて保持したい場合はこの関数に配列を用意し、そこに保持する方法も考えられますが、その場合は処理の粒度と合わせて適度な分割を行うと良いでしょう。

関数の呼び出し

先程のmkdirp関数をutil.jsxというJSXファイルに保存したとして話を進めます。

呼び出し自体は非常に簡単なので、詳細な説明は不要でしょう。

今開いているaiやpsdファイルと同階層に、hoge/moja/fugaという構造を持ったディレクトリを生成するサンプルです。

今回戻り値のpは意味をなしませんが、ちゃんと最終的なパスが取得できていることを確認するためalertで表示しています。

#include "util.jsx"

var doc = app.activeDocument;
var p = mkdirp(doc.path + '/hoge/moja/fuga');
alert(p);

大切なのは1行目。#includeディレクティブでファイルの読み込みを宣言し、続けて文字列で読み込むファイルを相対パスで記述します。

書き方のルールについて分かりやすくまとめている記事があったので下記に引用させていただきました。

  • 先頭に#、続けてディレクティブ名、スペース、引数。
  • 末尾にセミコロンはつけない。
  • 引数に複数の値を指定するときはセミコロンで区切る。
  • 引数の引用符は必須ではないけど、英数字以外を含む場合や、複数指定する場合は必要。'でも"でもOK。

引用: Adobe ExtendScriptのプリプロセッサディレクティブ - chalcedonyの外部記憶装置・出張版

まとめ

「サブディレクトリを含めてディレクトリを作成する」機能自体はmkdir -pを併用することで実現可能ですが、わざわざ処理をまたぐ必要もないでしょうし、非常によく使いそうなのでユーティリティ化しておくと良いのではないでしょうか。

動作検証は下記の通りです。動かなかったらすみません。

  • Mac: Illustrator/PhotoshopでCS5と最新のCC
  • Win: Illustrator/PhotoshopでCS5.5と最新のCC

*1:現在のドキュメントと同階層にファイルを生成する場合など