ProcessingAdventCalendar
今年もProcessingAdventCalendar2015[ref]ATNDで開催してます(https://atnd.org/events/72012)[/ref]が開催されております。一応主催者側なのですが、たまには記事を1つぐらい書こうかと思い、参加する運びとなりました。
12/27の予定でしたが、諸事情で本日の担当となりました。
今回の成果物
3Dプリンタを手に入れて数カ月がたちました。プログラミングで3Dモデルを作って、それを3Dプリンタで出力する、という目標を持っていたのですが、このたびようやく形になりました。やったねヽ(・-・)ノ
概要
Processing[ref]メディアアート向けプログラミング言語環境(https://processing.org/)[/ref]で3Dモデルを生成し、これを3Dプリンタで出力します。お手軽です。
過去にはカッティングプロッタのCraftRobo[ref]プリンタにカッターが付いたようなもの。紙を好きな形に切ることができます(http://craftrobo.jp/)[/ref]と連携したこともあります。(ProcessingとCraftROBOで、クリスマスカードを作成する)
ディスプレイに表示したり、プリンタで紙に印刷する以外にもいろいろなアウトプットの楽しみがあります。個人でも気軽に手出しができるものが増えてきてうれしいものですね。
今回使用したプリンタはXYZwareのダヴィンチ Jr 1.0[ref]ダヴィンチ Jr 1.0(https://jp.xyzprinting.com/product/da-Vinci-1.0-Junior)[/ref]になります。素材はPLA樹脂というプラスチックの素材です。積層式ですし、どうしても細かいところは荒くなってしまいますが、こうやって手軽に遊ぶのには最適です。
時間は1時間程度、コストは単純計算で約30円でした。
技術編
ソースコードは後半に貼ってあります。
ライフゲーム生成
3Dモデルを生成するわけですが、せっかくプログラミングで作るわけですから、ありきたりな形ではつまらないものです。そこでライフゲーム[ref]ライフゲーム(https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0)[/ref]を3D空間に描画し、それを基にモデルを生成するという方針にしました。
ライフゲームの作り方はここでは割愛します。
ちなみに各ボックスの間を開けているのは、3Dプリンタと相性が悪かったためです。うまく凹みが認識できないらしく。。
ほんとはくっつけたかったのですが、ここは妥協です。
マウスでモデルを移動したり回転したりして、隅々まで見れるようにしています。
ちなみにですが、3Dプリンタで出力するためには、(当たり前ですが)ポリゴンの内側は密閉されている必要があります。
オブジェクトファイル出力
描画した3Dオブジェクトを3Dファイルに変換するライブラリとして、NervousSystemのOBExport[ref]NervousSystem(http://n-e-r-v-o-u-s.com/tools/obj/)[/ref]を使用いたしました。Processing 3.0系であればライブラリのインポートメニューから、「obj」を検索すれば見つかります。
使い方は簡単で、インポートして、開始のbeginRecord()と終了のendRecord()を適当な位置に組み込めばOKです。
beginRecord()~endRecord()間で描画したポリゴンが指定したファイルに記録されます。
import nervoussystem.obj.*;
beginRecord("nervoussystem.obj.OBJExport", "v2.obj");
endRecord();
STL変換
今回使用する3Dプリンタ用のソフトXYZwareは、objファイルを読むことができません。しかしstlファイルを読むことができるため、obj→stl変換を行います。
変換ソフトとしてBlender[ref]Blender(http://www.blender.org/)[/ref]を使用します。
3Dプリンタ出力
XYZwareで、作成したstlファイルを読み込み、3Dプリンタへ出力します。
印刷風景
iPhoneのタイムラプス機能で撮影しました。なんでもiPhoneでできる便利な世の中です。
完成
ソースコード
import nervoussystem.obj.*; // obj変換ライブラリ
final int X_MAX = 20; // 横セル数
final int Y_MAX = 20; // 縦セル数
float[][] cells = new float[X_MAX][Y_MAX]; // セル
boolean record = false; // obj変換フラグ
final int EDGE = 10; // セル辺の長さ
final int HEIGHT = -3; // 底面の高さ
//--------------------------------------------------
// setup
//--------------------------------------------------
void setup() {
size(800, 800, P3D);
// 各セルを初期化
for (int y = 0; y < Y_MAX; y++) {
for (int x = 0; x < X_MAX; x++) {
cells[x][y] = 0;
}
}
}
//--------------------------------------------------
// draw
//--------------------------------------------------
void draw() {
// 描画モード切り替え(obj変換時は座標変換をするとobjの座標もずれるので。)
if (record) {
beginRecord("nervoussystem.obj.OBJExport", "v2.obj");
} else {
background(0);
directionalLight(255, 255, 255, 1, 1, -1);
ambientLight(32, 32, 32);
translate(width / 2 - EDGE * X_MAX / 2, height / 2 - EDGE * Y_MAX / 2);
translate(0, 0, mouseX);
rotateX(radians(-mouseY));
}
fill(255);
noStroke();
// ライフゲーム処理
life();
// 底面の描画
base();
// ライフゲームの結果をボックスで出力
for (int y = 1; y < Y_MAX - 1; y++) {
for (int x = 1; x < X_MAX - 1; x++) {
if(cells[x][y] == 1){
box(x, y, 5);
}
}
}
// obj変換中なら終了処理
if (record) {
endRecord();
print("recorded");
record = false;
}
}
//--------------------------------------------------
// ボックス描画
//--------------------------------------------------
void box(int x, int y, float z) {
// 壁面
beginShape(TRIANGLE_STRIP);
vertex(x * EDGE + 0 , y * EDGE + 0, 0);
vertex(x * EDGE + 0 , y * EDGE + 0, z);
vertex(x * EDGE + EDGE-1, y * EDGE + 0, 0);
vertex(x * EDGE + EDGE-1, y * EDGE + 0, z);
vertex(x * EDGE + EDGE-1, y * EDGE + EDGE-1, 0);
vertex(x * EDGE + EDGE-1, y * EDGE + EDGE-1, z);
vertex(x * EDGE + 0 , y * EDGE + EDGE-1, 0);
vertex(x * EDGE + 0 , y * EDGE + EDGE-1, z);
vertex(x * EDGE + 0 , y * EDGE + 0, 0);
vertex(x * EDGE + 0 , y * EDGE + 0, z);
endShape();
// 上面
beginShape(TRIANGLE_STRIP);
vertex(x * EDGE + 0, y * EDGE + 0, z);
vertex(x * EDGE + EDGE-1, y * EDGE + 0, z);
vertex(x * EDGE + 0, y * EDGE + EDGE-1, z);
vertex(x * EDGE + EDGE-1, y * EDGE + EDGE-1, z);
endShape();
}
//--------------------------------------------------
// ライフゲーム処理
//--------------------------------------------------
void life() {
float t[][] = new float[X_MAX][Y_MAX]; // テンポラリ
// テンポラリを初期化
for (int y = 0; y < Y_MAX; y++) {
for (int x = 0; x < X_MAX; x++) {
t[x][y] = 0;
}
}
// テンポラリに次の世代を書き込む
for (int y = 1; y < Y_MAX - 1; y++) {
for (int x = 1; x < X_MAX - 1; x++) {
int result = count(x, y);
if (result == 1 || result == 3) {
t[x][y] = 0;
} else {
t[x][y] = 1;
}
}
}
// テンポラリを適用
cells = t;
}
//--------------------------------------------------
// 自セルの周りの生存セルの数を計算する
//--------------------------------------------------
int count(int x, int y) {
int result = 0;
result += cells[x-1][y-1] == 1 ? 0 : 1;
result += cells[x+0][y-1] == 1 ? 0 : 1;
result += cells[x+1][y-1] == 1 ? 0 : 1;
result += cells[x-1][y+0] == 1 ? 0 : 1;
result += cells[x+1][y+0] == 1 ? 0 : 1;
result += cells[x-1][y+1] == 1 ? 0 : 1;
result += cells[x+0][y+1] == 1 ? 0 : 1;
result += cells[x+1][y+1] == 1 ? 0 : 1;
return result;
}
//--------------------------------------------------
// 底面の描画
//--------------------------------------------------
void base(){
// 上面
beginShape(TRIANGLE_STRIP);
vertex(0, 0, 0);
vertex(X_MAX * EDGE, 0, 0);
vertex(0, Y_MAX * EDGE, 0);
vertex(X_MAX * EDGE, Y_MAX * EDGE, 0);
endShape();
// 下面
beginShape(TRIANGLE_STRIP);
vertex(0, 0, HEIGHT);
vertex(X_MAX * EDGE, 0, HEIGHT);
vertex(0, Y_MAX * EDGE, HEIGHT);
vertex(X_MAX * EDGE, Y_MAX * EDGE, HEIGHT);
endShape();
// 壁面
beginShape(TRIANGLE_STRIP);
vertex(0, 0, HEIGHT);
vertex(0, 0, 0);
vertex(0, Y_MAX * EDGE, HEIGHT);
vertex(0, Y_MAX * EDGE, 0);
vertex(X_MAX * EDGE, Y_MAX * EDGE, HEIGHT);
vertex(X_MAX * EDGE, Y_MAX * EDGE, 0);
vertex(X_MAX * EDGE, 0, HEIGHT);
vertex(X_MAX * EDGE, 0, 0);
vertex(0, 0, HEIGHT);
vertex(0, 0, 0);
endShape();
}
// マウスクリックしたら、obj出力モードにする。
void mousePressed() {
record = true;
}
感想
もっと高価な3Dプリンタなら綺麗に出力できたり、凹みも問題なくできたりするのでしょうね。
今後もコンピュータならではのモデルを生成して創っていきたいものです。
皆様も素敵なProcessingライフをどうぞ。












コメント
コメント一覧 (1件)
[…] ライフゲームを3Dプリンタで出力する […]