3Dプリンターがあるとちょっとした小物入れも簡単に作れるのであると便利です。しかし実際はCADソフトで図面を書き、立体化し、STLファイルを出力し、3Dプリンターで出力するので手間はかかります。OpenSCADを使うとサイズなどのパラメーターを与えるだけで簡単にSTLファイルが作れます。

OpenSCADとは?

OpenSCADはCADソフトで、スクリプト言語で3Dのモデルを作成することができます。一般的なCADソフトはマウスで図形を描いて作成していきますが、OpenSCADはプログラムで記述します。

実は私はつい1週間くらい前にOpenSCADのことを知ったばかりで、この10インチラック用シェルフのモデルを見たときに、見慣れない Customize というボタンがあることに気づきました。何かなと思って押してみると、ブラウザ上で3Dモデルのプレビューが表示されて、値を入力できるようになっています。値を変更すると3Dモデルに反映。なにこれすごい!え?どうやってんのこれ??

Codeというタブを開くとプログラムが現れてやっと理解ができました。こうゆうものがあるんだ!新しい世界を見つけてしまいました。(むしろなんで今まで気づかなかった)

OpenSCADのプログラムは独特

OpenSCADのプログラムは独特でかなりクセがあります。プログラミング経験がある人は逆に、独特な記法に最初は戸惑うかもしれません。

// 4辺が角丸のキューブを作成する
module rounded_cube_4c(w, d, h, r) {
    cyh = 0.001;
    r2 = (r < 0) ? 0 : r;
    if (r2 > 0) {
        minkowski() {
            cube([w-2*r2, d-2*r2, h-cyh], center=true);
            cylinder(r=r2, h=cyh, center=true);
        }
    } else {
        cube([w, d, h], center=true);
    }
}

今ならAIに作ってもらえば早いんでしょうが、そもそも何ができるのか知ってないとAIに的確な指示もできません。まずは基本を学びましょう。OpenSCADの書き方はドキュメントに載っています。あーでもなんかいっぱい書いてあってとっつきにくいな、と思って「OpenSCAD 入門」で検索して出てきたこちらこちらのサイトを見て勉強しました。

あとはMaker Worldで Parametric というキーワードで検索すると、OpenSCADで作成された3Dモデルが出てくるので、ソースを読みながら、なるほどー、こうやってるのかー、って参考にしました。Parametricタグで検索してもがいいかもしれません。

BOSL2とは何ぞや?

唐突に出てくるinclude文。

include <BOSL2/std.scad>
include <BOSL2/walls.scad>

OpenSCADのドキュメントには載ってない関数の数々。何かライブラリなのかなと思って検索すると、BOSL2というライブラリがあることがわかりました。これを使うと簡単に記述できたり、高度な形状を簡単に作成できるようです。せっかくなので一通りチュートリアルをやって、これで何ができるのかを学びました。結局使いませんでしたが。

ケースを作る

今回はシンプルなケースを作ります。ただの四角い箱です。幅、奥行、高さを変更できるようにします。壁の厚みも変更できるようにして、角も丸くして持った時に痛くないようにします。

パラメーターも数字を入力せずにスライダーで変更できるようにしました。内寸も下に表示されるので、入れたいもののサイズが決まってるときも便利ですね。

// CASE Width (10 - 340mm)
width = 70.0;    // [10:1:340]
// CASE Depth (10 - 340mm)
depth = 50.0;    // [10:1:340]
// CASE Height (5 - 340mm)
height = 25.0;    // [5:1:340]
// Wall Tickness (0.5 - 5.0mm)
tickness = 2.0;   // [0.5:0.1:5.0]
// Rounding (0 - 100mm)
rounding = 5;    // [0:1:100]

こんな感じで開始値・ステップ・終了値が指定できるようです。これで作成したい値をセットしてSTLボタンを押せば、STLファイルが出力されます。今までは100均でケースを買ってきて、微妙にサイズが合わなかったりして不便なこともありましたが、もうこれでサイズピッタリです。

Webで公開

Bamb LabsのMaker Worldでこのモデルを公開しました!
Parametric Rounded Simple Case

OpenSCADをインストールしなくてもSTLファイルが作成できるので、よろしければ試してみていただければと思います。

自作のコードを試すなら こちら のページで実行することもできます。Thingiverseも.scadファイルに対応していますが、Maker Worldの方が使いやすいです。

ソースコード

/*
  Rounded Simple Case
  パラメトリックな角丸ケースを作成する

  Copyright (c) 2025 Kaz  (https://akibabara.com/blog/)
  Released under the CC BY-NC-SA 4.0 license.
  see https://creativecommons.org/licenses/by-nc-sa/4.0/

  Bambu Labs Parametric Model Maker
  https://makerworld.com/en/makerlab/parametricModelMaker
*/

/* [BASE] */

// CASE Width (10 - 340mm)
width = 70.0;    // [10:1:340]
// CASE Depth (10 - 340mm)
depth = 50.0;    // [10:1:340]
// CASE Height (5 - 340mm)
height = 25.0;    // [5:1:340]
// Wall Tickness (0.5 - 5.0mm)
tickness = 2.0;   // [0.5:0.1:5.0]
// Rounding (0 - 100mm)
rounding = 5;    // [0:1:100]

/* [OPTIONAL] */

// Bottom Fillet (0 - 5.0mm)
bottom_fillet = 0.0;    // [0:0.5:5.0]

// Stackable (support need)
stackable = false;

/* [CURVE QUALITY] */

// Minimum cutting angle (degrees)
$fa = 2;
// Minimum cutting length (mm)
$fs = 0.3;

// 壁の厚みの中まで延長するフィレットの長さ
//function add_fillet_len(r, t) = sqrt(r*r - (r-t)*(r-t));
// function add_fillet_len(r, t) = 
//     let(tmp = 2*r*t - t*t)
//     (tmp >= 0) ? sqrt(tmp) : 0;

// フィレットの作成(len:長さ, r=半径, rot:回転, dis:移動距離)
// module tri_fillet(len, r, rot, dis, fn=12) {
//     rotate([0, 0, rot]) translate([0, -dis+(r/2), 0]) 
//         difference() {
//             cube([len, r, r], center=true, $fn=3);
//             rotate([90, 0, 90]) translate([r/2, r/2, 0])
//                 cylinder(r=r, h=len+0.1, center=true, $fn=fn);
//         }
// }

// 4辺が角丸のキューブを作成する
module rounded_cube_4c(w, d, h, r) {
    cyh = 0.001;
    r2 = (r < 0) ? 0 : r;
    if (r2 > 0) {
        minkowski() {
            cube([w-2*r2, d-2*r2, h-cyh], center=true);
            cylinder(r=r2, h=cyh, center=true);
        }
    } else {
        cube([w, d, h], center=true);
    }
}

// メイン
module main(width, depth, height, rounding, tickness, rounding_b, stackable) {
    union() {
        // 箱を作る
        difference() {
            // 外側の箱
            rounded_cube_4c(width, depth, height, rounding);
            // 内側の箱
            translate([0, 0, tickness]) {
                hull() {
                    // 内側ケースの広い方
                    r = rounding - tickness;
                    translate([0, 0, rounding_b]) rounded_cube_4c(width-tickness*2, depth-tickness*2, height, r);
                    //  内側ケースの狭い方(フィレット幅を引いた分)
                    if (rounding_b > 0) {
                        iw = width - tickness*2 - rounding_b*2;
                        id = depth - tickness*2 - rounding_b*2;
                        ir2 = (iw <= r*2) ? iw/2 - 0.1 : r;
                        ir3 = (id <= ir2*2) ? id/2 - 0.1 : ir2;
                        rounded_cube_4c(iw, id, height, ir3);
                    }
                }
            }
        }
        // 底にフィレットを付ける(この方法ではカーブの部分にフィレットが作れない)
        // if (rounding_b > 0) {
        //     translate([0, 0, -height/2 + rounding_b/2 + tickness]) {
        //         d2_w = depth / 2 - tickness;
        //         d2_d = width / 2 - tickness;
        //         add = add_fillet_len(rounding, tickness) - 0.1;
        //         len_w = width - rounding*2 + add*2;
        //         len_d = depth - rounding*2 + add*2;
        //         tri_fillet(len_w, rounding_b, 0, d2_w);
        //         tri_fillet(len_w, rounding_b, 180, d2_w);
        //         tri_fillet(len_d, rounding_b, 90, d2_d);
        //         tri_fillet(len_d, rounding_b, 270, d2_d);
        //     }
        // }
        // スタック用の出っ張りを作る
        if (stackable) {
            footh = 2.0; // 出っ張りの高さ
            clearance = 0.3; // クリアランス
            translate([0, 0, -(height+footh)/2]) {
                w = width - (tickness + clearance) * 2;
                d = depth - (tickness + clearance) * 2;
                r = rounding - tickness - clearance;
                rounded_cube_4c(w, d, footh, r);
            }
        }
    }
    // 内寸情報を表示(STLには含まれない)
    in_w = width - tickness * 2;
    in_d = depth - tickness * 2;
    in_h = height - tickness;
    %translate([0, -depth/2-10, -height/2]) {
        linear_extrude(height = 0.01) {
            text("Inner Size ", size=5, halign="center", valign="center");
            translate([0,-10,0])
            text(str("W:", in_w, " D:", in_d, " H:", in_h, " mm"), size=5, halign="center", valign="center");
        }
    }
}

// エラー表示(STLには含まれない)
// module error_display(msg) {
//     bambu = true;
//     echo("*** ERROR *** ", msg);
//     /* Bambuだとエラーメッセージがすぐ消えてしまうので図形として出力する */
//     if ($preview || !bambu) {
//         color("red") linear_extrude(height = 0.01) {
//             text("*** ERROR ***", size=5, halign="center", valign="center");
//             translate([0,-10,0])
//             text(msg, size=5, halign="center", valign="center");
//         }
//     }
// }

// エラー処理
// if (width - rounding*2 <= 0) {
//     error_msg = "must be Width - Rounding*2 > 0";
//     error_display(error_msg);
// } else if (depth - rounding*2 <= 0) {
//     error_msg = "must be Depth - Rounding*2 > 0";
//     error_display(error_msg);
// } else if (height - tickness <= 0) {
//     error_msg = "must be Height - Bottom Tickness > 0";
//     error_display(error_msg);
// } else {

// メイン実行
rounding2 = (width <= rounding*2) ? width/2 - 0.1 : rounding;
rounding3 = (depth <= rounding2*2) ? depth/2 - 0.1 : rounding2;
main(width, depth, height, rounding3, tickness, bottom_fillet, stackable);

すでにコメントになってますが、底面と壁の間にフィレットを付けるのに苦労しました。Fusion360とかだと辺を選択してFキー押すだけでフィレットが作れますが、OpenSCADの場合は造形するか削るかしかないんですよね。丸い部分に沿ってフィレットをかけるには、

        minkowski() {
            cube([w-2*r2, d-2*r2, h-cyh], center=true);
            cylinder(r=r2, h=cyh, center=true);
        }

の cylinder() を sphere() にすることでできるんですが、その場合半径が同じになってしまいます。BOSL2だと cuboid() で実現できますがやはり同じです。なお、minkowski()関数は上記の場合、cube() の周りを cylinder() を転がしたときの形を作るもののようです。

いろいろと調べてみましたが解決策は見つからず…。今回のケースでは単に補強が目的だったので、フィレットではなく三角形の面取り(の逆方向)の形状にすることにしました。

                hull() {
                    // 内側ケースの広い方
                    r = rounding - tickness;
                    translate([0, 0, rounding_b]) rounded_cube_4c(width-tickness*2, depth-tickness*2, height, r);
                    //  内側ケースの狭い方(フィレット幅を引いた分)
                    if (rounding_b > 0) {
                        iw = width - tickness*2 - rounding_b*2;
                        id = depth - tickness*2 - rounding_b*2;
                        ir2 = (iw <= r*2) ? iw/2 - 0.1 : r;
                        ir3 = (id <= ir2*2) ? id/2 - 0.1 : ir2;
                        rounded_cube_4c(iw, id, height, ir3);
                    }
                }

hull() というのは包み込む動作をするようで、上記の例では2つの高さと大きさの異なる四角形(角は丸い)を包み込んでいます。

こうすることで内側の隙間が斜めに埋められて、台形上の形状になります。これを difference() を使ってくり抜けば、補強のためのフィレットになるというわけです。もしかしたらもっと良い方法があるのかもしれませんが、OpenSCADを知ったばかりでテクニックも何もしらないので、困ったときは力業でいくしかありません。

OpenSCADは面白い

いろいろとクセがあって難しいですが、サイズを変えることが簡単にできるのが、とても便利だと思いました。今回は1つだけの箱でしたが、穴がたくさんあるケースを作るときには特に役に立ちそうです。穴の個数やサイズを変えるというのは従来の3Dモデルの作り方では結構大変なので、それを計算による繰り返し処理で作れるというのはとても適しています。今後も活用していきたいと思います。

LINEで送る
Pocket