0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PythonSCAD】「プログラムによってモデルを生成する」チュートリアルその2

0
Last updated at Posted at 2026-08-19

はじめに

「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)

スクリーンショット 2026-08-19 134433.png

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)

image.png

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)

補足:この方法は「外形の角丸め(山側)」に強い定番です。wh はそれぞれ 2*r より大きくしてください(例:R6なら w>12, h>12)。
スクリーンショット 2026-08-19 135259.png

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 にしています(ピッタリ接触だけだと計算誤差で離れることがあるため)
  • circlecenter引数が使えないので、必ず translate(circle(...), [x, y]) で配置します
    image.png

各円が少し重なるように調整するには、どこを変更しますか。

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はマイナスにするルール

スクリーンショット 2026-08-19 140526.png

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)

調整のコツ(重なり量)

  • radius を小さくするほど、隣の square と重なりやすくなります
  • n_squares を増やしても、同じ半径なら密になって重なりやすくなります
    スクリーンショット 2026-08-19 141043.png

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)

使い方のコツ

  • Rが大きすぎると破綻します(例:上辺なら r_tl + r_tr <= w が必要)
  • 滑らかさは fn_val を上げる(例: 120)と円弧がきれいになります(ただし処理は重くなる)
    スクリーンショット 2026-08-19 141404.png

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の角)を丸める“丸め(ラウンドオーバー)”**の意味なら、別の作り方(上側だけを削るカッター等)が必要です。

スクリーンショット 2026-08-19 142248.png

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(ラウンドオーバー)
  • 下面エッジは丸めていません(上面だけ丸める構成)

スクリーンショット 2026-08-19 142421.png

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?