Pepperアニメーション

ペッパーにアニメーションをさせには0から自分でアニメーションを作成することもできるが、なかなかに難しいらしい。私は作ったことがありませんがpepperの勉強会に参加した際プロでも難しいので一般的には既存のアニメーションをインポートして使うのが良いとのことでした。

アニメーションのインポート

f:id:harumi_sagawa:20180807184115p:plain f:id:harumi_sagawa:20180807184400p:plain 今回はAnimalフォルダからゴリラのモーションをインポートしてみました。中身は下記のようになっています、こんな感じで指定していけばペッパーの動きを作れるようですがなかなか難しそうですね。
f:id:harumi_sagawa:20180807184607p:plain

コード

Animation animation = AnimationBuilder.with(qiContext)
                .withResources(R.raw.gorilla_b001)
                .build();
        animate = AnimateBuilder.with(qiContext)
                .withAnimation(animation)
                .build();
        Future<Void> animateFuture = animate.async().run();

アニメーションさせるタネに必要なのは上記のコードをonRobotFocusGaineの中に書くことになります。animation変数のwithResourcesにインポートしてきたアニメーションを指定しています。

話しながらアニメーション

話しながらアニメーションをすのは簡単で、onRobotFocusGaineの中にSayメソッドを入れてあげれば実現できます。しかし、注意しなくてはいけないのはasync()を指定して別スレッドにしてあげる必要があります。そうすることでSayとAnimationを並列に処理をすることができます

Say say = SayBuilder.with(qiContext)
                .withText("uhouho")
                .build();
        say.async().run();

Pepperがハローワールドするまで

ペッパーがハローワールドするまでの一連の流れを記述していきます。 ※pepperのエミュレータが動く前提で説明を行うのでまだの方は下記のリンクから設定をお願いします

Pepper SDK for Android — QiSDK

1.はじめにプロジェクトの作成から行います。ここは通常と変わらずなんでも大丈夫です。
f:id:harumi_sagawa:20180807123742p:plain 2.次にSDKですが、Marshmallowを選択して進めてください
f:id:harumi_sagawa:20180807123834p:plain 3.プロジェクトはエンプティアクティビティで作成します
f:id:harumi_sagawa:20180807124008p:plain 4.作成を行ったら下記の画像にあるようにFile →New→LobotAplication..を選択してください。
f:id:harumi_sagawa:20180807124037p:plain 5.するとこのようなポップアップが画面に表示されるので、okを選択します
f:id:harumi_sagawa:20180807124201p:plain 6.これで前準備はokです。MainActivityに移って継承するクラスを変更します
f:id:harumi_sagawa:20180807125105p:plain 7.継承してもそのままではimportできないのでリフレッシュボタン?見たいのなのを押してimportできるようにします
f:id:harumi_sagawa:20180807125153p:plain 8.これでimportができるようになったので、電球マークをクリックでインポートするかalt + enterのどちらかでインポートを行うとエラーが消えます
f:id:harumi_sagawa:20180807125329p:plain> 9.次にQiSDK.registerを記述します。onCreateないでこいつを呼ぶことによって、ペッパーを操作する関数に飛ばしていろんな命令ができます(たぶん)ここでAndroid Studioが自動生成してくれるので下記の画像のように進めましょう
f:id:harumi_sagawa:20180807130244p:plain f:id:harumi_sagawa:20180807130402p:plain すると3つの関数が@Overrideされて作成されます。

onRobotFocusGained()基本的にはこいつの中に入って来ます。アニメーションをさせたりペッパーに何かを話させるのはこの中です。
onRobotFocusLost()これはロボットが対象を見失ってしまったり何か問題が発生してしまった場合に通ります。
onRobotFocusRefusedこれはアプリが閉じられた際に行う処理だと思います。
私もまだpepperプログラミングを始めたばかりなので、上記の3つ認識が正しいのかどうかはちょっと不明ですが、基本的にはonRobotFocusGained()こいつでやっとけばいいってことです。 f:id:harumi_sagawa:20180807131241p:plain 10.QiSDK.registerを起動したらonDestroyでメモリの解放をしてあげる必要があります。公式リファレンスの中でも何回か説明がされていたのできっと大事なことなんだと思います。
f:id:harumi_sagawa:20180807131417p:plain

ここまで来たらあとはHallo Worldするだけです!

11.SayBuilderメソッドを使ってペッパーを喋らせて見ましょう。よく忘れてしまうのですが.run ()をしないと実行されないので注意してください。 f:id:harumi_sagawa:20180807132006p:plain

コード全文

package com.example.pepper.hellopepper;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.QiSDK;
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
import com.aldebaran.qi.sdk.builder.SayBuilder;
import com.aldebaran.qi.sdk.design.activity.RobotActivity;
import com.aldebaran.qi.sdk.object.conversation.Say;

public class MainActivity extends RobotActivity implements RobotLifecycleCallbacks {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        QiSDK.register(this, this);
    }
    @Override
    protected void onDestroy(){
        super.onDestroy();
        QiSDK.unregister(this, this);
    }

    @Override
    public void onRobotFocusGained(QiContext qiContext) {
        Say say = SayBuilder.with(qiContext)
                .withText("Hello World")
                .build();
        say.run();
    }

    @Override
    public void onRobotFocusLost() {

    }

    @Override
    public void onRobotFocusRefused(String reason) {

    }
}

Swift関数を返す関数もしかしてクロージャ?

最近クロージャについて勉強していたのですが、関数を戻り値として返せることがわかりました。第一級オブジェクトってやつですかね?
以下のコードがクロージャに当たるのかはわかりませんが覚えておくとすごい便利だと思います。

func helloFactory() -> (Int, String) -> () {
    func sayHello(count:Int = 10, words:String){
        for _ in 0...count{
            print(words)
        }
    }
    return sayHello
} 
//英語挨拶
let english = helloFactory()
english(3, "Hello")

//日本語の挨拶
let japanese = helloFactory()
japanese(10, "こんちわ")

まずhelloFactoriy()という関数を定義しています引数の値が(Int, String) -> ()となっていますが、Swiftでは関数を戻り値として返す時には(引数の型)->(戻り値)のように記述することができるそうです。

その次に、変数に対してhelloFactory関数を代入しています。
この時、変数の中にはhelloFactoryのreturnの値つまりsayHallo関数が入っています。なので、変数()と書いてやることで関数を実行することができます。今回は引数に数と文字列を入れるのでそれを入れてenglish(3, "Hello")こんな感じにしているというわけです。

コマンドラインメモ

毎回忘れてしまうので自分用のメモ

openコマンドでアプリケーションの指定

-aの後にアプリ系を記述することで、指定したアプリで開くことができる

open 開きたいファイル -a アプリ

//例---------------------------------
//hoge.swiftをcoteditorで開く場合
open hoge.swift -a coteditor

.swiftファイルをコマンドラインで実行

swift -oの後にコンパイル作成される実行ファイル名、コンパイルしたいswiftファイルの順に書きます。
実行ファイル名とswiftファイルの名前は一致していなくても構いませんが一致させたおいた方が後々わかりやすい気がします。

swiftc -o 実行ファイル swiftファイル
./実行ファイル

//例---------------------------------
swiftc -o hello.out hello.swift
./hello.out

VSクロージャ(javaScript)

以前からクロージャというものがわからずにいた。

私がメインで書いているのはSwiftなんですがクロージャからは逃げて生きてきた。しかしある本を読んでいる際javaScriptクロージャを扱ったコードにぶつかり戦って勝利しました。(※最強プログラマ達の助けを借りました)

books.google.co.jp

本の中に出てきた敵はこんなコードでした。 カウントアップして行くようなやつ

function makeCountor(){
  var count = 0;
  function push(){
    count++;
    conlose.log(count);
  }
  return push;
}

c = makeCountor();
c();  //->1
c();       //->2
c();      //->3

これをみて私がずっとモヤモヤしていたところがありました。

var count = 0;

こいつです。初期化してるやないかーい!
つまり

c = makeCountor();
c();     //->初期化されて0
c();       //->初期化されて0
c();      //->初期化されて0

絶対こうなると思っていました。だってvar count = 0;だし..
これはテラテイルに質問を投げた後に気づいたのですが、根本的な勘違いをしていました。
c = makeCountor();ここで何が起こっているかというとmakeCountor()関数のreturnで返された値が変数cの中に代入されています。すごい初歩的な話なのですが、私はcの中にmakeCountor関数が全て入っているものだと勘違いをしていました。
実際に返されているものはreturn push;つまり!

function push(){
    count++;
    conlose.log(count);
}

makeCountor関数内にあるpush関数なのです。理解してから考えるとすごい当たり前ですが、クロージャへの苦手意識からクロージャだからできるんじゃないかと疑心暗鬼になって頭の中がこんがらがっていました。

c = makeCountor(); //この行で1度だけ初期化が行われていますつまりcount = 0;
c(); //cの中にある関数を実行している...つまりpush関数を実行 count++;
c();    //上に同じ
c(); //上に同じ

push関数はcountをmakeCountorの中に見に行っています。しかし初期化はしていないのでcountの値は引き継がれてどんどんカウントアップして行くという結果です。
僕の頭の整理をかねて書き殴ってしまいましたが、誰かの理解の助けになれば幸いです。

teratail.com ※僕の幼稚な質問に答えたくれた最強プログラマ達の回答はここです

visual Studioフォントの組み込み

OSに直接組み込めばフォントはvisual studioが自動で読み込んで暮れて使えるのですが、自分のPC以外でアプリケーションの修正などを行いたい場合、その都度フォントをOSに入れるのは面倒なので、フォントをプロジェクト内に置きそれを参照する方法があります。
※今回はこちらのフォントをお借りしました

fontfree.me

絶対パス

Dim pfc As New System.Drawing.Text.PrivateFontCollection()
pfc.AddFontFile("C:\Users\Harumi_Sagawa\Downloads\GN-KillGothic_U\GN-KillGothic-U-KanaNB.ttf")
Dim f As New System.Drawing.Font(pfc.Families(0), 22)
Label1.Font = f

絶対パスでの指定方法です。これでもOSにインストールせずに独自のフォントを使うことができるのですが、フォントを毎回指定の場所に置かなければならないため不便です。

相対パス

//System.Environment.CurrentDirectory
//上記のコードで現在のディレクトリを取得することができる
Dim pfc As New System.Drawing.Text.PrivateFontCollection()
pfc.AddFontFile(System.Environment.CurrentDirectory + "/GN-KillGothic-U-KanaNB.ttf")
Dim f As New System.Drawing.Font(pfc.Families(0), 22)
Label1.Font = f

このように設定できます。visual studioのカレントディレクトリがわからず結構ハマっていたのですがSystem.Environment.CurrentDirectoryこいつを使うことでカレントディレクトリを取得することができるので、カレントディレクトリプラス使いたいフォントとすることで使用することができました。
ちなみに僕はカレントディレクトリがbinファイルのDebugがルートディレクトリになっていたのでDebugフォルダにフォントにtffファイルを直で突っ込んでいます。

追記
Releaseフォルダ内にも同様にフォントを入れたフォルダを作成しておく必要があります。デバッグ時の実行と、ビルド時の実行でパスが異なるようで、入れておかなかった場合にはエラーが出てしまいます。

最終的に今回のプロジェクトではFontSelectというクラスを作成し、.Designer.vbクラスにインスタンスを作成して呼び出しました。

Public Class FontSelect
    Dim NotoSansCJKjpBold As New System.Drawing.Text.PrivateFontCollection()

    Dim MakinasScrap As New System.Drawing.Text.PrivateFontCollection()

    Dim RobotoSlabBold As New System.Drawing.Text.PrivateFontCollection()


    'NotoSansCJKjpBoldフォント
    Public Function NotoFont() As FontFamily

        If NotoSansCJKjpBold.Families.Length = 0 Then
            Dim filePath As String = System.Environment.CurrentDirectory + "/myFont/NotoSansCJKjp/NotoSansCJKjp-Bold.otf"

            If System.IO.File.Exists(filePath) Then
                NotoSansCJKjpBold.AddFontFile(filePath)

            Else
                Return New FontFamily("Noto Sans CJK JP Bold")
            End If
        End If

        Return NotoSansCJKjpBold.Families(0)
    End Function


    'Makinasフォント1
    Public Function Makinas() As FontFamily

        If MakinasScrap.Families.Length = 0 Then
            Dim filePath As String = System.Environment.CurrentDirectory + "/myFont/makinas_scrap/Makinas-Scrap-5.otf"

            If System.IO.File.Exists(filePath) Then
                MakinasScrap.AddFontFile(filePath)

            Else
                Return New FontFamily("マキナス Scrap 5")
            End If
        End If
        Return MakinasScrap.Families(0)
    End Function

    'Robotoフォント
    Public Function Roboto() As FontFamily

        If RobotoSlabBold.Families.Length = 0 Then
            Dim filePath As String = System.Environment.CurrentDirectory + "/myFont/roboto-slab/RobotoSlab-Bold.ttf"

            If System.IO.File.Exists(filePath) Then
                RobotoSlabBold.AddFontFile(filePath)

            Else
                Return New FontFamily("Roboto Slab")
            End If
        End If

        Return RobotoSlabBold.Families(0)
    End Function
End Class

上記のクラスを作成して3つのフォントをプロジェクトに使いました。

If NotoSansCJKjpBold.Families.Length = 0 Then

このif文ですが、入れないとインスタンスをガンガン作成してしまい落ちたので入れました。

またまた追記
.designer.vbの下記の位置にインスタンスを作成しました。自動生成のファイルで書き換えたら危なそうですが書き換えちゃいました。フォームデザイナーからインスタンス作れないんだからしょうがないじゃないか!

'メモ: 以下のプロシージャは Windows フォーム デザイナで必要です。
'Windows フォーム デザイナを使用して変更できます。  
'コード エディタを使って変更しないでください。
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
###ここらへん####

フォントを設定してるところ

  Me.ButtonClose.Font = New System.Drawing.Font(FontSelect.NotoFont(), ...省略)

できた!windowsに入ってるfontアンインストールだ!

f:id:harumi_sagawa:20180724103659p:plain
あれ? windowsのシステムフォントの中にフォントがインストールされていないとdesigner.vbは見ることができないようです。(できる方法あったら教えてください)
相対パスを読みに行くのはデバッグorビルド時ってのはなんとなく想像つきますね。使うフォントはインストールして入れましょう! まぁでもこれでシステムフォントを参照しなくはなったので目的は達成できました。
最終的にインスタンスを作成する場所を変えてグローバル変数で宣言しました。

Public FontSelect As New FontSelect

※参考にさせていただいたサイト

作業フォルダのパスを取得/設定するには?[C#、VB]:.NET TIPS - @IT

VBでResourcesフォルダに画像を追加した際に起こるエラーの対処法

引き継いだコードなのですがResourcesフォルダ内に新たに画像を追加したり、削除したりすると下記のようなエラーが発生しました。
System.OutOfMemoryException: 種類 'System.OutOfMemoryException' の例外がスローされました
このエラーの対処法を記述していきます。
※Resourcesフォルダを削除するのでどこかに画像を一時避難させておいてください

↓エラー全文

エラー 1 The "GenerateResource" task failed unexpectedly. System.OutOfMemoryException: 種類 'System.OutOfMemoryException' の例外がスローされました。 場所 System.IO.MemoryStream.set_Capacity(Int32 value) 場所 System.IO.MemoryStream.EnsureCapacity(Int32 value) 場所 System.IO.MemoryStream.Write(Byte buffer, Int32 offset, Int32 count) 場所 System.IO.BinaryWriter.Write(Byte buffer) 場所 System.Runtime.Serialization.Formatters.Binary.BinaryWriter.WriteSingleArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, Int32 length, Int32 lowerBound, Array array) 場所 System.Runtime.Serialization.Formatters.Binary.BinaryWriter.WriteObjectByteArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, Int32 length, Int32 lowerBound, Byte byteA) 場所 System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteArray(WriteObjectInfo objectInfo, NameInfo memberNameInfo, WriteObjectInfo memberObjectInfo) 場所 System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo) 場所 System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header inHeaders, __BinaryWriter serWriter, Boolean fCheck) 場所 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header headers, Boolean fCheck) 場所 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph) 場所 System.Resources.ResourceWriter.WriteValue(ResourceTypeCode typeCode, Object value, BinaryWriter writer, IFormatter objFormatter) 場所 System.Resources.ResourceWriter.Generate() 場所 System.Resources.ResourceWriter.Dispose(Boolean disposing) 場所 System.Resources.ResourceWriter.Close() 場所 Microsoft.Build.Tasks.ProcessResourceFiles.WriteResources(IResourceWriter writer) 場所 Microsoft.Build.Tasks.ProcessResourceFiles.WriteResources(String filename) 場所 Microsoft.Build.Tasks.ProcessResourceFiles.ProcessFile(String inFile, String outFile) 場所 Microsoft.Build.Tasks.ProcessResourceFiles.Run(TaskLoggingHelper log, ITaskItem assemblyFilesList, ArrayList inputs, ArrayList outputs, Boolean sourcePath, String la nguage, String namespacename, String resourcesNamespace, String filename, String classname, Boolean publicClass) 場所 Microsoft.Build.Tasks.GenerateResource.Execute() 場所 Microsoft.Build.BuildEngine.TaskEngine.ExecuteInstantiatedTask(EngineProxy engineProxy, ItemBucket bucket, TaskExecutionMode howToExecuteTask, ITask task, Boolean& taskResult) CompMng

ググってもterateilで質問してもエラーをとることができなかったのですがいろいろと模索していたら解決できたので、共有。誰かの助けになれば幸いです。 1ヵ月無駄な時間を過ごしてしましました。

①ソリューションエクスプローラ上にあるResourcesフォルダを削除する
f:id:harumi_sagawa:20180709202939p:plain
②削除の確認ボタンが出るのでOK
f:id:harumi_sagawa:20180709203019p:plain
③エラーがたくさん出るのを確認
f:id:harumi_sagawa:20180709203058p:plain
④プロジェクトを保存する。画像は保存せずに閉じようとした場合。はいを選択
f:id:harumi_sagawa:20180709203229p:plain
⑤プロジェクトフォルダに移動しMyProjectを開く
f:id:harumi_sagawa:20180709203333p:plain
⑥2つのResourcesファイルを削除※多分これが問題
f:id:harumi_sagawa:20180709203453p:plain
⑦プロジェクトを開きプロジェクト→CompMsgのプロパティ
f:id:harumi_sagawa:20180709203634p:plain
⑧クリックしてResourcesを作成
f:id:harumi_sagawa:20180709203725p:plain
⑨ドラック&ドロップで画像をぶち込む

以上の手順でエラーは解決できると思います。簡単な手順なのですがVBを触るのもvisual studioを触るのも初めてだったのでかなり苦戦してしまいました...