Skip to content

Instantly share code, notes, and snippets.

@niratama
Last active August 30, 2026 19:42
Show Gist options
  • Select an option

  • Save niratama/bb80167406846818dd0c8028e50ba6fe to your computer and use it in GitHub Desktop.

Select an option

Save niratama/bb80167406846818dd0c8028e50ba6fe to your computer and use it in GitHub Desktop.
Geminiに作らせたCherry MXキースイッチ向けキーキャップ
// ========================================================
// JIS / ISO Enter Keycap Generator (Uniform Scale Edition)
// ========================================================
$fn = 48; // コーナー解像度
dish_fn = 120; // 天面シリンダー凹解像度
// ========================================================
// 1. 基本パラメータ設定 (mm)
// ========================================================
unit_pitch = 19.05; // 1U標準ピッチ
gap_size = 1.05; // 隣接キーとの隙間
// JIS Enter寸法 (U単位)
top_u_x = 1.50; // 上段幅 (1.5U)
bot_u_x = 1.25; // 下段幅 (1.25U)
// 外形寸法 (底面)
b_w_top = top_u_x * unit_pitch - gap_size; // 約 27.525 mm
b_w_bot = bot_u_x * unit_pitch - gap_size; // 約 22.7625 mm
b_h_total = 2.0 * unit_pitch - gap_size; // 約 37.05 mm (全高/奥行き)
// 高さ & テーパー
height = 10.0; // 全高 (OEM Row2/3相当)
wall_thick = 1.5; // 外壁肉厚
shrink_val = 2.2; // 側面テーパー絞り込み量 (片側約 2.2mm)
// 角丸パラメータ
corner_r = 1.2; // 垂直コーナー丸み半径 (mm)
// 天面ディッシュ設定
dish_depth = 0.7; // くぼみ深さ (mm)
// ステム設定
stem_outer_dia = 5.6;
stem_cross_len = 4.15;
stem_cross_wid = 1.30;
stem_cross_dep = 4.2;
stem_inset = 0.5;
box_clearance_size = 7.0;
// ========================================================
// 2. 幾何座標・天面曲率の自動計算
// ========================================================
// 境界座標(下段スイッチ中心 (0,0) 基準)
xr = b_w_bot / 2; // 右端 (+11.38mm)
xbl = -b_w_bot / 2; // 下段左端 (-11.38mm)
xtl = xr - b_w_top; // 上段左端 (-16.14mm)
yb = -(unit_pitch - gap_size) / 2; // 最下端 (-9.00mm)
ym = yb + unit_pitch; // 段差部 (+10.05mm)
yt = yb + (2.0 * unit_pitch); // 最上端 (+28.05mm)
// 逆L字全体のバウンディングボックス中心(スケーリング中心)
center_x = (xtl + xr) / 2; // -2.38mm
center_y = (yb + yt) / 2; // +9.525mm
// テーパーのスケーリング係数(底面から天面への収縮比率)
scale_x = (b_w_top - shrink_val * 2) / b_w_top;
scale_y = (b_h_total - shrink_val * 2) / b_h_total;
// 3連ステム位置
stem_y_bot = 0.0; // 下段 (Row 2 中心)
stem_y_mid = unit_pitch / 2; // 中央 (9.525mm)
stem_y_top = unit_pitch; // 上段 (Row 3 中心 / 19.05mm)
// 上段天面幅からシリンダー凹半径 R を自動計算
top_dish_target_w = (b_w_top - shrink_val * 2) - 1.0;
dish_radius = (pow(top_dish_target_w / 2, 2) / (2 * dish_depth)) + (dish_depth / 2);
// ========================================================
// 3. 2D 逆L字プロファイル
// ========================================================
// 2D角丸逆L字ポリゴン(スケーリング中心 (0,0) を基準に定義)
module jis_l_2d_centered(offset_val = 0, r = corner_r) {
// 中心 (center_x, center_y) を原点にオフセット
offset(r = r)
offset(delta = -r + offset_val)
polygon(points = [
[xr - center_x, yb - center_y], // 1. 右下
[xr - center_x, yt - center_y], // 2. 右上
[xtl - center_x, yt - center_y], // 3. 左上
[xtl - center_x, ym - center_y], // 4. 段差左
[xbl - center_x, ym - center_y], // 5. 段差内角
[xbl - center_x, yb - center_y] // 6. 左下
]);
}
// 均等スケーリングによる歪みゼロの一体型ソリッド
module jis_l_extruded_solid(w_inset = 0, h_total = height) {
// 内部中抜き用のアジャスト
sx = (w_inset > 0) ? (b_w_top - (shrink_val + w_inset) * 2) / (b_w_top - w_inset * 2) : scale_x;
sy = (w_inset > 0) ? (b_h_total - (shrink_val + w_inset) * 2) / (b_h_total - w_inset * 2) : scale_y;
cr = (w_inset > 0) ? max(0.2, corner_r - 0.4) : corner_r;
translate([center_x, center_y, 0])
linear_extrude(height = h_total, scale = [sx, sy], convexity = 10)
jis_l_2d_centered(offset_val = -w_inset, r = cr);
}
// ========================================================
// 4. 主要パーツ
// ========================================================
// キーキャップ外殻
module keycap_shell() {
difference() {
// 1. 均等テーパー外郭(歪み・ねじれ・切れ目ゼロ)
jis_l_extruded_solid(0, height);
// 2. 内部空洞(中抜き)
translate([0, 0, -0.1])
jis_l_extruded_solid(wall_thick, height - wall_thick);
// 3. 上段1.5U幅全体に均等に広がるシリンダー凹
translate([center_x, center_y, height + dish_radius - dish_depth])
rotate([90, 0, 0])
cylinder(r = dish_radius, h = b_h_total * 2.5, center = true, $fn = dish_fn);
}
}
// 十字ステム(単体)
module single_mx_stem(stem_h) {
stem_bottom_z = stem_inset;
difference() {
translate([0, 0, stem_bottom_z])
cylinder(r = stem_outer_dia / 2, h = stem_h);
translate([0, 0, stem_bottom_z - 0.1]) {
translate([-stem_cross_len/2, -stem_cross_wid/2, 0])
cube([stem_cross_len, stem_cross_wid, stem_cross_dep + 0.1]);
translate([-stem_cross_wid/2, -stem_cross_len/2, 0])
cube([stem_cross_wid, stem_cross_len, stem_cross_dep + 0.1]);
cylinder(r1 = stem_cross_len/2 * 0.9, r2 = 0, h = 0.6);
}
}
}
// ステム配置(3連ステム: 下段・中央・上段)
module all_stems() {
stem_h = height - wall_thick - stem_inset;
translate([0, stem_y_bot, 0]) single_mx_stem(stem_h);
translate([0, stem_y_mid, 0]) single_mx_stem(stem_h);
translate([0, stem_y_top, 0]) single_mx_stem(stem_h);
}
// 補強リブ
module support_ribs() {
rib_thick = 1.0;
rib_base_z = 3.5;
rib_height = height - wall_thick - rib_base_z;
if (rib_height > 0) {
difference() {
intersection() {
jis_l_extruded_solid(wall_thick, height - wall_thick);
translate([-rib_thick/2, yb, rib_base_z])
cube([rib_thick, yt - yb, rib_height]);
}
// スイッチ・スタビ逃げ
translate([-box_clearance_size/2, stem_y_bot - box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
translate([-box_clearance_size/2, stem_y_mid - box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
translate([-box_clearance_size/2, stem_y_top - box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
}
}
}
// ========================================================
// 最終組み立て
// ========================================================
union() {
keycap_shell();
all_stems();
support_ribs();
}
// ========================================================
// Cherry MX Smooth Parametric Keycap Generator
// ========================================================
$fn = 48; // 外形・コーナーの基本解像度
dish_fn = 120; // 天面ディッシュ/円筒の専用高解像度
// ========================================================
// 1. キーサイズ・形状タイプ設定
// ========================================================
// --- キーサイズ (U単位) ---
key_u_x = 6.25; // 横幅: 1.0(通常), 2.0(Back), 2.25(Enter), 6.25(Spacebar)
key_u_y = 1.0; // 縦幅: 通常 1.0
// --- 天面形状タイプ ---
// "concave" : なめらかな凹(1Uは高精度球面、長手キーは高精度シリンダー)
// "convex" : なめらかな凸(多段ロフトによる真円弧かまぼこドーム)
// "flat" : 平面天面
dish_type = "convex";
// --- 天面の曲率・深さ設定 ---
dish_depth = 0.9; // 凹の深さ、または凸の盛り上がり高さ (mm)
dish_radius = 45.0; // 曲率半径 (mm)
convex_steps= 16; // 凸天面の分割ステップ数(12〜20推奨)
// --- 行(Row)別プロファイル ---
height = 9.0; // 基準高さ (mm)
tilt_angle = 0.0; // 前後傾斜角(度)
top_offset_y = 0.0; // 前後重心オフセット (mm)
// ========================================================
// 2. 寸法自動計算 & 固定パラメータ (mm)
// ========================================================
unit_pitch = 19.05; // 1Uピッチ (0.75インチ)
gap_size = 1.05; // 隣接クリアランス
bottom_size_x = key_u_x * unit_pitch - gap_size;
bottom_size_y = key_u_y * unit_pitch - gap_size;
top_size_x = max(2.0, bottom_size_x - 5.5);
top_size_y = max(2.0, bottom_size_y - 4.5);
wall_thick = 1.5; // 外壁肉厚
corner_r = 1.2; // 縦コーナー丸み半径
top_bevel_r = 0.8; // 天面エッジ丸み半径
// ステム・クリアランス設定
stem_outer_dia = 5.6;
stem_cross_len = 4.15;
stem_cross_wid = 1.30;
stem_cross_dep = 4.2;
stem_inset = 0.5;
box_clearance_size = 7.0;
// ========================================================
// スタビライザー間隔(Cherry MX標準)
// ========================================================
function get_stab_spacing(u) =
(u >= 6.0) ? 100.0 :
(u >= 2.0) ? 23.8 :
0.0;
stab_spacing_x = get_stab_spacing(key_u_x);
stab_spacing_y = get_stab_spacing(key_u_y);
// ========================================================
// モジュール定義
// ========================================================
// 2D角丸四角形
module rounded_rect_2d(size_x, size_y, r) {
rx = max(0.01, size_x/2 - r);
ry = max(0.01, size_y/2 - r);
hull() {
translate([-rx, -ry]) circle(r = r);
translate([ rx, -ry]) circle(r = r);
translate([-rx, ry]) circle(r = r);
translate([ rx, ry]) circle(r = r);
}
}
// なめらかな外殻ソリッド(Convex多段曲面対応)
module ultra_smooth_solid(b_w, b_d, t_w, t_d, h, tilt, off_y, c_r, b_r, is_convex, c_depth) {
hull() {
// 1. 底面スライス
linear_extrude(height = 0.05)
rounded_rect_2d(b_w, b_d, c_r);
// 2. 天面エッジ直前スライス(テーパー部)
h_trans = h - b_r;
translate([0, off_y * (h_trans / h), h_trans])
rotate([tilt, 0, 0])
linear_extrude(height = 0.05)
rounded_rect_2d(
b_w + (t_w - b_w) * (h_trans / h),
b_d + (t_d - b_d) * (h_trans / h),
c_r
);
// 3. 天面エッジ部スライス
translate([0, off_y, h])
rotate([tilt, 0, 0])
linear_extrude(height = 0.05)
rounded_rect_2d(
max(1.0, t_w - b_r * 2),
max(1.0, t_d - b_r * 2),
max(0.1, c_r - b_r)
);
// 4. Convex(凸)多段円弧サンプリング
if (is_convex) {
for (i = [1 : convex_steps]) {
// 0度(エッジ)〜90度(頂点)を正弦波・余弦波でサンプリング
angle = (i / convex_steps) * 90;
// 高さ:サインカーブで滑らかに上昇
step_z = h + sin(angle) * c_depth;
// 前後幅(Y):コサインカーブで外側から中央へ滑らかに収縮
ratio_y = cos(angle);
step_d = max(0.5, (t_d - b_r * 2) * ratio_y);
// 1U凸キーはX軸方向も収縮、長手キー(スペースバー等)はX幅をフル維持
step_w = (key_u_x > 1.25)
? max(1.0, t_w - b_r * 2)
: max(0.5, (t_w - b_r * 2) * ratio_y);
translate([0, off_y, step_z])
rotate([tilt, 0, 0])
linear_extrude(height = 0.05)
rounded_rect_2d(
step_w,
step_d,
max(0.1, (c_r - b_r) * ratio_y)
);
}
}
}
}
// キーキャップ外殻
module keycap_shell() {
is_convex = (dish_type == "convex");
difference() {
// 外形ソリッド
ultra_smooth_solid(
bottom_size_x, bottom_size_y,
top_size_x, top_size_y,
height, tilt_angle, top_offset_y,
corner_r, top_bevel_r,
is_convex, dish_depth
);
// 内部空洞(内壁は底付き防止のためConvexドーム化せずフラットで中抜き)
ultra_smooth_solid(
bottom_size_x - wall_thick * 2, bottom_size_y - wall_thick * 2,
top_size_x - wall_thick * 2, top_size_y - wall_thick * 2,
height - wall_thick, tilt_angle, top_offset_y,
max(0.2, corner_r - 0.3), 0.01,
false, 0
);
// Concave(凹)削り込み(高解像度)
if (dish_type == "concave") {
translate([0, top_offset_y, height])
rotate([tilt_angle, 0, 0]) {
if (key_u_x > 1.25) {
// 高精度シリンダー削り込み
translate([0, 0, dish_radius - dish_depth])
rotate([0, 90, 0])
cylinder(r = dish_radius, h = bottom_size_x * 2, center = true, $fn = dish_fn);
} else {
// 高精度球面削り込み
translate([0, 0, dish_radius - dish_depth])
sphere(r = dish_radius, $fn = dish_fn);
}
}
}
}
}
// 十字ステム(単体)
module single_mx_stem() {
stem_bottom_z = stem_inset;
min_top_h = height - (top_size_y / 2) * sin(abs(tilt_angle));
stem_height = min_top_h - wall_thick - stem_inset;
difference() {
translate([0, 0, stem_bottom_z])
cylinder(r = stem_outer_dia / 2, h = stem_height);
translate([0, 0, stem_bottom_z - 0.1]) {
translate([-stem_cross_len/2, -stem_cross_wid/2, 0])
cube([stem_cross_len, stem_cross_wid, stem_cross_dep + 0.1]);
translate([-stem_cross_wid/2, -stem_cross_len/2, 0])
cube([stem_cross_wid, stem_cross_len, stem_cross_dep + 0.1]);
cylinder(r1 = stem_cross_len/2 * 0.9, r2 = 0, h = 0.6);
}
}
}
// ステム配置(中心 + スタビライザー)
module all_stems() {
single_mx_stem();
if (stab_spacing_x > 0) {
translate([-stab_spacing_x / 2, 0, 0]) single_mx_stem();
translate([ stab_spacing_x / 2, 0, 0]) single_mx_stem();
}
if (stab_spacing_y > 0) {
translate([0, -stab_spacing_y / 2, 0]) single_mx_stem();
translate([0, stab_spacing_y / 2, 0]) single_mx_stem();
}
}
// 補強リブ
module support_ribs() {
rib_thick = 1.0;
rib_base_z = 3.5;
min_top_h = height - (top_size_y / 2) * sin(abs(tilt_angle));
rib_height = min_top_h - wall_thick - rib_base_z;
if (rib_height > 0) {
difference() {
intersection() {
ultra_smooth_solid(
bottom_size_x - wall_thick * 2, bottom_size_y - wall_thick * 2,
top_size_x - wall_thick * 2, top_size_y - wall_thick * 2,
height - wall_thick, tilt_angle, top_offset_y,
max(0.2, corner_r - 0.3), 0.01,
false, 0
);
union() {
translate([-bottom_size_x/2, -rib_thick/2, rib_base_z])
cube([bottom_size_x, rib_thick, rib_height]);
translate([-rib_thick/2, -bottom_size_y/2, rib_base_z])
cube([rib_thick, bottom_size_y, rib_height]);
}
}
// スイッチ・スタビ逃げ
translate([-box_clearance_size/2, -box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
if (stab_spacing_x > 0) {
translate([-stab_spacing_x/2 - box_clearance_size/2, -box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
translate([ stab_spacing_x/2 - box_clearance_size/2, -box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
}
if (stab_spacing_y > 0) {
translate([-box_clearance_size/2, -stab_spacing_y/2 - box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
translate([-box_clearance_size/2, stab_spacing_y/2 - box_clearance_size/2, rib_base_z - 0.1])
cube([box_clearance_size, box_clearance_size, rib_height + 0.2]);
}
}
}
}
// ========================================================
// 最終組み立て
// ========================================================
union() {
keycap_shell();
all_stems();
support_ribs();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment