ライフゲームを3Dプリンタで出力する

ProcessingAdventCalendar

今年もProcessingAdventCalendar2015[ref]ATNDで開催してます(https://atnd.org/events/72012)[/ref]が開催されております。一応主催者側なのですが、たまには記事を1つぐらい書こうかと思い、参加する運びとなりました。

12/27の予定でしたが、諸事情で本日の担当となりました。

今回の成果物

3Dプリンタを手に入れて数カ月がたちました。プログラミングで3Dモデルを作って、それを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」を検索すれば見つかります。

Processingにライブラリ管理が統合されたので、使うのがだいぶ楽になりました。

Processingにライブラリ管理が統合されたので、使うのがだいぶ楽になりました。

使い方は簡単で、インポートして、開始の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]を使用します。

メニューから作成したobjファイルをインポートします。

メニューから作成したobjファイルをインポートします。

objファイルを読み込むことができました。

objファイルを読み込むことができました。

このままだと大きいので、Scaleを小さくします。右側のツールバーで変更できます。

このままだと大きいので、Scaleを小さくします。右側のツールバーで変更できます。

stl形式でエクスポートします。

stl形式でエクスポートします。

3Dプリンタ出力

XYZwareで、作成したstlファイルを読み込み、3Dプリンタへ出力します。

印刷できるようになりました。

印刷できるようになりました。

印刷風景

iPhoneのタイムラプス機能で撮影しました。なんでもiPhoneでできる便利な世の中です。

完成

完成ヽ(・-・)ノ ※Processing上は逐次ライフゲームが進むため、出力ファイルと実行画面で模様が違うのはご了承ください。

完成ヽ(・-・)ノ
※Processing上は逐次ライフゲームが進むため、出力ファイルと実行画面で模様が違うのはご了承ください。

ソースコード

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ライフをどうぞ。

 

 

  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

目次