p5.fab: p5.jsから3Dプリンタをコントロールする

本記事は、Processing Advent Calendar 2024の16日目の記事です。

去年の記事:p5.jsでデジタル刺繍
一昨年の記事:p5.jsとmicro:bitでフィジカルコンピューティング

概要

本稿では、p5.jsの拡張ライブラリ「p5.fab」を使って、p5.jsから3Dプリンタを操作する方法を紹介します。

une2tube

はじめに

近頃の3Dプリンタの性能向上と、それに伴った低価格化は目覚ましいですね。 特にBanbuLabは圧倒的な印刷速度と出力のクォリティで、 それまでの定番であったClearity Ender 3の地位をあっというまに奪ってしまいました。

Ender3Pro BambuLab
Ender3Pro BambuLab

そんなわけで筆者の職場にもヒーターやベルトの劣化などのメンテナンスが面倒で放置されたEnder3Proがゴロゴロと転がっています。

こうした3Dモデルの素早い印刷には向かなくったEnder3Proも、XYZ+Eの4軸の位置制御と温度管理が可能なプログラマブルな装置として捉え直すとまだまだいろいろな可能性が眠っています。

本稿では、Blair Subbaramanらによって開発されたp5.jsから3Dプリンタを直接操作するためのライブラリ「p5.fab」を使って、 3Dプリンタの構成要素を活かしたハックを探ってみることにしました。

普段のp5.jsでは画面上の二次元のピクセル上に絵を描きますが、p5.fabを使うと3Dプリンタを「空中にお絵かきできる3Dペン」のように扱えます。溶かしたフィラメントを自由自在に動かして、実際の三次元空間に立体的な作品を作ることができるのです。

本稿で紹介する内容は、3Dプリンタをメーカーの定められた使用法外で使用するものです。 これらの行為は自己責任で行うものとし、本稿の内容に起因するいかなる損害(本体の破損、火傷などの身体的損傷、火災等を含むがこれらに限定されない)について、筆者は一切の責任を負いかねます。 実施される場合は、安全性を十分に確認し、関連する法令・規制を遵守した上で行ってください。

3Dプリンタの構成とG-code

3Dプリンタの構成

まずは3Dプリンタの基本的な構成要素を見ていきましょう。

装置区分 構成要素
移動装置 - XYZ軸の移動機構
- フィラメント押し出し装置
- 各軸の0点検出スイッチ
温度管理 - フィラメント押し出し装置のヒーター
- フィラメント押し出し装置のクーラーファン
- ヒーターテーブル
コントローラー - 移動制御
- 温度制御
- フィラメント押出制御
- 制御コードの解釈

Ender3のような熱溶解積層方式(FDM)の3Dプリンタは、 XYZ軸の移動機構にグルーガンのようにフィラメントを溶かしながらニュルニュルと押し出す装置のE軸をとりつけたというような構成になっています。

基本的には、CNCミリングマシンやレーザー加工機といった、多軸の移動テーブルを利用する工作機械のCNC三兄弟といった感じです。

通常の3Dプリンタの運用では、CADソフト等で作成した3dモデルを、スライサーソフトでツールパスに変換し、3Dプリンタがそのツールパスをたどりながら溶かしたフィラメントを吐き出し、3d物体を出力するわけです。

G-codeの命令文

3Dプリンタの出力には、ツールパスやフィラメント押し出し装置の温度などの全ての動きを指定してやる必要があります。これらの指示は「G-code」という命令文で行います。 G-codeは3Dプリンタに対して、どこに移動し、どのくらいフィラメントを押し出し、どの程度の温度で動作するかなどを細かく指示できる言語です。

たとえば


G1 X10 Y20 Z30 E40 F50 ; 移動命令

という命令では、

  • G: 移動命令
    • G1: 直線移動(フィラメント押し出しあり)
    • X,Y,Z: X,Y,Z軸に10mm,20mm,30mm移動
    • E: E軸のフィラメントを40mm押し出し
    • F: 最大速度を50mmに指定

というように、3Dプリンタの動作指示が解釈されるわけです。

G-codeの命令一覧

以下に3Dプリンタをコントロールするための代表的なG-codeの命令一覧を示します。

コマンド 説明
G0/G1 移動命令 G1 X10 Y20 Z30 F3000 ; XYZ軸を指定位置に移動
G1 X10 Y20 Z30 E5 F3000 ; XYZ軸を指定位置に移動しながらフィラメントを5mm押し出し
G28 ホーミング(原点復帰) G28 ; 全軸ホーミング
G28 X ; X軸のみホーミング
G90 絶対位置モード G90 ; 絶対位置モードに設定
G91 相対位置モード G91 ; 相対位置モードに設定
G92 現在位置を指定値に設定 G92 X0 Y0 Z0 ; 現在位置を原点として設定
M104 ノズル温度設定 M104 S200 ; ノズル温度を200度に設定
M140 ベッド温度設定 M140 S60 ; ベッド温度を60度に設定
M106 ファン制御 M106 S255 ; ファンを最大速度で回転(S0-255)
M107 ファン停止 M107 ; ファンを停止
M84 モーター電源オフ M84 ; すべてのモーターの電源をオフ

3DプリンタをG-codeで直接操作してみる

👈️やってみよう👉️

では実際に、3Dプリンタとの通信の確認を含めて、シリアルターミナルから3Dプリンタ(Ender3Proを使用)を操作してみましょう。

  1. 3DプリンタとPCをUSBケーブルで接続します
  2. WebSerialターミナル: https://webserial.io/ を開きます
  3. ボーレートを115200に設定、改行コードをnew lineに設定します
  4. “select serial port”ボタンを押して、3Dプリンタのポート番号を選択
    • macOSの場合は/dev/cu.usbmodem1******
    • Windowsの場合はCOM* (デバイスマネージャーで確認できます)
  5. ポート番号を選択したら、”connect”ボタンを押して接続します
  6. G28と入力して、3Dプリンタが原点復帰するか確認します
  7. 以下のコマンドを入力して、3Dプリンタの動作を確認します

 G28 ; 原点復帰
 G1 X100 Y100 Z50 F3000 ; 中央付近まで移動
 M104 S200 ; ノズルを200度に加熱
 M140 S60 ; ベッドを60度に加熱
 G1 E50 F100 ; フィラメントを50mm押し出し
 M104 S0 ; ノズルの加熱を停止
 M140 S0 ; ベッドの加熱を停止
 M84 ; モーターをオフ

p5.fabで3Dプリンタを操作してみる

シリアルターミナルからG-codeによって3Dプリンタを直接できたところで、いよいよp5.fabを使って3Dプリンタを操作してみましょう。

p5.fabのコードはこちらからダウンロードできます。 また、 あるいは オンラインエディタ で、p5.fabを試してみることができます。

editor.p5js.orgを利用している場合は、 index.htmlに以下のコードを追加してください。

 <script src="https://unpkg.com/p5-webserial@0.1.1/build/p5.webserial.js"></script>
 <script src="https://machineagency.github.io/p5.fab/lib/p5.fab.js"></script> 
 <!-- 筆者による改良版
  <script src="https://nkymut.github.io/p5.fab/lib/p5.fab.js"></script> 
  -->

p5.fabのテンプレート

以下にp5.fabの基本的なテンプレートを示します。

p5.fabのテンプレート

let fab;

function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  fab = createFab();

  let connectButton = createButton("connect");
  connectButton.position(20, 20);
  connectButton.mousePressed(function () {
    fab.serial.requestPort(); // シリアルポートの選択ダイアログを表示して接続
    //fab.connectPrinter(); // 選択したシリアルポートに接続
  });

  let printButton = createButton("print");
  printButton.position(20, 60);
  printButton.mousePressed(function () {
    fab.print(); // start streaming the commands to printer
  });

  let stopButton = createButton("stop");
  stopButton.position(20, 100);
  stopButton.mousePressed(function () {
    fab.stopPrint(); // stop streaming the commands to printer.
  });

  let exportButton = createButton("export");
  exportButton.position(20, 140);
  exportButton.mousePressed(function () {
    fab.exportG-code(); // export G-code to a file.
  });
}

function fabDraw() {
  // ここに造形プロセスを記述
}
function draw() {
  orbitControl(2, 2, 0.1);
  background(255);
  fab.render(); // 3Dプリンタのツールパスを描画
}

https://editor.p5js.org/didny/sketches/vPwDpre81

3Dプリンタとの通信

p5.fabの3Dプリンタとの通信プロセスは、以下のようになっています。

  1. createFab()でp5.fabのインスタンスを作成
  2. fab.serial.requestPort()でシリアルポートの選択ダイアログを表示して接続
    • 筆者の改良バージョンでは、fab.connectPrinter()でシリアルポートの選択ダイアログを表示して接続
  3. fabDraw()内に3Dプリンタの動作を記述
  4. fab.print()fabDraw()内のコマンドを送信して3Dプリンタを動かす
  5. fab.stopPrint()fabDraw()内のコマンドの送信を停止

筆者の改良バージョンでは、これに加えて USBポートを介して制御できないBambuLabのような3Dプリンタのため、 fab.exportG-code()fabDraw()内のコマンドをG-codeファイルとして出力することができます。

p5.fabで図形を描く

では早速、実際に簡単な図形を描いてみましょう。 以下の表に主要な関数と対応するG-codeをまとめました。 各関数は内部で対応するGコードをプリンタへ送信するコマンドリストに追加します。

p5.fabの代表的な関数一覧

関数 説明 G-code
fab = createFab(); p5.fabのインスタンスを作成 -
fab.autoHome(); すべての軸のホーミング(原点復帰)を実行 G28 ; すべての軸をホーム位置に移動
fab.setTemps(205, 60); ノズル温度を205度、ベッド温度を60度に設定 M104 S205 ; ノズル温度を205度に設定
M140 S60 ; ベッド温度を60度に設定
fab.moveRetract(x, y, z); フィラメントを押し出さずに指定位置(x,y,z)に移動 G0 X{x} Y{y} Z{z} ; 指定位置に移動
fab.moveExtrude(x, y, z, amount, speed); フィラメントを押し出しながら指定位置に移動 G1 X{x} Y{y} Z{z} E{amount} F{speed} ; フィラメントを押し出しながら移動
fab.introLine(); プリントベッド左側にテストラインを描画 G1 X0 Y0 Z0 E5 F100 ; フィラメントを5mm押し出しながら原点に移動
fab.presentPart(); 造形物を取り出しやすい位置まで退避  
fab.setAbsolutePosition(); すべての軸(X/Y/Z/押出機)を絶対位置モードに設定 G90 ; 絶対位置モードに設定
fab.setERelative(); 押出機のみを相対位置モードに設定 M83 ; 押出機を相対位置モードに設定
fab.finishPrint(); プリントを終了(ヒーター・ファン停止、モーターオフ) M104 S0 ; ノズルの加熱を停止
M140 S0 ; ベッドの加熱を停止
M107 ; ファンを停止
M84 ; モーターをオフ

円筒を描く

p5.fabによるパラメトリックな造形の手始めとして、 円筒を描いてみましょう。

p5.fab円筒サンプル
function setup() {
  // キャンバスを作成し、WEBGLモードで3D表示を可能にする
  createCanvas(windowWidth, windowHeight, WEBGL);
  // p5.fabオブジェクトを作成
  fab = createFab();
}

function fabDraw() {
  // プリンターの初期設定
  fab.setAbsolutePosition(); // 全軸(X/Y/Z/押出機)を絶対位置モードに設定
  fab.setERelative(); // 押出機のみ相対位置モードに設定
  fab.autoHome(); // プリンターをホームポジションに移動
  fab.setTemps(205, 60); // ノズル温度205℃、ベッド温度60℃に設定(フィラメントに適した温度を使用すること)
  fab.introLine(); // プリントベッド左側にテストラインを描画

  // 円筒のパラメータ設定
  let r = 25;              // 円筒の半径
  let layerHeight = 0.2;   // 層の高さ
  let h = 10;              // 円筒の高さ
  
  // プリントベッドの中心座標を取得
  let center = new p5.Vector(fab.centerX, fab.centerY);

  // 層ごとのループ処理
  for (let z = layerHeight; z < h; z += layerHeight) {
    // 一層内での円周方向のループ処理(200分割)
    for (let t = 0; t <= TWO_PI; t += TWO_PI / 200) {
      if (z == layerHeight && t == 0) {
        // 最初の層の開始点への移動(フィラメント押出なし)
        fab.moveRetract(r * cos(t) + center.x, r * sin(t) + center.y, z);
      } else {
        // フィラメントを押出しながら円周上を移動
        fab.moveExtrude(
          r * cos(t) + center.x,  // X座標: 中心からの距離×cos(角度)
          r * sin(t) + center.y,  // Y座標: 中心からの距離×sin(角度)
          z                       // Z座標: 現在の層の高さ
        );
      }
    }
  }

  // プリント終了処理
  fab.presentPart();   // 完成したパーツを取り出しやすい位置に移動
  fab.finishPrint();   // プリントを終了(ヒーター・ファン停止、モーターオフ)
}

function draw() {
  // プレビュー表示の更新
  background(255);     // 背景を白に設定
  fab.render();        // FABオブジェクトの3Dプレビューを描画
}

https://editor.p5js.org/didny/sketches/GrdPo8kjF

円筒の出力結果

 
円筒

このように、p5.fabを使うことで、3Dプリンタの動作をJavaScriptのコードで直感的に制御できます。

うねうねチューブ

単純な円筒ではつまらないので、 次は、サイン波でフィラメント押し出しの動きを変調させて うねうねと波打つチューブを作ってみましょう。

p5.fabうねうねチューブ
function fabDraw() {
  // プリンターの初期設定
  fab.setAbsolutePosition(); // 全軸(X/Y/Z/押出機)を絶対位置モードに設定
  fab.setERelative(); // 押出機のみ相対位置モードに設定
  fab.autoHome(); // プリンターをホームポジションに移動
  fab.setTemps(205, 60); // ノズル温度205℃、ベッド温度60℃に設定(フィラメントに適した温度を使用すること)
  fab.introLine(); // プリントベッド左側にテストラインを描画

  // チューブのパラメータ設定
  let r = 35;              // チューブの基本半径
  let layerHeight = 0.2;   // 層の高さ
  let h = 20;              // チューブの全体の高さ
  let s = 25;              // 未使用パラメータ
  let a = 5;               // サイン波の振幅
  let f = 8;               // サイン波の周波数
  let center = new p5.Vector(fab.centerX, fab.centerY); // プリントベッドの中心座標

  // 層ごとのループ
  for (let z = layerHeight; z < h; z += layerHeight) {
    // X軸方向の振幅を計算(高さに応じて変化)
    let ampX = a / 2 * sin(z * f / 10);

    // 一層内での円周方向のループ
    for (let t = 0; t <= TWO_PI; t += TWO_PI / 200) {
      if (z == layerHeight && t == 0) {
        // 最初の層の開始点への移動(フィラメント押出なし)
        fab.moveRetract(r * cos(t) + center.x, r * sin(t) + center.y, z);
      } else {
        // Y軸方向の振幅を計算(角度に応じて変化)
        let ampY = a * sin(t * f * 2);

        // フィラメントを押出しながら次の点に移動
        fab.moveExtrude(
          r * cos(t) + ampX + center.x,  // X座標: 基本円形 + サイン波による変調
          r * sin(t) + ampY + center.y,  // Y座標: 基本円形 + サイン波による変調
          z                              // Z座標: 現在の層の高さ
        );
      }
    }
  }

  // プリント終了処理
  fab.presentPart();   // 完成したパーツを見やすい位置に移動
  fab.finishPrint();   // プリントを終了
}

https://editor.p5js.org/didny/sketches/_q68M_pdK

うねうねチューブの出力結果

   
うねうねチューブ うねうねチューブの正面

うねうねブレスレット

いかにもジェネレーティブデザインといったような、波打つ形状のブレスレットが完成しました。

このような形状は、3DモデリングソフトのRhinoceros+GrasshopperOpenSCADでも作れますが、p5.fabには大きな利点があります。 p5.fabでは、3Dメッシュモデルに出力してスライサーでG-codeに変換する必要がありません。 代わりに、3Dプリンタのフィラメント押出プロセスを直接プログラムで制御できるので、 いわばピクセルパーフェクトならぬフィラメントパーフェクトな造形が、p5.jsによる直感的な記述で可能になります。

まとめ

本稿では、p5.fabによって3Dプリンタをコントロールする方法を紹介しました。

p5.fabを使うことで、3Dプリンタの動作をp5.jsで直接制御し、従来の3Dモデリングとスライサーを介した造形とは異なるアプローチで、より自由度の高い造形表現が可能になります。特に、フィラメントの押出量や移動速度、温度などの低レベルな制御を直接プログラムできることで、従来の3D印刷の枠を超えた創造的な活用が期待できます。

今回は時間が足りずに紹介できませんでしたが、

  • カメラと組み合わせてコマ撮り用3次元リグとして使う
  • フィラメント押出機の代わりに筆やペンを取り付けてドローイングマシンとして使う

などなど、p5.jsと3Dプリンタの組み合わせには、まだまだ色々な可能性が残されています。

「プリント品質がイマイチで思うような造形ができない…」

「3Dモデリングソフトの操作が難しくて挫折してしまった…」

そんな理由で使わなくなってしまった3Dプリンタも、p5.fabを使えば新しい可能性が広がります。

シンプルなプログラミングで直感的に造形できるp5.fabなら、 3Dプリンタをもっと気軽に、もっと創造的に活用できるはずです。

この機会に、眠っている3Dプリンタに新しい命を吹き込んでみませんか?

というわけで、よい年末をー

参考文献