blenderでgRPC(クライアント&サーバ)を動かした
背景
blenderのpythonにちょっかい出して遊んでいる。 gRPCがpythonでも動くことを知ったので、blender + gRPCをやってみることに。 今回は、macOS mojaveで取り組んだ。
準備
blenderにpipを入れる
以下のQiitaの記事のようにまずpipを入れる
クライアント編
.protoファイルから生成したモジュールを読み込むには
Googleの公式サンプルがそのままでは、.protoから生成したであろうモジュールが 見つからないエラーとなってしまった。
動的にモジュールのサーチパスを追加するには
以下のようにsysモジュールで対応できた
import sys sys.path.append('/Users/junichi/work/grpc/examples/python/helloworld/tmp')
この対応以外はそのまま動いた。
blenderでgRPCクライアントを動かした
諸事情により、ポートを50051から50053に変更して動かしている。
サーバーをblenderのpythonで以下のように動かしておく。
/Applications/blender.app/Contents/Resources/2.80/python/bin/python3.7m greeter_server.py
クライアントのコードは以下。
from __future__ import print_function import logging import bpy import os import sys import grpc sys.path.append('/Users/junichi/work/grpc/examples/python/helloworld') import helloworld_pb2 import helloworld_pb2_grpc def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): def draw(self, context): self.layout.label(text = message) #self.layout.label(message) bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) def run(): # NOTE(gRPC Python Team): .close() is possible on a channel and should be # used in circumstances in which the with statement does not fit the needs # of the code. with grpc.insecure_channel('localhost:50053') as channel: stub = helloworld_pb2_grpc.GreeterStub(channel) response = stub.SayHello(helloworld_pb2.HelloRequest(name='you')) #print("Greeter client received: " + response.message) ShowMessageBox(message = "Greeter client received: " + response.message) logging.basicConfig() run()
実行結果
サーバ編
Google純正のgrpcモジュールのthreadを使った方法だとblenderが即死
blenderのGUIにかかわる処理を行わなければセーフなのだけれども、 例えば、クライアント編のようにダイアログを受信時に表示するなどしたとたん、 blenderが即死する。
これは、おそらくメインスレッド以外でGUI関連の処理を行っているからだと思われる。
Pythonでも3.6以降はコルーチンが使える
threadの利用はなんとなく、Blenderと相性悪い予感はしていたので、困ったなぁとあきらめかけていたが、 最近の3系のpythonではthreadの他にコルーチンが扱えることを知った。
そこで調べてみると、grpcをピュアpythonで実装したgrpclibなるモジュールがあり、こちらはコルーチンを使用することが サンプルにも記載されていて、こちらを使うことにした。
純正grpcの.protoファイルから生成したモジュールがそのまま使えなかった
grpclibのREADMEに従い、.protoファイルからモジュールを生成した。
/Users/junichi/work/grpc/examples/python/helloworld/ mkdir tmp cd tmp cp ../ export PATH=$PATH:/Applications/blender.app/Contents/Resources/2.80/python/bin /Applications/blender.app/Contents/Resources/2.80/python/bin/python3.7m -m grpc_tools.protoc -I ../../../protos/ --python_out=. --python_grpc_out=. helloworld.proto
スクリプト実行中でも様子を見れるようにした
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
を入れることで、gRPCのクライアントから要求があるたびに、ビューポートが更新され、 それっぽい動きになった。
成果物
import sys import asyncio import time #from grpclib.utils import graceful_exit from grpclib.server import Server from grpclib.client import Channel import bpy sys.path.append('/Users/junichi/work/grpc/examples/python/helloworld/tmp') import helloworld_pb2 import helloworld_grpc def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): def draw(self, context): self.layout.label(text = message) bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) class Greeter(helloworld_grpc.GreeterBase): gy = 0 async def SayHello(self, stream): request = await stream.recv_message() message = f'Hello, {request.name}!' bpy.ops.mesh.primitive_cube_add(size=1, view_align=False, enter_editmode=False, location=(0, self.gy, 0)) bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) self.gy=self.gy+5 ShowMessageBox("This is a message : %s" % request.name) await stream.send_message(helloworld_pb2.HelloReply(message=message)) async def test(*,host='127.0.0.1',port=50052): loop = asyncio.get_event_loop() # start server server = Server([Greeter()], loop=loop) await server.start(host, port) print(f'Serving on {host}:{port}') await asyncio.sleep(30) server.close() await server.wait_closed() asyncio.run(test())
実行結果
学んだこと
- pythonのモジュールパスを動的に追加する方法。
- マルチスレッドでGUI操作には要注意。
- Pythonでもコルーチンがつかえる。
- grpclibモジュールを使えば、BlenderでもgRPCのサーバを立てられ、GUI処理も普通に記述できる。
- pythonスクリプト実行中に3D viewを更新する方法を知った。
課題
- マルチスレッドで処理を行い、メインスレッドになんらかの方法で通信してGUI処理を行う方法を探す
参考資料
- grpc.io
- github.com
- blender.stackexchange.com
- blender.stackexchange.com
- 『「コルーチン」とは何だったのか?』の裏話的な何か、とPythonコルーチン - bonotakeの日記