参考サイト
上記、サンプルコードを参考に、build123d用のcq-editorを使って、実行できるように調整しました。
PythonSCADコミュニティ、CadQueryコミュニティ、build123dコミュニティ、開発者、記事執筆者に感謝申し上げます。ありがとうございます。
build123dの存在
CadQueryコミュニティから派生した後継的ライブラリで、同じOCCT(Open CASCADE Technology)カーネルであり、よりPython的な書き方(代数モード、コンテキストマネージャ)ができます。CadQueryと並べて一度触って比較する価値があります。
MaterialXを使って表示できるようで、未検証です。
build123d用のcq-editorを使う
cq-editorのfork、build123dのコードが使えます。解凍してダブルクリックです。
GUI editor based on PyQT. This fork has changes from jdegenstein to allow easier use with build123d.
CQ-editor-Windows.zip(384MB)
解凍後のフォルダ容量は、約1.15 GBになります。
**以下、コピペして実行してみてください。**CAD学習、プログラミング学習の参考になればと思います。
40 x 20 x 10 mm の箱, 貫通穴(半径3)
from build123d import *
# 40 x 20 x 10 mm の箱(既定で原点中心)
part = Box(40, 20, 10)
# 中心に直径6mmの貫通穴(半径3, 高さは十分に)
part -= Cylinder(radius=3, height=20)
show_object(part)
make_bracket
from dataclasses import dataclass
from build123d import *
@dataclass
class BracketParams:
L: float = 40.0 # 長さ
W: float = 20.0 # 幅
T: float = 4.0 # 厚み
hole_d: float = 3.4 # 取付穴径(M3ゆるめ)
margin: float = 6.0 # 穴の端からの距離
def make_bracket(p: BracketParams):
plate = Box(p.L, p.W, p.T)
# 長辺方向の両端に取付穴を2つ
x = p.L/2 - p.margin
for sx in (-1, 1):
plate -= Pos(sx * x, 0, 0) * Cylinder(radius=p.hole_d/2, height=p.T*2)
# 角を丸める(Z方向の縦エッジに2mmフィレット)
plate = fillet(plate.edges().filter_by(Axis.Z), radius=2)
return plate
bracket = make_bracket(BracketParams())
show_object(bracket)
12. Defining an Edge with a Spline
from build123d import *
pts = [
(55, 30),
(50, 35),
(40, 30),
(30, 20),
(20, 25),
(10, 20),
(0, 20),
]
with BuildPart() as ex12:
with BuildSketch() as ex12_sk:
with BuildLine() as ex12_ln:
l1 = Spline(pts)
l2 = Line((55, 30), (60, 0))
l3 = Line((60, 0), (0, 0))
l4 = Line((0, 0), (0, 20))
make_face()
extrude(amount=10)
show_object(ex12)
17. Mirroring From Faces
from build123d import *
a, b = 30, 20
with BuildPart() as ex17:
with BuildSketch() as ex17_sk:
RegularPolygon(radius=a, side_count=5)
extrude(amount=b)
mirror(ex17.part, about=Plane(ex17.faces().group_by(Axis.Y)[0][0]))
show_object(ex17)
18. Creating Placements on Faces
from build123d import *
length, width, thickness = 80.0, 60.0, 10.0
a, b = 4, 5
with BuildPart() as ex18:
Box(length, width, thickness)
chamfer(ex18.edges().group_by(Axis.Z)[-1], length=a)
fillet(ex18.edges().filter_by(Axis.Z), radius=b)
with BuildSketch(ex18.faces().sort_by(Axis.Z)[-1]):
Rectangle(2 * b, 2 * b)
extrude(amount=-thickness, mode=Mode.SUBTRACT)
show_object(ex18)
19. Locating a placement on a vertex
from build123d import *
length, thickness = 80.0, 10.0
with BuildPart() as ex19:
with BuildSketch() as ex19_sk:
RegularPolygon(radius=length / 2, side_count=7)
extrude(amount=thickness)
topf = ex19.faces().sort_by(Axis.Z)[-1]
vtx = topf.vertices().group_by(Axis.X)[-1][0]
vtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))
vtx2 = topf.vertices().sort_by(vtx2Axis)[-1]
with BuildSketch(topf) as ex19_sk2:
with Locations((vtx.X, vtx.Y), (vtx2.X, vtx2.Y)):
Circle(radius=length / 8)
extrude(amount=-thickness, mode=Mode.SUBTRACT)
show_object(ex19)
23. Revolve
from build123d import *
pts = [
(-25, 35),
(-25, 0),
(-20, 0),
(-20, 5),
(-15, 10),
(-15, 35),
]
with BuildPart() as ex23:
with BuildSketch(Plane.XZ) as ex23_sk:
with BuildLine() as ex23_ln:
l1 = Polyline(pts)
l2 = Line(l1 @ 1, l1 @ 0)
make_face()
with Locations((0, 35)):
Circle(25)
split(bisect_by=Plane.ZY)
revolve(axis=Axis.Z)
show_object(ex23)
24. Loft
from build123d import *
length, width, thickness = 80.0, 60.0, 10.0
with BuildPart() as ex24:
Box(length, length, thickness)
with BuildSketch(ex24.faces().group_by(Axis.Z)[0][0]) as ex24_sk:
Circle(length / 3)
with BuildSketch(ex24_sk.faces()[0].offset(length / 2)) as ex24_sk2:
Rectangle(length / 6, width / 6)
loft()
show_object(ex24)
25. Offset Sketch
from build123d import *
rad, offs = 50, 10
with BuildPart() as ex25:
with BuildSketch() as ex25_sk1:
RegularPolygon(radius=rad, side_count=5)
with BuildSketch(Plane.XY.offset(15)) as ex25_sk2:
RegularPolygon(radius=rad, side_count=5)
offset(amount=offs)
with BuildSketch(Plane.XY.offset(30)) as ex25_sk3:
RegularPolygon(radius=rad, side_count=5)
offset(amount=offs, kind=Kind.INTERSECTION)
extrude(amount=1)
show_object(ex25)
AIからの回答
高校生がbuild123dを習得するとしたら、Builder modeで記述していく練習を優先したほうが良いですか。
Builder modeとAlgebra modeは、どちらもマスターするのでしょうか。混在した記述も大丈夫なのでしょうか。
どちらも「マスターする」必要はありますが、身構えるほどの二重学習ではありません。まずその構造から説明します。
2つのモードの関係
BuilderモードとAlgebraモードは、別々の言語ではなく同じ部品(Box、Cylinder、Plane、セレクタ…)を組み合わせる作法が2通りあるだけです。学習量の9割は共通で、違うのは「合成のさせ方」だけです。
- Algebraモード: 結果を自分で変数に持ち、
+-&で明示的に合成する。隠れた状態がゼロ - Builderモード:
with BuildPart()という文脈(コンテキスト)が結果を自動で受け取り、mode=Mode.SUBTRACTで合成方法を指定する
つまり、前回の「暗黙の状態を避ける」という観点でいうと、実はBuilderモードのほうが暗黙性が高いのです。Box(…)と書くだけで囲んでいるビルダーに自動追加される——これは便利ですが、初学者には「どこにも代入していないのに、なぜ形が増えるのか」という魔法に見えます。Algebraモードの ex2 = Box(…) → ex2 -= Cylinder(…) は、変数への代入と演算というPythonの授業でそのまま説明できる形をしています。
混在は大丈夫か
公式に認められた使い方で、実務上も普通に行われます。橋渡しはシンプルで、ビルダーの結果はex2.partやex2.sketchで取り出せばAlgebraの世界のオブジェクトになり、逆にAlgebraで作ったオブジェクトはビルダー内でadd(obj)すれば文脈に取り込めます。よくあるのは「複雑な2DスケッチだけBuilderで組み、立体の足し引きはAlgebraで書く」という折衷です。
ただし教材の中では混在を推奨しないことをお勧めします。技術的に安全でも、生徒には「2つのルールがいつ切り替わるのか」という認知負荷になります。単元ごとにどちらかに統一し、両者の接続は上級者向けに1例だけ見せる、が良い塩梅だと思います。
高校生はどちらを優先すべきか
前回の提案と同じ結論になりますが、理由を今回の例に即して言うと——Algebraモードから入るべきだと考えます。
第一に、PythonSCADからの移行コストがほぼゼロです。difference()で引いていた頭のまま-=で引けます。生徒は「新しいCADを覚える」のではなく「同じ考え方が厳密曲面でも通じる」ことを体験します。
第二に、デバッグと読解の教育効果です。Algebraモードは各段階が変数なので、show_object(step1)と途中経過を確かめながら進めます。「動かして、見て、直す」というプログラミング教育の基本ループがそのまま回ります。
では、Builderモードはいつ要るのか。スケッチ駆動の設計と配置の単元からです。BuildSketchで輪郭を描いて押し出す、GridLocationsで穴を並べる——先日のL字フックのような課題はBuilderモードの独壇場で、しかもこの「スケッチ→フィーチャー」という手順はFusion 360やSolidWorksの操作モデルそのものです。つまり、
- Algebraモード——CSG的な立体の組み合わせ(PythonSCADの続きとして)
- Builderモード——スケッチ・配置・穴あけ(市販CADへの橋渡しとして)
という役割分担で両方に到達する、と設計すれば「どちらもマスター」が自然に達成されます。優先順位というより、**Algebraが入口、Builderが出口(進路接続)**という一本の道です。
導入のつなぎとしては、同じ「箱から円筒を引く」をPythonSCAD版・Algebra版・Builder版の3並びで見せる1枚が効くと思います。生徒は3つの書き方が同じ意味であることを一目で理解し、「書き方は道具、考え方が本体」という先生のメッセージがそのまま伝わります。
PythonSCADでは、.でつなぐメソッドチェーンがありますが、Algebraモードも同じでしょうか。
はい、同じ感覚で使えます。ただし「チェーン」と呼ばれるものには2種類あって、その区別が今回の一連の話の核心なので、整理してお答えします。
2種類のメソッドチェーン
PythonSCADの cube(...).translate(...).rotate(...) は、完成した立体に変換を掛けると、新しい立体が返ってくるという連鎖です。各段階の値はどこで切っても完全な立体で、途中を変数に受ければいつでも確認できます。
build123dのAlgebraモードもこれと同じ性質を持っています。すべての形状はただのPythonオブジェクトなので、変換メソッドが新しい形状を返し、ドットでつなげます。
part = Box(10, 20, 5).rotate(Axis.Z, 45).moved(Location((30, 0, 0)))
これはPythonSCADのチェーンと完全に同型で、途中で切って step1 = Box(10, 20, 5).rotate(Axis.Z, 45) と受けても意味は変わりません。
一方、CadQueryのWorkplaneチェーンは見た目は似ていても別物です。各段階が「立体」ではなく「作業平面スタック+保留中のワイヤ+累積した原点移動」という見えない作業状態を運んでいて、途中で切ると意味が変わることがあります。L字フックでずれた原因はこれでした。つまり——チェーンという書き方自体は問題ではなく、チェーンが運んでいるものが「値」か「状態」かが問題だった、というのが正確な整理です。PythonSCADとbuild123d Algebraは前者、CadQueryは後者です。
Algebraモードにはもう一つの書き方がある
build123dらしい流儀として、変換を掛け算演算子で書く方法もあります。
part = Pos(30, 0, 0) * Rot(Z=45) * Box(10, 20, 5)
数学の変換行列と同じく右から左に適用されます(まず45度回し、それから移動)。メソッドチェーンは左から右に読むので、実は適用順の読み方が逆になります。どちらも正しいAlgebraモードですが、教材では最初どちらかに統一したほうが混乱がありません。
高校生向けには、まずメソッドチェーン(または1行1変数の逐次代入)からをお勧めします。PythonSCADで既に馴染んだ読み方と一致し、「左から順に起きる」ので実行順が直感と合います。Pos * Rot * 形状の演算子スタイルは、数学の授業で行列や写像の合成に触れる頃に「実はこう書くと数学と同じ形になる」と見せると、プログラミングと数学がつながる良い教材になります——書き方が2つあること自体を、合成の順序を考えさせる教材に転用できるわけです。
参考資料








