Keyboard Layout Editor JSONの使い方

この記事はキーボード #2 Advent Calendar 2020の8日目の記事です。

昨日は@F_YUUCHIさんの2020年にやったこと/できなかったこと&これからの話でした。

adventar.org

TL;DR

  • キーボードの配列を表現するのに Keyboard Layout Editorで使われているデータ形式が便利です
  • この記事では、そのデータ形式について述べ、デシリアライズとレイアウトを行います
  • デシリアライズは ijprest/kle-serial を使用すると簡単に行なえます

概要

Keyboard Layout Editor(以下、KLE)ではキーの位置や角度を自由に移動させてレイアウトを検討することができます。

View post on imgur.com
imgur.com

レイアウトをJSONとしてダウンロードすることができます。 そのJSONをアップロードすればそのレイアウトを再度編集することができて便利です。

ユースケース例

PCB上にキーを自動で配置する

KiCadでキーを自動で配置したいと思ったときに、入力ファイルとしてKLE JSONを使うことができます。

View post on imgur.com
imgur.com

yskoht.hatenablog.com

タイピングゲームのキーボードをカスタマイズする

KLE JSONを変更・編集できれば、タイピングゲームのキーボードをユーザーの好きにカスタマイズできます。

View post on imgur.com
imgur.com

yskoht.hatenablog.com

データ形式

データ形式の仕様は以下で説明されています。

github.com

この記事ではいくつかの例を通してデータ形式の主要な部分を理解したいと思います。

配列

{
  ["a", "b"],
  ["c", "d"]
}

f:id:yskoht:20201205124437p:plain:w480

1つ目の配列は y = 0, 2つ目の配列は y = 1, ... と配列ごとに行がインクリメントされます。各配列の1つ目の要素は x = 0, 2つ目の要素は x = 1, ... と列がインクリメントされます。

各キーの x, y は以下のようになっています。

a: { y: 0, x: 0 }
b: { y: 0, x: 1 }
c: { y: 1, x: 0 }
d: { y: 1, x: 1 }

位置( x, y )

{
  ["a", {x:1,y:0.5}, "b"],
  ["c", {y:-0.5},"d", {y:1},"e"],
  ["f"]
}

f:id:yskoht:20201205124335p:plain:w480

x, y に指定した値を、現在の x, y に足すことができます。

各キーの x, y は以下のようになっています。 x の値は要素ごとにインクリメントされ、配列ごとにリセットされます。 y の値は配列ごとにインクリメントされ、状態は後続のキーに保持されます。

a: { y: 0, x: 0 }
b: { y: 0.5, x: 2 } # 元の値 { y: 0, x: 1 } にオプションに指定した値が足されている
c: { y: 1.5, x: 0 } # y はインクリメントされる。x は0にリセットされる
d: { y: 1, x: 1 }   # y -= 0.5
e: { y: 2, x: 2 }   # y += 1
f: { y: 3, x: 0 }

幅/高さ ( w, h )

{
  ["a", {w:1.5,h:1.5}, "b"],
  [{w:2,h:2}, "c", {h:2}, "d"],
  ["e"]
}

f:id:yskoht:20201205131707p:plain:w480

キーの幅/高さを指定できます。xには wが加算されるので、隣のキーとは重なりませんが、 yはそのままなので上下のキーと重なってしまいます。 重ならないようにするにはyを指定する必要があります。

oddly-shaped keys ( x2, y2, w2, h2 )

ISO Enterのようなキーを表現できます。

{
  [{x:0.25,w:1.25,h:2,w2:1.5,h2:1,x2:-0.25},""]
}

f:id:yskoht:20201205133101p:plain:w480

これは以下のように、赤と青の2つのキーの重なりで表現されています。

f:id:yskoht:20201205133509p:plain

赤のキーは x:0.25, w:1.25, h:2が指定されており、青のキーは w2:1.5, h2:1, x2:-0.25が指定されています。青のキーに x2:-0.25 が指定されているのは x:0.25 で加えられたオフセットを戻すためです。

角度 ( r, rx, ry )

※この項目はドキュメントに書かれてないので、自分で調べた範囲のことを書いています。

キーの傾きを表現します。

{
  [{r:15},""]
}

f:id:yskoht:20201205134355p:plain:w480

r でキーの角度(度)を指定します。 rx, ry は回転の原点です。 transform-origin: rx, ry;を指定し、transform: rotate(r); することを想定されているように思います。

transform-origin - CSS: カスケーディングスタイルシート | MDN

f:id:yskoht:20201205141047p:plain:w480

以下のような仕様があるようです。

  • rx, ry はデフォルト 0
  • rは各配列の先頭にしか指定できない
  • r, rx, ryは後続のキーにも影響する
  • rx または ryが指定されると xrxyry にリセットされる

デシリアライズ

KLE JSONをデシリアライズし、各キーのx, y, r, rx, ry, w, hなどを求めます。

オープンソースライブラリを使う

以下のライブラリを使うとデシリアライズが簡単にできます

github.com

const kle = require('@ijprest/kle-serial')

const keyboard = kle.Serial.deserialize([
  ["a", "b"],
])

console.log(JSON.stringify(keyboard))

出力

{
  "meta": {
    "author": "",
    "backcolor": "#eeeeee",
    "background": null,
    "name": "",
    "notes": "",
    "radii": "",
    "switchBrand": "",
    "switchMount": "",
    "switchType": ""
  },
  "keys": [
    {
      "color": "#cccccc",
      "labels": [
        "a"
      ],
      "textColor": [],
      "textSize": [],
      "default": {
        "textColor": "#000000",
        "textSize": 3
      },
      "x": 0,
      "y": 0,
      "width": 1,
      "height": 1,
      "x2": 0,
      "y2": 0,
      "width2": 1,
      "height2": 1,
      "rotation_x": 0,
      "rotation_y": 0,
      "rotation_angle": 0,
      "decal": false,
      "ghost": false,
      "stepped": false,
      "nub": false,
      "profile": "",
      "sm": "",
      "sb": "",
      "st": ""
    },
    {
      "color": "#cccccc",
      "labels": [
        "b"
      ],
      "textColor": [],
      "textSize": [],
      "default": {
        "textColor": "#000000",
        "textSize": 3
      },
      "x": 1,
      "y": 0,
      "width": 1,
      "height": 1,
      "x2": 0,
      "y2": 0,
      "width2": 1,
      "height2": 1,
      "rotation_x": 0,
      "rotation_y": 0,
      "rotation_angle": 0,
      "decal": false,
      "ghost": false,
      "stepped": false,
      "nub": false,
      "profile": "",
      "sm": "",
      "sb": "",
      "st": ""
    }
  ]
}

現状だとrx, ry が指定されたときに、x, yrx, ryが設定されないようで、以下のPRが出ています。

github.com

自分でデシリアライズしてみる

KiCadのプラグインを作るときに自作したものを貼っておきます。

出力

{'x': 0, 'y': 0, 'w': 1, 'h': 1, 'x2': 0, 'y2': 0, 'w2': 1, 'h2': 1, 'r': 0, 'rx': 0, 'ry': 0, 'label': ['a']}
{'x': 1, 'y': 0, 'w': 1, 'h': 1, 'x2': 0, 'y2': 0, 'w2': 1, 'h2': 1, 'r': 0, 'rx': 0, 'ry': 0, 'label': ['b']}
{'x': 0, 'y': 1, 'w': 1, 'h': 1, 'x2': 0, 'y2': 0, 'w2': 1, 'h2': 1, 'r': 0, 'rx': 0, 'ry': 0, 'label': ['c']}
{'x': 1, 'y': 1, 'w': 1, 'h': 1, 'x2': 0, 'y2': 0, 'w2': 1, 'h2': 1, 'r': 0, 'rx': 0, 'ry': 0, 'label': ['d']}

レイアウト

デシリアライズにより各キーの位置関係と角度がわかったら、実際に描画したくなると思います。

雑にHTMLを出力してみます。ラッパーでtransform: rotate してから left, topで位置を指定します。

const kle = require('@ijprest/kle-serial')

// ErgoDox
const keyboard = kle.Serial.deserialize([
  [{x:3.5},"#\n3",{x:10.5},"*\n8"],
  [{y:-0.875,x:2.5},"@\n2",{x:1},"$\n4",{x:8.5},"&\n7",{x:1},"(\n9"],
  [{y:-0.875,x:5.5},"%\n5",{a:7},"",{x:4.5},"",{a:4},"^\n6"],
  [{y:-0.875,a:7,w:1.5},"",{a:4},"!\n1",{x:14.5},")\n0",{a:7,w:1.5},""],
  [{y:-0.375,x:3.5,a:4},"E",{x:10.5},"I"],
  [{y:-0.875,x:2.5},"W",{x:1},"R",{x:8.5},"U",{x:1},"O"],
  [{y:-0.875,x:5.5},"T",{a:7,h:1.5},"",{x:4.5,h:1.5},"",{a:4},"Y"],
  [{y:-0.875,a:7,w:1.5},"",{a:4},"Q",{x:14.5},"P",{a:7,w:1.5},""],
  [{y:-0.375,x:3.5,a:4},"D",{x:10.5},"K"],
  [{y:-0.875,x:2.5},"S",{x:1},"F",{x:8.5},"J",{x:1},"L"],
  [{y:-0.875,x:5.5},"G",{x:6.5},"H"],
  [{y:-0.875,a:7,w:1.5},"",{a:4},"A",{x:14.5},":\n;",{a:7,w:1.5},""],
  [{y:-0.625,x:6.5,h:1.5},"",{x:4.5,h:1.5},""],
  [{y:-0.75,x:3.5,a:4},"C",{x:10.5},"<\n,"],
  [{y:-0.875,x:2.5},"X",{x:1},"V",{x:8.5},"M",{x:1},">\n."],
  [{y:-0.875,x:5.5},"B",{x:6.5},"N"],
  [{y:-0.875,a:7,w:1.5},"",{a:4},"Z",{x:14.5},"?\n/",{a:7,w:1.5},""],
  [{y:-0.375,x:3.5},"",{x:10.5},""],
  [{y:-0.875,x:2.5},"",{x:1},"",{x:8.5},"",{x:1},""],
  [{y:-0.75,x:0.5},"","",{x:14.5},"",""],
  [{r:30,rx:6.5,ry:4.25,y:-1,x:1},"",""],
  [{h:2},"",{h:2},"",""],
  [{x:2},""],
  [{r:-30,rx:13,y:-1,x:-3},"",""],
  [{x:-3},"",{h:2},"",{h:2},""],
  [{x:-3},""]
])

console.log('<div style="position: relative;">')

keyboard.keys.forEach(key => {
  const coef = 50

  let wrapperStyle = ''
  wrapperStyle += 'position: absolute;'
  wrapperStyle += `transform: rotate(${key.rotation_angle}deg);`
  wrapperStyle += `transform-origin: ${key.rotation_x * coef}px ${key.rotation_y * coef}px;`

  let innerStyle = ''
  innerStyle += 'border-style: solid;'
  innerStyle += 'border-width: 1px;'
  innerStyle += 'position: absolute;'
  innerStyle += `left: ${key.x * coef}px;`
  innerStyle += `top: ${key.y * coef}px;`
  innerStyle += `width: ${key.width * coef - 4}px;`
  innerStyle += `height: ${key.height * coef - 4}px;`

  const label = key.labels[0] || ''

  console.log(`<div style="${wrapperStyle}">`)
  console.log(`  <div style="${innerStyle}">${label}</div>`)
  console.log(`</div>`)
})

console.log('</div>')

結果

f:id:yskoht:20201206001808p:plain

最後に

キーボード関連のツールを作ろうと思ったときに、この記事がなにかの役に立てれば幸いです。

この記事はpakbd rev3で書かれました。

参考

AAQのDocker環境備忘録

yskoht/aaqをDocker化して Docker Hub に公開したので備忘録です。

aaqの開発環境を構築する

$ git clone git@github.com:yskoht/aaq.git
$ docker run -it -v $(pwd):/root ruby:2.7.1-alpine /bin/sh
% apk add gcc libc-dev make git pkgconfig imagemagick6 imagemagick6-dev imagemagick6-libs
% bundle
% bundle exec aaq # コマンド実行
% bundle exec rake spec # テスト実行

DockerHubに公開する

$ docker login

$ docker image build -t yskoht/aaq:0.1.3 .
$ docker push yskoht/aaq:0.1.3

$ docker images
REPOSITORY                                              TAG                                              IMAGE ID            CREATED              SIZE
yskoht/aaq                                              0.1.3                                            1a119dc7a71f        About a minute ago   157MB

$ docker tag 1a119dc7a71f yskoht/aaq:latest
$ docker push yskoht/aaq:latest

以下のコマンドでaaqを実行できるようになりました。

$ docker run --rm -v $(pwd):/root yskoht/aaq aaq Octocat/Octocat.png --color

コピーしてコメントアウトするVS Codeプラグインを作った

Duplicate, Then Comment-out

f:id:yskoht:20200403210439p:plain

DuplicateThenCommentOut - Visual Studio Marketplace

コピーしてコメントアウト?

f:id:yskoht:20200403204021g:plain

プログラミングをしていると既存のコードを保持した上で、少し変更して動作を確認したいことがよくあります。そういう場合、自分は対象箇所をコピーしてコメントアウトするという操作を行います。

従来は、

  1. 範囲選択
  2. コピー&ペースト
  3. 範囲選択
  4. コメントアウト

と1回ずつ範囲選択して、コピペとコメントアウトをする必要がありました。 このプラグインを使うと、

  1. 範囲選択
  2. プラグイン実行

の2ステップで同じことを実現できます。

上のgifは範囲選択をしたあと、コマンドパレットを呼び出してから(Cmd + Shift + P)、Duplicate then Comment-outを選択してプラグインを実行している様子です。

ショートカットの設定

ショートカットを設定しておけば、いちいちコマンドパレットを呼び出す必要がなくなります。keybindings.jsonに以下のように追記し、key を好きなようにカスタマイズすれば、好きなショートカットでプラグインを実行できるようになります。

  {
    "key": "ctrl+shift+/",
    "command": "extension.duplicateThenCommentOut"
  },

ソースコード

github.com

Enjoy!

Binary Indexed Tree

AtCoder Beginner Contest 153

atcoder.jp

BIT知らなかった。

BIT

github.com

参考

F - Silver Fox vs Monster

class BIT
  def initialize(n)
    @tree = Array.new(n + 1, 0)
  end

  def add(i, diff)
    while i < @tree.size
      @tree[i] += diff
      i += (i & -i)
    end
  end

  def sum(i)
    t = 0
    while i > 0 do
      t += @tree[i]
      i -= (i & -i)
    end
    return t
  end
end

n, d, a = gets.chomp.split.map(&:to_i)

ms = []
n.times do
  ms.push gets.chomp.split.map(&:to_i)
end
ms = ms.sort_by { |x| x[0] }
ms.push [1<<60, 0]

dst = {}
n.times do |i|
  x, _ = *ms[i]
  r = x + 2*d
  k = ms.bsearch_index { |m| m[0] > r }
  dst[i] = k
end

bit = BIT.new(n+1)

res = 0
n.times do |i|
  x, h = *ms[i]
  t = bit.sum(i + 1)
  h -= t
  next if h <= 0

  cnt = (h + a - 1) / a
  res += cnt
  damage = cnt * a

  bit.add(i + 1, damage)
  bit.add(dst[i] + 1, -damage)
end

puts res

参考

My Technology Roadmap for DIY Keyboard (2018 ~ 2019)

新しいキーボードを作る際に技術的なチャレンジを毎回決めているのですが、 キーボードを作り始めてから2年経ったのでできるようになったことをまとめておきます。

9key (2018-01-13)

  • キーマトリクス回路の実装
  • QMK firmwareのビルド&フラッシュ
  • ICSP (in-circuit serial programming)を用いたプログラムの書き込み

yskoht.hatenablog.com

Thumbhelix (2018-05-01)

  • KiCadを用いたPCBの作成
    • シンボルライブラリの作成
    • フットプリントライブラリの作成
    • Eeschemaで回路図を作り、Pcbnewで配置配線
    • ALL PCBでPCB発注
  • アクリルでケースの作成
    • アクリルケースの図面作成
    • Ponokoで発注
    • coromozaでレーザー加工
  • I2Cライブラリの使用
  • Tokyo Mechanical Keyboard Meetup Vol.4に展示

yskoht.hatenablog.com

pakbd Prototype (2018-08-31)

  • 分割キーボードの作成
  • Tokyo Mechanical Keyboard Meetup Vol.5に展示

yskoht.hatenablog.com

pakbd Rev1 (2018-11-03)

  • PCBに切れ目を入れて5行版と4行版に対応
  • 4行レイアウトの実装
  • Cherry MX用スイッチソケットを用いたスイッチのホットスワップ
  • きりいたドットコムでステンレスプレートの発注
  • TRSケーブルの自作
  • QMK firmwareの整備

yskoht.hatenablog.com

pakbd Rev2 (2019-02-24)

  • 6行レイアウトの実装
  • スタビライザーの使用

pakbd Rev3 (2019-03-30)

  • 薄型化
  • アクリル積層ケースの作成
  • ProMicro上面実装

Business Card Keyboard (2019-09-29)

  • USB-C化
  • SK6812MINI(LED)対応PCBの作成
  • Kailh Choc用スイッチソケットを用いたスイッチのホットスワップ
  • ALL PCBでステンシルの発注
  • 自宅ホットプレートリフロー
  • 脱ProMicro (ATmega32U4の基板実装)
  • PCBのシルクにイラストの挿入

yskoht.hatenablog.com

棚卸し

8月のキーボードでやりたいことを列挙していたので棚卸しする。57%達成。

  • 基板のリファクタリング(ProMicro、スイッチ位置の修正。シルク入れ等) →✅
  • QMKファームウェアの整備 →✅
  • 薄型化 →✅
  • LED対応 →✅
  • ホットスワップソケット対応 →✅
  • ミドルプレートの作成 →✅
  • ステンレスプレートの作成 →✅
  • tilt機構 →❌
    • 興味がなくなったのでキャンセル
  • 左右一体で使う場合のロック機構→❌
    • あまり持ち運ばないのでキャンセル
  • トラックポイント→❌
    • そろそろDIY Keyboardにおいての回答がほしい。継続
  • USB-C化 →✅
  • 静電容量無接点スイッチ対応→❌
    • 勉強が足りない。継続
  • ARM化、脱QMK→❌
    • 勉強が足りない。継続
  • Bluethooth化 →❓ yskoht.hatenablog.com

今後のやりたいこと

来年はファームウェア周りをやっていきたい

  • ファームウェアの自作
  • ARM化
  • ポインティングデバイス
  • 静電容量無接点スイッチ対応

番外編 (作ったツールなど)

Keymapviz (2018-03-01)

QMK Firmwareのkeymap.cからアスキーアートを出力するスクリプト。PyPlに登録したんですが、いまいまJSONを出力するのが壊れていますね。すみません。正月休み中に直します🙇(あと記事が古くなっているのも直したい。直接でPNGが出力できるようにもしたい)→直しました(2019-01-05)

yskoht.hatenablog.com

Keyboard Layouter Plugin (2019-04-01)

KiCadでスイッチを自動配置するプラグイン。

yskoht.hatenablog.com

HardTyping (2019-12-25)

タイピングサイト。がんばっていきたいです。よろしくお願いします🙇

yskoht.hatenablog.com

自作キーボード向けタイピングサイトを作った

この記事はキーボード #2 Advent Calendar 2019の25日目の記事です。昨日は😢🔰神🔰😢さんのトラックボールマウスを分解してArduino Microで動かした話でした。かなりヌルヌル動いていてすごい。

adventar.org

自作キーボード向けタイピングサイト

自作キーボードを作ったいいけれど、キーマップに馴染むまでが大変で、いい感じに練習できるツールがあったらいいなと思って作りました。

hardtyping.io

特徴① レイアウトの変更が可能

レイアウトをKeyboard Layout Editor形式のJSONで自由に変更できます。JSONの詳細はレイアウトの設定を御覧ください。

f:id:yskoht:20191224212914g:plain

特徴② レイヤーに対応

数字や記号はRAIZEレイヤーやLOWERレイヤーに割り当てられることが多いですが、既存のタイピングアプリではなかなか練習しづらいです。HardTypingでは押下するキーとレイヤーを変更するためのもモディファイアキーも一緒にハイライトしてくれます。

f:id:yskoht:20191224220411g:plain

今後の展望

現在ベータ版です。やりたいことは色々あるんですがまだ全然できていません。

  • タイピングする文章をユーザーが投稿できるようにしたい
  • キーマップをユーザーがシェアできるようにしたい
    • keymap.cからレイアウト設定用のJSONを出力したり、JSONからkeymap.cを出力したりしたい
  • ランキング機能をつけたい
  • コンテンツを増やしたい
  • かな入力に対応したい
  • キーボード上のホットスポットを可視化したい
  • 統計情報を出力したい

新しい配列を気軽に試したり、練習できたり、配列の試行錯誤に便利なツールになれば良いなと思います。ゆっくりやっていこうと思うので今後ともよろしくお願いします🙇‍♂️

この記事はpakbd rev3を使って書かれました。

名刺サイズキーボードを作った

Business Card Keyboard

meishi展1に行ってからずっと作りたいなと思っていた名刺サイズ(91mm×55mm)のキーボードを作りました。

f:id:yskoht:20190929135905p:plain

Feature

ATmega32U4の基板実装

f:id:yskoht:20190929135912p:plain

今回やりたかったことのメイントピックは脱ProMicroでした。回路図は公開されているので問題なかったですが、部品は何を使えばよいのかよくわからない感じです。今回は「適当に集める→動いた」で良かったですが、HomeReflowKit – スイッチサイエンスを改めてよく見たら部品表が載っていたので、これを集めればよかったのかもしれません(というか、このHomeReflowKitが欲しい)。個別に部品を買うと50個、100個単位でしか買えなかったりするので、部品が大量に余ってしまいました。

USB-C化

f:id:yskoht:20190929140025p:plain

やりたかったことの2つ目はUSB-C化でした。使った部品は以下です。

このパーツの素晴らしいところはピッチ変換基板があり、しかもそのEagleファイルがGithubに公開されている2ところです。

EagleファイルをKiCadに変換するだけでフットプリントの用意ができました。そして、「これは手ハンダは無理だ」となり、自宅リフローを決意しました。

ALL PCBでステンシルの発注

PCBとステンシルをALL PCBで発注しました。ステンシルが$20.00、PCBが$5.00、送料は$27.00で合計$52.00でした。「ステンシル高ぇ、送料高ぇ」と思っていたら届いたものが思いの外大きく、そりゃこれくらいしますね、という感じでした。

自宅ホットプレートリフロー

f:id:yskoht:20190929182433p:plain
ハンダ盛りすぎ

f:id:yskoht:20190929182445p:plain
チップマウンター(手)

f:id:yskoht:20190929182449p:plain
「このホットプレートじゃ60%キーボードの基板とか入らないじゃん」と後になって思いました

f:id:yskoht:20190929182458p:plain
「早く冷まさなくては」とビビっていたんですが、ATmega32U4がハンダ不良起こしていたり、基板の隅のパーツがついていなかったりしたのでもっとじっくりリフローするべきでした。

f:id:yskoht:20190929182503p:plain
手ハンダとはまた違う出来栄え

SK6812MINI (LED) 対応PCBの作成

f:id:yskoht:20190929185607j:plain

せっかくなのでLEDも付けました。フットプリントはHelixを参考にさせていただきました。シンボルはKiCadに入っていたものを使ったため、シンボルとフットプリントの端子番号が整合せず、間違って配線してしまったので、PCBを再発注しました。横着せずにシンボルも自分で作ったほうが安全ですね。最初に発注した基板はSK6812MINIがぴったりハマっていい感じだったのですが、再発注した基板は若干緩い感じで製造誤差を実感しました。

Kailh Choc用スイッチソケットを用いたスイッチのホットスワップ

f:id:yskoht:20190929191152p:plain

今回はKailhのChocスイッチを使いたかったのでCPG135001S30を使いました。フットプリント作るのめんどくさいなと思っていたところ、foostan/kbd: Publish data for Keyboardで公開されていることを知りました。速攻でスター付けて利用させていただきました。ありがとうございます。

PCBのシルクにイラストの挿入

f:id:yskoht:20190929191358p:plain

線とビアは重ならないようにしたほうが良さそうです。

失敗したこと

スレーブ側でhas_usbが1になってしまった

今回、参考にした回路図3にはVBUSのラインにダイオードが付いていませんでした。

f:id:yskoht:20190929233156p:plain

なので、マスター側のVCCをTRRSケーブルを通じてスレーブ側のVCCにつなげると、スレーブ側のUVCCもHIGHになり、スレーブ側でもhas_usbが1になってしまいました。

Pro micro スイッチサイエンス版の回路図にはしっかりダイオードが入っているので、こちらを参考にするのが良さそうだなと思います。