Files
Beyond-KLE/PHYレイヤー.md
T
ewdernaandClaude Opus 5 6d0eba9346 phy: 複数のバリアントで成立するキーはVariantごとに複製して置く
隣り合うキャップが同じフットプリントを食い合う基板(MACRO1)があり、選択を独立な
VariantGroupに必ず分解できるわけではない。分解を強いると、キャップが重なる組み合わせを
作れてしまう。複製してもELCは接続先を、IDXは番号を共有できるので他レイヤーは壊れない。
どのキーノードが同じ物理キーかはBeyond KLEが判定せず、設計者の情報として扱う。

前のコミットで「独立したVariantGroupに分けて表す」と書いたが、MACRO1は下段を含めると
分解できない。あわせて訂正する。

Journal: journal/2026-09-21-variant-shared-key.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01246a5KFLmwcRXriZvX8fdJ
2026-09-21 21:34:10 +09:00

24 KiB
Raw Blame History

PHYレイヤー

概要

PHYレイヤーは、キースイッチやキーキャップ、その他周辺部品の物理的な配置に関する情報を提供する。
付加的な情報として、matrix情報(ELCレイヤー)や入力キー情報(MAP/IMEレイヤー)を含ませることができるが、必須ではない。

構造

キースイッチ/キーキャップの配置

各キーはスイッチ・キャップの情報を統合された状態で持つ。 スイッチ位置は、キャップの (w, h) 矩形の中心からのオフセットとしてキーごとに持つ(既定値0、単位U、キーのローカル座標系)。 Stepped Caps Lockのようにスイッチがキャップ中心からずれるキーがあり、キャップ形状だけではスイッチ位置が決まらないため。
以下、単に「キー」と呼ぶ場合、キーキャップの位置・サイズを表すオブジェクトをさすこととする。

プロファイル設定

1ファイル内に複数のプロファイルを持たせることができる。プロファイルはノードではなくファイル単位に置き、 キーはIDで参照する。同じ定義を複数のキーに複製しないため、実体ではなく参照とする。

プロファイルが規定するのは次の2つ。

  • ユニットサイズ: 1Uが何mmか。x軸・y軸それぞれ
  • ビジュアル表現: キー上面の矩形を、外形の各辺(上下左右)から内側への距離と、角のRで表す

上面の距離を4辺それぞれ持つのは、上面が外形の中心からずれるプロファイルを表せるようにするため。 プロファイル内の距離はすべてmmで持つ。実寸を規定する定義なので、U単位の値を混ぜない。

座標と回転

各ノードは、親ノードから見た相対的な配置を持つ。絶対座標は保持しない。 クラスター単位の移動・回転を親ノード1つの書き換えで済ませるため、相対座標を正本とする。

配置は「親の座標系で θ 回転してから (x, y) 平行移動」と定義する。

p_parent = Translate(x, y) · Rotate(θ) · p_child

この定義により、(x, y) は自分のローカル原点が親座標系のどこに来るかを表し、θ は自分の向きを表す。 回転軸は常に自分自身のローカル原点であり、パラメータとしては持たない。 キーもクラスターも同じ変換を持つため、キーごとに向きが異なる配置のためにクラスターを作る必要はない。

ローカル原点はキーキャップの左上角とする。座標単位はU。1Uのサイズはプロファイルで指定する。

座標・回転角度は、手作業での入力時に0.25U単位・15度単位でスナップするが正規化しない。 グリッドは操作開始時の値を基準とする相対グリッドとし、ローカル原点を基準とした絶対グリッドにはしない。 計算で得た値にはスナップを適用しない。組み替えで生じた非グリッド値を丸めると絶対配置が動くため。

KLE JSONやPCB向けの出力は、階層を畳んで絶対座標に変換した導出物として生成する。

キーのサイズ

0.25U単位でスナップするが正規化しない。
サイズはローカルの値であり、親の変換を受けない。

ISO Enterのように長方形でないキーは、サイズに2つ目の矩形(x2, y2, w2, h2)を加えて表す。 2つ目の矩形の位置は、キーのローカル原点からの相対値とする。

部品グループとスイッチの向き

PHYはスイッチの品種(MX-3pin、Choc V1 など)もソケット方式も持たない。 どのフットプリントを使うかはPCB設計ツール側の選択であり、Beyond KLEはその手前までを扱う。

キーが持つのは次の2つ。

  • 部品グループ(partGroup): 同じ部品を使うキーをまとめるラベル。省略可
  • スイッチの向き(orientation): 0/90/180/270の4方向。キーのローカル座標系に対する値で、既定は0。90度と270度は別物として扱う

部品グループの中身はBeyond KLEが解釈しない。穴・パッドの配置も、低背かどうかも持たない。 グループごとの実フットプリントはPCB出力時に割り当てる。 不均一な構成で「どのキーが同じ部品か」という設計者の意図だけを保存するために持つ。

スイッチのブランドも持たない。同じボードに対して利用者が好きなスイッチを選べるため、ボード設計の情報ではない。

キーごとの検出方式(接点マトリクス / アナログ / 光学)はELCが持ち、PHYは持たない。 ホールセンサのように端子数も配線トポロジも接点スイッチと異なるものがあり、これは電気的な性質のため。

per-key LED

キーはper-key LEDの有無をbooleanで持つ。位置も向きも持たない。 per-key LEDの位置はスイッチのフットプリント内で決まり、向きはキーのorientationに従うため、 設計者が決められるのは載せるか載せないかだけ。

載せるキーと載せないキーが混在する構成を表せるよう、キーごとに持つ。キーごとの固定値であり、 条件によって有無が変わる構成は扱わない。

部品ノード

LED・OLED・ロータリーエンコーダ・ネジ穴のように、設計者が位置を決めるキー以外の部品は、 キーと同じ変換を持つノードとして木に置く。per-key LED以外のLEDもここに入る。

部品ノードが持つのは変換と部品グループだけ。品種・端子配置・フットプリント・表裏は持たない。 どの部品を実際に載せるかはPCB設計ツール側の選択であり、キーと同じ原則が適用される。

スイッチの向き(orientation)に当たる軸は持たない。キーが2軸を持つのはキーキャップとスイッチが 独立に回せるためで、部品ノードにその二重構造はない。向きは変換の回転角度で表す。

部品グループ名のうち @ で始まるものは予約で、エディタ上の描画を変えるために使う。 フットプリントの選択には使わない。予約名以外および省略時は既定の形で描く。 ユーザーが付ける名前は @ で始めない限り予約と衝突しない。

部品ノードはクラスターの子になれる。クラスターを動かせば配下の部品も追随する。

LEDの配線はPHYが持たない。LEDの識別番号もPHYは持たない。番号は配線で決まるため、ELCが持つ。 IDXはLEDの番号を持たない。RGB効果が必要とする位置と区分は、PHYのノード配置と種別から得る。

デカールとゴースト

デカールとゴーストはキーとして扱い、フラグで区別する。KLEの d, g に対応する。

ラベル

キーはラベルを参考情報として持つ。KLE JSONの取り込みでは照合に使う。

グルーピング

複数のキーを「クラスター」としてまとめて運用することができる。
クラスターのみにフォーカスして編集が可能。
クラスターの原点が、配下のノードの回転中心になる。

内部処理での利用:

  • クラスターごとの一括編集・移動・回転
  • 階層構造による管理(クラスターの入れ子構造)
  • 編集時のフォーカス制御
  • 回転中心を保存したい場合はクラスターにする(ノード自身は回転中心を持たないため)

外部連携での扱い:

  • クラスター構造はBeyond KLEのデータにのみ存在する。外部形式へは階層を畳んだキー配置だけを出力する

レイアウトバリアント

一部のキークラスタを別の構成と交換できるようにした基板がある。 \ | と Enter を ISO Enter と1Uキーに変える、スペースキー周りのキー数を変える、といったもの。 交換にはスイッチの差し替えが伴い、同時に成立するのは1つだけである。

PHYは全バリアントのキーを同時に持つ。基板上のフットプリントは選ばれなかった側も実在するため、 どれか1つに畳んだ状態を正本にできない。

排他関係は木構造で表す。バリアントグループ(VariantGroup)と、その子であるバリアント(Variant)の 2種類のノードを置く。Variantの配下にあるノードが、そのバリアントを選んだときに実装されるものになる。

  • VariantGroupの子はVariantだけ。VariantはVariantGroupの直下にしか現れない
  • 選択肢が3つ以上ある場合はVariantを3つ以上置く
  • 配下にノードを持たないVariantを置いてよい(「エンコーダーを載せない」など)
  • 部品ノードもVariantの配下に置ける。1Uキーとロータリーエンコーダーの選択がこれにあたる
  • Variantの中に別のVariantGroupを置ける。あるバリアントを選んだときだけ現れる選択肢を表す
  • 選択箇所が複数ある場合はVariantGroupを複数置く。同じ親の下に並べてよい
  • 選択が互いに独立なら、VariantGroupを分けて選択肢の数を減らしてよい。 ANSI/ISOとBackspaceの分割のように、独立な2つの選択が1つの選択肢リストに畳まれている基板がある

VariantGroupはクラスターと同じ変換を持つ。Variantは変換を持たない。 バリアントは同じ場所を占める代替物であり、選択によって配下の絶対配置が動く構造を作らないため。 1つのバリアントの中で回転中心が要る場合は、その中にクラスターを置く。

サイズや形が変わるキーは、同じキーを書き換えるのではなく、別のキーノードとして各Variantに置く。 ANSI EnterとISO Enterは別のIDを持つ2つのキーであり、同じ場所に重なる。

同じ位置・同じサイズのキーが複数のバリアントで成立する場合も同じで、Variantごとに別のキーノードを置く。 隣り合うキャップが同じフットプリントを食い合う基板があり、選択を分解できるとは限らないため。 どのキーノードが同じ物理キーを指すかはBeyond KLEが判定しない。設計者が知っている情報として扱う。

VariantGroupは既定のバリアントを1つ持つ。KLE JSONのように単一の配置を要求する出力は、 VariantGroupごとにバリアントを1つ選んでから階層を畳んで生成する。既定値はそのときの初期選択になる。 編集中にどのバリアントを表示しているかはエディタの状態であり、PHYには保存しない。

Variantをまたぐノードの移動は、クラスター間の移動と同じreparentとして扱う。絶対配置は変わらない。 組み替えの対象を同一の親に限る規則がそのまま効くため、複数のVariantにまたがる選択は組み替えできない。

選択はPHYを畳むときにだけ使う。他レイヤーはPHYのノードIDを参照するだけで、選択を見ない。

クラスターの組み替え

クラスター構造は、配置を作ったあとから組み替える。KLE JSONから取り込んだ平坦なキー配置に クラスターを与える経路がこれしかないため、エディタの基本操作として持つ。 (エディタの操作仕様。将来ツール側のドキュメントへ移す)

操作は次の2つの演算に還元される。どちらも対象の絶対配置を変えない。

  • reparent: 親を変える。新しい親の変換を M_C、元の変換を M_i として M_i' = M_C⁻¹ · M_i
  • reframe: 自分の変換を変え、配下を逆補正する。M_i' = M_C'⁻¹ · M_C · M_i
操作 内部
選択をクラスター化 / 既存クラスターへの出し入れ / クラスター解除 reparent
クラスターの原点移動・基準角度の変更 reframe
クラスターの移動・回転(配下が追随する) 自分の変換の書き換え

Transform は回転と平行移動だけを持つ剛体変換であり、合成と逆変換について閉じている。 組み替えの結果は必ず Transform で表せる。サイズ・switchOffset・orientation はローカル値なので影響を受けない。

  • 組み替えの対象は同一の親を持つノードに限る。複数の親にまたがる選択は組み替えできない
  • クラスター化したときの初期値は、原点を選択の境界矩形の左上、基準角度を0とする。 ただし選択したノード全部が同じ回転角度を持つ場合はその角度を基準角度に採り、境界矩形もその角度の座標系で取る
  • 組み替えで生じた非グリッド値はそのまま保持する。クラスターを解除して親に戻ったノードも同じ

保存形式

各ノードは親から見た変換を持つ。キー・クラスター・部品ノード・バリアントノードを混在させた木構造として保持する。 各ノードはIDを持つ。木の中のノードを一意に指すために持ち、キーのIDは他レイヤーからの参照にも使う。 プロファイルは木の外に置き、ファイル単位の配列として持つ。

type Profile = {
    id: string,
    name: string,
    unitSize: { x: number, y: number }, // 1Uのmm数
    visualOffset?: {                    // キー上面。単位はmm
        top: number,                    // 外形の各辺から内側への距離
        right: number,
        bottom: number,
        left: number,
        radius: number
    }
}
type Transform = {
    x: number,        // 親座標系での位置
    y: number,
    rotation: number  // 親座標系での回転角度(度)
}
type Size = {
    w: number,        // ローカル値。親の変換を受けない
    h: number,
    x2?: number,      // 長方形でないキーの2つ目の矩形。ローカル原点からの相対値
    y2?: number,
    w2?: number,
    h2?: number
}
type Key = {
    id: string,
    transform: Transform,
    size: Size,
    profile: string,  // Profile.id を参照
    decal?: boolean,
    ghost?: boolean,
    labels?: string[], // 参考情報
    switchOffset?: { x: number, y: number }, // (w, h) 矩形の中心から。省略時は0
    partGroup?: string, // 同じ部品を使うキーをまとめるラベル。中身は解釈しない
    orientation?: 0 | 90 | 180 | 270, // スイッチの向き。キーのローカル座標系。省略時は0
    perKeyLED?: boolean // per-key LEDの有無。省略時はfalse
}
type Component = {
    id: string,
    transform: Transform,
    partGroup?: string // `@` 始まりは描画用の予約名
}
type Node = Key | Cluster | Component | VariantGroup
type Cluster = {
    id: string,
    transform: Transform,
    children: Node[]
}
type VariantGroup = {
    id: string,
    name: string,        // UI上の選択肢の見出し
    transform: Transform,
    default: string,     // 既定のVariantのid
    children: Variant[]  // 2つ以上。並びが表示順
}
type Variant = {
    id: string,
    name: string,
    children: Node[]     // 空でもよい
}
type PhyFile = {
    version: string,
    metadata: {
        name?: string,
        author?: string,
        description?: string,
        created?: string,
        modified?: string
    },
    profiles: Profile[], // 1つ以上
    root: Node[]
}

連携用出力形式

KLEとの関係

KLEは、PHYデータを表示できる既存のビュワーとして扱う。 KLEで編集された時点で、そのデータはBeyond KLEのデータではない。

  • KLE JSONにはBeyond KLE固有の情報(キーID、クラスター構造など)を含めない。出力は素のKLE JSONで、KLE JSONを読む他ツールにそのまま渡せる
  • KLE JSONの取り込みは、手元のPHYデータとの照合で行う。IDや埋め込み情報には頼らない
  • 過去のKLE JSONは、PHYデータがなくても読み込めなければならない。変換を伴ってよい

KLE JSON出力

階層を畳んで絶対座標に変換し、標準的なKLE JSON形式で出力する。

type ConfigObject = {
    x?: number,
    y?: number,
    w?: number,
    h?: number,
    x2?: number,
    y2?: number,
    w2?: number,
    h2?: number,
    r?: number,     // 回転角度
    rx?: number,    // 回転中心
    ry?: number,
    l?: boolean,
    n?: boolean,
    d?: boolean,
    c?: string,
    t?: string,
    g?: boolean,
    a?: number,
    f?: number,
    f2?: number,
    fa?: number[],
    p?: string,
    sm?: string,
    sb?: string,
    st?: string
}

type KeyLabel = string  // 表示用ラベルのみ。識別情報は含めない
const kleData = (ConfigObject | KeyLabel)[][]
  • 回転角度は正規化せずそのまま出力する。KLEのデータは任意の角度を受け付け、15度刻みはKLEのUI上の操作単位にすぎない
  • rx, ry はPHYに持たないので、出力時に選ぶ。同じ絶対角度を持つキーには共通の値を与える (クラスターの原点が使える)。KLEが1行にまとめるのは r, rx, ry が一致し y が等しいキーだけという制約による
  • 書き出す x, y は丸める。行のまとまりは y の厳密一致で決まるので、絶対座標への変換で入った誤差が残ると行が割れる
  • ユニットサイズ(mm)、ビジュアルオフセット、クラスター構造、部品ノード、per-key LEDはKLE JSONに出力されない
  • バリアント構造はKLE JSONに出力しない。VariantGroupごとにバリアントを1つ選び、その配下だけを畳んで出力する
  • 部品グループ・スイッチの向きはKLE JSONに出力しない。sm, sb, st には取り込み時に保持した値をそのまま書き戻す
  • p, sm, sb, st は空文字に戻す指定がKLE側で無視される。値を持つキーと持たないキーが混在する配置は、 KLEで開いた時点で後続のキーに前の値が残る
  • KLEの p はキーキャップのプロファイル名(SA、DCSなど)であり、PHYのプロファイルとは対応させない

KLE JSONの取り込み

既存のPHYデータを畳んだ配置に対して、KLE JSONの各キーを形状(位置・サイズ・回転)とラベルで照合する。

  • 照合できたキーは元のノードとして扱う。KLE側で配置が変わっていれば、元の親から見た相対座標に戻して反映する
  • 照合できないキーは root 直下に新規キーとして置く。クラスターとバリアントへの割り当てはユーザーが行う
  • 照合の対象は、出力時に選んだバリアントの配下にあるキーに限る。選ばれなかったバリアントのキーは照合にも削除にも関与しない
  • 構造の復元は保証しない。KLE側での編集が大きいほど、クラスター構造は失われる
  • 配列の順番は照合に使わない。KLEは保存時にキーを「角度 → rx → ry → y → x」の順に並べ替えるため、順番は形状から決まる以上の情報を持たない

r, rx, ry を行の先頭以外に置いたKLE JSONが公開されている。KLE本家はこれを読み込まずエラーにする。

KLEの rx, ry, r はキーの絶対配置 (x, y, rotation) に変換して取り込む。元の rx, ry は保持しない。

見た目の属性(c, t, p, a, f, f2, fa, l, n)は、KLE JSONへ書き戻せるように保持する。どの層で持つかは決まっていない。

KLEの sm, sb, st(ファイル単位では switchMount, switchBrand, switchType)も同様に、不透明なまま保持して書き戻す。 Beyond KLEはスイッチの品種を持たないため、対応づける先がない。

検討済み・非採用

  • スイッチの品種・ソケット方式をPHYで持つ (journal/2026-09-20-switch-terminal-reversal.md)
  • LED専用ノード (journal/2026-09-20-led-node.md)
  • per-key LEDに座標を持たせる (journal/2026-09-20-led-node.md)
  • KLE取り込み時のクラスター自動推定 (journal/2026-09-20-cluster-manual-grouping.md)
  • レイアウトバリアントをキーごとのタグとファイル単位の宣言で持つ (journal/2026-09-21-multi-layout.md)

未確認事項

P-2 KLE JSON取り込み時の照合基準

  • 位置づけ: 本文「KLE JSONの取り込み」の具体化に必要な未解決の問い
  • 現状の認識: 形状の許容誤差、ラベルの扱い、複数候補がある場合の決め方が未定
  • 格上げ条件: 照合規則を決めたうえで、KLE本家でキーの移動・追加・削除をしたJSONを取り込み、意図どおりに対応づくことを確認する

P-6 キーキャップの見た目情報を持つ層

  • 位置づけ: 本文「見た目の属性は保持する。どの層で持つかは決まっていない」の未解決部分
  • 現状の認識: c, t, p, a, f系に加え、l, n もキーキャップの見た目属性であり、PHYとは別の層が要りそう。退避だけして書き戻す sm, sb, st も同じ置き場の問題を持つ。そもそもキーキャップは設計時に決める必要が薄く、カスタムキーボードの文脈に属するため、Beyond KLEの扱う範囲かどうかも未定。ファイル単位の backcolor, radii, background, css も同じ問いに含まれる
  • 格上げ条件: キーキャップの情報をBeyond KLEの範囲に含めるかをユーザーが決め、保持する層を決める

P-8 ラベルをMAPに適用するか

  • 位置づけ: 未解決の問い。本文ではラベルを参考情報として持つことだけを決めている
  • 現状の認識: どのラベルがMAPに適用できるかは、おそらく機械的には判断できない。自動で適用してユーザーが不要なものを落とす方式も考えられる
  • 格上げ条件: 適用するか、する場合は自動適用とユーザーによる取捨の流れをユーザーが決める

P-12 部品の表裏を描画でどう扱うか

  • 位置づけ: 本文「部品ノードは表裏を持たない」の残余
  • 現状の認識: どちらの面に載るかはPCB設計ツールが決める情報なのでPHYには持たない。ただしエディタ上で裏面の部品を描き分けたくなる可能性があり、@ 始まりの予約グループ名だけで表現できるかは未検証
  • 格上げ条件: 予約グループ名による描画を実装し、表裏の描き分けが必要になるか、必要なら予約名で足りるかを確認する

P-13 reframeと通常の移動・回転をエディタ上でどう区別するか

  • 位置づけ: 本文「クラスターの組み替え」の残余。データモデル側は決まっており、操作系だけが未定
  • 現状の認識: クラスターへのフォーカス状態に紐づけ(中に入っている間は配下の絶対配置が変わらない)、 そのうえで原点ハンドルと通常の移動・回転ハンドルを分ける案が有力。専用モードを設ける案もある
  • 格上げ条件: 操作系をユーザーが決める

P-14 クラスター原点の吸着先

  • 位置づけ: 未解決の問い。数値入力以外に、原点をどこへ吸着させるか
  • 現状の認識: キーのローカル原点(左上角)・キー中心・境界矩形の角と中心が候補。相対グリッドとは別の機構になる
  • 格上げ条件: 原点移動を実装し、吸着なしで足りるか、足りない場合どの候補が要るかを確認する

P-16 KLE本家が拒否するJSONを取り込むか

  • 位置づけ: 未解決の問い。本文「KLE JSONの取り込み」が扱えていない
  • 現状の認識: r, rx, ry を行の先頭以外に置いたJSONが公開されている(1505件中1件)。KLE本家はエラーにするが、 VIAのパーサーは読める。本家に合わせて拒否するか、読めるものは読むかが未定
  • 格上げ条件: 取り込みの寛容さの方針をユーザーが決める