Last active
August 30, 2026 19:42
-
-
Save niratama/bb80167406846818dd0c8028e50ba6fe to your computer and use it in GitHub Desktop.
Geminiに作らせたCherry MXキースイッチ向けキーキャップ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ======================================================== | |
| // 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(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ======================================================== | |
| // 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