idx: コミュニティレイアウトの並びが取捨だけでは得られない場合を記録する
QMKの実データで、コミュニティレイアウトとの173組のうち30組(すべてISO系)が `LAYOUT_all` の並びの部分列にならない。同じマトリクス位置がレイアウトによって 別の物理キーになり、残ったキーの読み順が変わるため。 埋め方は未定として未確認事項に残す。 Journal: journal/2026-09-21-community-layout-order.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01246a5KFLmwcRXriZvX8fdJ
This commit is contained in:
+10
-7
@@ -53,7 +53,10 @@ row / col 自体がELCの正本ではなく出力時の派生情報である。
|
||||
|
||||
PHYがレイアウトバリアントを持つ場合も、キー番号の空間は1つとする。バリアントごとにIDXを分けない。
|
||||
キー番号は公開インターフェースであり、どの空間のものかを断らずに解釈できなければならない。
|
||||
特定のレイアウト向けの並びは、選んだバリアントに属さないキーを落として得られる派生情報として扱う [推測:P-1]。
|
||||
特定のレイアウト向けの並びは、選んだバリアントに属さないキーを落として得る。
|
||||
ただしこれだけでは足りない場合がある。QMKのコミュニティレイアウトでは、同じマトリクス位置が
|
||||
レイアウトによって別の物理キーになり(ANSIのバックスラッシュとISOの `#~`)、残ったキーの読み順が変わる。
|
||||
ISO系で引数順が一致しない。
|
||||
|
||||
排他なキー同士は、同じキー番号を共有できる。`order` の要素にキーIDの配列を書くと、
|
||||
その位置の番号を配列内のキーで共有する。
|
||||
@@ -109,10 +112,10 @@ type IDX = {
|
||||
|
||||
## 未確認事項
|
||||
|
||||
### P-1 特定レイアウト向けの並びが既存の慣習と一致するか
|
||||
### P-1 一致しないコミュニティレイアウトの並びをどう与えるか
|
||||
|
||||
- 位置づけ: 本文「レイアウトバリアント」が前提にしている
|
||||
- 現状の認識: `order` からキーを落として得た並びが、QMKのコミュニティレイアウト(`LAYOUT_60_ansi` など)が
|
||||
要求する引数順と一致するかを確認していない。一致しないなら、レイアウトごとの並びを別に持つ必要が出る
|
||||
- 格上げ条件: 実在のキーボードの `LAYOUT_all` とコミュニティレイアウトのマクロを読み、
|
||||
前者から後者がキーの取捨だけで得られるかを確認する
|
||||
- 位置づけ: 本文「レイアウトバリアント」の残余。一致しない場合があることは確認済み
|
||||
- 現状の認識: QMKの実データで、コミュニティレイアウトとの173組のうち30組(すべてISO系)が
|
||||
`order` からの取捨では得られない。並べ替えの規則を与えるか、レイアウトごとの並びを持つかが未定。
|
||||
後者は「レイアウトごとに別のIDXを持つ」として一度落としている
|
||||
- 格上げ条件: 埋め方をユーザーが決める
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# コミュニティレイアウトの引数順がキーの取捨だけで得られるか
|
||||
|
||||
IDX P-1 の検証。
|
||||
|
||||
## 結論
|
||||
|
||||
得られない場合がある。ISO系のコミュニティレイアウトで、`LAYOUT_all` の並びからキーを落としただけでは
|
||||
引数順が合わない。QMKの実データで 173組中30組。全部ISO系だった。
|
||||
|
||||
原因は、同じマトリクス位置が、レイアウトによって別の物理キーになるため。
|
||||
|
||||
| レイアウト | matrix (2,12) の位置とサイズ |
|
||||
| --- | --- |
|
||||
| `LAYOUT_all` | (13.5, 1) 1.5U — ANSIのバックスラッシュ |
|
||||
| `LAYOUT_60_iso` | (12.75, 2) 1U — ISOの `#~` |
|
||||
|
||||
キーが物理的に別の場所へ移るので、読み順での位置も変わる。`LAYOUT_all` では2行目に、
|
||||
ISOでは3行目に現れる。取捨では並べ替えられない。
|
||||
|
||||
PHYの用語では、この2つは別のキーノードである(サイズや形が変わるキーは別のIDを持つ)。
|
||||
「選んだバリアントに属さないキーを落とす」だけでは、残ったキーの並びが変わらないので、
|
||||
コミュニティレイアウトが要求する順にならない。
|
||||
|
||||
## 調べ方
|
||||
|
||||
QMKのキーボード一覧3759件から400件を無作為抽出し、`keyboards.qmk.fm` の info.json を取得した。
|
||||
`LAYOUT_all`(無ければ `LAYOUT`)を基準に、同じボードの他のレイアウトと比較した。
|
||||
|
||||
| 対象 | 件数 |
|
||||
| --- | --- |
|
||||
| 取得したボード | 400 |
|
||||
| 複数のレイアウトを持つボード | 146 |
|
||||
| community_layouts に載っているレイアウトとの組 | 173 |
|
||||
| うち基準の並びの部分列になっている | 143 |
|
||||
| うち部分列にならない | 30 (すべてISO系) |
|
||||
| うち同じマトリクス位置で形状が違うキーを含む | 172 |
|
||||
|
||||
部分列にならない例: alf/dc60, argyle, artifact/stratum, cannonkeys/brutalv2_65, era/linx3/n87,
|
||||
kapcave/gskt00, karlb/kbic65, kbdfans/kbd67/rev2, lucid/phantom_solder, phdesign/ph60/multi,
|
||||
rmi_kb/tkl_ff/v2 の各ISOレイアウト。
|
||||
|
||||
era/linx3/n87 は `LAYOUT_tkl_ansi` に `LAYOUT_all` へ無いマトリクス位置が1つある。
|
||||
QMKの `LAYOUT_all` は全バリアントの重ね合わせではなく、それ自体が1つの選択でしかない
|
||||
(1つのマトリクス位置を2回書けないため)。PHYが全バリアントを同時に持つのとは別物である。
|
||||
|
||||
## 172/173 が「形状違い」を含むことについて
|
||||
|
||||
ANSI系のレイアウトでも、Backspaceが2Uか1U×2か、右Shiftの幅、Tsanganの下段など、
|
||||
同じマトリクス位置のキーの形が変わる組がほとんどである。これらは並び順は保たれるが、
|
||||
PHY上は別のキーノードになる。IDXが「キーIDの並び」で番号を持つ以上、
|
||||
落とすだけでなく差し替えが要ることは変わらない。
|
||||
|
||||
## 検証スクリプト
|
||||
|
||||
```py
|
||||
# QMKのinfo.jsonを無作為抽出で取得する。qmk_list.json は
|
||||
# curl -o qmk_list.json https://keyboards.qmk.fm/v1/keyboard_list.json
|
||||
import json, random, urllib.request, concurrent.futures, os
|
||||
kbs=json.load(open('qmk_list.json'))['keyboards']
|
||||
random.seed(42)
|
||||
os.makedirs('qmk', exist_ok=True)
|
||||
def fetch(kb):
|
||||
p='qmk/'+kb.replace('/','__')+'.json'
|
||||
if os.path.exists(p): return True
|
||||
try:
|
||||
with urllib.request.urlopen(f'https://keyboards.qmk.fm/v1/keyboards/{kb}/info.json', timeout=30) as r:
|
||||
open(p,'wb').write(r.read())
|
||||
return True
|
||||
except Exception as e:
|
||||
return str(e)[:60]
|
||||
with concurrent.futures.ThreadPoolExecutor(8) as ex:
|
||||
res=list(ex.map(fetch, random.sample(kbs, 400)))
|
||||
print('fetched', sum(1 for r in res if r is True), 'failed', sum(1 for r in res if r is not True))
|
||||
```
|
||||
|
||||
```py
|
||||
# LAYOUT_all の並びから、他のレイアウトがキーの取捨だけで得られるか
|
||||
import json, glob, collections
|
||||
st=collections.Counter(); rows=[]
|
||||
for f in glob.glob('qmk/*.json'):
|
||||
d=json.load(open(f,encoding='utf-8'))
|
||||
for kb,v in (d.get('keyboards') or {}).items():
|
||||
lays=v.get('layouts') or {}
|
||||
if not lays: continue
|
||||
st['boards']+=1
|
||||
if len(lays)>1: st['multi-layout boards']+=1
|
||||
base=next((n for n in ('LAYOUT_all','LAYOUT') if n in lays), None)
|
||||
if base is None or len(lays)<2: continue
|
||||
shape=lambda k:(k.get('x'),k.get('y'),k.get('w',1),k.get('h',1),k.get('r',0))
|
||||
shA={}
|
||||
for k in lays[base]['layout']: shA.setdefault(tuple(k['matrix']),[]).append(shape(k))
|
||||
A=[tuple(k['matrix']) for k in lays[base]['layout']]
|
||||
for name,l in lays.items():
|
||||
if name==base: continue
|
||||
B=[tuple(k['matrix']) for k in l['layout']]
|
||||
it=iter(A); subseq=all(any(a==b for a in it) for b in B) # 部分列か
|
||||
rows.append(dict(kb=kb, name=name, subseq=subseq,
|
||||
missing=sum(1 for m in B if m not in set(A)),
|
||||
shapediff=sum(1 for k in l['layout']
|
||||
if tuple(k['matrix']) in shA and shape(k) not in shA[tuple(k['matrix'])]),
|
||||
community=name.replace('LAYOUT_','') in (v.get('community_layouts') or [])))
|
||||
print(st)
|
||||
comm=[r for r in rows if r['community']]
|
||||
print('community layout pairs:', len(comm))
|
||||
print(' 部分列になる:', sum(1 for r in comm if r['subseq']))
|
||||
print(' 部分列にならない:', sum(1 for r in comm if not r['subseq']))
|
||||
print(' baseに無いキーを含む:', sum(1 for r in comm if r['missing']))
|
||||
print(' 同じmatrixで形状が違うキーを含む:', sum(1 for r in comm if r['shapediff']))
|
||||
for r in comm:
|
||||
if not r['subseq']: print(' NG', r['kb'], r['name'])
|
||||
```
|
||||
Reference in New Issue
Block a user