Viveで作ったモデルを3Dプリント

February 20, 2017

VR内でブロックを繋いで作った形状をSTLとして出力し、3Dプリントするデモを作りました。

img-alternative-text

Vive

HMDはコントローラが付属しているHTC Viveを使用。開発環境はUnityの最新版(5.5.1)。
UnityでのViveの開発方法はこちらのサイトで丸わかりです。
Unity+HTC Vive開発メモ

ViveをPCにつなぎ、Asset StoreからSteamVRをダウロード&インポート。
2017.2.20現在、APIを更新しろと言ってくるので素直にGo Ahead!

img-alternative-text

するとSteamVR_Settingsなるダイアログが開きます。プロジェクト設定を推奨に書き換えてくれるので、素直にAccept All.

img-alternative-text

Assets/SteamVR/Scenes/exampleを開きサンプルのシーンを実行すると、いきなりHMDやコントローラーのトラッキングが始まります。超簡単。それにしてももうちょっと見ている方向が分かりやすいサンプルにしてくれたらいいのに。

img-alternative-text

新規Sceneに導入するには、 デフォルトのカメラをオフにして、Assets/SteamVR/Prefab/CameraRigをシーンに置きます。そこがシーンの原点になります。

Viveコントローラの位置は、CameraRigの中のController(left or right)のtransformで取得できます。
トリガーやボタンは、ControllerにアタッチされているSteam VR_Tracked Objectコンポーネントからデバイス番号を得て、デバイスを取得し、GetTouchDown()などのメソッドで状態を取得します。

void Update(){
 SteamVR_TrackedObject trackedObject = GetComponent();
 var device = SteamVR_Controller.Input((int) trackedObject.index);
 if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger)) {
  //トリガーが深く引かれた
 }
}

詳しくはこちらを参照。

今回のデモでは、コントローラーの位置と回転をそのまま使って、Prefabとして登録しておいたオブジェクトを配置します。また後でSTLとして書き出す時のためにGameObjectのGeneric Listに加えておきます。

Vector3 p = transform.position;
Quaternion q = transform.rotation;
GameObject go = Instantiate(YedaPrefab,p,q);
YedaObjects.Add(go);

コントローラーの先のオブジェクトを目安に積み上げます。

img-alternative-text

例えば、こんな形を作ります。

img-alternative-text

書き出し

メッシュのSTLへの書き出しは、こちらのアセットを利用しました。

img-alternative-text

STL
GameObjectの配列を与えると、自動的にメッシュを一体化してバイナリかテキストのSTLとして出力してくれます。

今回のデモでは、コントローラーのメニューボタンを押した時に、STLファイルへの書き出しを行います。先ほどモデルのGameObjectを登録したGeneric Listから変換した配列とファイルパスをSTL.ExportBinary()に引数として渡します。

void Update(){
 // ~ //
 if (device.GetPressDown(SteamVR_Controller.ButtonMask.ApplicationMenu)) {
 ExportToBinarySTL ();
}
public void ExportToBinarySTL(){
 GameObject[] objects = YedaObjects.ToArray ();
 string filePath = DefaultDirectory() + "/stl_example_binary.stl";
 STL.ExportBinary( objects, filePath );
}

3Dプリント

吐き出されたデータをプリントするところはとりあえず手動。使用する3DプリンタはFLASHFORGE CreatorPro。Makerbot Replicator 2の互換機で、デュアルヘッドが特徴です。FLASHFORGEオリジナルのスライスソフトFlashPrintで、プリントデータを作成。ちなみにソフトの説明ではCreatorProが対象機種に含まれていませんが、ちゃんと使えました。

img-alternative-text

完成

img-alternative-text

荒い設定で積層痕が年輪みたいになりました。

もともと、Yeda Jumboという磁石を内蔵した木製の積み木のVR化が目的で、今回は入っていないけど、末端同士を磁力的な力で繋ぐようにしたり、ピースの大きさを変えられるようにして、Yeda Jumboみたいなこともやりたいと思います。

Fusion360のCAMでNCデータを作る

September 19, 2016

外部から読み込んだ3角ポリゴンのSTLから、Autodesk Fusion360のCAM機能を使って、CNCフライス盤のNCデータを作ります。

参考サイト



頂点数の変更

Autodesk ReMake に3角ポリゴンのSTLを読み込みます。ReMakeは写真から3Dモデルを作るために使うツールのようですが、ポリゴン数の調整や変換もできるので、その機能を利用します。
左側のメニューからExportを選択。書き出し形式としてFusion360(OBJ Quad)を選ぶと4角ポリゴンのObjになります。同時に、頂点数を減らすこともできます。


変換

書き出したObjをFusion360で、2回変換します。
まず、ファイルをアップロードして、メッシュデータとして開きます。


次にメッシュを選択し、変換を実行。スプライン形状になります。


最後にもう一度変換を実行。ソリッドモデルになります。


CAM

あとは通常のCAMの作業と同じです。モードをCAMに切替え、セットアップで座標系とワークのサイズを指定。加工毎に切削の設定を行い、ツールパスを生成します。


ツールパスを選択してシミュレーションを見る事ができます。



初めての作業なので、慎重にスタイロフォームでテスト。


切削完了。シミュレーションで確認できる時間と、実際の切削時間はかなり近いです。

有孔ボードの壁を立てた

August 6, 2016

道具類を引っ掛けるために、有孔ボードの壁を立てました。
今回は@yuqico が設計、図面、材料の調達をしてくれました。

image-1652

柱を立てる

ディアウォールという商品を使って、2x4材の柱を床と天井で突っ張るように立てます。

ディアウォールは、天井側と床側がセットになっていて、天井側にはスプリングが入っています。パネの力と裏側のグリップで固定されます。

今回は白を使いましたが、ダークブランとライトブラウンもあるので、内装に合わせて使うと良さそうです。

image-1651

説明書には、柱の長さは、床から天井までの高さマイナス45mmとありますが、実際に使用した方のブログでは、少しキツめのほうがしっかり固定されると書いてあったので、マイナス40mmとしました。

うちは天高が2690mmだったので、1820mmと830mmの材を金具で連結しました。

image-1650

金具はこんな感じ。シンプソン金具というメーカーの2x4材用の連結金具。これを
皿木ネジ 3.1mm x 25mmで固定します。下穴は開けなくても大丈夫でした。

image-1649

柱の上下にディアウォールを嵌めます。天井に押し付けるように斜めに入れ、最後は床側の柱を壁に押し当てます。ゆるかった場合には、調整の用プレートが2枚付属しているので、それを挟んで調整します。

image-1648

有孔ボードをネジで止める

トラス頭タッピングネジ3mm x 16mmにワッシャーを入れて、有孔ボードの穴から柱に固定します。こちらも下穴無しでOKでした。

有孔ボードの大きさは、1830mm x 920mm x 5.5mm、穴の径は5mmです。
穴のピッチは25mmと表記されていますが、1インチ(25.4)の金具が問題なく使えました。

image-1647

フックを取り付ける

パンチングポード用フックとして、いろいろな種類が売られています。左側の2つは、引っ掛けるだけなので、フックの移動がしやすいです。一番右側のタイプは、ストッパーが付いていて抜けにくくなっています。

image-1646

これまで、工具類は箱に入れていましたが、必要な時にサッと使えるので大変便利になりました。それに見た目もかっこいいですよね。

image-1645

材料

和室の床に防音対策

July 4, 2016

自宅の和室を、木工作業などをする作業部屋に改装しました。鉄筋コンクリートのマンションの角部屋なので、横方向への心配はあまり無く、主に床への振動・衝撃の対策を行っています。

部屋の全体。 - Spherical Image - RICOH THETA


防音について

防音と言っても、どういう部材をどういう順番で使えばいいのかわからないので、まずはいろいろなサイトを見てみました。特にここがわかりやすかったです。
>アン・ノイズ 騒音の種類と対策

固体音
足音、洗濯機の揺れなどの振動が建物を伝わって感じられる音。衝撃の伝わり方は建物の構造によるので、後から対策するのは難しい。

対策
衝撃緩和:弾力のあるマットなどで揺れや衝撃を吸収して、建物に伝わる前に防ぐ。

空気音
人の声やスピーカーからの音楽など空気を伝わる音。低音は遮りにくい。

対策
吸音:スポンジのような多孔性の素材で空気の振動を受け止め、穴の中で熱に変換する。
遮音:重い素材の表面で音を反射する。


購入した部材

遮音シートはゴム製のため、コンクリートに癒着しそうな気がしたので、一番下に養生シートを敷きました。床には遮音シートと防振マットの2重構造とします。防振マットは少し柔らかいので、表面に合板を敷きます。


・化粧合板
・防振マット
・遮音シート
・養生シート
・コンクリート

遮音材
大建工業 遮音シート455H
455mm x 6000mm x 2.8mm(6畳間で3巻使用)

防振材
P防振マット
910mm x 455mm x 10mm(6畳間で21枚使用)


化粧合板
SHARP・BOARD・ミディアムオークMO2.5t3*6尺高機能プリント合板
910mm x 1820mm x 2.6mm(6畳間で6枚使用)


養生シート
ハンディ・クラウン 布コロナマスカー
1100mm x 2500mm (6畳間で1巻使用)
今回使用した材料は、全て大型のカッターで切断しています。


ちなみに、今回使った材料は全てカッターで切断できます。


施工手順

1. まずは畳を剥がし、コンクリートに影響が出ないように、養生シートを敷きました。


2. ゴム製の遮音シート。重みで音を遮るので、とても重いです。


3. 防振マット。細かく砕いたゴムを圧縮したような感じ。


4. 最後に化粧合板を張ります。そのままだと反って端が浮いてしまったので、裏側をガムテープで貼り付けました。


完成

これで床の防音ができました。音の性質と対策について、ネットの情報はサイトによって言葉の使い方がまちまちだったりして、ちょっと混乱してしまいます。今回の施工も本当に効果があるかわかりません。苦情が来ないことを願うばかりです。。

次は工具を収納する有孔ボードや壁の防音対策をしていこうと思っています。早くCNCフライス盤やスライド丸ノコを気兼ね無く使いたいなあ。

部屋の全体。 - Spherical Image - RICOH THETA

MacBook用のUSB-Cアダプタ

May 29, 2016
新しくMacBookを買ったのですが、USB-Cが1つしか無いことに加え、使用条件の縛りが多くてハブ選びに苦労したのでメモ。

条件

・サブディスプレイとして、D-SubとHDMI(4K)に繋ぎたい
・社内の規則で、ネットは有線のみ
・使用中に電源が切れては困るので、USB Power Delivery有り

最初に候補としたのは、当然Appleの「USB-C Digital AV Multiportアダプタ」。


openFrameworksでアイコンを変える

May 16, 2016

oFのアプリのアイコンを変えようと思って、サクッとできそうなのにちょっとハマったので、自分用メモ。

環境

OS : OSX 10.11

Xcode : 7.3.1

oF : 0.9.3

画像ファイルからアイコンファイルを作る

Image2icon」というアプリが便利。元画像を放り込むと、「hoge.icns」というアイコン用のファイルに変換してくれる。基本無料だけど、課金するといろんなテンプレートから選べるようになる。

image-281

アイコンファイルを設定する

oFプロジェクトのbin/data/フォルダの中に作ったアイコンファイルを入れる。

image-280

Xcodeで「Project.xcconfig」を以下のようにファイル名とパスを設定する。今回はファイル名を標準のままにしたので、パスだけ変えている。

ICON_NAME_DEBUG = icon-debug.icns
ICON_NAME_RELEASE = icon.icns
//ICON_FILE_PATH = $(OF_PATH)/libs/openFrameworksCompiled/project/osx/
ICON_FILE_PATH = bin/data/
image-279

更新されない時

・Finderの表示方法を変える。リストからアイコンへ変更するなど。

・アイコンのキャッシュを消す。
Macアプリのアイコンがキャッシュで変わらない時の対処法

完成

オリジナルのアイコンになるとちょっと嬉しい

image-278

電子楽器を自分で作れる「Ototo」

October 30, 2015

Ototoは、野菜や飲み物・金属の食器などを繋いで、電子楽器を簡単に作ることができるデバイスです。
http://www.ototo.fm

image-1760

入手方法

こちらの公式通販ショップから購入できます。注文してから1週間くらいで届きました。
>>オンラインショップ

オープンセサミ

外箱を開けるとこんな可愛らしい箱が。わくわくしてきます。

image-1759
image-1758
image-1757
image-1756

中身は、本体ボード1つ・ワニ口クリップたくさん・説明書1冊。

使ってみる

説明書をめくると、最初の使い方が書いてあります。単三電池2個をボードにセットし、右側の電源スイッチをON(目のアイコンがパチッと開いている方)にします。ボード手前のセンサー部分を触ると、中央のスピーカーから音が鳴ります。じゃじゃーん!

image-1755
image-1754

次に付属のワニ口クリップを使って、センサーと電気を通す物(しめじなど)を繋ぎます。スピーカー横の2つのボタンを同時押しすると、リセットがかかってチューニングされます。繋いだ物を触ると音が鳴ります。鳴らない場合は、電池を交換するかUSBケーブルを繋いでみてください。

こんな感じで、食べ物や液体、金属の食器など電気を通す物を鍵盤として使うことができました。簡単で楽しいです。

>>Processing.org

ProcessingでMidiを使うには、「MidiBus」というライブラリを使います。

Processingのメニューから、スケッチ - ライブラリのインポート - ライブラリの追加 を選択します。ライブラリ導入のウィンドウが開くので、検索窓に「midi」と入れると、「the MidiBus」という項目が表示されます。これを選択して右下のInstallボタンを押すと、ダウンロード・インストールが実行されます。

image-1753

使い方で注意するところは、MidiBusオブジェクトを実体化する時に、入出力のMIDI機器を名前で指定する必要があります。Ototoの場合は"Unknown name"と指定します。他のデバイスと重複した場合にどうなるかは不明。今回は出力機器を使わないので、3つ目のパラメータは-1としています。

noteOn()で押された瞬間、noteOff()で離された瞬間を取得できます。パラメータとして渡される「pitch」で押された鍵盤を判断しています。

import themidibus.*; //Import the libraryMidiBus myBus; // The MidiBusfloat spring = 0.05;float gravity = 0.1;float friction = -0.9;int id = 0;ArrayList balls = new ArrayList();void setup() {  //fullScreen();  size(displayWidth, displayHeight );  noStroke();  MidiBus.list();    myBus = new MidiBus(this, "Unknown name", -1 ); //ototo is "Unknown name"}void draw() {  background(0);  int numBalls = balls.size();  for (int i = 0; i < numBalls; i++) {    balls.get(i).collide();    balls.get(i).move();    balls.get(i).display();  }}void addBall(float _x, float _y, color _color) {  Ball b = new Ball( _x, _y, random(100, 200), 0, balls, _color );  balls.add( b );}// MIDI Event --------------------------------------void noteOn(int channel, int pitch, int velocity) {  // Receive a noteOn  println();  println("Note On:");  println("--------");  println("Channel:"+channel);  println("Pitch:"+pitch);  println("Velocity:"+velocity);  switch( pitch ) {  case 61 :    addBall( random(width), 0, color( 127, 127, 127 ) );    break;  case 63 :    addBall( random(width), 0, color( 255, 255, 0 ));    break;  case 66 :    addBall( random(width), 0, color(  255, 127, 0 ));    break;  case 68 :    addBall( random(width), 0, color(  255, 255, 255 ));    break;  case 70 :    addBall( random(width), 0, color(  0, 255, 0 ));    break;  }}void noteOff(int channel, int pitch, int velocity) {  // Receive a noteOff  println();  println("Note Off:");  println("--------");  println("Channel:"+channel);  println("Pitch:"+pitch);  println("Velocity:"+velocity);}void controllerChange(int channel, int number, int value) {  // Receive a controllerChange  println();  println("Controller Change:");  println("--------");  println("Channel:"+channel);  println("Number:"+number);  println("Value:"+value);}class Ball {  float x, y;  float diameter;  float vx = 0;  float vy = 0;  int id;  ArrayList others;  color ballColor;  Ball(float xin, float yin, float din, int idin, ArrayList oin, color _color ) {    x = xin;    y = yin;    diameter = din;    id = idin;    others = oin;    ballColor = _color;    id++;  }   void collide() {    int numBalls = balls.size();    for (int i = id + 1; i < numBalls; i++) {      float dx = others.get(i).x - x;      float dy = others.get(i).y - y;      float distance = sqrt(dx*dx + dy*dy);      float minDist = others.get(i).diameter/2 + diameter/2;      if (distance < minDist) {         float angle = atan2(dy, dx);        float targetX = x + cos(angle) * minDist;        float targetY = y + sin(angle) * minDist;        float ax = (targetX - others.get(i).x) * spring;        float ay = (targetY - others.get(i).y) * spring;        vx -= ax;        vy -= ay;        others.get(i).vx += ax;        others.get(i).vy += ay;      }    }  }  void move() {    vy += gravity;    x += vx;    y += vy;    if (x + diameter/2 > width) {      x = width - diameter/2;      vx *= friction;    } else if (x - diameter/2 < 0) {      x = diameter/2;      vx *= friction;    }    if (y + diameter/2 > height) {      y = height - diameter/2;      vy *= friction;    } else if (y - diameter/2 < 0) {
      y = diameter/2;
      vy *= friction;
    }
  }

  void display() {
    fill( ballColor );
    ellipse(x, y, diameter, diameter);
  }
}