メインコンテンツまでスキップ

07. Basis と Mount のプレイグラウンド

デバイスの マウントトランスフォーム をインタラクティブに操作し、configure.basis、configure.preset、configure.mount がワイヤー上でどのように連携するかを示します。インタラクティブな操作の焦点を configure コマンドに保つため、水平方向の床を固定しています。

このチュートリアルで学べること:

  • 基底の並べ替えの設定("XZY" → Y 軸が上のアプリケーションフレーム)
  • プリセット(arm_front)の選択と、それが何を設定するかの理解
  • 回転クォータニオンを使った、実行時の マウント の上書き(キーボード操作)
  • 排他ルール: mount と preset は、同じ device configure ブロック内に共存できません
  • ワンショットの configure の動作: キーを 1 回押すごとに、configure メッセージが 1 つだけ送信されます
  • (C++ Glaze)std::optional を使った、排他的なフィールドのモデル化

ワークフロー​

  1. 最初のメッセージで、セッションプロファイル、configure.basis: "XZY"、configure.preset: arm_front を送信します。固定された床のために set_cursor_force の送信を開始します。
  2. 各ティックで、カーソルの Y を読み取り、force_y = max(0, (floor_pos - y) * stiffness) を計算して送信します。
  3. ユーザーがマウント回転キーを押したら、pending_configure = true のフラグを立てます。
  4. 次のティックで、configure.mount ブロックを構築します。そのトランスフォームの rotation は単位クォータニオンです(現在のピッチとヨーから、Z、次いで X の順に合成したもの)。preset は省略します。両者はワイヤー上で排他的だからです。
  5. リセットキー(R)で上書きを解除します。次の configure では、再び preset が使われます。

パラメータ​

名前既定値用途
BASIS"XZY"軸の並べ替え — Y 軸が上のアプリケーションフレーム
DEVICE_PRESET / DEVICE_CONFIG_PRESET"arm_front"名前付きの プリセット — 原点はデバイスのベース
FLOOR_POS_Y0.0 m固定された床の平面(アプリケーションの Y 軸)
STIFFNESS1000 N/m床のばね定数
MOUNT_STEP_DEG10°キー押下ごとの回転量
PRINT_EVERY_MS200テレメトリの間引き間隔

操作​

キー動作
W / Sマウントを、デバイスの +X 軸まわりに ±10° 回転(ピッチ)
A / Dマウントを、デバイスの +Z 軸まわりに ±10° 回転(ヨー)
Rマウントをリセット — プリセットに戻します
H操作方法を表示
Q終了
mount と preset は同時に指定できません

サービスは、両方を含む device configure ブロックを拒否します。ユーザーがマウントを上書きすると、このチュートリアルでは、以降のすべての configure から preset を省略します。R を押すと、次の configure で preset が再び有効になり、mount は削除されます。

入力モデルの違い

C++ のバリアントは、バックグラウンドの stdin スレッドで行単位の入力を読み取ります(文字ごとに ENTER を押します)。Python は、メインの非同期ループでリアルタイムにキー入力を取得するため、keyboard パッケージを使用します。ENTER は不要です。キーもコマンドも同じです。

読み取る状態フィールド​

data.inverse3[i].state から読み取ります:

  • cursor_position.y — vec3。床への貫入量の計算に使用します
  • current_cursor_force — テレメトリ用に報告されます

送信/受信​

ペイロードの形式は、すべてのバリアントで同じです。注目すべき違いは、各実装が排他的な mount / preset の分岐をどのように構築するか、そして入力スレッドが WebSocket スレッドにどのように通知するかです。

keyboard パッケージでリアルタイムのキー入力をポーリングする、単一の非同期ループです。pending_configure は、キーハンドラーによって設定されるグローバルフラグで、configure ブロックが送信されるたびにクリアされます。

async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)

if first_message:
first_message = False
device_id = data["inverse3"][0]["device_id"]
# Handshake: profile + basis + preset
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": device_id,
"configure": build_configure_block(first_handshake=True),
# -> {"basis": {"permutation": "XZY"},
# "preset": {"preset": "arm_front"}}
}],
}
else:
handle_key_inputs() # may set pending_configure = True (classic, not shown)

y = data["inverse3"][0]["state"]["cursor_position"]["y"]
force_y = 0.0 if y > FLOOR_POS_Y else (FLOOR_POS_Y - y) * STIFFNESS

entry = {
"device_id": device_id,
"commands": {"set_cursor_force":
{"vector": {"x": 0.0, "y": force_y, "z": 0.0}}},
}
if pending_configure:
entry["configure"] = build_configure_block(first_handshake=False)
# -> {"mount": {...}} OR {"preset": {...}} (never both)
pending_configure = False

request_msg = {"inverse3": [entry]}

await websocket.send(json.dumps(request_msg))
SDK インストーラーに同梱

チュートリアル 07 は SDK と一緒にローカルにもインストールされています。サービスのインストールディレクトリ配下の tutorials/07-haply-inverse-basis-and-mount/ を確認してください。

ソース: Python · C++ · C++ Glaze

関連項目: 基底の並べ替え · マウントとワークスペース · デバイスの設定 · 制御コマンド(set_cursor_force) · 型(transform) · チュートリアル 04(Hello Floor)