TouchDesignerで作った絵をTouchDesignerから印刷する

この記事は[TouchDesigner Advent Calendar 2022](https://qiita.com/advent-calendar/2022/touchdesigner) 17日目の記事です。

今年は、TouchDesigner使いの人間が抱く3大欲求の一つである、
**TouchDesignerで作った絵をTouchDesignerから印刷する**やり方をご紹介したいと思います。
※Window用です。

今回のプロジェクトファイルは下記にアップしています。

https://github.com/atsonic/TouchDesigner-print

## システム構成
まずはシステム構成を考えます。
![スクリーンショット 2022-12-16 122221.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/3ab9cab7-e9ef-94ec-f795-84065b9b3fe5.png)

本来であれば **venv作って必要なライブラリインストールしてTouchDesignerの中から印刷命令！** と行きたいところなのですが、印刷に使うPythonライブラリである **「[pywin32](https://pypi.org/project/pywin32/)」が仮想環境から使えないらしい** ということがわかりました。

ということでsubprocessで外部pythonを実行しています。
※pythonはグローバルのpythonを使います。

それと出力ファイル名をバリデートしている箇所がありますが、フォルダ監視で間違って変なファイルを検出して印刷してしまわないようなシステム構成にしています。

## プログラム
そして実際のプログラムです。
TouchDesignerのオペレーターはそれほど多くありませんね。
![スクリーンショット 2022-12-16 154701.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/d43c713e-3a60-f091-960d-f1a55833bac6.png)

以下流れ
1. 画像出力先のフォルダ（今回はimagesフォルダ）を外部のPythonで監視開始（後述）
2.  **Button** 押下で**null1**のCookが走る
3.  **CHOP EXECUTE DAT（createFileName）** でファイル名を生成して**Text DAT（fileName）** にタイムスタンプのファイル名を出力し、**moviefileout1**のrecordを実行
4. 監視プログラムが画像ファイルを検出しそのファイル名をOSCで通知、**udpin1**に検出したファイル名を送信
5. 4のタイミングで**udpin1_callbacks**が走り、受信したファイル名と、3で生成したファイル名を比較
6. 5のバリデーションで問題なければ**print**を実行。※本当はここでプリント処理を記述したかったのですが、**subprocessで外部pythonを実行**しています。（後述）
7. 外部の印刷用pythonが走り、印刷が完了すると**udpin2**に成功の通知が来る
※今回はエラーハンドリングは行わず成功が返るだけになっています。


### フォルダ監視プログラム
これはpythonの **[Watchdog](https://pythonhosted.org/watchdog/)** をpipでグローバルにインストールして使っています。
コマンドプロンプトで実行してシステムに常駐させます。

```python
import os
import time
import sys
import socket
from argparse import ArgumentParser

from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer

class MyWatchHandler(FileSystemEventHandler):
    def __init__(self):
        super().__init__()

    def on_any_event(self, event):
        pass

    def on_created(self, event):
        #ファイルが生成されたときにUDPでファイル名を送信
        sendUDP(os.path.basename(event.src_path))

def msg_to_bytes(msg):
    return str(msg).encode('utf-8')

def sendUDP(basename):
    upd_ip = "127.0.0.1" #TouchDesigner側（udpin1）と合わせる
    udp_port = int(7000) #TouchDesigner側（udpin1）と合わせる
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(msg_to_bytes(basename), (upd_ip, udp_port))

def monitor():
    event_handler = MyWatchHandler()
    observer = Observer()
    observer.schedule(event_handler, '../images/', recursive=True)#監視するフォルダを指定する
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


def main():
    monitor()


if __name__ == "__main__":
    main()
```

### subprocess部分
ここは去年のアドカレでも少し記述しました。同じように記述します。

https://qiita.com/atsonic/items/c944998351b156e28cc7
```python
import os
import subprocess

#オペレーターの名前は適宜変更
port = str(op('udpin2').par.port.val) 
file = '{}/images/'.format(project.folder) + str(op('fileName').text)

#自分の環境のPythonを指定
cmd_python = 'C:/Users/[user]/AppData/Local/Programs/Python/Python39/python.exe'

#実行するpythonファイルを指定
cmd_python_script = '{}/scripts/print.py'.format(project.folder)
python_args = [cmd_python, cmd_python_script]

#OSCに使うポート番号と、印刷するためのファイルパスを引数で渡す指定
script_args = ['-p', port, '-f', file]
cmd_args = python_args + script_args
op('udpin2').par.clear.pulse()

#ここでプロンプトのウィンドウを非表示にする設定
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW

#コマンド実行
subprocess.Popen(cmd_args, startupinfo=si)
```

### 印刷のプログラム
下記の記事等を参考にpythonから印刷するコードを書いて実行するだけです。
その際にport番号やOSCの送受信の記述も入れています。

https://qiita.com/all_fort/items/b31853befe0a4ec0d032

実行するとwindowsのシステムで既定になっているプリンターのキューに入るようになっています。

以上で終わりです。
よいお年をー😀