はじめに
「PythonSCAD」を使って、コンピュータグラフィックスの学習、勉強のようなことができればと、サンプルをつくりました。
PythonSCADについて
PythonSCADは「単なる別のモデリングソフトウェア」ではなく、「プログラムによってモデルを生成する」設計手法です。モデリングがコードになると、モデルはもはやファイルではなく、進化するルールの集合体のようなものになります。新しい可能性ですね。
- 3Dプリント部品(ベース、ブラケット、シェル)
- 仕様の異なるモデルの一括生成
- パラメトリック構造部品設計
- プログラミングとエンジニアリング設計の統合教育
PythonSCADコミュニティ、OpenSCADコミュニティ、CadQueryコミュニティ、build123dコミュニティ、開発者、記事執筆者に感謝申し上げます。ありがとうございます。
001. 赤色の球体を平面の上に1個配置してください。
完成コード(PythonSCAD)
# ==========================================
# 赤色の球体(平面の上に配置)
# 作成日: 2026/08/18
# ==========================================
from pythonscad import *
# --- パラメータ設定 (単位: mm) ---
plane_size = 120.0 # 平面(正方形)の一辺
plane_th = 2.0 # 平面の厚み(薄い板として表現)
ball_r = 15.0 # 球の半径
fn_val = 80 # 球の滑らかさ
# --- メイン処理 ---
plane = cube([plane_size, plane_size, plane_th], center=False) # Z=0が底面
ball = translate(sphere(r=ball_r, fn=fn_val), [0, 0, plane_th + ball_r]) # 平面の上に接地
result = plane + color(ball, "red")
show(result)
002. 球体半径1を10 x 10の100個並べる。位置Zは、random.random()*0.5とします。
完成コード(PythonSCAD)
# ==========================================
# ランダムZの球 10x10 配置
# 作成日: 2026/08/18
# ==========================================
from pythonscad import *
import random
# --- パラメータ設定 (単位: mm) ---
n_x = 10 # X方向の個数
n_y = 10 # Y方向の個数
ball_r = 1.0 # 球の半径
pitch = 2.5 * ball_r # 球の中心間隔(重なり防止)
z_amp = 0.5 # Zのランダム幅(0〜0.5)
fn_val = 60 # 球の分割数
# 必要なら再現性のために固定(不要ならコメントアウト)
# random.seed(0)
# --- メイン処理 ---
balls = None
# 原点中心に並べる(i, j を -4.5 〜 +4.5 にする)
for i in range(n_x):
for j in range(n_y):
x = (i - (n_x - 1) / 2) * pitch
y = (j - (n_y - 1) / 2) * pitch
z = random.random() * z_amp
b = translate(sphere(r=ball_r, fn=fn_val), [x, y, z])
balls = b if balls is None else (balls + b)
show(balls)
003. 長方形 w = 80.0 幅 (X方向), h = 50.0 高さ (Y方向), すべての角にR6をつけてください
xy平面での2D図形の作成です。厚みはありません。renderボタンを押すと、赤い線で輪郭が出ます。
完成コード(PythonSCAD)
# ==========================================
# 2D 角丸長方形(全コーナー R6)
# 作成日: 2026/08/18
# ==========================================
from pythonscad import *
# --- パラメータ設定 (単位: mm) ---
w = 80.0 # 幅 (X方向)
h = 50.0 # 高さ (Y方向)
r = 6.0 # 角R
fn_val = 80 # 円弧の滑らかさ(大きいほど滑らか)
# --- メイン処理 ---
# 角丸の基本: (小さくした長方形) ⊕ (円) = 全コーナーR付き長方形
core = square([w - 2 * r, h - 2 * r], center=True) # square は fn 不可
rounder = circle(r=r, fn=fn_val) # circle は center 不可
result = minkowski(core, rounder)
show(result)
補足:この方法は「外形の角丸め(山側)」に強い定番です。w と h はそれぞれ 2*r より大きくしてください(例:R6なら w>12, h>12)。

004. +xy平面上を中心にして、複数の2Dのcircleを円周上につながったまま配置するようにしてください。
完成コード(PythonSCAD)
# ==========================================
# 円周上に「つながった」円(2D circle)を並べる
# 作成日: 2026/08/18
# ==========================================
from pythonscad import *
from math import pi, sin, cos
# --- パラメータ設定 (単位: mm) ---
n = 12 # 円の個数
ring_r = 30.0 # 配置する円周(中心からの距離)
fn_val = 80 # 円の滑らかさ
eps = 0.05 # 「確実につながる」ための微小オーバーラップ量
# 隣り合う円の中心間距離(円周上の chord 長)
chord = 2.0 * ring_r * sin(pi / n)
# つながる条件: center間距離 <= 2*circle_r
# 「確実に接続」したいので eps だけ大きくする(少し重ねる)
circle_r = chord / 2.0 + eps
# --- 関数定義 ---
def connected_circles_on_ring(n, ring_r, circle_r, fn):
"""原点中心のXY平面上で、円周上に円を並べて(重ねて)つなげる"""
result = None
for i in range(n):
ang = 2.0 * pi * i / n
x = ring_r * cos(ang)
y = ring_r * sin(ang)
c = translate(circle(r=circle_r, fn=fn), [x, y]) # 2D移動なので [x, y] の2要素
result = c if (result is None) else (result + c) # 2Dも + で結合
return result
# --- メイン処理 ---
result = connected_circles_on_ring(n, ring_r, circle_r, fn_val)
show(result)
ポイント:
-
XY平面の中心(原点[0,0])を基準に、
cos/sinで円周座標を出しています - 円が「つながったまま」になるように、隣同士が少し重なるよう
circle_r = chord/2 + epsにしています(ピッタリ接触だけだと計算誤差で離れることがあるため) -
circleは center引数が使えないので、必ずtranslate(circle(...), [x, y])で配置します
各円が少し重なるように調整するには、どこを変更しますか。
2Dのcircleを「円周上でつながる(接する)」ように並べる考え方
半径 r の円を、円周上に n 個並べて隣同士がちょうど接するには、隣り合う円の中心間距離(弦の長さ)が 2r になればOKです。
- 円周半径を
Rとすると、弦の長さは2R * sin(pi/n) - 接する条件:
2R * sin(pi/n) = 2r - よって
R = r / sin(pi/n)
この R を使って、角度 theta = 2*pi*k/n の位置に中心を置けば、XY平面上で中心対称に円が並びます。
2. 完成コード(PythonSCAD)
# ==========================================
# 2D: 円周上で接するcircle配置(リング状)
# 作成日: 2026/08/06
# ==========================================
from pythonscad import *
from math import pi, sin, cos
# --- パラメータ設定 (単位: mm) ---
circle_r = 5.0 # 小円の半径
count_n = 12 # 円の個数(3以上推奨)
fn_val = 80 # 円の分割数
gap = 0.0 # すき間(+で離す / -で食い込ませる)。0でちょうど接する
# --- 関数(モジュール)定義 ---
def circles_on_ring(r, n, fn, gap_mm=0.0):
"""
XY平面で原点中心に、半径rのcircleをn個、円周上に配置して隣同士が接するように並べる。
gap_mm: 隣接円の中心距離を (2r + gap_mm) にしたいときに使う(0で接する)
"""
if n < 3:
raise ValueError("count_n は 3 以上にしてください(円周配置のため)。")
# 接する(またはgap込み)ための円周半径 R を計算
# 2R*sin(pi/n) = 2r + gap
R = (r + gap_mm / 2.0) / sin(pi / n)
result_2d = None
for k in range(n):
theta = 2.0 * pi * k / n
x = R * cos(theta)
y = R * sin(theta)
c = translate(circle(r=r, fn=fn), [x, y]) # 2D移動なので [x, y] の2要素
result_2d = c if result_2d is None else (result_2d + c)
return result_2d
# --- メイン処理 ---
result = circles_on_ring(circle_r, count_n, fn_val, gap_mm=gap)
show(result)
重なり量は、このコードだと gap(または関数引数の gap_mm) で調整します。
-
gap = 0.0:ちょうど接する -
gap > 0.0:中心間距離が増えるので 離れる(すき間ができる) -
gap < 0.0:中心間距離が減るので 重なる(オーバーラップする)
つまり、少し重ねたいなら gap をマイナスにします。
変更箇所(最小)
gap = -0.6 # 例:0.6mmぶん重なる方向(マイナスで重なり)
なぜこれで重なる?
関数内で円周半径 R をこう計算しています:
R = (r + gap_mm / 2.0) / sin(pi / n)
ここで gap_mm をマイナスにすると、円周半径 R が小さくなり、各円の中心が内側に寄ります。結果として隣同士の中心距離が 2r + gap_mm になって、gap_mm の分だけ食い込みます。
もし「何mm重ねたいか」を overlap という名前で指定したいなら、こんな書き方もできます(意味が直感的になります):
overlap = 0.6 # 重なり量(mm)
gap = -overlap # gapはマイナスにするルール
005. +xy平面上を中心にして、複数の2Dのsquareを円周上に少し重なったまま配置するようにしてください。各squareは、回転させません。
完成コード(PythonSCAD)
# ==========================================
# 2D: 円周上に square を少し重ねて配置(回転なし)
# 作成日: 2026/08/06
# ==========================================
from pythonscad import *
from math import pi, sin, cos
# --- パラメータ設定 (単位: mm) ---
n_squares = 16 # squareの個数
side = 20.0 # squareの一辺
radius = 28.0 # 配置半径(小さくすると隣同士が重なりやすい)
fn_val = 120 # 角度計算のため(形状には直接使わないが、慣習として置いてOK)
# --- 関数(モジュール)定義 ---
def ring_squares(n, s, r):
"""XY平面の原点中心に、回転なしのsquareを円周上へ配置して足し合わせる(2D)"""
base_sq = square([s, s], center=True)
result = None
for i in range(n):
th = 2 * pi * i / n
x = r * cos(th)
y = r * sin(th)
# 2D図形のtranslateは [X, Y] の2要素(Zを入れるとTypeError)
sq_i = translate(base_sq, [x, y])
result = sq_i if result is None else (result + sq_i)
return result
# --- メイン処理 ---
shape2d = ring_squares(n_squares, side, radius)
show(shape2d)
調整のコツ(重なり量)
006. 2Dの正方形、または長方形の角に異なる数値のRをつけて表示
異なるR(角丸め)を4隅それぞれに付ける方法(2D)
offset(r=...) や minkowski(...) は「全部同じR」には簡単ですが、角ごとに別Rはできません。
そこで、外周を 直線+1/4円弧(点群) として作り、polygon() で表示します。
完成コード(PythonSCAD)
# ==========================================
# 角ごとに異なるRの長方形(2D)
# 作成日: 2026/08/06
# ==========================================
from pythonscad import *
from math import pi, sin, cos
# --- パラメータ設定 (単位: mm) ---
w = 80.0 # 幅 (X方向)
h = 50.0 # 高さ (Y方向)
r_tl = 6.0 # 左上 R
r_tr = 14.0 # 右上 R
r_br = 4.0 # 右下 R
r_bl = 10.0 # 左下 R
fn_val = 80 # 円弧の滑らかさ(大きいほど滑らか)
eps = 0.001 # 数値誤差対策(ほぼ不要だが保険)
# --- 関数(モジュール)定義 ---
def arc_points(cx, cy, r, deg_start, deg_end, steps):
"""
円弧の点列を作る(2D)
deg_start -> deg_end を含むように分割して点を返す
※ polygon用なので [x, y] の「リスト」で返す
"""
if r <= 0:
return []
a0 = deg_start * pi / 180.0
a1 = deg_end * pi / 180.0
pts = []
for i in range(steps + 1):
t = i / steps
a = a0 + (a1 - a0) * t
pts.append([cx + r * cos(a), cy + r * sin(a)])
return pts
def rounded_rect_2d_asym(w, h, r_tl, r_tr, r_br, r_bl, fn):
"""
4隅それぞれ別Rの長方形(中心は原点)
反時計回りに外周点群を作って polygon へ渡す
"""
# --- 入力チェック(設計ミスの早期発見) ---
if w <= 0 or h <= 0:
raise ValueError("w と h は正の値にしてください。")
for r in [r_tl, r_tr, r_br, r_bl]:
if r < 0:
raise ValueError("R は 0以上にしてください。")
# 隣り合うRの合計が辺長を超えると形状が破綻する
if (r_tl + r_tr) > w + eps or (r_bl + r_br) > w + eps:
raise ValueError("上辺/下辺: r_left + r_right が w を超えています。")
if (r_tl + r_bl) > h + eps or (r_tr + r_br) > h + eps:
raise ValueError("左辺/右辺: r_top + r_bottom が h を超えています。")
# 1/4周あたりの分割数(最低3点は欲しい)
steps_q = max(3, int(fn / 4))
xL = -w / 2.0
xR = w / 2.0
yB = -h / 2.0
yT = h / 2.0
# 各コーナー円弧中心
c_tl = [xL + r_tl, yT - r_tl]
c_tr = [xR - r_tr, yT - r_tr]
c_br = [xR - r_br, yB + r_br]
c_bl = [xL + r_bl, yB + r_bl]
pts = []
# 開始点(上辺・左上Rの終点)
start = [xL + r_tl, yT]
pts.append(start)
# 上辺(左上R終点 -> 右上R始点)
pts.append([xR - r_tr, yT])
# 右上R(90° -> 0°)
arc_tr = arc_points(c_tr[0], c_tr[1], r_tr, 90.0, 0.0, steps_q)
pts += arc_tr[1:] # 最初の点はすでに上辺終点と重なるので除く
# 右辺(右上R終点 -> 右下R始点)
pts.append([xR, yB + r_br])
# 右下R(0° -> -90°)
arc_br = arc_points(c_br[0], c_br[1], r_br, 0.0, -90.0, steps_q)
pts += arc_br[1:]
# 下辺(右下R終点 -> 左下R始点)
pts.append([xL + r_bl, yB])
# 左下R(-90° -> -180°)
arc_bl = arc_points(c_bl[0], c_bl[1], r_bl, -90.0, -180.0, steps_q)
pts += arc_bl[1:]
# 左辺(左下R終点 -> 左上R始点)
pts.append([xL, yT - r_tl])
# 左上R(180° -> 90°)※最後は start と同じ点になるので末尾は除く
arc_tl = arc_points(c_tl[0], c_tl[1], r_tl, 180.0, 90.0, steps_q)
pts += arc_tl[1:-1]
return polygon(pts)
# --- メイン処理 ---
shape2d = rounded_rect_2d_asym(w, h, r_tl, r_tr, r_br, r_bl, fn_val)
result = color(shape2d, "skyblue")
show(result)
使い方のコツ
007 底面は100×100mmにR8をかけた2D断面にし、押し出しで高さ50mmにする。
1. 完成コード(PythonSCAD)
# ==========================================
# R8角丸の100x100ベースを押し出し(高さ50)
# 作成日: 2026/08/18
# ==========================================
from pythonscad import *
# --- パラメータ設定 (単位: mm) ---
width = 100.0 # X方向
depth = 100.0 # Y方向
height = 50.0 # Z方向(押し出し高さ)
r_edge = 8.0 # 角丸R
fn_val = 80 # 円の分割数(滑らかさ)
# --- 関数定義 ---
def rounded_rect_2d(w, d, r, fn):
"""
2Dの角丸四角形(中心は原点)
minkowski(四角形, 円) で R を作る
"""
core = square([w - 2.0 * r, d - 2.0 * r], center=True)
return minkowski(core, circle(r=r, fn=fn))
# --- メイン処理 ---
base2d = rounded_rect_2d(width, depth, r_edge, fn_val)
# 押し出しはZ=0を底面にして上へ積む(center=False の動き)
result = linear_extrude(base2d, height=height)
show(result)
補足
この作り方だと、底面の2D断面がR8なので、押し出した後の上面外周の平面輪郭(角)も同じR8になります(押し出しは輪郭をそのまま上にコピーするため)。
「上面の外周エッジR8」が、**上面と側面の交線(3Dの角)を丸める“丸め(ラウンドオーバー)”**の意味なら、別の作り方(上側だけを削るカッター等)が必要です。
008. 上面と側面の交線(3Dの角)を丸める“丸め(ラウンドオーバー)”
1. 完成コード(PythonSCAD)
# ==========================================
# 100x100 (R8) を高さ50で押し出し
# 上面の外周エッジだけ 3Dフィレット R8(ラウンドオーバー)
# 作成日: 2026/08/18
# ==========================================
from pythonscad import *
# --- パラメータ設定 (単位: mm) ---
w = 100.0 # X方向
d = 100.0 # Y方向
h = 50.0 # Z方向(全高)
r_plan = 8.0 # 底面2D断面の角丸R(平面R)
r_top = 8.0 # 上面外周エッジの3DフィレットR
fn_val = 96 # 円・球の分割数(滑らかさ)
eps = 0.05 # 交差/切り出しの安全マージン
# --- 関数定義 ---
def rounded_rect_2d(w, d, r, fn):
"""2Dの角丸四角形(中心は原点)"""
core = square([w - 2.0 * r, d - 2.0 * r], center=True)
return minkowski(core, circle(r=r, fn=fn))
def top_edge_roundover_only(base2d, w, d, h, r_top, fn, eps):
"""
上面外周エッジだけをRで丸める。
手順:
1) 角が立った押し出しボディ(sharp)
2) 3Dで全エッジRになるボディ(rounded_all)を別途作る(minkowski)
3) 下側は sharp を、上側r_top帯だけ rounded_all を採用して合成(intersectionで帯を切り出す)
"""
# 1) 角が立った押し出し(底面Z=0基準)
sharp = linear_extrude(base2d, height=h)
# 2) 全エッジRのボディを作るため、2Dを内側に r_top オフセットし、Zも上下 r_top 分だけ縮めたコアを作る
core2d = offset(base2d, r=-r_top)
core_h = h - 2.0 * r_top
core3d = translate(linear_extrude(core2d, height=core_h), [0, 0, r_top])
rounded_all = minkowski(core3d, sphere(r=r_top, fn=fn))
# 3) 下側帯・上側帯を slab(大きい直方体)で切り出して合成
big = max(w, d) + 4.0 * r_top + 10.0 # 十分大きく
slab_lower = translate(
cube([big, big, (h - r_top) + eps], center=False),
[-big / 2.0, -big / 2.0, 0.0]
)
slab_upper = translate(
cube([big, big, r_top + 2.0 * eps], center=False),
[-big / 2.0, -big / 2.0, (h - r_top) - eps]
)
bottom_part = intersection(sharp, slab_lower)
top_part = intersection(rounded_all, slab_upper)
return bottom_part + top_part
# --- メイン処理 ---
base2d = rounded_rect_2d(w, d, r_plan, fn_val)
result = top_edge_roundover_only(base2d, w, d, h, r_top, fn_val, eps)
show(result)
確認ポイント
- 底面の平面輪郭:100×100 の角丸 R8(2D)
- 上面の外周エッジ:上面(水平面)と側面(垂直面)が交わる角を 3DでR8(ラウンドオーバー)
- 下面エッジは丸めていません(上面だけ丸める構成)
参考資料






