3次元配列のキーボードをSolidPythonで設計してみる

この記事はキーボード #2 Advent Calendar 2024の記事です。

adventar.org

昨日は、IKeJIさんの「KMK Firmware入門」でした。 私もKMK使ってます。

本題

3次元配列キーボードって自作キーボードの夢ですよね。 私もいつかは3次元のキー配置と思いながら、dactyl manuformのレポジトリを眺めてみたりしたことはあるんですが、ちょっと読む気が起きないコードが置いてあります。

dactyl-manuform/src/dactyl_keyboard/dactyl.clj at master · abstracthat/dactyl-manuform · GitHub

dactyl manuformでも使われているOpenSCADは マウスで画面を見ながら少しずつ形状をモデリングするのではなくて、 コードで形状を記述して、それを形状に変換します。

普通のCADとやってることは同じなんですが、ループを回して同じ形を複製したり、 パラメータ調整して作り直したりするのは容易に思います。 OpenSCADの言語だけでもいろいろできるんですが、 SolidPythonを使うとさらに簡潔に、高機能な言語で操作できるので、少し敷居が下がってきます。

SolidPythonで記述して、 pythonで実行するとOpenSCADのファイルを出力します。 OpenSCADで形状を確認して、SolidPythonを直す。 完成したら、OpenSCADでSTLへ変換し、 最終的に3Dプリンタで出力する、という流れを考えています。

GitHub - SolidCode/SolidPython: A python frontend for solid modelling that compiles to OpenSCAD

ここでは、SolidPythonを使って、3x2のキーボードを設計してみます。 solidpythonをインストールしておいてください。 solidpython2というのもあってややこしいですが、微妙に異なるもののようです。

1から7のステップに分けました。 それぞれのファイルの全体をこちらに置いておきます。 ここからはコードの要点のみ解説します。

github.com

1. 立方体を作る 1_cube.py

まず1キー用のケースを作っていきます。 cubeで立方体を作ります。 cubeは頂点の一つが座標0, 0, 0になるので、スイッチの中心をx=0, y=0、スイッチプレート上面をz=0になるようにtranslateで移動しておきます。

from solid import *

# ケースになる部分
body = translate([-12, -12, -19])(
    cube([24, 24, 20])
)

scad_render_to_file(body, '1_cube.scad')

実行すると1_cube.scadというファイルができるので、これをOpenSCADで開いてください。 図のように表示されるはずです。

2. スイッチの穴になる部分 2_hole.py

立方体にスイッチを刺すための穴を空けていきます。 ここでは、穴の形(穴を反転した形)をモデリングします。 unionは足し算です。 3つの立方体を合体して一つにしています。

# ケースからカットする部分
hole = union()(
    # スイッチの上側をカットする部分
    translate([-10, -10, 0])(
        cube([20, 20, 10])
    ),
    # スイッチの穴
    translate([-7, -7, -49])(
        cube([14, 14, 50])
    ),
    # スイッチプレートの下側
    translate([-10, -10, -51.5])( # プレート厚み1.5mm
        cube([20, 20, 50])
    ),
)

3. 1キーのケース 3_1key.py

そして、引き算であるdifferenceを使って立方体1からカット部分2を削除します。

kbd = difference()(
    body,
    hole
)

4. 6キー分作る 4_6keys.py

6キー分のx, y座標リストを用意して、作った1キーをtranslateしつつ最後にunionで結合します。

position = [
    [19.05 * 0, 19.05 * 0],
    [19.05 * 0, 19.05 * 1],
    [19.05 * 0, 19.05 * 2],
    [19.05 * 1, 19.05 * 0],
    [19.05 * 1, 19.05 * 1],
    [19.05 * 1, 19.05 * 2],
]

kbd6u = union()(
    [translate([x, y, 0])(kbd) for x, y in position]
)

しかし、単純に結合すると、重なっておかしくなっているところがたくさんありますね。

5. unionとdifferenceの順序を変えて余計な部分を除去する 5_6keys-2.py

そこで、1キー分を作ってから6個足し算するのではなくて、 ケース部分1を6個結合 - カット部分2を6個結合という風に、順序を変えます。

body6 = union()(
    [translate([x, y, 0])(body) for x, y in position]
)

hole6 = union()(
    [translate([x, y, 0])(hole) for x, y in position]
)

kbd = difference()(
  body6,
  hole6
)

6. スイッチを回転させる 6_6keys-rot.py

いよいよ3Dキーボードらしく、キーの向きを変えていきます。 positionリストにz位置と角度を追加して、回転してから移動し結合します。

# スイッチの位置 x, y, z と角度[度] rx, ry
position = [
    [19.05 * 0, 19.05 * 0,  1, -5, 5],
    [19.05 * 0, 19.05 * 1,  0,  0, 5],
    [19.05 * 0, 19.05 * 2,  1,  5, 5],
    [19.05 * 1, 19.05 * 0,  0, -5, 0],
    [19.05 * 1, 19.05 * 1, -1,  0, 0],
    [19.05 * 1, 19.05 * 2,  0,  5, 0],
]

# ケースを合体
case6 = union()(
    [
        translate([x, y, z])(
            rotate([rx, ry, 0])(body)
        ) for x, y, z, rx, ry in position
    ]
)

# カット部分を合体
hole6 = union()(
    [
        translate([x, y, z])(
            rotate([rx, ry, 0])(hole)
        ) for x, y, z, rx, ry in position
    ]
)

7. 底を平らにカットして完成 7_6keys-comp.py

底がガタガタしているので、平らにします。

# ケースの底を平らにカットするための立方体
bottom = translate([-20, -20, -29])(
  cube([100, 100, 20])
)

# ケースからカット部分を引く
kbd = difference()(
  case6,
  hole6,
  bottom
)

こんな感じでさわりだけを解説してみました。

天キー Vol.7

天キーには2種類の3Dキーボードのモックアップを持っていきました。 球体をつなぎ合わせたものと、正20面体をつなぎ合わせたものがあります。 それらのコードもgithubに置いておきます。 2025年はこれを動作するキーボードにできたらいいな。

この記事は自作のキーボード742と薙刀式で書きました。

明日は、yharaさんの「人体の非対称性について」です。