text-to-cadで作った棚のSTLは、寸法どおりか?

gen
gen

@gennnn ・ 44本

サムネイル

text-to-cadのスキルに書かれた手順どおりに、日本語の依頼3本から壁掛けの棚、スマホスタンド、L字ブラケットのスクリプトを書き、STEPとSTLに出して寸法をPythonで測りました。外形と穴の位置は依頼との差が0.000mmで、STLの穴径だけが最大0.006mm細く出ます。それより引っかかったのは、3本とも依頼文に書いていない数字が残っていたことでした。

text-to-cadで確かめたのは、コードを書いたあとの半分

text-to-cadは、Claude CodeやCodex、CursorなどのAIエージェントに足すスキル集です。頼まれた形をエージェントが build123d(OpenCascadeを土台にしたPythonのCADライブラリ)で書き、cadgen がそのスクリプトからSTEPやSTLを出します。Zooの Text-to-CAD とは名前が同じだけの別物です。

2026年10月17日時点でスターは20,985、ライセンスはMIT、最新はv0.7.21(10月11日公開)です。

仕事は前半と後半に分かれます。前半は依頼文から寸法を読み取ってスクリプトを書くところで、ここがAIの担当。後半は、そのスクリプトをcadgenで動かしてSTEPとSTLにするところです。今回はAIのAPIキーを使わず、前半を僕が引き受けてSKILL.mdの手順どおりに書いたので、測ったのは後半、「依頼の数字を入れたスクリプトから、その数字どおりのファイルが出るか」です。

動かしたのは使い捨てのLinuxコンテナ(python:3.12-bookworm、arm64)で、cadgenは0.7.21に固定しました。STEPはcadgenの read_step とbuild123dで、STLは trimesh で別々に測っています。

依頼
測った箇所
STEPの差
STLの差
スマホスタンド
幅80mm、奥行き70mm、溝12mm、角度60度
0.000mm、0.000度
0.000mm、0.000度
壁掛けの棚
幅200mm、奥行き100mm、穴の位置、穴径4mm
0.000mm
位置0.000mm、穴径−0.006mm
L字ブラケット
外形50×30×50mm、穴の位置、穴径4.5mm
0.000mm
位置0.000mm、穴径−0.002mm

STLは3本とも面に隙間のない閉じたメッシュ(watertight)で、本体も1つずつでした。

ただ、0.000が並ぶのは、僕が依頼の数字をそのままスクリプトに入れたからです。AIが依頼文を正しく読めるかの証明にはなりません。言えるのは、数字さえ決まれば、道具立てはそれをSTEPでもSTLでも崩さないということ。じゃあ、その数字はどこで決まるのか。ここからが本題です。

スマホスタンドの60度は、どの面の角度か

スマホスタンドの出力をレンダリングした画像。直方体の上面に、奥へ斜めに傾いた溝が幅いっぱいに切られている
スマホスタンドを作って。幅80mm、奥行き70mm、角度は60度、スマホの厚さ12mmの溝を付けて。

数字が4つあるのに、形は決まりきりません。高さが書いていないので35mmにし、溝は底の中心を手前から約20mm、高さ10mmの位置に置いて、幅いっぱいに貫通させました。

迷ったのは「角度は60度」です。僕は溝の面と水平面のなす角と読み、スマホが水平から60度で寝る形にしました。垂直からの倒れ具合と読めば溝の面は水平から30度になり、まったく別のスタンドが出てきます。

測ると、溝の幅は12.000mm、角度は60.0000度で、STEPもSTLも一致です。平面だけの形なのでSTLは三角形28個で済み、体積の差は0.001mm³でした。

スマホを置いたとき、画面は水平から何度くらいが見やすいですか?その数字と、どの面から測るかを依頼文に入れておけば、この迷いは起きません。

壁掛けの棚の依頼文には、背板の高さが無かった

壁掛けの棚の出力をレンダリングした画像。長い棚板の奥に低い背板が立ち、背板の左右に小さなネジ穴が1つずつある
壁掛けの小さな棚を作って。幅200mm、奥行き100mm、板厚8mm。壁に留める直径4mmのネジ穴を2つ、穴の中心は左右の端から30mm、高さ方向は中央で。

形は、棚板(200×100×8mm)の奥に背板を立てたL字にして、ネジ穴は背板を壁に向かって貫かせました。で、ここで手が止まります。背板の高さが、依頼文のどこにも無いんです。

「高さ方向は中央」の位置は、背板の高さで決まります。40mmなら穴は下から20mm、60mmなら30mm。SKILL.mdには「結果に大きく影響する情報が足りなければ聞く」(Ask for missing information when it materially affects the requested result.)とあり、本来は聞き返す場面です。今回は聞き返す相手がいないので、40mmと仮定しました。

項目
依頼
STEP
STL(既定)
STL(印刷向け)
幅
200mm
200.0000
200.0000
200.0000
奥行き
100mm
100.0000
100.0000
100.0000
穴の中心(左右)
端から30mm
30.0、170.0
30.0、170.0
30.0、170.0
穴の中心(高さ)
中央(仮定40mmの中央)
20.0
20.0
20.0
穴径
4mm
4.0000
3.9941
3.9961

スクリプトは、箱を2つ足して円柱を2本引くだけです。出力先はデコレータで宣言するので、1回動かせばSTEPと2種類のSTLが出ます。

from cadgen import build123d as bd
from cadgen import step, stl

WIDTH = 200.0     # X
DEPTH = 100.0     # Y(壁から手前)
T = 8.0           # 板厚
BACK_H = 40.0     # 背板の高さ(仮定)
HOLE_D = 4.0
HOLE_EDGE = 30.0


def _shelf() -> bd.Shape:
    A = (bd.Align.MIN, bd.Align.MIN, bd.Align.MIN)
    board = bd.Box(WIDTH, DEPTH, T, align=A)           # 棚板 z 0..8
    back = bd.Box(WIDTH, T, BACK_H, align=A)           # 背板 y 0..8, z 0..40
    body = board + back
    for x in (HOLE_EDGE, WIDTH - HOLE_EDGE):
        # Y方向に背板を貫く穴。高さは背板の中央
        cyl = bd.Cylinder(HOLE_D / 2, T + 4, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER))
        cyl = cyl.rotate(bd.Axis.X, 90).moved(bd.Location((x, T / 2, BACK_H / 2)))
        body = body - cyl
    return body


@step(out="../STEP/shelf.step")
@stl(out="../STL/shelf.stl")
@stl(out="../STL/print/shelf.stl", mesh_tolerance=4e-4)
def shelf():
    s = _shelf()
    s.label = "shelf"
    return s

BACK_H = 40.0 の1行が、依頼文に無かった数字です。棚を頼むとき、背板の高さまで書いていますか?

L字ブラケットのM4ネジ穴は、何mmで開けるのか?

L字ブラケットの出力をレンダリングした画像。立った脚と寝た脚に、丸い貫通穴が2つずつ開いている
ネジ穴付きのL字ブラケットを作って。各辺50mm×30mm、板厚5mm、M4ネジ用の穴を4つ。穴は端から10mm、等間隔で。

「M4ネジ用」には穴径の数字がありません。M4はネジの直径が4mmなので、ちょうど4mmではきつく、通し穴の目安として4.5mmにしました。穴は各脚に2つずつ、長さ50mmの両端から10mm(10mmと40mm)、幅30mmの中央と読み、50mmはもう一方の脚の板厚を含めた外寸として作っています。

結果は、外形が50×30×50mmちょうど、穴の中心は両脚とも10mmと40mm、幅方向15mmで差0.000mm。穴径はSTEPで4.5000mm、STLで4.4981〜4.4985mmです。外寸か内寸か、4.5mmか別の値か。どれを選んでも、cadgenはその数字どおりに出してきます。

text-to-cadへの依頼文で、先に埋めておく数字

3本で僕が決めた値を並べると、依頼文に足すべき一文がそのまま見えてきます。

依頼
書いていなかった値
今回決めた値
書き足す一文(例)
スマホスタンド
高さ、角度を測る面
高さ35mm、溝の面と水平面の角
「高さ35mm。溝の面は水平から60度」
壁掛けの棚
背板の高さ
40mm
「背板の高さ40mm」
L字ブラケット
穴径、外寸か内寸か
4.5mm、外寸
「穴は直径4.5mmの貫通穴。50mmは外寸」

「M4用」「中央」「60度」は、人間同士なら通じます。でもスクリプトに落とす段階では、誰かが必ず数字を1つ選んでいる。その選択を依頼文の側で先に済ませておくほうが、聞き返しを待つより狙った形に早く近づけるはずです。

STLの穴が0.006mm細いのは、多角形で近似するから

STEPは円を円のまま持ちますが、STLは三角形の集まりなので、穴は多角形になります。4mmの穴の断面は、既定の細かさで88頂点、印刷向けの mesh_tolerance=4e-4 で112頂点でした。頂点はすべて円周の上に乗るので、頂点から測る最大の直径はどちらも4.000mm。ただ辺は円の内側を通るぶん、断面に円を当てはめた直径は3.9941mm、3.9961mmと少し細くなります。

細かくしても、増えるのは三角形の数だけでした(棚は380個から476個)。外形と穴の位置は動きません。ドキュメントにも「メッシュの画像だけでは正確な寸法や形のつながりは確定しない」(A mesh image does not establish exact dimensions or topology.)とあり、寸法を確かめるならSTEPで、が基本です。

じゃあ0.006mmは、印刷するとどれくらいの重さなのか。Claude CodeとOpenSCADで設計して印刷し、ノギスで測ったクラスメソッドの記事では、Bambu Lab A1 miniとPETGで穴が公称値より約0.2mm小さく仕上がったと報告されています。STLの近似はその30分の1ほどなので、穴のクリアランスで効いてくるのはプリンタ側の癖のほうになりそうです。

印刷向けのチェックをするdfam-checkスキルもSTLに回したところ、最小壁厚は棚8.0mm、ブラケット5.0mmと出ました。ただスマホスタンドは8.075mmで、STLの頂点から測った溝の下の厚み7.0mmより大きい値です(原因は未確認)。dfam-checkは合否を言わず数字だけを返す設計なので、薄い場所は自分でも測っておくと安心です。

Node 20とlibGL.so.1、text-to-cadのSKILL.mdに書いていない前提

形のスクリプトは3本とも1回で通ったのに、環境では3回止まりました。これは罠でした。

1回目は cadgen doctor です。初回はcadgen本体の59パッケージの取得に27秒かかり、そのあとカーネルの読み込みで落ちました。apt-get install libgl1 libglib2.0-0(8秒)で kernel OK に変わります。

kernel   FAILED — ImportError: libGL.so.1: cannot open shared object file: No such file or directory

2回目はモデルを動かしたときで、STEPは保存されたのに、STLの書き出しで落ちました。

FAILED: NodeUnavailable: node was not found on PATH. The DXF and mesh-export builders run in Node. Install Node.js (>=20), or point CADGEN_NODE at an existing node binary.

STLや3MF、GLBの書き出しはNodeで動きます。READMEの要件にはNode.js 20以上とあるのに、cadのSKILL.mdのセットアップの節には出てきません。Node 22.12.0を入れたら3本とも通り、1本0.4〜0.6秒でSTEPと2種類のSTLが出ました。ここからはマジで速いです。

3回目はスナップショットで、初回にヘッドレスブラウザをダウンロードしたあと、libnspr4.so が無くて TargetClosedError になりました。次のライブラリを入れると、1.7秒で画像が出ます。

apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
  libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 \
  libasound2 libpango-1.0-0 libcairo2

libGL.so.1とブラウザ用のライブラリは、READMEにもSKILL.mdにも書かれていません。Linuxで動かすなら、Node 20以上とこの2組を先に入れておけば、僕が止まった3か所は避けられます。

最初の cadgen doctor では、使用統計とクラッシュレポートを既定で送るとも表示されました。READMEによればファイルやパス、プロンプトは含まれず、uvx cadgen telemetry off か DO_NOT_TRACK=1 で止められます。Windows 11はSmart App Controlが署名のないネイティブモジュールを止めるので、無効にするかWSLで、とREADMEにあります。

Claude Codeなら、READMEにあるインストールのコマンドは次の2行です。

claude plugin marketplace add earthtojake/text-to-cad#latest
claude plugin install text-to-cad@earthtojake

最初の1本は、棚でもスタンドでも、決まっていない数字を全部埋めた依頼文で頼んでみてください。出てきたSTEPを測って依頼どおりなら、あとはSTLの近似とプリンタの癖を見るだけです。