【Maya】コマンド入力でアンロックするダイアログを作る

この記事はMaya Python Advent Calendar 2017の6日目の記事です。
枠が余っていたのでネタで埋めていくスタイル。

f:id:tm8r:20171205130755g:plain
こういうのを作っていきます。

準備

cmdsでやるのはしんどそうなのでPySideを使います。
また、Maya2017以降にも対応するため、PySideはQt.py経由で使うことにします。

Qt.pyに関してはこのあたりをご参照ください。
Qt.pyを使ってQtDesignerのuiをMaya2017対応させる - するめとめがね
Qt.pyでPySideとPySide2を共通コードで使う - Qiita

作り方を考える

連続したキーボード入力を取得して、これが期待する順番と一致しているかをチェックできればよさそうです。
また、一定時間入力がなかった場合はリセットしたいところ。
というわけで、インタンス変数として以下のものが必要そうです。

  • 正規のキーの順番を管理するリスト
  • 入力されたキーの順番を管理するリスト
  • 現在の時間
  • 入力されたキーの順番のリセットを行うための時間の閾値

また、そもそもキー入力のイベントを取得する必要がありますが、PySideには各種イベントを取得するメソッドが用意されており、適切なメソッドをoverrideすることでイベントを受け取ることができます。
今回はkeyPressEventを使用しますが、こちらの詳細は先日書いた以下の記事をご参照ください。
tm8r.hateblo.jp

他のイベントを受け取りたい場合、各リファレンスのEvent Handlerの項目を見ながら要件に合ったものをoverrideしましょう。
QWidget Class | Qt Widgets 5.9

やっていく

というわけで書いたコードは以下のようなものになります。
surume/keyboard_command.py at master · tm8r/surume · GitHub

keyPressEventのところに注目してみましょう。

def keyPressEvent(self, event):
    if event.isAutoRepeat():
        return

    # アンロック済みだったら何もしない
    if self.unlocked:
        return

    # Macのキー名を正しく取得する対応
    pressed = QtGui.QKeySequence(event.key()).toString(QtGui.QKeySequence.NativeText)

    # 入力コマンドが存在して、かつ現在の時間と前回のキー入力時の時間の差分が閾値を超えてたらリセット
    if self.current_command and time.time() - self.prev_time > self.reset_threshold:
        self._reset_command()
        self.current_command.append(pressed)
        self._reflect_keys(self.current_command)
        self.command_field.setStyleSheet("background-color:none")
        return

    # 入力コマンドとキー入力時間の更新
    self.current_command.append(pressed)
    self.prev_time = time.time()

    # 入力コマンドが正規のキーのリストの長さに満ちてない場合は表示だけ更新して返す
    if len(self.current_command) < self.command_sequence_length:
        self._reflect_keys(self.current_command)
        return

    # 入力コマンドの長さが十分な場合、末尾から必要な長さだけ取得し、正規のキーのリストと比較
    # 間違っていれば表示を更新して返す
    target_command = self.current_command[-self.command_sequence_length:]
    self._reflect_keys(target_command)
    if target_command != self.command_sequence:
        self.command_field.setStyleSheet("background-color:#ff0000")
        return

    # ここまで到達すると入力コマンドがあっているので、表示を更新してアンロックする
    self.command_field.setStyleSheet("background-color:#00ff00")

    self.unlocked = True
    self.button.setEnabled(True)
    self.button.setText("Unlock")

def _reset_command(self):
    self.current_command = []
    self.prev_time = time.time()

コメントの通りですが、入力キーとキー入力時間を都度更新し、正規のキーのリストと合致するかをチェックしています。

ちなみにこの作りだと正しいキーの数がバレる脆弱性があるので、そこは期待する仕様次第でよしなに書き換えてください。

呼び出してみる

上のモジュールを呼び出しているのはこちら。
surume/keyboard_command_example.py at master · tm8r/surume · GitHub

具体的にはこのようなコードで呼び出せます。

def _create_cube_callback(self):
    KeyboardCommandWindow.show_ui(callback=self._create_cube)

callbackを指定することで、一番最初のgifのようにキー入力が合っていたときだけ好きなメソッドを実行することができます。

また、以下のようにすれば正規のキーのリストも渡せるので、用途に応じて異なるコマンドにすることもできます。

def _create_cube_callback(self):
    var sequence = [u"↑",u"X",u"↓",u"B",u"L",u"Y",u"R",u"A"]
    KeyboardCommandWindow.show_ui(command_sequence =sequence, callback=self._create_cube)

補足

id:ryunnnuさんが仰ってるのを見て気付いたんですけど、QTabWidgetって左右キーフックするんですよね。
というわけでkeyboard_command.pyと同じようなkeyPressEventをQTabWidgetが存在するWidgetに実装するとうまくいきません。

QTabWidgetを継承したクラスを作ってkeyPressEventをoverrideすればよいかというと、それも上手く行かず。
具体的に言うと左右の矢印キーだけkeyPressEventメソッドに入ってこず、keyReleaseEventには入ってくる、という感じ。
また、eventFilterで乗っ取るのも上手く行かなかったのでこのあたりはちょっと謎です。
Qtのイベントに関してもうちょっと理解が必要そう。

というわけで、色々試していたところ、QTabWidgetのFocusPolicyを変更することで回避できました。

self.tab_layout.setFocusPolicy(QtCore.Qt.NoFocus)

多分インプットフィールドがTabで移動できなくなるという副作用があるのでアレですが、手が空いたときにもう少し調べてみるつもりです。

おわり

というわけでタイトル通りコマンド入力でアンロックするダイアログの作成ができました!
夢が広がりますね!僕は使い道があんまり思いついてません!

次はリンゴ酸さんの「【MayaPySide】ちょっとおしゃれなUIメソッド【二日目】 番外編」です!
Maya Python Advent Calendar 2017 - Qiita

スポンサーリンク