pythonのライブラリtriangleをIllustrator用に使ってみる

先日見つけた、pythonで三角形分割をするライブラリ triangleIllustratorで使ってみるの巻。

f:id:shspage:20170408134153p:plain

このライブラリはドロネー三角形分割とかボロノイ分割に加えて、 高品質な三角形分割をするメソッドもあるのです。

三角形分割の品質を上げるには Ruppert’s algorithm などがあり、 仕組みは比較的シンプルなのですが実装するとなるととても面倒なのです。 ライブラリがあるなら使わない手はない、というわけです。

Illustratorからpythonを呼べればいいんですけど、
何かいい方法あるのかもしれませんが、
とりあえずファイルを介してデータをやりとりすることにします。

1. Illustratorで元になるパスを作る

文字をアウトライン化しました。

f:id:shspage:20170408133659p:plain

ベジエ曲線python側で扱えないので、 以前作ったスクリプトbrokenCurve.jsx 」で曲線を直線に分割しました。

これは曲線部分の「高さ」が指定値以下になるように分割するやつで、

f:id:shspage:20170408133727p:plain

できればアンカー間の距離が近くなりすぎないような制限も入れたりしたいところですけど。 とりあえず。

f:id:shspage:20170408133747p:plain

直線化したもののグループと複合パスを解除して、 「」になる部分はまとめてグループ化して最前面にしておきます。
穴は分かりやすいように黄色にしました。

2. 座標の情報をファイルに書き出す

Illustratorスクリプトで座標の情報をファイルに書き出します。

python側では “vertices”, “segments”, “holes” をキーとする辞書が必要ですので、 書き出すのはその元になる情報です。

vertices は2D座標の配列、 segments はverticesのインデックスのペアの配列です。 holes はオプションで、穴になる部分の内側の点の座標(穴ごとに1つ)の配列です。

function main(){
    var paths = [];
    var sel = activeDocument.selection
    extractGroup(sel, paths);
    // 穴は最前面のグループにする
    
    var vertices = [];  // 頂点座標(x, y)の配列
    var segments = [];  // 頂点インデックスのペアの配列
    var holes = [];     // 穴の中に含まれる座標(x, y)の配列

    // verticesとsegmentsの要素を生成
    var idx_base = 0;
    for(var ip = 0; ip < paths.length; ip++){
        var pp = paths[ip].pathPoints;
        
        for(var i = 0; i < pp.length; i++){
            var j = i == pp.length - 1 ? 0 : i + 1;
    
            var anc = pp[i].anchor;
            vertices.push(anc[0] + " " + anc[1]);
            
            var seg = (idx_base + i) + " " + (idx_base + j);
            segments.push(seg);
        }
        
        idx_base += pp.length;
    }

    // 穴の要素を生成(最前面がグループの場合)
    if(sel[0].typename == "GroupItem"){
        for(var i = 0; i < sel[0].pathItems.length; i++){
            with(sel[0].pathItems[i]){
                // 中心の座標を穴の目印にする
                holes.push((left + width / 2) + " " + (top - height / 2));
            }
        }
    }

    // ファイルに書き出し
    var fw = new File("~/data.txt");
    if(fw.open("w")){
        fw.write(vertices.join(",") + "\r\n");
        fw.write(segments.join(",") + "\r\n");
        fw.write(holes.join(",") + "\r\n");
        fw.close();
        alert("saved");
    } else {
        alert("failed to save");
    }
}

function extractGroup(sel, paths){
    for(var i = 0; i < sel.length; i++){
        if(sel[i].typename == "PathItem"){
            paths.push(sel[i]);
        } else if(sel[i].typename == "GroupItem"){
            extractGroup(sel[i].pageItems, paths);
        }
    }
}
main();

書き出されたファイルはこんな感じ。

175.3955078125 -36.068359375,174.177705535043 -31.5290598790816,...
0 1,1 2,2 3,3 4,4 5,5 6,6 0,7 8,8 9,9 10,10 11,11 12,12 13,13 14,...
170.613525390625 -32.98388671875,128.70361328125 -38.602783203125

3. pythonで処理

ファイルをpythonで読み取って処理します。

triangulateのオプションは色々あって、一部を挙げると、
p” は Planar Straight Line Graph を構成します。穴がある場合はこれをやらないとダメっぽい。
q” は 三角形の角が20度以下にならないように分割品質を向上させます。(角度は指定も可)
a” は 三角形の最大の面積を制限します。値を小さくすると三角形の数が増えます。

triangulate が返す辞書には以下のキーがあります。 ‘segment_markers’, ‘segments’, ‘holes’, ‘vertices’, ‘vertex_markers’, ‘triangles’

ここでは vertices と triangles だけ使います。 triangles は頂点インデックス3つの組み合わせの配列です。

import numpy as np
import triangle

tri = {}
f = open("data.txt", "r")
r = f.readline().strip().split(",")
tri["vertices"] = np.array([map(float, x.split(" ")) for x in r])

r = f.readline().strip().split(",")
tri["segments"] = np.array([map(int, x.split(" ")) for x in r], dtype=np.int)

r = f.readline().strip().split(",")
if r[0] != "":
    tri["holes"] = np.array([map(float, x.split(" ")) for x in r])
f.close()

# triangulate!
result = triangle.triangulate(tri,'pqa10')

vs = result["vertices"]

fw = open("data_out.txt", "w")
for t in result["triangles"]:
    fw.write(\
      "%f %f" % tuple(vs[t[0]].tolist())\
      + " %f %f" % tuple(vs[t[1]].tolist())\
      + " %f %f" % tuple(vs[t[2]].tolist())\
      + "\n")
fw.close()

複雑な形状だと無理があるのか、実行すると時々pythonが異常終了します。 この例の場合は結局「tri」と「angle」で別々に処理しました。

書き出されたファイルはこんな感じ。

15.524214 -50.253923 17.989927 -52.059161 17.915429 -47.283785
16.083425 -39.977783 19.255991 -39.439819 17.169918 -36.654585
20.664551 -36.546631 18.826192 -33.653442 17.169918 -36.654585
...

4. Illustratorで描画

書き出したファイルをIllustratorスクリプトで読み込んで描画させます。

function main(){
    var f = new File("~/data_out.txt");
    if(!f.open("r")){
        alert("failed to open");
        return;
    }
    var lines = f.read();
    f.close();

    var data = lines.split("\n");

    for(var i = 0; i < data.length; i++){
        var vs = data[i].split(" ");

        var r = [];
        for(var j = 0; j < vs.length; j+= 2){
            r.push([parseFloat(vs[j]), parseFloat(vs[j+1])]);
        }
        var path = activeDocument.activeLayer.pathItems.add();
        path.setEntirePath(r);
        path.closed = true;
    }
}
main();

f:id:shspage:20170408134124p:plain

これだけだと面白くないので、 以前作った「 noiseFill.jsx 」というスクリプトで色分けしました。

f:id:shspage:20170408134149p:plain

灰色だと寂しいので、 Illustratorの「カラーを編集」の機能で適当に再配色してみました。

f:id:shspage:20170408134153p:plain

なんとなく春らしく。

という感じです。

おしまい。

newLayer extension 1.2.6

前に作ったIllustrator CC以降用のエクステンションですが、
最近レイヤー数が多いものを作っていて地味に役立ったので、
少し改良して再掲。

基本機能

  • カラーチップを押すと新規レイヤーを追加します。
  • auto ボタンを押すと適当な色でレイヤーを追加します。
  • チェックボックスにチェックがついているときは現在のレイヤーの色を変えます。
  • チェックはマウスポインタがパネルの外に出た時点で外れます。

これらの機能により、しつこく出てくる黄色・黒・灰色レイヤーの回避を補助します。

レイヤーパレットの下にくっつけておくのがおすすめ。

改良点

改良1チェックボックスがチェックしにくいので、横の文字の部分をクリックしても チェックの付け外しができるようにしました。

改良2:パネル上をドラッグしたときに、文字部分が選択反転状態にならないようにしました。

ダウンロード

aicc_ext_newLayer126.zip -> http://shspage.com/ex/files.html

余談

Windowsだとエクステンションのインストール場所は C:\Users\ユーザー名\AppData\Roaming\Adobe\CEP\extensions にもできて、こっちの場合「ユーザー名」さんしか使えないのだけど、 中身を簡単にいじりたいときにフォルダの権限変えたりしなくていいから、 ZXPInstallerでもこっちに入れる選択肢があったらいいのにと 思うのだけど何か問題が起きたりとかあるのかな? 私は今のところ特にありません。

3つの円の隙間に円を描く

図形を描く系のコードでやっている計算を図解するシリーズ。

(Illustrator用のJavaScript script編)

これは基礎知識級のことなのかもしれないのですが、
私は初めて知って面白いと思ったので、

本題

1. 同じ大きさの3円の場合

f:id:shspage:20170331233303p:plain

同じ大きさの円を3つ適当に描いたとき、3つの円に接する円が描けます。
(3円が直線上に並んでいるときを除く。)

こんなふうに。

f:id:shspage:20170331233321p:plain

しくみ

3つのうち2つの円に接する円の中心は、 2円の中心から等距離の点の軌跡、 つまり2円の中心を結んだ線の垂直二等分線上にあります。

f:id:shspage:20170331233432p:plain

中心を結んで三角形をつくると、 各辺の垂直二等分線の交点は三角形の外心(外接円の中心)なので、 3円に接する円の中心が1点に決まるのは当然といえば当然なわけです。

f:id:shspage:20170331233502p:plain

仕組みが分かると詰まらない気もしますが、 ブログの性格上、スクリプトにしたものを掲載します。

gist.github.com

円が重なってる場合はこんなふうになっちゃいますねこれ。

外側で接するとは言ってないからこれもアリか。

f:id:shspage:20170331234442p:plain

2. 違う大きさの3円の場合

雰囲気的には描けそうなんですが、これが思ったより難しく、

調べたらこれは「アポロニウスの問題」というものだそうで、 ネット上で色々な解法が見つけられますがどれもややこしいです。

手に負えそうな解法のひとつを参考にさせていただき、 スクリプトにしてみましたが、このシリーズに載せるには複雑になってしまいました。 内容はまた何かの機会に図解したいと思います。

Illustratorで3円に接する円を描く · GitHub

1つだけ補足すると、3円に接する円の半径を大きく(or小さく)した場合、 3円のほうの半径を同じ分だけ小さく(or大きく)すると接した状態が保たれる のだそうで(アポロニウスの問題 - Wikipedia )、スクリプトに使った解法でもこの事実を利用しています。

上の「1.」でやっていることも、三角形の外接円に接する頂点を半径0の円と 考えると、3つの円に接するケースはそれらの半径を増やしただけのことともいえます。


おわび】前回、脚注2で円の中心と半径を簡単に求める方法として 載せたものが間違っていたため修正しました。