100均の収納ケース SIKIRI-0 を自在に仕切れる『SIKIRI-0 トレイメーカー』をリリースしました!ブラウザ上でトレイのレイアウトを設計すると、3Dプリントが可能な .3mf形式のファイルがダウンロードできるサービスです。今回は作成した経緯や、どうやって動いているのかなどを書いていきたいと思います。使用方法についてはこちらのページをご覧ください。

作成したきっかけ

山田化学のSIKIRIシリーズはとても便利で、色々なパーツの整理に使っていました。しかし収納するパーツのサイズは様々で、SIKRI-xだと大きすぎ、SIKIRI-yだと小さすぎたり、小さいものと大きいものを1つに入れたときに綺麗に収納できずに困っていました。自分でトレイを作れたら便利だなぁ、SIKIRI-0に仕切り付けちゃえばよくね? ってことから始まりました。

最初のバージョン

最初に作ったものは、SIKIRI-0にすっぽり入るサイズの大きなトレイを自動生成するものでした。OpenSCADという、3Dモデルをプログラミング言語で記述して生成できるCADツールを使用しました。サイズを変えたり個数が変わるような3Dモデルを作るには、このようなプログラムで生成できるツールの方が適しています。

プログラムはこんな感じに記述します。OpenSCADのプログラムは関数型プログラミングと言うそうで、ちょっとクセが強い言語です。

// トレーの外周の形状を作成する
module all_cubic(w, d, h, sa, a1m) {
    rounding = (a1m) ? [0, BASE_ROUND, BASE_ROUND, 0] : BASE_ROUND; // A1 miniの場合は左側のみ作る
    prismoid(size1=[w, d], size2=[w+sa, d+sa], h=h, rounding=rounding);
}

// トレーの壁を作成する
module only_wall(w, d, h, sa, t1, t2, a1m) {
    difference() {
        all_cubic(w, d, h, sa, a1m);
        up(t2) {
            isk_w = w - t1 * 2;
            isk_d = d - t1 * 2;
            all_cubic(isk_w, isk_d, h+0.01, sa, a1m);
        }
    }
}

変数への代入は1回きりだったり、どこに代入する命令を書いても初期化時に代入されているとか、普通のプログラムの感覚でやろうとするとかなり戸惑います。CADソフトで設計しなくてもプログラムで3Dモデルが作れるのが面白くて、OpenSCADのドキュメントを見ながら苦行に耐えました覚えました。

しかし結局このバージョンは実際には使われませんでした。当時私が持っていた3DプリンターはBambu Lab A1 Miniで、これは大きすぎてプリントできません。一応A1 Miniでプリントするためのオプションも作ってみたのですが、サイズの制約が出てきます。

もう一つの理由は、パーツを収納する前に仕切りのサイズを決めないといけない点がありました。収納したいパーツは増えたり減ったりします。将来必要になるサイズなんてわかりませんし、その度に作り直すのも非効率的です。苦労して作ったのにボツになりました。

トレイ差し替え方式に方針変更

作ったあとから変更したいところだけ差し替えできる「トレイ」方式に方針を変更しました。追加するパーツが入らなければ、それが入るトレイに差し替えるだけ。今まで入っていたパーツはそのままでいけます。あらかじめトレイを数パターン作っておけば、必要なときにすぐに収納できますね。前回作ったOpenSCADのプログラムを修正して、1つのトレイを作るプログラムを作りました。

グリッド方式の採用

SIKIRI-0の内寸は約 225 × 157.5mm で、よく見ると10:7の比率だということに気づきました。22.5 x 22.5mmの正方形のセルが、横10個、縦7個ぴったりきれいに収まります。正方形のグリッドなら縦でも横でも同じサイズのものが置けます。角を丸めてあげればケースの端でも中でもどこにでも置けます。そこでサイズの自由度は多少減りますが、22.5mm四方のグリッドに配置する方式でいくことにしました。

トレイをレイアウトするUIの作成

OpenSCADのクッソ使いにくいシンプルなパラメーター入力画面でいちいちサイズを設定するのは面倒でした。OpenSCADでSTLファイルを生成するには、パラメーター変更→レンダリング→STL保存、というステップで進む必要があり、これを何度も繰り返すのは大変です。幸いOpenSCADにはコマンドラインで実行することもできます。

openscad -D 'NUM_H=2'-D 'NUM_V=7' -o output.stl scafprogram.scad

コマンド実行で生成できれば自動化できますね。

Web UIとバックエンドの作成

ブラウザ上でトレイをレイアウトして、一気にトレイを作成できたら便利です。ということで、ここから先はAIに頑張って作ってもらいました。フロントエンドはHTML+CSS+JavaScript、バックエンドはPythonで、PythonからOpenSCADを呼んでいます。

この水色のトレイをマウスのドラッグでサイズを変更したり移動したりします。これを自力で実装するのは大変そうですが、Composer 1.5が難なく作ってくれました。Composer 1.5ってすごく賢いわけではないんですが、高速なので普段使いにはちょうどいいです。ハマったときだけ別のモデルにして解決しています。

完成したものを使ってみると・・・これは便利すぎる!自分で使うだけじゃもったいないからもっと広めたい!

公開版の作成

とはいえ、いきなりルーターのポートを開けてPCに接続させるわけにもいきません。PCを24時間起動していないといけませんし、パラメーターに細工されたらPC上で任意のコマンドが実行されてしまいます。WebサーバーもPython内蔵のものなので同時アクセスに向きません。AIを使えばサクッと簡単なアプリが作れてしまいますが、それを公開させるとなると話は全然変わってきます。

システム構成

今回は以下のようなキュー方式を採用しました。

Web側は受付のみに徹し、直接3Dモデルの生成は実行しません。一旦キューにデータを保存したら、完了するまで待機します。キューマネージャーはキューの状態を監視し、新しいキューがあったら1つだけ取り出して3Dモデル合成プログラムを実行します。3Dモデル合成プログラムは必要な数だけOpenSCADを実行して、生成されたSTLファイルを合成し、1つの3MFファイルを作成します。3MFファイルが完成するとWeb UIにダウンロードボタンが現れる仕組みです。

キュー方式を採用することで同時接続時の負荷上昇を抑制できますし、将来負荷分散をする際にも改修が容易な設計になっています。

専用のサーバーを用意

まず必要になるのが24時間起動しているサーバー。元々うちではQNAPのNASでVirtualization Stationという仮想化環境を稼働しているので、ここにインスタンスを追加することにしました。おそらく今どきのラズパイより遅いでしょう。

OSはuBuntu 24 Serverを採用。私は今までRedHat系を使っていたのですが、AI関係のプログラムをRedHat系Linuxで動かそうとすると、インストールに苦労した上に結局エラーで動かないということがあり、最近はuBuntuを好んで使うようになりました。これからはuBuntuを覚えた方が便利そうです。

WWWサーバーはApache2 + PHPで構成しました。uBuntuではApache2の設定の作法がRedHat系とは全然違って少し戸惑いましたが、慣れれば便利でいいと思いました。

DMZの作成

サーバーを公開するうえでは避けて通れないのが不正アクセス。どんなにプログラムで対策していたとしても、未知の脆弱性には無力です。公開サーバーを踏み台にしてLAN内に入り込まれたら大変なので、公開サーバーはDMZネットワークに設置することにしました。OpenWrtでDMZ用のルーターを作成して、ファイアウォールの設定でLANへのOutboundを禁止しています。もちろんサーバー側でもファイアウォールでLANへのOutboundを禁止していますが、root権限を乗っ取られたら元も子もないので、多重防衛をしています。

Cloudflare Tunnelの利用

うちのアクセス回線はフレッツ光クロスでIPv6接続をしているので、今までのようにルーターにポートを開けてNATでポートフォワーディングするようなことはできません。そこでCloudflare Tunnelを利用してインターネットへ公開しています。Cloudflareを使うことでSSLのサーバー証明書も作成してくれますし、DNSもCloudflareで管理しているので、サブドメインを追加してサーバーに割り当てるのも簡単です。

REST APIの採用

Pythonの内蔵WWWサーバーでやっていたときは単にJSONを送信して、生成が終わるとファイルが返ってくるような単純な構成でした。公開版ではフロントエンドとバックエンドが非同期で動いているので、REST APIで管理するようにしました。

主なREST API
・処理を受け付けるAPI → 戻り値 UUID
・進捗状況を返すAPI → 戻り値 進捗状況の情報
・ダウンロードAPI → ファイルを返す
・キューの混雑状況を返すAPI → 戻り値 現在キューにある数
・アクセスカウンター → 戻り値 作成回数や個数

処理を受け付けたときに発行されるUUIDを使って進捗状況を確認し、生成が完了したらダウンロードへと遷移するのが大まかな流れです。

キューマネージャーのデーモン化

キューマネージャーはSystemdを使って起動するようにしました。

/etc/systemd/system/sikiri-queue.service
[Unit]
Description=Sikiri-0 Queue Manager
After=network.target

[Service]
Environment="PATH=/path/to/hogehoge/.local/bin:/usr/local/bin:/usr/bin:/bin"
Type=simple
User=hogehoge
Group=hugahuga
WorkingDirectory=/path/to/hogehoge/sikiri
ExecStart=/path/to/hogehoge/.local/bin/uv run queue_manager.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

古いファイルの削除

生成した.3mfファイルや実行済みのキューファイルがサーバーに溜まらないように、crontabで定期的に削除しています。

STLファイルのキャッシュ

1つのトレイの3DモデルデータはSTLファイルとして生成されます。生成された複数のSTLファイルを合成して3MFファイルを生成しています。この中で最も処理が重いのが、OpenSCADでSTLファイルを生成する処理です。トレイはサイズが同じなら誰が作っても同じSTLファイルが生成されますので、同じサイズならわざわざ生成しなくても再利用すれば処理も高速になります。同じサイズのトレイを複数作る場合も、最初の1個目だけ生成して、2個目以降はキャッシュを読みます。

キャッシュの生成ルールは単純で、OpenSCADの引数をsha256でハッシュ化しているだけです。一致するファイルが存在し、更新が一定期間内だった場合はキャッシュか利用されるようになっています。

3MFファイルの合成

3MFファイル(3D Manufacturing Format)の生成にはtrimeshライブラリを使用しました。このライブラリを使うと、STLファイルを読み込んで配置を調整して3MF形式で出力するようなことが簡単にできます。

import trimesh
import numpy as np

# Sceneを作成
scene = trimesh.Scene()

# STLを読み込み
mesh1 = trimesh.load("part1.stl")
mesh2 = trimesh.load("part2.stl")

# 配置用の変換行列を作る
transform1 = np.eye(4)
transform1[0, 3] = 0     # X
transform1[1, 3] = 0     # Y
transform1[2, 3] = 0     # Z

transform2 = np.eye(4)
transform2[0, 3] = 40    # X方向に40mm移動
transform2[1, 3] = 0
transform2[2, 3] = 0

# Sceneに追加
scene.add_geometry(mesh1, transform=transform1)
scene.add_geometry(mesh2, transform=transform2)

# 3MFとして保存
scene.export("plate.3mf")

このプログラムはChatGPTが作ったサンプルコードで実際のものとは異なりますが、だいたいこんな感じで個々のトレイのSTLファイルを並べて、Webの画面と同じレイアウトで出力されるようにしています。実際には5mm間隔にして、プリント時にくっつかないようにしています。

本当はフィラメントの情報なども付けたかったのですが、色を指定してもスライサーで反映されず…。指定方法が他にあるようなので、実際の3MFファイルを解析してみないとわかりませんね。

トレイをストックして組み合わせて使おう!

サイズをあらかじめ決めておいて、トレイをストックしておくとすぐに使えて便利です。こちらは横幅2U、縦幅7Uのサイズに統一して、内側の部屋の数を 1, 2, 3, 4, 8 の5パターンにしたものです。

このトレイを収納するフレームも作ってみました。ファイルのダウンロードはこちらから。5個用と10個用があります。硬いのでペンチが必要かもしれません。固く締めすぎるとトレイが全部入らないかも。

今後の予定

とりあえず作って満足してしまったので気分次第ですが、今後のアップデートするとしたら、実際に使っていて不便に感じたところの改善あたりになると思います。

今感じてるところは、グリッドが22.5×22.5mmと割と大きいので、パーツによっては大きすぎる・小さすぎるというのがどうしても出てきます。グリッドを半分の 11.25 x 11.25mm のサイズで 20:14 に分割するのがあってもいいかなぁと思いました。

ちなみにこの宣伝用の画像はChatGPTに作ってもらいました。左下のプレート上の文字を見るとなんかおかしなことになってますね。入力した画像をそのまま使ってくれればいいのに、なぜか文字の所だけは文字と認識して中途半端に崩れてしまっています。ChatGPTで期待通りにできなかった部分はNanobanana 2で修正して、最後の手直しをGIMPで行いました。

SIKIRI-0 トレイメーカー
https://maker.akibabara.com/sikiri

LINEで送る
Pocket