Raspberry PI Case
example for AI modeling https://zenn.dev/polygoro/articles/009-ai-raspberry-pi-case?locale=en
License
CC0 1.0# ============================================================
# Raspberry Pi ケース (3B+ / 4B / 5 共通、トレイ + 上蓋)
#
# 固定方法: 上蓋はスナップ式。蓋裏の爪 4本がトレイ側壁の窓に掛かり、
# 蓋裏のリップが側壁内側にはまって位置決めする。外すときは窓から
# 爪を押し込む。蓋にねじは使わない。
# 基板は M2.5 × 6 なべ小ねじ 4本で上からスタンドオフ (下穴 φ2.1、
# 床まで貫通) にセルフタップする。ねじなしでスタンドオフに載せる
# だけでも、蓋裏との間 19.5mm で暴れる以外の実害はない。
#
# 寸法根拠: Raspberry Pi 公式 mechanical drawing (2026-09-16 取得)
# 3B+ raspberry-pi-3-b-plus-mechanical-drawing.pdf
# 4B RP-008343-DS / 5 RP-008347-DS
# drawing 自体に "approximate / for reference only" の注記あり。
# ポート開口はプラグ外形を見込んだ実用クリアランスで広めに取ってある。
#
# 基板座標系(このファイルの bx / by): 基板左下が原点
# X = 85mm の長辺方向 GPIO ヘッダは Y = 56 側
# Y = 56mm の短辺方向 USB/LAN は X = 85 側
# 電源 / 映像 / AV は Y = 0 側 microSD は X = 0 側
# ワールド座標はケース中心が原点なので wx = bx - 42.5, wy = by - 28
# ============================================================
@param choices:["Pi3B+", "Pi4B", "Pi5"] desc:"対象モデル" group:"Model"
model = "Pi5"
@param choices:["tray", "lid", "both"] desc:"出力パーツ" group:"Model"
part = "both"
is_3b = model == "Pi3B+"
is_4b = model == "Pi4B"
# ---- 基板 (全モデル共通) --------------------------------
board_w = 85.0
board_d = 56.0
pcb_t = 1.4
hole_in = 3.5 # 取付穴の各辺からの距離
hole_px = 58.0 # 取付穴ピッチ X
hole_py = 49.0 # 取付穴ピッチ Y
# ---- ポート位置 (モデル別) ------------------------------
# Y=0 辺: 電源 / 映像 / AV の基板X中心
pwr_x = if is_3b then 10.6 else (if is_4b then 7.7 else 11.2)
hdmi_a_x = if is_3b then 32.0 else (if is_4b then 22.5 else 25.8)
hdmi_b_x = if is_4b then 36.0 else 39.2
av_x = if is_3b then 53.5 else 54.0
# X=85 辺: LAN / USB の基板Y中心。4B だけ LAN と USB が上下逆
lan_y = if is_3b then 11.5 else (if is_4b then 45.75 else 10.2)
usb_a_y = if is_3b then 29.0 else (if is_4b then 9.0 else 29.1)
usb_b_y = if is_3b then 47.0 else (if is_4b then 27.0 else 47.0)
two_hdmi = if is_3b then false else true
has_av = if model == "Pi5" then false else true
# ---- 開口サイズ (プラグ外形 + クリアランス) --------------
usb_w = 15.2 # USB-A 2段スタック 13.2 + 1.0
usb_h = 17.0 # 部品高 16.0 + 1.0
lan_w = 17.0 # RJ45 シェル 15.9 + 1.1。4B は LAN が Y=45.75 と端に寄って
lan_h = 15.0 # いるので、これ以上広げると角Rまで 0.5mm を切る
pwr_w = if is_3b then 11.0 else 12.0
pwr_h = 7.0
hdmi_w = if is_3b then 17.5 else 12.0
hdmi_h = if is_3b then 10.0 else 8.0
av_r = 5.0
av_z = 3.0 # 基板上面からジャック軸心まで
sd_w = 18.0 # microSD 抜き差し用スロット (広めに確保)
# ---- ケース ---------------------------------------------
@param 0.3..1.5 step:0.05 desc:"基板外周クリアランス(片側)" group:"Case"
clr = 0.75
@param 1.6..3.5 step:0.1 desc:"肉厚(側壁・床とも)" group:"Case"
wall = 2.0
@param 2..8 step:0.5 desc:"外形角R" group:"Case"
corner_r = 4.0
@param 2.5..8 step:0.5 desc:"基板裏面クリアランス" group:"Case"
stand_h = 3.5
@param 1.6..3 step:0.1 desc:"蓋の板厚" group:"Case"
lid_t = 2.0
@param type:"bool" desc:"蓋に GPIO ヘッダ用スリットを開ける" group:"Case"
gpio_slot = false
comp_h = 16.0 # 基板上の最大部品高 (USB-A スタック)
comp_clr = 1.0 # 開口上端の余裕
win_top = 2.5 # 開口上端から壁天面までの残り肉
stand_r = 3.0 # スタンドオフ外径 φ6
pilot_r = 1.05 # M2.5 セルフタップ下穴 φ2.1 (スタンドオフと床を貫通)
# ---- スナップ (蓋裏のリップと爪、側壁の窓) ------------------
# 爪は片持ち梁。壁との隙間 tab_clr を除いた hook_d 分だけ内側にたわんで
# 壁天面を越え、窓に掛かる。たわみ ε = 1.5 t δ / L² = 1.5×1.2×0.6/10² ≈ 1.1%
# で PLA でも余裕がある。爪の位置は 3 モデルのポート開口を避けた場所: 前壁・後壁は基板 X=74、左壁は基板 Y=14 と 42。右壁は
# USB/LAN の開口が壁天面 3.5mm まで迫っていて窓を切れないので爪なし
lip_h = 2.0 # リップの高さ (蓋裏から下)
lip_t = 1.2 # リップの厚さ
lip_clr = 0.25 # リップ外面と側壁内面の隙間 (片側)
tab_w = 8.0 # 爪の幅
tab_t = 1.2 # 爪の板厚
tab_len = 10.0 # 爪の長さ (蓋裏から先端まで)
tab_clr = 0.2 # 爪外面と側壁内面の隙間
hook_d = 0.8 # 爪の出っ張り (壁内面からの掛かり = hook_d - tab_clr)
hook_h = 1.8 # 爪の高さ (45°の導入斜面 0.8 + 掛かり面 1.0)
win_clr = 0.5 # 窓の幅方向クリアランス (片側)
tab_bx = 74.0 # 前壁・後壁の爪の基板 X
tab_by = 14.0 # 左壁の爪の基板 Y (14 と 56-14=42)
case_w = board_w + clr * 2 + wall * 2
case_d = board_d + clr * 2 + wall * 2
board_z = wall + stand_h + pcb_t # 基板上面の高さ
tray_h = board_z + comp_h + comp_clr + win_top # トレイ壁の天面
win_z0 = board_z - 1.0 # 側壁開口の下端
# 取付穴のワールド座標
hx0 = hole_in - board_w / 2
hx1 = hx0 + hole_px
hy0 = hole_in - board_d / 2
hy1 = hy0 + hole_py
# 壁を確実に貫くためのツール厚と、各壁の中心座標
cut_t = 8.0
front_y = 0 - case_d / 2
back_y = case_d / 2
right_x = case_w / 2
left_x = 0 - case_w / 2
in_x = case_w / 2 - wall # 側壁内面
in_y = case_d / 2 - wall
# ---- 開口のツール -----------------------------------------
# 前壁(基板 Y=0 側): 基板X中心 bx、幅 w、高さ h、下端 z0
def fwin(bx, w, h, z0) = box w cut_t h at:((bx - board_w / 2), front_y, (z0 + h / 2))
# 右壁(基板 X=85 側): 基板Y中心 by、幅 w、高さ h、下端 z0
def rwin(by, w, h, z0) = box cut_t w h at:(right_x, (by - board_d / 2), (z0 + h / 2))
hdmi_pair = union [fwin(hdmi_a_x, hdmi_w, hdmi_h, win_z0),
fwin(hdmi_b_x, hdmi_w, hdmi_h, win_z0)]
hdmi_one = fwin(hdmi_a_x, hdmi_w, hdmi_h, win_z0)
hdmi_cut = if two_hdmi then hdmi_pair else hdmi_one
av_cut_on = cylinder av_r cut_t
| rotate 90 0 0
| translate (av_x - board_w / 2) front_y (board_z + av_z)
# Pi5 には AV ジャックがない。ツールを「消す」手段がないので、
# 既に開けた HDMI-A と同じツールを重ねて実質 no-op にする
av_cut = if has_av then av_cut_on else (fwin(hdmi_a_x, hdmi_w, hdmi_h, win_z0))
sd_cut = box cut_t sd_w (board_z + 0.5 - wall)
| translate left_x 0 ((wall + board_z + 0.5) / 2)
port_cuts = union [
fwin(pwr_x, pwr_w, pwr_h, win_z0),
hdmi_cut,
av_cut,
rwin(lan_y, lan_w, lan_h, win_z0),
rwin(usb_a_y, usb_w, usb_h, win_z0),
rwin(usb_b_y, usb_w, usb_h, win_z0),
sd_cut
]
# ---- ねじまわり -----------------------------------------
standoff = cylinder stand_r stand_h | floor | translate 0 0 wall
standoffs = union [
standoff | translate hx0 hy0 0,
standoff | translate hx1 hy0 0,
standoff | translate hx0 hy1 0,
standoff | translate hx1 hy1 0
]
pilot = cylinder pilot_r (wall + stand_h + 2) | floor | translate 0 0 (0 - 1)
pilots = union [
pilot | translate hx0 hy0 0,
pilot | translate hx1 hy0 0,
pilot | translate hx0 hy1 0,
pilot | translate hx1 hy1 0
]
# ---- 床の通気スリット -----------------------------------
floor_vent = union [box 30 3 (wall + 2) at:(0, (0 - 14 + i * 7), (wall / 2)) for i in range(5)]
# ---- スナップの窓 (トレイ側壁) ----------------------------
# 窓の上辺 = 爪の掛かり面 + 0.1 (蓋が壁天面に着座した状態で掛かる)
# 窓の下辺 = 爪の先端 - 0.5
tab_x = tab_bx - board_w / 2
tab_y = tab_by - board_d / 2
win_w = tab_w + win_clr * 2
win_h = hook_h + 0.6
win_zc = tray_h - tab_len + hook_h / 2 - 0.2
snap_wins = union [
box win_w cut_t win_h at:(tab_x, front_y, win_zc),
box win_w cut_t win_h at:(tab_x, back_y, win_zc),
box cut_t win_w win_h at:(left_x, tab_y, win_zc),
box cut_t win_w win_h at:(left_x, (0 - tab_y), win_zc)
]
# ---- トレイ ---------------------------------------------
tray = box case_w case_d tray_h
| floor
| edges "=Z" | fillet corner_r
| faces ">Z" | shell wall
| union standoffs
| diff port_cuts
| diff pilots
| diff floor_vent
| diff snap_wins
# ---- 上蓋 (組立位置で作る) ------------------------------
lid_plate = box case_w case_d lid_t
| floor
| edges "=Z" | fillet corner_r
| translate 0 0 tray_h
# ---- スナップのリップと爪 (蓋裏、組立位置) ------------------
# リップ: 側壁内面に lip_clr の隙間ではまる枠。上に 1mm 食い込ませて蓋板と
# 確実に融合させる。爪の位置は切り欠いて、爪を蓋板から生えた片持ち梁にする
lip_w = case_w - wall * 2 - lip_clr * 2
lip_d = case_d - wall * 2 - lip_clr * 2
lip_ro = corner_r - wall - lip_clr
lip_ri = lip_ro - lip_t
lip_o0 = box lip_w lip_d (lip_h + 1) | floor
lip_o = if lip_ro > 0.3 then (lip_o0 | edges "=Z" | fillet lip_ro) else lip_o0
lip_i0 = box (lip_w - lip_t * 2) (lip_d - lip_t * 2) (lip_h + 3) | floor | translate 0 0 (0 - 1)
lip_i = if lip_ri > 0.3 then (lip_i0 | edges "=Z" | fillet lip_ri) else lip_i0
slot_w = tab_w + 1.6
slot_t = lip_t + lip_clr + 4
slot_h = lip_h + 4
lip_slots = union [
box slot_w slot_t slot_h at:(tab_x, (0 - in_y), 0),
box slot_w slot_t slot_h at:(tab_x, in_y, 0),
box slot_t slot_w slot_h at:((0 - in_x), tab_y, 0),
box slot_t slot_w slot_h at:((0 - in_x), (0 - tab_y), 0)
]
lip = (lip_o - lip_i) | diff lip_slots | translate 0 0 (tray_h - lip_h)
# 爪: YZ 断面 (u = 壁外向き、v = 蓋裏からの高さ) を X に押し出す。
# 外面 u=0 を壁内面から tab_clr だけ内側に置く。上端 v=+1 は蓋板への食い込み
tab0 = workplane YZ
| sketch [((0 - tab_t), 1), ((0 - tab_t), (0 - tab_len)), (0, (0 - tab_len)),
(hook_d, (0 - tab_len + hook_d)), (hook_d, (0 - tab_len + hook_h)),
(0, (0 - tab_len + hook_h)), (0, 1)]
| extrude tab_w
| translate (0 - tab_w / 2) (in_y - tab_clr) tray_h # 後壁 (+Y 向き)
tabs = union [
tab0 | translate tab_x 0 0,
tab0 | rotate 0 0 180 | translate tab_x 0 0, # 前壁 (-Y 向き)
tab0 | rotate 0 0 90 | translate (in_y - in_x) tab_y 0, # 左壁 (-X 向き)
tab0 | rotate 0 0 90 | translate (in_y - in_x) (0 - tab_y) 0
]
# GPIO ヘッダ用スリット (既定オフ): 基板 X 7.0..58.0、Y 48.5..55.0
# ヘッダは樹脂ボディ 50.8mm・ピン範囲 7.9..56.1mm なので、51mm で全ピンに届く。
# Y 側は後壁のリップ (内面が基板 Y 55.3) が上限。2x20 の IDC ソケット
# (約 52.5 × 8.9mm) は Y 方向で通らない。
gpio_cut = box 51 6.5 (lid_t + 2) at:((32.5 - board_w / 2), (51.75 - board_d / 2), (tray_h + lid_t / 2))
lid_vent = union [box 45 2.5 (lid_t + 2) at:(0, (0 - 15 + i * 6), (tray_h + lid_t / 2)) for i in range(6)]
# スリットを開けないときは diff 自体を省く。体積の動かない材料除去は
# poly verify が check.no-effect として弾くため、no-op のツールは置かない
lid_body = lid_plate
| union lip
| union tabs
| diff lid_vent
lid_asm = if gpio_slot then (lid_body | diff gpio_cut) else lid_body
lid_print = lid_asm | rotate 180 0 0 | floor
# ---- 出力 -----------------------------------------------
both = union [tray, lid_print | translate 0 (case_d + 5) 0]
if part == "tray" then tray else (if part == "lid" then lid_print else both)