【LabVIEW】マウスカーソル動作再現VI【解説あり】

2020年12月2日LabVIEW,プログラミング

CLIP STUDIO PAINTの手ブレ補正が数値ごとにどれくらい効いているのかを目視出来るようにしたいなと考え、本プログラムを作成しました。

手ブレ補正の効果を確認した結果は下記の記事に載せてます。

本記事の要約

  • Labviewに標準であるデバイス制御を用いてディスプレイ上のマウスカーソル位置を記録
  • 取得したカーソル位置をWindowsのAPI(user32.dll)、SetCursorPos、Mouse Event機能を用いて軌跡を再現

ソフトの概要

ソフトの機能コンセプト

  • ペンタブレットまたはマウスで入力した軌跡を記録する
  • ペンタブレットで入力されてた軌跡を再現する。
  • フロントパネル上にカーソルがある場合は軌跡を記録しない
  • 入力中の時間を計測出来るようにする

最初にペンタブレットまたはマウスで入力(線を引く)した後に、再生ボタンを押すとカーソルが移動した軌跡を再現してくれます。時間計測などの一部機能は後で必要と思い追加しました。

LabVIEW VIファイル配布

LabVIEWファイル形式で配布をしています。
https://drive.google.com/file/d/197lk-yEfOVDA8iORaMiMwQAowwa6HTaQ/view?usp=sharing

正直、汎用的なソフトではないため、本ソフト自体を使うことはないと考えています。しかし、プログラム的には使い回せる部分もあると思いますので下記に解説してます。

ソフトの説明

上図の画像をクリックするとVIスニペットの画像を開くことが出来ます。
Labview2019以降のブロックダイアグラムにドラッグ&ドロップするとVIを再現可能です。

複数の処理を同時に行う必要はありませんでしたのでプログラムを「簡易ステートマシン」の構成で作成をしています。大まかにはイベントストラクチャでの条件を満たした場合のみ、次の動作に移るという流れになっています。

左から順に解説をしていきます。

フロントパネルの制御器と表示器を初期化

まず最初に、フロントパネルにある制御器と表示器をデフォルトの値に変更します(表示値を初期化)。

VIのリファレンスをインボークノードに入力し、「デフォルト値」⇒「全てをデフォルト設定に戻す」で変更できます。※事前にフロントパネル上で制御器と表示器のデフォルト値を設定しておく必要があります。

LabVIEW8.0以前だと初期化はもう少し複雑になりますが、8.0以降であれば上記の方法で対応可能です。

次に、WindowsAPIのUser32.dllの「GetForegroundWindow」を用いて、アクティブウィンドウのハンドルを取得します。特に値を渡す必要はないため、戻り値のみになります。

下記の記事で「GetForegroundWindow」の設定詳細について解説してます。

「GetForegroundWindow」で取得をしたハンドルを「ウィンドウの表示順番を変更する」サブVIに渡します。この際、アクティブウィンドウを一番トップで表示しつづけたかったため「-1」も渡しています。

ウィンドウの順番を変更

ここでもWindowsAPIのUser32.dllにある「SetWindowPos Function」を用いてウィンドウの表示順番を変更します。常にトップで表示させたかったので、今回は「HWND_TOPMOST」で設定してます。

後で何回か、ウィンドウの順番を変更する必要があったため、サブVIとして作成しています。

各種の値渡しについては下記のページに詳細が記載されています。
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos

ライブラリ関数ノードの設定は下記の記事を参照してください。
https://canvas-cluster.com/mouse-point-spuit/

入力デバイス初期化&閉じる

今回作ったソフトはマウスのみを制御しますが、キーボードとマウスを同時に使用する場合もあるので、デイバス類をまとめて初期化出来るサブVIを作成しました。※プログラムによってはマウスのみ初期化すれば良いので、無駄な処理を行っている部分ではあります。

ケースストラクチャを用いて、ブールの入力が「FALSE」の場合は、各種デバイスを初期化してリファレンスをクラスタでまとめます。ブールのデフォルト値は「FALSE」になっているためブールへの入力がない場合はデバイスを初期化します。

ブールへの入力が「TRUE」の場合は全てのデバイスをまとめて閉じ、デバイスのリファレンスが格納されていたクラスタの中を空にします。今回はプログラムを終了する直前に使用しています。

簡易ステートマシン

プログラムを作成するに際に大まかな動作フローを考えました。プログラムを作り始めた動機が思いつきであったため、詳細な動作についてはあまり深くは考えていませんでした。

しかし、大まかなフローを作った段階で並列処理を行う必要ないことがわかったため、プログラムの構成を簡易ステートマシンで作成することにしました。

簡易ステートマシンの構成がよく判らない場合についてはLabVIEWのプロジェクト作成からもテンプレートを呼び出すことは可能です。(テンプレートの詳細情報からより詳しい説明を見ることが出来ます。)

シーケンスストラクチャには「列挙定数」を用いて入力を行っています。接続をしている「列挙定数」については複数箇所での使用を想定しているため、タイプ定義を行っておきます。

列挙定数にタイプ定義を行っておくと、コピーをした「列挙定数」もタイプ定義を変更した際にまとめて変更がかかります。
※ステートマシンを作るときはタイプ定義を行っておくとシーケンスが増えた場合の変更が容易です。

シーケンスストラクチャ:待機

プログラム起動時は、まず待機モードに移行します。待機モードにイベントストラクチャを配置することで、特定の動作をした場合に指定の処理へ移行することが出来ます。※イベントストラクチャの詳細については後述

シーケンスストラクチャ:マウス座標取得

マウス(ペンタブレット)のカーソル座標を取得するための処理部分になります。座標を取得し続ける必要があるため、Whileループで処理を行っています。

Whileループ内の主な処理内容

  • マウスからの入力がある場合はカーソル座標取得をし続けます。
  • 取得した座標は指標を用いて出力をします。
  • 1px以上の移動が確認出来た場合のみ、ドラッグ時間として計算してます。
  • ループの待機時間を8msにしているのは8ms未満だと移動していないと誤検知する場合があったため。

※複数のストロークを座標データとしてまとめることも出来たのですが、マウス制御の処理が複雑になるため、1ストロークの座標処理のみとなっていえます。(Whileループ後にあるケースストラクチャをFalseで固定している理由)

シーケンスストラクチャ:マウスの軌跡を再生

User32.dllの「SetCursorPos」と「Mouse_evet」関数を用いて、マウスの制御を行います。マウスカーソルの移動とクリック(ドラッグ)も再現させます。

複数の箇所で使用するサブVIではありませんが、可読性を考慮してサブVI化してます。水平座標と垂直座標のリファレンスを入力しているのは、再生中のマウスカーソルの座標を何となく表示したかったためです。特に意味はありません。

「SetCursorPos」と「Mouse_evet」でマウスカーソルの軌跡を再現させる場合の処理になります。
アクティブウィンドウを変更するために、一度開始位置までマウスカーソルを移動させて左クリックを行う処理を行ってから、マウスカーソルの軌跡再現を行っています。

マウスの座標をForループに入力することにより指標配列で順に座標データが出力されるので「SetCursorPos」関数でマウスカーソルを移動させていきます。この時にマウスの左ボタンが押し続けられたままになっているのでドラッグ状態となり線が引かれます。Forループが終了をしたら左ボタンを放します。

シーケンスストラクチャ:プログラム終了

プログラムを終了させるためにメインのWhileループを終了させる必要があります。Whileループが終了するようにTrue出力を行います。メインのWhileループ終了端子にはOR関数を手前に配置してプログラムにエラーが発生した場合もプログラムが終了するようにしておきます。

※ループ内のエラーワイヤー処理については適切に行う必要があります。今回のプログラムの場合はエラーワイヤーをWhileループの終了端子に接続していない場合でも大きな問題にはなりませんが、ハードウェアを制御しているプログラムの場合は注意が必要です。

イベントストラクチャの動作

イベントストラクチャが関係するボタンの振り分けは上図の4カ所となっています。①についてはイベントによる動作ではなく、ブールとして使用しています。

①イベントストラクチャ:タイムアウト

イベントストラクチャで「タイムアウト」によるイベント発生を使用したいと考えたので左上の砂時計に数値の定数を接続します。上図では「1ms」待機し特にイベントが発生しなければタイムアウトのイベントが発生するようにしてあります。

プログラムの動作速度やイベント内容に応じてタイムアウトの時間を設定します。タイムアウトさせない場合は未接続または「-1」を接続します。

タイムアウトの内容

  1. デバイスの制御からマウスの座標とボタン入力の情報を取得します。
  2. 自作したサブVIでフロントパネルの領域内にマウスカーソルがあるのかを主としたマウスの座標と比較をして「True」「False」を判定します。マウスカーソルが領域内にある場合、「True」で出力されるため、Notで反転してFalseにしています。
  3. カーソルの軌跡許可ボタンが「ON」、マウスの入力が「ON」、フロントパネル外にマウスカーソルがある場合、AND設定ししている複合演算から「True」が出力されるため、比較の「選択関数」出力されから「マウス座標取得」モードへ移行します。Flaseの場合は待機モードを継続します。

フロントパネルの領域内にマウスカーソルがあるのかを確認する場合は上図の通り。

VIのリファレンスをプロパティノードに渡し、「フロントパネルウィンドウ」⇒「パネルの境界」より、フロントパネルの座標とサイズを取得することが出来る。

あとは、左右上下の範囲内にマウスカーソルがいるのかを「範囲内と強制関数」を用いて、ブールを出力することで範囲内にいるのかを確認することが可能。

②イベントストラクチャ:カーソル軌跡再生

②の再生ボタンをクリックした場合に「カーソル軌跡再生」のイベントストラクチャに入ります。

カーソル軌跡の内容

  1. マウス座標軌跡のデータがあるのかをまず確認します。データ数が1以上あればデータありとしてTrueを出力します。
  2. データがあれば、「マウスの軌跡を再生」のモードへ移行します。無ければ「待機」を継続します。
  3. データが無い場合、アクティブウィンドウの表示順番を変更し、ダイアログボックスを表示させます。ダイアログボックスを表示させる時に、「HWND_TOPMOST」でVIウィンドウを設定しているとフリーズします。ダイアログボックスを表示させるときには表示順番を変更してください。

③イベントストラクチャ:軌跡データキャンセル

③のキャンセルボタンをクリックした場合に「軌跡データキャンセル」のイベントストラクチャに入ります。

軌跡データのキャンセルの内容

  1. カーソル位置表示器の値を「0」にしています。(気分的になんとなく)
  2. ②と同様にダイアログボックスを表示する際にウィンドウの順番を変更し表示しています。削除したのを判りやすくしているだけ
  3. マウス軌跡の配列データに空の配列データを入力し、初期化してます。
  4. 次の処理は無いため待機モードへ移動します。

④イベントストラクチャ:プログラム終了

④の停止ボタンをクリックするとプログラム終了のモードへ移行します。特にイベントストラクチャ内での処理は無く、プログラムのモードを変更するだけの処理となっています。