KLE互換ドキュメント2本を廃止し、裏取りした事実をPHYに書き戻す
kle-compatibility.md / kle-compatibility-analysis.md は埋め込み前提の 設計で、KLE往復の決定と矛盾していた。KLE本家ソースで確認できた事実だけを PHYレイヤー.md に書き戻し、2本は削除した。 - ConfigObject の回転角度キーを ra から r に修正し、fa sm sb st を追加 - KLE出力で失われる情報、KLEの p がPHYのプロファイルと別物であることを明記 - KLEは保存時にキーを並べ替えるため、配列順は照合に使わない - 過去のKLE JSONはPHYデータなしでも読めなければならない、という制約を追加 - 未確認事項に P-3 クラスター自動推定、P-4 KLE固有の形状・属性の受け止め先を追加 Journal: journal/2026-09-18-kle-compat-docs-retire.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,6 @@ PHY(物理配置) / ELC(電気配線) / IDX(キー識別) / MAP(キーマップ)
|
||||
| `keyboard-design-layers.md` | レイヤー構造とツール分割方針(最上位) |
|
||||
| `PHYレイヤー.md` | PHY層の仕様 |
|
||||
| `phase1-json-schema.md` | 各層のJSON Schema |
|
||||
| `kle-compatibility.md` | KLEとの双方向変換仕様 |
|
||||
| `kle-compatibility-analysis.md` | KLE互換性の調査・案比較 |
|
||||
| `keyboard-making-workflow.md` | 既存の自作キーボード制作フローとの対応 |
|
||||
| `typescript-implementation-plan.md` | 実装計画・技術スタック |
|
||||
|
||||
|
||||
+32
-4
@@ -98,6 +98,7 @@ KLEで編集された時点で、そのデータはBeyond KLEのデータでは
|
||||
|
||||
- KLE JSONにはBeyond KLE固有の情報(キーID、クラスター構造など)を含めない。出力は素のKLE JSONで、KLE JSONを読む他ツールにそのまま渡せる
|
||||
- KLE JSONの取り込みは、手元のPHYデータとの照合で行う。IDや埋め込み情報には頼らない
|
||||
- 過去のKLE JSONは、PHYデータがなくても読み込めなければならない。変換を伴ってよい
|
||||
|
||||
### KLE JSON出力
|
||||
|
||||
@@ -113,9 +114,9 @@ type ConfigObject = {
|
||||
y2?: number,
|
||||
w2?: number,
|
||||
h2?: number,
|
||||
rx?: number,
|
||||
r?: number, // 回転角度
|
||||
rx?: number, // 回転中心
|
||||
ry?: number,
|
||||
ra?: number,
|
||||
l?: boolean,
|
||||
n?: boolean,
|
||||
d?: boolean,
|
||||
@@ -125,14 +126,22 @@ type ConfigObject = {
|
||||
a?: number,
|
||||
f?: number,
|
||||
f2?: number,
|
||||
fa?: number[],
|
||||
p?: string,
|
||||
s?: string
|
||||
sm?: string,
|
||||
sb?: string,
|
||||
st?: string
|
||||
}
|
||||
|
||||
type KeyLabel = string // 表示用ラベルのみ。識別情報は含めない
|
||||
const kleData = (ConfigObject | KeyLabel)[][]
|
||||
```
|
||||
|
||||
- 回転角度は正規化せずそのまま出力する。KLEのデータは任意の角度を受け付け、15度刻みはKLEのUI上の操作単位にすぎない
|
||||
- rx, ry はPHYに持たないので、出力時に選ぶ
|
||||
- ユニットサイズ(mm)、ビジュアルオフセット、クラスター構造はKLE JSONに出力されない
|
||||
- KLEの `p` はキーキャップのプロファイル名(SA、DCSなど)であり、PHYのプロファイルとは対応させない
|
||||
|
||||
### KLE JSONの取り込み
|
||||
|
||||
既存のPHYデータを畳んだ配置に対して、KLE JSONの各キーを形状(位置・サイズ・回転)とラベルで照合する。
|
||||
@@ -140,6 +149,9 @@ const kleData = (ConfigObject | KeyLabel)[][]
|
||||
- 照合できたキーは元のノードとして扱う。KLE側で配置が変わっていれば、元の親から見た相対座標に戻して反映する
|
||||
- 照合できないキーは root 直下に新規キーとして置く。クラスターへの割り当てはユーザーが行う
|
||||
- 構造の復元は保証しない。KLE側での編集が大きいほど、クラスター構造は失われる
|
||||
- 配列の順番は照合に使わない。KLEは保存時にキーを「角度 → rx → ry → y → x」の順に並べ替えるため、順番は形状から決まる以上の情報を持たない
|
||||
|
||||
KLEの rx, ry, r はキーの絶対配置 (x, y, rotation) に変換して取り込む。元の rx, ry は保持しない。
|
||||
|
||||
## 検討済み・非採用
|
||||
|
||||
@@ -154,10 +166,26 @@ const kleData = (ConfigObject | KeyLabel)[][]
|
||||
|
||||
- 位置づけ: 本文が前提にしているものではない。連携用出力形式の設計に関わる未解決の問い
|
||||
- 現状の認識: 階層を畳んでKLEに読み込ませると行のまとまりが崩れる。他レイヤーの情報を使ってでも行ごとにまとめたいが、方法は未検討
|
||||
- 手がかり: KLEは r, rx, ry が同じで y が等しいキーを1行にまとめる。同じ絶対角度を持つキーに共通の rx, ry(例えばクラスターの原点)を選べば、行にまとめられる見込み
|
||||
- 格上げ条件: 回転を含むPHYデータをKLE JSONに変換してKLE本家に読み込ませ、rotation groupと行配列がどう再構成されるかを確認する
|
||||
|
||||
### P-2 KLE JSON取り込み時の照合基準
|
||||
|
||||
- 位置づけ: 本文「KLE JSONの取り込み」の具体化に必要な未解決の問い
|
||||
- 現状の認識: 形状の許容誤差、ラベルの扱い、複数候補がある場合の決め方が未定。KLEが保存時にキーの並び順を変えるかは未確認で、配列順を照合に使えるかも分からない
|
||||
- 現状の認識: 形状の許容誤差、ラベルの扱い、複数候補がある場合の決め方が未定
|
||||
- 格上げ条件: 照合規則を決めたうえで、KLE本家でキーの移動・追加・削除をしたJSONを取り込み、意図どおりに対応づくことを確認する
|
||||
|
||||
### P-3 KLE JSON取り込み時のクラスター自動推定
|
||||
|
||||
- 位置づけ: 未解決の問い。PHYデータなしで過去のKLE JSONを取り込むとき、座標の近さや角度からクラスターを推定するか
|
||||
- 現状の認識: 採用しない可能性が高い。ただし代わりに欲しい機能があり、それを思い出すきっかけとして残している
|
||||
- 格上げ条件: 代わりの機能を定義し、この推定を採るか捨てるかをユーザーが決める
|
||||
|
||||
### P-4 KLE固有の形状と属性の受け止め先
|
||||
|
||||
- 位置づけ: 本文「過去のKLE JSONは読み込めなければならない」を満たすために必要な未解決の問い
|
||||
- 現状の認識: 現在の Key 型(transform, size, profile)では次を受け止められない
|
||||
- 長方形でないキー(x2, y2, w2, h2。ISO Enterなど)。受け止められないとISO配列が読めない。ローカルの値なので Size の拡張で相対座標モデルに収まる見込み
|
||||
- デカールとゴースト(d, g)。PHYで持つか、取り込み時に捨てるか
|
||||
- 見た目の属性(ラベル、c, t, p, f系, l, n)。落としてよいか、別の層で保持するか。ラベルは照合にも使う
|
||||
- 格上げ条件: 各項目について、PHYで持つ・他の層で持つ・捨てるのいずれかをユーザーが決め、Key 型に反映する
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# KLE互換ドキュメント2本の廃止と書き戻し
|
||||
|
||||
## 結論
|
||||
|
||||
- kle-compatibility.md / kle-compatibility-analysis.md を作業ツリーから削除した
|
||||
- 原本: `git show 1a9a2a0:kle-compatibility.md` / `git show 1a9a2a0:kle-compatibility-analysis.md`
|
||||
- 経緯: compatibility は初期PHYドキュメントをもとに検討しながら更新したもの。analysis はそれをさらに分析したもの(ユーザー談)
|
||||
- PHYレイヤー.md に書き戻したもの(KLE本家ソースで裏取り済み)
|
||||
- ConfigObject の修正: `ra` → `r`、`fa` `sm` `sb` `st` を追加
|
||||
- 回転角度は正規化せず出力できる(15度はUIの操作単位)
|
||||
- ユニットサイズ・ビジュアルオフセット・クラスター構造はKLEに出力されない
|
||||
- KLEの `p` はキーキャップのプロファイル名で、PHYのプロファイルとは別物
|
||||
- KLEは保存時にキーを並べ替えるので、配列順は照合に使えない
|
||||
- P-1 に行まとめの手がかりを追記
|
||||
- 新たに本文に加えた制約: 過去のKLE JSONはPHYデータなしでも読めなければならない(ユーザー要件。変換は可)
|
||||
- 未確認事項として立てたもの: P-3 クラスター自動推定(未決。代わりに欲しい機能を思い出すきっかけ)、P-4 KLE固有の形状・属性の受け止め先
|
||||
|
||||
## 各記述の行き先
|
||||
|
||||
| 元の記述 | 行き先 |
|
||||
|---|---|
|
||||
| 階層を畳む flattenElements | 既にPHYにある(階層を畳んで絶対座標に変換) |
|
||||
| ID保持方法の比較、`_beyondkle` ハイブリッド、ID復元 | 捨てた。2026-09-18-kle-roundtrip で不採用 |
|
||||
| 往復で構造が一致することを保証するテスト | 捨てた。「構造の復元は保証しない」と矛盾 |
|
||||
| ConversionSettings, バージョン管理メタデータ | 捨てた。埋め込み前提 |
|
||||
| autoDetectClusters | PHY 未確認事項 P-3 |
|
||||
| CLI コマンド例 (export/import) | 捨てた。CLIの形は実装計画側で決める |
|
||||
| KLE Key クラスのプロパティ一覧、互換性マトリックス | 事実部分のみ ConfigObject とKLE出力の制約として書き戻し |
|
||||
| 「プロファイル設定 ↔ p が1:1対応」 | 誤り。KLEの p はキャップ形状名 |
|
||||
| KLE UI改造の項目・難易度・80/15/5%・フェーズ分け | 捨てた。KLE改造か自作かを検討するときは原本を git show で読む |
|
||||
| ゴーストキーによる階層クラスター | 捨てた。2026-09-18-kle-roundtrip で不採用 |
|
||||
| 15度回転制約の分析 | 結論のみ書き戻し。analysis 中の kb.js 引用コードは実物と一致(kb.js:817) |
|
||||
|
||||
## 検証ログ
|
||||
|
||||
ijprest/keyboard-layout-editor master (2026-09-18 取得)
|
||||
|
||||
- serial.js:92-100 sortKeys。角度((a+360)%360) → rotation_x → rotation_y → y → x の順に並べ替える
|
||||
- serial.js:187 serialize の中で sortKeys を呼ぶ。保存のたびに並べ替わる
|
||||
- serial.js:193-198 行が切り替わる条件は y の変化か (r, rx, ry) の変化
|
||||
- serial.js:219-269 出力されるプロパティは r rx ry y x c t g p sm sb st a f f2 fa w h w2 h2 x2 y2 n l d で決め打ち。未知のプロパティは落ちる
|
||||
- 2026-09-18-kle-roundtrip で「未確認」とした「シリアライザは既知プロパティしか書かない」はこれで確定
|
||||
- serial.js:105 defaultKeyProps で profile は "cap appearance" に分類されている
|
||||
- serial.js:176-178 メタデータは keyboard.meta の全キーをループで書き出す。未知のメタデータキーも保存時に残る(今回の決定では使わない)
|
||||
- kb.js:78 rotateStep = 15。kb.js:817 プロパティ入力は ±180 にクランプするだけ。kb.js:1119 キー操作による回転は Math.round で整数度に丸める
|
||||
- kb.css:94 `.ghosted { opacity: 0.5; }`。ゴーストキーは半透明で表示される。analysis の「見た目に影響しない」は誤り
|
||||
@@ -1,453 +0,0 @@
|
||||
# PHYレイヤーとKLE(Keyboard Layout Editor)の互換性分析
|
||||
|
||||
## 概要
|
||||
|
||||
検討中のPHYレイヤー構造とKeyboard Layout Editor(KLE)の互換性について詳細に分析した結果をまとめます。KLEをベースとしたフロントエンドの実現可能性を評価します。
|
||||
|
||||
## KLEデータモデルの特徴
|
||||
|
||||
### KLEで対応可能な主要プロパティ
|
||||
|
||||
KLEの`Key`オブジェクトは以下の構造を持ちます:
|
||||
|
||||
```typescript
|
||||
export class Key {
|
||||
// 位置・サイズ関連
|
||||
x: number; // 絶対位置 (U単位)
|
||||
y: number; // 絶対位置 (U単位)
|
||||
width: number; // サイズ (U単位)
|
||||
height: number; // サイズ (U単位)
|
||||
|
||||
// 回転関連
|
||||
rotation_x: number; // 回転中心X座標
|
||||
rotation_y: number; // 回転中心Y座標
|
||||
rotation_angle: number; // 回転角度
|
||||
|
||||
// 複雑な形状対応
|
||||
x2: number; // 第2矩形の相対位置
|
||||
y2: number; // 第2矩形の相対位置
|
||||
width2: number; // 第2矩形のサイズ
|
||||
height2: number; // 第2矩形のサイズ
|
||||
|
||||
// 表示・装飾関連
|
||||
color: string; // キーキャップ色
|
||||
labels: string[]; // 最大12個のラベル
|
||||
textColor: Array<string | undefined>;
|
||||
textSize: Array<number | undefined>;
|
||||
profile: string; // キープロファイル
|
||||
|
||||
// フラグ類
|
||||
decal: boolean; // デカールモード
|
||||
ghost: boolean; // ゴーストモード
|
||||
stepped: boolean; // ステップキー
|
||||
nub: boolean; // ホーミング突起
|
||||
|
||||
// スイッチ情報
|
||||
sm: string; // スイッチマウント
|
||||
sb: string; // スイッチブランド
|
||||
st: string; // スイッチタイプ
|
||||
}
|
||||
```
|
||||
|
||||
## 互換性マトリックス
|
||||
|
||||
### ✅ 完全対応可能
|
||||
|
||||
| PHYレイヤー機能 | KLEプロパティ | 備考 |
|
||||
|---|---|---|
|
||||
| キー座標(x,y) | `x`, `y` | 1:1対応、U単位 |
|
||||
| キーサイズ(w,h) | `width`, `height` | 1:1対応、U単位 |
|
||||
| 回転角度 | `rotation_angle` | 1:1対応 |
|
||||
| 回転中心 | `rotation_x`, `rotation_y` | 1:1対応 |
|
||||
| 0.25U単位スナップ | - | KLEも小数点座標対応 |
|
||||
| 複雑形状キー | `x2`, `y2`, `width2`, `height2` | ISO Enter等に対応 |
|
||||
| プロファイル設定 | `profile` | SA、DCS、OEM等対応 |
|
||||
|
||||
### ⚠️ 部分対応・要改造
|
||||
|
||||
| PHYレイヤー機能 | KLE対応状況 | 改造内容 |
|
||||
|---|---|---|
|
||||
| クラスター管理 | 非対応 | カスタムメタデータとしてlabelsに格納 |
|
||||
| 複数プロファイル | 一部対応 | メタデータで管理、UI拡張必要 |
|
||||
| ユニットサイズ設定 | 非対応 | グローバル設定として追加実装 |
|
||||
| ビジュアルオフセット | 非対応 | レンダリング層で独自実装 |
|
||||
|
||||
### ❌ 対応困難・KLE制約
|
||||
|
||||
| PHYレイヤー機能 | KLE制約 | 回避策 |
|
||||
|---|---|---|
|
||||
| 15度以外の回転角度 | UI制約(内部は対応可能) | UI改造またはAPI直接操作 |
|
||||
| 階層的クラスター | データモデル非対応 | ゴーストキーによるメタデータ保存 |
|
||||
| クラスター別回転中心 | 直接非対応 | 各キーの回転中心で代替 |
|
||||
|
||||
## 具体的な実装戦略
|
||||
|
||||
### 1. KLEベースデータ変換
|
||||
|
||||
PHYレイヤーからKLE形式への変換:
|
||||
|
||||
```typescript
|
||||
// PHYレイヤー → KLE変換例
|
||||
function convertToKLE(phyData: PHYLayer): KLEData {
|
||||
return phyData.elements.map(element => {
|
||||
if (element.type === 'key') {
|
||||
return {
|
||||
x: element.position.x,
|
||||
y: element.position.y,
|
||||
width: element.size.width,
|
||||
height: element.size.height,
|
||||
rotation_x: element.position.rotationOrigin?.x || 0,
|
||||
rotation_y: element.position.rotationOrigin?.y || 0,
|
||||
rotation_angle: element.position.rotation || 0,
|
||||
// クラスター情報をlabelsの特定位置に格納
|
||||
labels: [element.clusterId || '', '', '', '', '', '', '', '', '', '', '', ''],
|
||||
// プロファイル情報
|
||||
profile: element.profile || 'OEM'
|
||||
};
|
||||
}
|
||||
// クラスターの場合は展開処理
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 2. KLE UI改造点
|
||||
|
||||
#### 必要な改造項目
|
||||
|
||||
1. **クラスター管理UI**
|
||||
- クラスター作成・編集パネル
|
||||
- クラスター単位の選択・移動
|
||||
- クラスター階層表示
|
||||
|
||||
2. **プロファイル管理**
|
||||
- 複数プロファイル切り替えUI
|
||||
- プロファイル別ユニットサイズ設定
|
||||
|
||||
3. **メタデータ拡張**
|
||||
- PHYレイヤー固有情報の保存
|
||||
- 互換性情報の管理
|
||||
|
||||
#### UI改造の難易度
|
||||
|
||||
- **低難易度**: 既存プロパティパネルの拡張
|
||||
- **中難易度**: クラスター管理機能の追加
|
||||
- **高難易度**: レンダリングエンジンの改造
|
||||
|
||||
### 3. 制限事項と回避策
|
||||
|
||||
#### 現在のKLE制約
|
||||
|
||||
1. **データモデル制約**
|
||||
- フラットな配列構造のみ対応
|
||||
- 階層的クラスター管理は困難
|
||||
|
||||
2. **レンダリング制約**
|
||||
- HTMLベースのレンダリング
|
||||
- 複雑な視覚効果には限界
|
||||
|
||||
#### 回避策
|
||||
|
||||
1. **段階的対応**
|
||||
- Phase 1: 基本的な位置・サイズ・回転のみ対応
|
||||
- Phase 2: クラスター管理機能追加
|
||||
- Phase 3: 高度なビジュアル機能
|
||||
|
||||
2. **ハイブリッドアプローチ**
|
||||
- KLEをベースエディタとして使用
|
||||
- 独自機能は別パネルで管理
|
||||
- 最終出力時にPHYレイヤー形式で統合
|
||||
|
||||
## 推奨実装アプローチ
|
||||
|
||||
### フェーズ1: KLE最小改造アプローチ(推奨)
|
||||
|
||||
1. **KLEの既存機能をそのまま活用**
|
||||
- 基本的な配置・サイズ・回転
|
||||
- プロファイル設定
|
||||
- 色・ラベル設定
|
||||
|
||||
2. **最小限の拡張**
|
||||
- カスタムメタデータ保存機能
|
||||
- PHYレイヤー形式エクスポート
|
||||
|
||||
3. **段階的機能追加**
|
||||
- クラスター管理(Phase 2)
|
||||
- 高度なビジュアル機能(Phase 3)
|
||||
|
||||
### 実装優先順位
|
||||
|
||||
1. **高優先度** - 即座に対応可能
|
||||
- ✅ 基本位置・サイズ・回転
|
||||
- ✅ プロファイル設定
|
||||
- ✅ KLE→PHYレイヤー変換
|
||||
|
||||
2. **中優先度** - 改造が必要だが実現可能
|
||||
- ⚠️ 簡易クラスター管理
|
||||
- ⚠️ 複数プロファイル対応
|
||||
- ⚠️ カスタムユニットサイズ
|
||||
|
||||
3. **低優先度** - 大規模改造が必要
|
||||
- ❌ 階層的クラスター
|
||||
- ❌ 高度なビジュアルオフセット
|
||||
- ❌ リアルタイム3Dプレビュー
|
||||
|
||||
## 結論
|
||||
|
||||
**KLEをベースとしたPHYレイヤーエディタの実現は十分可能**です。
|
||||
|
||||
### 対応可能な機能(約80%)
|
||||
|
||||
- 基本的なキー配置・サイズ・回転
|
||||
- 複雑形状キー(ISO Enter等)
|
||||
- プロファイル管理
|
||||
- 基本的なクラスター管理
|
||||
|
||||
### 改造が必要な機能(約15%)
|
||||
|
||||
- 高度なクラスター管理
|
||||
- 複数プロファイル切り替え
|
||||
- カスタムユニットサイズ
|
||||
|
||||
### 対応困難な機能(約5%)
|
||||
|
||||
- 完全階層的クラスター構造
|
||||
- 高度なビジュアルエフェクト
|
||||
|
||||
PHYレイヤーの主要機能はKLEで十分実現可能であり、段階的な改造により完全対応も期待できます。
|
||||
|
||||
## 階層型クラスターの実装戦略
|
||||
|
||||
### 問題提起
|
||||
|
||||
ユーザーからの要求:
|
||||
|
||||
- **階層型クラスター管理は絶対に実装したい**
|
||||
- 出力時はフラット化してもOK(階層情報を何らかの形で記録できれば)
|
||||
- ラベルに突っ込むと見づらくなる懸念
|
||||
- **ゴーストキーによる情報保存**のアイデア
|
||||
|
||||
### ゴーストキーアプローチの詳細設計
|
||||
|
||||
#### 基本概念
|
||||
|
||||
クラスター情報を`ghost: true`のキーとして保存し、実際のキーレンダリングには影響を与えずにメタデータとして扱う。
|
||||
|
||||
```typescript
|
||||
// クラスター情報保存用ゴーストキー
|
||||
type ClusterGhostKey = {
|
||||
ghost: true,
|
||||
decal: false,
|
||||
x: number, // クラスター範囲の左上角
|
||||
y: number, // クラスター範囲の左上角
|
||||
width: number, // クラスター範囲の幅
|
||||
height: number, // クラスター範囲の高さ
|
||||
rotation_x: number, // クラスター回転中心
|
||||
rotation_y: number, // クラスター回転中心
|
||||
rotation_angle: number, // クラスター回転角度
|
||||
color: "#transparent", // 透明
|
||||
labels: [
|
||||
"CLUSTER_META", // [0] 識別子
|
||||
clusterId, // [1] クラスターID
|
||||
parentClusterId, // [2] 親クラスターID(階層用)
|
||||
clusterName, // [3] クラスター名
|
||||
JSON.stringify(metadata), // [4] その他メタデータ
|
||||
"", "", "", "", "", "", "" // [5-11] 予約
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 階層構造の表現
|
||||
|
||||
```typescript
|
||||
// 例:分割キーボードの左右ブロック + 各ブロック内のキークラスター
|
||||
const clusterHierarchy = {
|
||||
"root": {
|
||||
children: ["left_block", "right_block"]
|
||||
},
|
||||
"left_block": {
|
||||
parent: "root",
|
||||
children: ["left_alpha", "left_modifiers"]
|
||||
},
|
||||
"right_block": {
|
||||
parent: "root",
|
||||
children: ["right_alpha", "right_modifiers"]
|
||||
},
|
||||
"left_alpha": {
|
||||
parent: "left_block",
|
||||
keyIds: ["Q", "W", "E", "R", "T", "A", "S", "D", "F", "G", ...]
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### KLE UI拡張による階層管理
|
||||
|
||||
1. **クラスター管理パネル**
|
||||
|
||||
```html
|
||||
<!-- 新しいサイドパネル -->
|
||||
<div id="cluster-manager">
|
||||
<tree-view>
|
||||
<cluster-node id="root">
|
||||
<cluster-node id="left_block">
|
||||
<cluster-node id="left_alpha" />
|
||||
<cluster-node id="left_modifiers" />
|
||||
</cluster-node>
|
||||
<cluster-node id="right_block">
|
||||
<cluster-node id="right_alpha" />
|
||||
<cluster-node id="right_modifiers" />
|
||||
</cluster-node>
|
||||
</cluster-node>
|
||||
</tree-view>
|
||||
</div>
|
||||
```
|
||||
|
||||
2. **クラスター操作機能**
|
||||
- ドラッグ&ドロップによるキーのクラスター移動
|
||||
- 階層ツリーでのクラスター選択
|
||||
- クラスター単位での一括変換(移動・回転・リサイズ)
|
||||
- クラスター単位でのコピー&ペースト
|
||||
|
||||
### データフロー設計
|
||||
|
||||
#### 1. 編集時(UI → 内部データ)
|
||||
|
||||
```javascript
|
||||
// クラスター作成時
|
||||
function createCluster(name, parentId, selectedKeys) {
|
||||
const clusterId = generateUniqueId();
|
||||
|
||||
// ゴーストキーを作成
|
||||
const ghostKey = createClusterGhostKey({
|
||||
id: clusterId,
|
||||
name: name,
|
||||
parentId: parentId,
|
||||
boundingBox: calculateBoundingBox(selectedKeys)
|
||||
});
|
||||
|
||||
// 選択されたキーにクラスターIDを付与
|
||||
selectedKeys.forEach(key => {
|
||||
key.clusterId = clusterId; // カスタムプロパティとして追加
|
||||
});
|
||||
|
||||
// KLEのキー配列に追加
|
||||
scope.keys().push(ghostKey);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 保存時(内部データ → KLE JSON)
|
||||
|
||||
```javascript
|
||||
function serializeWithClusters(keyboard) {
|
||||
const kleData = serial.serialize(keyboard);
|
||||
|
||||
// 通常のキーとゴーストキーが混在した状態で保存
|
||||
// ゴーストキーがクラスター情報を保持
|
||||
return kleData;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 読み込み時(KLE JSON → 内部データ)
|
||||
|
||||
```javascript
|
||||
function deserializeWithClusters(kleData) {
|
||||
const keyboard = serial.deserialize(kleData);
|
||||
|
||||
// ゴーストキーからクラスター情報を復元
|
||||
const clusterMap = new Map();
|
||||
const normalKeys = [];
|
||||
|
||||
keyboard.keys.forEach(key => {
|
||||
if (key.ghost && key.labels[0] === 'CLUSTER_META') {
|
||||
const clusterId = key.labels[1];
|
||||
const parentId = key.labels[2];
|
||||
const metadata = JSON.parse(key.labels[4]);
|
||||
|
||||
clusterMap.set(clusterId, {
|
||||
ghostKey: key,
|
||||
parentId: parentId,
|
||||
metadata: metadata,
|
||||
children: new Set()
|
||||
});
|
||||
} else {
|
||||
normalKeys.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
// 階層構造を再構築
|
||||
buildClusterHierarchy(clusterMap);
|
||||
|
||||
return { keyboard: { ...keyboard, keys: normalKeys }, clusters: clusterMap };
|
||||
}
|
||||
```
|
||||
|
||||
### 15度回転制約について
|
||||
|
||||
#### 現在の制約の詳細
|
||||
|
||||
KLEのソースコード分析結果:
|
||||
|
||||
```javascript
|
||||
$scope.rotateStep = 15; // UI上のデフォルト回転ステップ
|
||||
|
||||
// 実際の制約チェック(kb.js内)
|
||||
rotation_angle: function() {
|
||||
return Math.max(-180, Math.min(180, value)); // ±180度の範囲制約のみ
|
||||
}
|
||||
```
|
||||
|
||||
**つまり:**
|
||||
|
||||
- **UIでは15度ステップで回転操作**(ユーザビリティのため)
|
||||
- **内部データには制約なし**(任意の角度を保存可能)
|
||||
- **JSON出力も制約なし**(小数点を含む任意の角度値)
|
||||
|
||||
#### PHYレイヤーでの対応
|
||||
|
||||
```javascript
|
||||
// PHY → KLE変換時に任意角度をそのまま渡す
|
||||
function convertRotation(phyRotation) {
|
||||
return phyRotation; // 制約なし、そのまま変換
|
||||
}
|
||||
|
||||
// KLE UI拡張で任意角度入力を許可
|
||||
function enableCustomRotation() {
|
||||
// 既存の15度ステップに加えて、直接入力機能を追加
|
||||
scope.customRotateStep = 1; // 1度ステップ
|
||||
scope.allowCustomAngle = true; // 任意角度入力許可
|
||||
}
|
||||
```
|
||||
|
||||
## 実装優先度の再評価
|
||||
|
||||
### 修正版優先度
|
||||
|
||||
1. **最高優先度** - 即座に対応可能
|
||||
- ✅ 基本位置・サイズ・回転(任意角度対応)
|
||||
- ✅ プロファイル設定
|
||||
- ✅ KLE→PHYレイヤー変換
|
||||
|
||||
2. **高優先度** - ゴーストキーアプローチで対応
|
||||
- 🔶 階層型クラスター管理(ゴーストキー方式)
|
||||
- 🔶 クラスターメタデータ保存
|
||||
- 🔶 クラスター単位操作UI
|
||||
|
||||
3. **中優先度** - UI改造が必要
|
||||
- ⚠️ 複数プロファイル対応
|
||||
- ⚠️ カスタムユニットサイズ
|
||||
- ⚠️ 15度以外の回転角度UI対応
|
||||
|
||||
4. **低優先度** - 大規模改造が必要
|
||||
- ❌ 高度なビジュアルオフセット
|
||||
- ❌ リアルタイム3Dプレビュー
|
||||
|
||||
### 実装結論
|
||||
|
||||
**ゴーストキーアプローチにより、階層型クラスター管理は実現可能**です。この方法なら:
|
||||
|
||||
- KLEの既存データモデルを活用
|
||||
- 見た目に影響しない形でメタデータ保存
|
||||
- 階層構造の完全な再現が可能
|
||||
- 段階的な実装が可能
|
||||
|
||||
階層型クラスターが「絶対実装したい」要求に対して、技術的に実現可能な解決策を提供できます。
|
||||
@@ -1,249 +0,0 @@
|
||||
# KLE JSON 互換性設計
|
||||
|
||||
## 双方向変換戦略
|
||||
|
||||
### Beyond KLE → KLE JSON
|
||||
|
||||
#### 1. フラット化プロセス
|
||||
|
||||
```typescript
|
||||
interface ConversionOptions {
|
||||
preserveIds: boolean; // IDを保持するか
|
||||
preserveClusterInfo: boolean; // クラスター情報を保持するか
|
||||
idEmbedMethod: 'metadata' | 'label' | 'comment';
|
||||
}
|
||||
|
||||
function convertToKLE(beyondKle: PHYLayer, options: ConversionOptions): KLELayout {
|
||||
const flatKeys = flattenElements(beyondKle.elements);
|
||||
return {
|
||||
name: beyondKle.metadata.name,
|
||||
keys: flatKeys.map(key => convertKey(key, options))
|
||||
};
|
||||
}
|
||||
|
||||
function flattenElements(elements: Element[], parentTransform?: Transform): FlatKey[] {
|
||||
const result: FlatKey[] = [];
|
||||
|
||||
for (const element of elements) {
|
||||
if (element.type === 'key') {
|
||||
const absolutePos = applyTransform(element.position, parentTransform);
|
||||
result.push({
|
||||
...element,
|
||||
absolutePosition: absolutePos,
|
||||
clusterPath: getClusterPath(element)
|
||||
});
|
||||
} else if (element.type === 'cluster') {
|
||||
const clusterTransform = combineTransforms(parentTransform, element.position);
|
||||
result.push(...flattenElements(element.elements, clusterTransform));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. ID保持方法の比較
|
||||
|
||||
| 方法 | メリット | デメリット | KLE互換性 |
|
||||
|------|----------|------------|-----------|
|
||||
| `_beyondkle` メタデータ | 構造化された情報保持 | KLEパーサーが無視する可能性 | △ |
|
||||
| ラベル9番目位置 | 確実に保存される | 表示されてしまう | ○ |
|
||||
| コメントフィールド | 非表示 | 標準的でない | △ |
|
||||
|
||||
#### 3. 推奨アプローチ: ハイブリッド方式
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Split Layout (Generated by Beyond KLE)",
|
||||
"_beyondkle": {
|
||||
"version": "1.0.0",
|
||||
"sourceFile": "layout.beyond.json",
|
||||
"conversionSettings": {
|
||||
"flattenClusters": true,
|
||||
"preserveRotations": true
|
||||
}
|
||||
},
|
||||
"keys": [
|
||||
{
|
||||
"x": 0, "y": 0, "w": 1, "h": 1,
|
||||
"t": "#000000\n\n\n\n\n\n\n\n\nk00",
|
||||
"_beyondkle": {
|
||||
"id": "k00",
|
||||
"clusterPath": ["left_half", "left_main"],
|
||||
"relativePosition": { "x": 0, "y": 0 },
|
||||
"absolutePosition": { "x": 0, "y": 0 }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### KLE JSON → Beyond KLE
|
||||
|
||||
#### 1. ID復元戦略
|
||||
|
||||
```typescript
|
||||
function convertFromKLE(kleLayout: KLELayout): PHYLayer {
|
||||
// 1. メタデータからBeyond KLE情報を取得
|
||||
const beyondKleInfo = kleLayout._beyondkle;
|
||||
|
||||
if (beyondKleInfo?.sourceFile) {
|
||||
// 元のBeyond KLEファイルが指定されている場合
|
||||
return reconstructFromSource(kleLayout, beyondKleInfo);
|
||||
}
|
||||
|
||||
// 2. キーからIDとクラスター情報を抽出
|
||||
const keys = kleLayout.keys.map(kleKey => {
|
||||
const id = extractId(kleKey);
|
||||
const clusterInfo = extractClusterInfo(kleKey);
|
||||
|
||||
return {
|
||||
type: 'key' as const,
|
||||
id: id || generateId(kleKey),
|
||||
profile: 'default',
|
||||
position: { x: kleKey.x, y: kleKey.y },
|
||||
size: { width: kleKey.w || 1, height: kleKey.h || 1 },
|
||||
metadata: extractMetadata(kleKey)
|
||||
};
|
||||
});
|
||||
|
||||
// 3. クラスター構造の再構築
|
||||
return reconstructClusters(keys);
|
||||
}
|
||||
|
||||
function extractId(kleKey: any): string | null {
|
||||
// 1. _beyondkle メタデータから
|
||||
if (kleKey._beyondkle?.id) return kleKey._beyondkle.id;
|
||||
|
||||
// 2. ラベル9番目から
|
||||
if (kleKey.labels?.[8]) return kleKey.labels[8];
|
||||
|
||||
// 3. t文字列の解析
|
||||
const tMatch = kleKey.t?.match(/\n([a-zA-Z0-9_]+)$/);
|
||||
if (tMatch) return tMatch[1];
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. クラスター再構築アルゴリズム
|
||||
|
||||
```typescript
|
||||
function reconstructClusters(flatKeys: Key[]): Element[] {
|
||||
// クラスター情報が保持されている場合
|
||||
const clusteredKeys = groupByCluster(flatKeys);
|
||||
|
||||
if (Object.keys(clusteredKeys).length > 1) {
|
||||
return Object.entries(clusteredKeys).map(([clusterName, keys]) => ({
|
||||
type: 'cluster' as const,
|
||||
id: clusterName,
|
||||
name: clusterName,
|
||||
position: calculateClusterOrigin(keys),
|
||||
elements: keys.map(key => ({
|
||||
...key,
|
||||
position: makeRelativeToCluster(key.position, clusterOrigin)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
// クラスター情報がない場合は自動推定
|
||||
return autoDetectClusters(flatKeys);
|
||||
}
|
||||
|
||||
function autoDetectClusters(keys: Key[]): Element[] {
|
||||
// 座標の近さとレイアウトパターンからクラスターを推定
|
||||
// 1. 連続する矩形領域の検出
|
||||
// 2. 回転角度の類似性
|
||||
// 3. 一定の間隔での分離
|
||||
|
||||
const clusters = detectSpatialClusters(keys);
|
||||
|
||||
if (clusters.length === 1) {
|
||||
// 単一クラスターの場合はフラットに
|
||||
return keys;
|
||||
}
|
||||
|
||||
return clusters;
|
||||
}
|
||||
```
|
||||
|
||||
## 実装における考慮事項
|
||||
|
||||
### 1. バージョン管理
|
||||
|
||||
```json
|
||||
{
|
||||
"_beyondkle": {
|
||||
"version": "1.0.0",
|
||||
"generator": "beyond-kle-converter",
|
||||
"timestamp": "2025-11-09T12:00:00Z",
|
||||
"preservedData": {
|
||||
"clusters": true,
|
||||
"rotations": true,
|
||||
"profiles": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 設定可能な変換オプション
|
||||
|
||||
```typescript
|
||||
interface ConversionSettings {
|
||||
// ID保持方法
|
||||
idPreservation: 'metadata' | 'label' | 'both' | 'none';
|
||||
|
||||
// クラスター処理
|
||||
clusterHandling: 'preserve' | 'flatten' | 'auto';
|
||||
|
||||
// 回転の処理
|
||||
rotationHandling: 'preserve' | 'flatten';
|
||||
|
||||
// プロファイル情報
|
||||
includeProfiles: boolean;
|
||||
|
||||
// 互換性レベル
|
||||
compatibilityLevel: 'strict' | 'extended' | 'beyondkle';
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 往復変換の整合性保証
|
||||
|
||||
```typescript
|
||||
// テストケース
|
||||
const originalBeyondKLE = loadBeyondKLE('test.beyond.json');
|
||||
const kleJson = convertToKLE(originalBeyondKLE, settings);
|
||||
const reconstructed = convertFromKLE(kleJson);
|
||||
|
||||
// 構造的等価性の検証
|
||||
assert(structurallyEquivalent(originalBeyondKLE, reconstructed));
|
||||
```
|
||||
|
||||
## ワークフロー例
|
||||
|
||||
### 1. Beyond KLE → 外部ツール → Beyond KLE
|
||||
|
||||
```bash
|
||||
# Beyond KLE → KLE JSON (ID付き)
|
||||
beyond-kle export --format=kle --preserve-ids layout.beyond.json
|
||||
|
||||
# 外部ツール(KLE等)で編集
|
||||
# ...
|
||||
|
||||
# KLE JSON → Beyond KLE (構造復元)
|
||||
beyond-kle import --format=kle --auto-cluster edited.kle.json
|
||||
```
|
||||
|
||||
### 2. 段階的移行
|
||||
|
||||
```bash
|
||||
# 既存のKLE JSON → Beyond KLE
|
||||
beyond-kle import legacy.kle.json
|
||||
|
||||
# Beyond KLE で編集・管理
|
||||
|
||||
# 必要に応じてKLE JSONで共有
|
||||
beyond-kle export --format=kle-compatible project.beyond.json
|
||||
```
|
||||
|
||||
この設計により、Beyond KLEの高度な機能を使いながらも、既存のKLEエコシステムとの互換性を保てると思いますが、いかがでしょうか?
|
||||
Reference in New Issue
Block a user