HyでBlenderのAdd-on Tutorialをやっていく

この記事は、[Lisp Advent Carendar 2019](https://qiita.com/advent-calendar/2019/lisp) の 8日目の記事です。

# この記事の要旨
Python 上で動作する Clojure ライクな Lisp の方言である Hy を使い、DCC ツールである Blender の Add-on を作成してみます。

- Hy
 - [ドキュメント](http://docs.hylang.org/en/stable/)
 - [GitHub リポジトリ](https://github.com/hylang/hy)

- Blender
 - https://www.blender.org
 - [Blender 2.81 Reference Manual](https://docs.blender.org/manual/en/latest/)
 - [Blender 2.81 Python API Documentation](https://docs.blender.org/api/current/)

# 対象読者
- Python でやっていることを Lisp でやりたい人
- Blender を Lisp でいじりたい人

# 環境構築
以下、Windows 10 の場合です。
他 OS の方は適宜読み替えてください（多少の説明はします）。

## Python の環境構築
まず、Python 3 (64bit) をインストールします。
現時点 (2019/12) 最新の 3.8 系か、一つ前の 3.7 が良いでしょう。
公式サイトからインストーラを落としてきてインストールします。
32bit 版をインストールしないように注意しましょう。

Python 3 がインストール出来たら、環境を汚さないために Python の仮想環境である ```venv``` を作成し、そこに Hy をインストールします。今回は、ホームディレクトリ下の ```.virtualenvs``` ディレクトリ内に、```venv_hy_blender``` という名前で作成します。
リリース版最新の Hy 0.17.0 にはまだ Type Annotation が実装されていないため、GitHub から 最新の master ブランチをインストールします。

Windows 10 の cmd では下記のように入力します。

```dos
$ cd %USERPROFILE%
$ mkdir .virtualenvs
$ cd .virtualenvs
$ py -3 -m venv venv_hy_blender
$ cd venv_hy_blender
$ Scripts\activate.bat
$ python -m pip install git+https://github.com/hylang/hy.git
$ deactivate.bat
```

```py``` は Windows 専用のコマンドなので、他の OS の場合は ```py -3``` の部分をその OS での Python 3 インタプリタ起動コマンドに置き換えます（大抵、```python3``` または ```python``` です）。
また、仮想環境を有効にするコマンド activate も OS や Shell ごとに異なるため、下記リンク「venv --- 仮想環境の作成」内の「仮想環境を有効化するためのコマンド」を参照ください。

- [venv --- 仮想環境の作成](https://docs.python.org/ja/3/library/venv.html)

## Blender の環境構築
公式から現状最新の Blender 2.81 のインストーラを落としてインストールします。

# Blender の Add-on 作成
以下が、Blender Add-on Tutorial の Bringing It All Together の Python コードを Hy に書き換えたものです。

- [Add-on Tutorial](https://docs.blender.org/manual/en/latest/advanced/scripting/addon_tutorial.html)

Python を使っている人ならば、Python ほぼそのままで書けるということが分かると思います。
これを ```blender_sandbox``` ディレクトリに作成するものとします。

Hy の現行のドキュメントにない Type Annotation だけ説明しますと、```(^型 変数名)``` です。詳細は「[Implement PEP 3107 & 526 annotations](https://github.com/hylang/hy/commit/4845565caa71cc155d979f8909990691db45dd26)」を参照ください。

```hylang:cursor_array.hy
(import bpy)

(setv bl-info {
  :name "Cursor Array"
  :blender (, 2 81 0)
  :category "Object"
})


(defclass ObjectCursorArray [(. bpy types Operator)]
  "Object Cursor Array"
  (setv bl-idname "object.cursor_array")
  (setv bl-label "Cursor Array")
  (setv bl-options #{"REGISTER" "UNDO"})

  (^((. bpy props IntProperty) :name "Steps" :default 2 :min 1 :max 100) total)

  (defn execute [self context]
    (setv scene (. context scene))
    (setv cursor (. scene cursor location))
    (setv obj (. context active-object))

    (for [i (range (. self total))]
      (setv obj-new ((. obj copy)))
      ((. scene collection objects link) obj-new)

      (setv factor (/ i (. self total)))
      (setv (. obj-new location) (+ (* (. obj location) factor) (* cursor (- 1.0 factor)))))

    #{"FINISHED"}))


(defn menu_func [self context]
  ((. self layout operator) (. ObjectCursorArray bl-idname)))

; store keymaps here to access after registration
(setv addon-keymaps [])


(defn register []
  ((. bpy utils register-class) ObjectCursorArray)
  ((. bpy types VIEW3D-MT-object append) menu-func)

  ;; handle the keymap
  (setv wm (. bpy context window-manager))
  ;; Note that in background mode (no GUI available), keyconfigs are not available either,
  ;; so we have to check this to avoid nasty errors in background case.
  (setv kc (. wm keyconfigs addon))
  (when kc
    (setv km ((. wm keyconfigs addon keymaps new) :name "Object Mode" :space-type "EMPTY"))
    (setv kmi ((. km keymap-items new) (. ObjectCursorArray bl-idname) "T" "PRESS" :ctrl True :shift True))
    (setv (. kmi properties total) 4)
    ((. addon-keymaps append) (, km kmi))))

(defn unregister []
  ;; Note: when unregistering, it's usually good practice to do it in reverse order you registered.
  ;; Can avoid strange issues like keymap still referring to operators already unregistered...
  ;; handle the keymap
  (for [(, km kmi) addon-keymaps]
    ((. km keymap-items remove) kmi))
  ((. addon-keymaps clear))

  ((. bpy utils unregister-class) ObjectCursorArray)
  ((. bpy types VIEW3D-MT-object remove) menu-func))


(when (= --name-- "__main__")
    (register))
```

# Blender 起動用バッチファイル作成
```blender_sandbox``` ディレクトリに ```Blender2.81.bat``` という名前で起動用バッチファイルを作成します。
```venv_hy_blender``` の ```site-packages``` と、作成した Add-on のディレクトリ（```blender_sandbox```）にパスを通し、Blender を起動するバッチファイルです。

```site-packages``` は Python のサードパーティー製モジュールのインストール先ディレクトリで、Python 本体とそれぞれの仮想環境が持っています。
本来であれば ```activate```・```deactivate``` で仮想環境を切り替え、その ```site-packages``` を使うものですが、今回は Blender 2.81 内蔵の Python 3.7.4 に ```venv_hy_blender``` 内の Hy をインポートして使うために、若干邪法っぽいことをしています。

```dos:Blender2.81.bat
@ECHO OFF
SETLOCAL

SET THISDIR=%~dp0
SET PYTHONPATH=%THISDIR%;%USERPROFILE%\.virtualenvs\venv_hy_blender\Lib\site-packages;%PYTHONPATH%
SET BLENDER_EXE="C:\Program Files\Blender Foundation\Blender 2.81\blender.exe"

%BLENDER_EXE%
```

# 実行
このバッチファイルから Blender を起動し、上のメニューバーの ```Scripting``` を選択します。
Console から上記スクリプトをインポートするために、下記のように入力します。

```python
>>> import hy
>>> import cursor_array
>>> cursor_array.register()
```

<img width="1920" alt="2019-12-08_13h08_05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543140/aff7255d-de2e-42cb-679f-5b09cff5f5a3.png">


これで、```Layout``` > ```Object``` から ```Cursor Array``` を実行できるようになります。やってみましょう。

<img width="1920" alt="2019-12-08_12h58_51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543140/0b6d5dea-7e30-004a-9b68-685330496ecc.png">

<img width="1920" alt="2019-12-08_13h09_11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543140/9c7eae47-d04c-fb80-4565-a85d2d5524f0.png">

<img width="1920" alt="2019-12-08_13h09_18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543140/814ba9e4-bb48-4e6b-0237-78dcc50f9ece.png">

<img width="1920" alt="2019-12-08_13h09_25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543140/872d69b8-c021-fb59-9f5d-795dc8e68d8d.png">

<img width="1920" alt="2019-12-08_13h09_35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543140/2061b31a-ab7a-1513-2123-00c46e135cb5.png">

<img width="1920" alt="2019-12-08_13h09_45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543140/022b84b5-d5b4-06ae-71c8-19d57f2f424a.png">

はい。

# まとめ・補足
- 仮想環境 ```venv``` 上で Python の環境構築すると元環境が汚されないので良いです。
- Hy 0.17.0 ではまだ Type Annotation が使えませんが、今後使えるようになります。がっつり Hy を使いたい人は正式リリースを待ちましょう。
- Python の邪法を駆使するなどして、いろいろなところで（例えばツール組み込みの Python 上で）Hy を動かすことができます。
- 3DCG 界隈のツールは、ほとんどがスクリプト言語として Python を採用しているため、Hy も動きます。ただし現状は Blender などの一部のツール以外は Python 2 です。Hy は 0.17.0 まで Python 2 もサポートしていますが、今後はサポートしなくなるとのことです。