Vuforia Studio 重畳AR備忘録：プリント基板でARを活用する その３

#はじめに

Vuforia Studioでは、現実にデジタルを重畳表示するARを簡単に作成できる。その例として、プリント基板でARを活用する例を、備忘録としておく。
・[Vuforia Studio 重畳AR備忘録：プリント基板でARを活用する　その２](https://qiita.com/tm181001/items/560ef489fb451bfa06d7)
では、プリント基板上の位置データをCSVファイルから読み取り、一定時間間隔で位置を ARの矢印で示す例を作成した。今回は、位置のリストを表示して、リストから選択した位置を矢印で表示する。下図は今回の作成結果。
![PCB_AR_Post10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/2e335dc3-934c-a2be-38a3-62ecf5333b74.gif)

#前提

この備忘録は、Vuforia Studioで、ARコンテンツを独力で作成できる人で、
・[Vuforia Studio 重畳AR備忘録：プリント基板でARを活用する](https://qiita.com/tm181001/items/45f77242a7826d2b3248) 
・[Vuforia Studio 重畳AR備忘録：プリント基板でARを活用する　その２](https://qiita.com/tm181001/items/560ef489fb451bfa06d7) 
を実施している人が対象。

#プリント基板にて、リストから選択した位置を ARで表示する例

###位置の一覧をリスト表示する

**その２**では、CSVファイルから位置データを JSONで取得した。JSONを使うと、Vuforia Studioのリストウィジェットでデータを一覧表示できる。

まずは、2Dキャンパスにて、リストウィジェットをドラッグ＆ドロップで中央パネルに追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/1f062961-e666-ac33-f7a8-3bf78060d55e.png)
Javascriptに、追加したリストウィジェット（list-1）の list に、位置データのJSONデータ（jsondata1）をセットする処理を追加する。（ // リストにJSONをセット　のコメントがある１行）

```javaScript:
// ファイルからテキストを取得して、JSONデータ化
$scope.readCsv2Json = function(){
  $http.get(filepath + csvfile)
  .success(function(data, status, headers, config) {
    // テキストのCSVを読み取りJSONデータ化
    $scope.csv2Json(data);
    //alert(JSON.stringify(jsondata1)); // プレビュー確認用
    $scope.setWidgetProp('list-1', 'list', jsondata1); // リストにJSONをセット
  })
  .error(function(data, status, headers, config) {
    //alert(csvfile + '読み取りエラー'); // プレビュー確認用
  });
}
```
2Dキャンパスで、追加したリストウィジェットを選択し、画面右の表示フィールド欄に、表示させるデータ名「refdes」を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/d3cbce16-daaa-e3fe-f8c2-a27f789cb4e0.png)
プレビューしてみる。位置をリスト表示できた。が、画面をふさいでしまい、使いものにならない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/7ef83329-6704-e882-1612-d03b709f4d62.png)

###位置のリスト表示の見栄えを変更する

見栄えを変更するには、CSSを設定する。画面左の少し下にある「アプリケーション」を選択して開き、以下のスタイルを追加する。

```css:
// リストの文字
.listlabel1 {
  background-color: gray; // 背景色
  opacity: 0.7; // 不透明度
  color: white; // 文字色
  font-size: 14px; // フォントサイズ
}
```
リストウィジェットのクラスに、スタイルの「listlabel1」を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/5b4cfd8b-f6ec-03cd-828c-527c98f2b894.png)
プレビューしてみる。リスト表示は半透明になり、少し見やすくなったが、背後が隠れてしまい、まだ使いものにならない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/7e4077c8-a405-ef72-12c3-39db9ef3583c.png)

###位置のリスト表示の場所を移動する

「アプリケーション」に、さらに以下のスタイルを追加する。

```css:
// リストの位置
.listposition1 {
  position: fixed;  // 相対的でなく絶対的な位置で固定
  left: 70%;        // 左から 70%の位置
  top: 15%;         // 上から 15%の位置
  width: 25%;       // 25%の幅
  height: 70%;      // 70%の高さ
  overflow: scroll; // データが多い場合はスクロール
}
```
このスタイルは、リストウィジェットに対してでなく、リストウィジェットの配置先のウィジェットに指定する必要がある。そこで、グリッドレイアウトウィジェットをドラッグ＆ドロップで中央パネルに配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/079f5e23-3094-6807-0627-2707237c0938.png)
そして、リストウィジェットをドラッグ＆ドロップでグリッドレイアウトウィジェットの columnの下に移動する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/6306ef64-0bf6-771e-0b6d-fc848180cb39.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/a083d77d-02ba-f026-7c7c-74b40de74dea.png)
ドラッグ＆ドロップや、切り取り＆貼り付けでウィジェットを移動して配置変更が簡単にできるのはうれしい！
グリッドレイアウトウィジェットを選択して、クラスに、「listposition1」を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/e0a725e3-4d91-9972-2fd9-06fac9859c9c.png)
プレビューしてみる。リストが端に寄ったので、これなら使えそうだ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/1ec884be-ba84-3bd7-58ba-7cb3b3ac6e38.png)

###リストから選択したら、その位置を ARの矢印で示すようにする

まずは、一定時間間隔で表示位置を切り替えていた処理を止める。startAnimation() の行の先頭に // を追加してコメントアウトする。

```javaScript:
$timeout(function() {
  $scope.setWidgetProp('3DImage-1','x', 0); // 横
  $scope.setWidgetProp('3DImage-1','z', 0); // 縦
  $scope.setWidgetProp('3DImage-1','y', 0); // 高さ
  $scope.setWidgetProp('label-2','text', pcbNumber); // 基板番号
  $scope.setWidgetProp('label-3','text', pcbName); // 基板名
  //$scope.startAnimation();
 },5000);
```
リストで選択したデータを取得して、矢印の 3Dイメージの位置座標を設定する処理を追加する。

```javaScript:
// リストから選択したときに実行する処理
$scope.selectFromList1 = function(event, data){
  // 名前を表示
  $scope.setWidgetProp('label-1', 'text', data['refdes']); //　高さ 0 （プレビュー時）
  // 位置を設定（位置データの原点が基板表面の左下隅のため、Studio用の位置を下記の通り算出）
  if(topData){
    // 表面用
    $scope.setWidgetProp('3DImage-1','x',  Number(data['x']) *0.001 - brd_W /2); // 横位置（mm -> メートル）
  }
  else{
    // 裏面用
    $scope.setWidgetProp('3DImage-1','x', -Number(data['x']) *0.001 + brd_W /2); // 横位置（mm -> メートル）
  }
  $scope.setWidgetProp('3DImage-1','z', -Number(data['z']) *0.001 + brd_D /2); // 縦位置（mm -> メートル）
  if(previewMode){
    $scope.setWidgetProp('3DImage-1','y',  0); //　高さ 0 （プレビュー時）
  }
  else {
    $scope.setWidgetProp('3DImage-1','y',  Number(data['y']) *0.001); // 高さ
  }
}
```
処理を追加したので、その処理を使うように、リストウィジェットに設定を加える。リストウィジェットを選択して、画面右の下にある「アイテムクリック」の右の「JS」をクリックして入力欄を表示して、上記で追加したファンクションを selectFromList1(event, data);  と入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/8e0c4d11-4730-55e0-5baa-353f2c12c928.png)
プレビューしてみる。リストから位置を選択すると、その位置に ARの矢印が移動する。出来た！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/da0fc313-0d61-2f20-acda-5a0e0ea35910.png)


###位置のリスト表示を先頭文字ごとにフィルタする

上記の例では位置データが少なかったが、実際のプリント基板での位置データは桁違いに多くなり数千を超えることもある。そのように多い場合には、上記のようにすべてをリスト表示すると使いものにならない。そこで、リストをフィルタして表示数を減らしてみる。フィルタする方法は色々と考えられるが、ここでは単純に、先頭文字でフィルタすることを試す。

先頭文字を選択できるようにする必要がある。その選択表示を追加するにあたり、この機会に、基板番号、基板名、選択位置、の３つも合わせて、2Dレイアウトの上部パネルに配置することにする。グリッドレイアウトウィジェットを使い、上部パネルを４分割する。まずは、グリッドレイアウトウィジェットをドラッグ＆ドロップで上部パネルに配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/8dd651d5-e394-2a2f-a659-5e19c9ca5bd2.png)
次に、追加したグリッドレイアウトの column を選択して、画面右下の「列を追加」の左側の「＋」アイコンを３回クリックして、列を３つ増やす。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/74edc559-c685-440a-753a-6c71a934c7be.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/7c26db3a-a9d2-b60f-f175-479a33db9731.png)
列が増えたので、基板番号（label-2）、基板名（label-3）、選択した位置（label-1）をドラッグ＆ドロップで上部パネルの左側から各列に移動する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/ca34b02c-d3a2-fe01-2b8c-1db0f7dd80bb.png)

先頭文字の選択のために、選択ウィジェットをドラッグ＆ドロップで上部パネルの一番右の列に配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/9a11fa04-8dab-3a23-8889-0a0fd2be7237.png)
追加した選択ウィジェットを選択して、画面右の値フィールドと表示フィールド欄に、これから割り当てるJSONデータの中の labelの値を使うように、「label」と入力する。また、何の選択かを表示するためにウィジェットのラベル欄に「位置」と入力する。そして、選択ウィジェットで選択変更（値変更）したときに実行するファンクションとして、 selectFromDropdown(data); を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/9e1c0700-adc4-fc27-1f5e-bc9f676562ca.png)


続けて、JavaScriptを編集する。CSVの読み取り時に、位置名（refdes）の先頭文字を取得して、JSONデータに 'label' の値として追加する。下記コードのうち、「// 位置名（refdes）を一時格納」 から下の３行分が変更箇所となる。

```javaScript:
// テキストのCSVを読み取りJSONデータ化
$scope.csv2Json = function(data){
  var rowArray = data.split('\n');
  // 基板番号を取得
  var dataArray = rowArray[0].split(',');
  pcbNumber = dataArray[1];
  // 基板名を取得
  dataArray = rowArray[1].split(',');
  pcbName = dataArray[1];
  // 位置データを取得
  var startRow = 3;
  for(var i = startRow;  i<rowArray.length;  i++){
    dataArray = rowArray[i].split(',');
    data1 = {};
    var refdes = dataArray[0]; // 位置名（refdes）を一時格納
    data1['label'] = refdes.substring(0, 1); // 位置名（refdes）の先頭文字
    data1['refdes'] = refdes;
    data1['x'] = dataArray[1];
    data1['z'] = dataArray[2];
    data1['y'] = dataArray[3];
    if(!topData){
      data1['y'] = dataArray[4];
    }
    jsondata1.push(data1);
  }
  datalength1 = jsondata1.length;
}
```
また、CSV読み取り後にリストにJSONをセットしていたが、フィルタしたデータをリストにセットするように変更するため、「// リストにJSONをセット」 の一行の先頭に「//」を追加してコメントアウトする。また、その行の下に、位置の先頭文字を選択ウィジェットにセットする $scope.setLabels(); を追加する。

```javaScript:
// ファイルからテキストを取得して、JSONデータ化
$scope.readCsv2Json = function(){
  $http.get(filepath + csvfile)
  .success(function(data, status, headers, config) {
    // テキストのCSVを読み取りJSONデータ化
    $scope.csv2Json(data);
    //alert(JSON.stringify(jsondata1)); // プレビュー確認用
    //$scope.setWidgetProp('list-1', 'list', jsondata1); // リストにJSONをセット
    // refdesの先頭文字を取得して、選択ウィジェットにセット
    $scope.setLabels();
  })
  .error(function(data, status, headers, config) {
    //alert(csvfile + '読み取りエラー'); // プレビュー確認用
  });
}
```
位置の先頭文字を選択ウィジェットにセットする $scope.setLabels() として、以下を追加する。位置のJSONデータ jsondata1 から、先頭文字の 'label' の値を重複なく取り出して labeldata を取得し、それを選択ウィジェットにセットしている。

```javaScript:
var labeldata = []; // 位置名（refdes）の先頭文字のJSON（重複削除）
var data2 = {};

// 位置の先頭文字を JSONデータの labeldata に追加して、選択ウィジェットの listにセットする
$scope.setLabels = function(){
  var map = new Map(jsondata1.map(o => [o['label'], '']));  // 重複削除
  map.forEach(forEachFunc1);
  $scope.setWidgetProp('select-1','list', labeldata); // 選択に先頭文字データをセット
}

// MapのキーをJSONデータ化
function forEachFunc1(value, key, mapObj) {
  data2 = {};
  data2['label'] = key;
  labeldata.push(data2);
}
```
すでに、選択ウィジェットで選択したときに実行するファンクションとして selectFromDropdown(data); を入力していた。このファンクションを追加する。

```javaScript:
// 選択ウィジェットで選択したときに実行する処理
$scope.selectFromDropdown= function(data){
  // jsondataを先頭文字(data)でフィルタしたデータをリストにセット
  $scope.setWidgetProp('list-1', 'list', $scope.jsonFilter1(data));
}

// 先頭文字でJSONデータをフィルタ
$scope.jsonFilter1 = function(val){
  var matchData = jsondata1.filter(
    function(item, index){
      if(item['label'] == val) return true;
    }
  );
  return matchData;
}
```

プレビューしてみる。位置の先頭文字を選択したら、位置リストがその先頭文字ではじまるものにフィルタされている。その中から位置を選択すると、基板上での位置に ARの矢印が表示された！　出来た！
![PCB_AR_Post10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/2e335dc3-934c-a2be-38a3-62ecf5333b74.gif)
CSVの位置データは、**その２**では 7か所の位置だけだったが、ここでは、115か所の位置で実施している。

尚、基板番号、基板名、選択位置は、以下のスタイルを追加するとともに、グリッドレイアウトの各列のクラスに col1 を入力して背景色とフォントサイズを変更した。

```css:
// 列のスタイル
.col1 {
  background-color: white; // 背景色
  font-size: 16px;         // フォントサイズ
}
```
また、各列に対して、フレックス方向を列のままで、位置揃えを「中央」に変更することで、上下位置を中央に変更している。また、一応、「折り返しなし」に変更している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297270/34cb09be-9c8f-b913-dc82-7ec8923341cb.png)

###位置データが１万か所の場合で試してみる。

ダミーデータとして１万か所の位置の CSVファイルを作成して、試してみた。フィルタ後の位置数が千を超えていても、表示で目立つ遅れは無かった。

#おわりに

Vuforia Studioで、イメージターゲットを使い、プリント基板実物に対してピン位置などを 矢印の ARで重ね合わせ表示できる例について、位置を選択可能にした。また、位置の数が膨大な場合を想定して、位置のリストをフィルタして利用する例も確認した。位置データが１万でも特に遅延は発生せずに利用できることも確認した。フィルタを工夫するなどすれば、業務用の大規模基板でも十分利用可能と思われる。実際には１万ものデータは必要ないと思われるので、すべてを AR化するのではなく、目的に応じて、CSVデータにするときに不要な位置データは省くことも必要だろう。

作成した ARのプロジェクトは、UIと JavaScriptを裏面用のビューにも追加すれば、テンプレートとして使うこともできる。Vuforia Studioでは、ARのプロジェクトを「名前を付けて保存」して複製できる。複製したら、表面と裏面にて、基板の写真と CSVファイルの更新登録をするだけで、他のプリント基板でもすぐに今回の ARを使える。ARは作成が大変で工数がかかるのでは？との懸念は、Vuforia Studioでは当てはまらなさそうだ！

尚、電子工作の基板にしても、業務用基板にしても、誤った位置へ接続して通電することで壊してしまうことがある点は、くれぐれも注意が必要。位置が密集する箇所などで、位置表示が誤っていないか、あいまいな位置になっていないかなど、ARの作成結果をテスト評価する工程は必要だろう。
本投稿を参考にして実基板に ARを活用するにあたっては、本投稿は一切の責任を負わないのでご注意を。

#注意

CSVデータに日本語などのマルチバイト文字を含む場合には、CSVを UTF-8 形式で保存すれば、Studioでの読み取り時に文字化けしない。

#参考

・[Vuforia Studio 重畳AR備忘録：プリント基板でARを活用する](https://qiita.com/tm181001/items/45f77242a7826d2b3248)

・[Vuforia Studio 重畳AR備忘録：プリント基板でARを活用する　その２](https://qiita.com/tm181001/items/560ef489fb451bfa06d7)