RemacsのmacOSのGUI版が復活して動かせるようになってた
こんばんは、最近はkindle fireで兄弟でFPS系のゲームをやっていることが多くなってきました。@kjunichiです。
背景
build
git clone https://github.com/remacs/remacs.git cd reamcs ./autogen.sh ./configure --with-ns --prefix=/opt/remacs make make install
make install
しないと、--with-nsをつけてconfigureしていると、Remacs.appを起動しても即死するだけになるので注意。
また、make installしても--with-ns付きの場合は、特にprefixで指定したフォルダにコピーされるようなこともない模様。
実行結果
spacemacsもフツーに動く。
git pullしたら、ビルドできなくなる問題
git pull make clean make
すると結構な確率で以下のようなエラーでビルドが失敗する。
Invalid time specification make[2]: *** [loaddefs.el] Error 255 make[1]: *** [../lisp/loaddefs.el] Error 2 make: *** [src] Error 2
傾向と対策
head config.log
この出力される./configureのオプションを暗記しておく。
rm -rf * git reset --hard
headコマンドの先ほどの結果を参考に
./configure --with-ns --prefix=/opt/remacs make
この方法は、以下のISSUEで知った。
関連するrebuild.fmの回
関連記事
15年前の記事
12年前の記事
7年前の記事
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の日記
関連記事
1年前の記事
puppeteerで動的にウインドウの大きさを変更するには
こんばんは。先週は次男が発熱で実家に居て、週末に自宅に帰ってきた@kjunichiです。
背景
puppeteerで非ヘッドレスモード(Chromiumが画面に表示されるモード)でブラウズしている様子をキャプチャーする際にChromiumのウインドウのサイズを変更したくなった。
page.setViewportでは、ウィンドウのサイズは変わらない
page.setViewportでは、ウインドウのサイズは変わらなかった。puppeteerでpdf等のファイルの保存した際の画面サイズは このメソッドで変更されるが、画面に表示されているウインドウのサイズは変わらなかった。
Chrominumのウインドウのサイズを変更するには
以下のISSUEを見つけた
ここに、ヒントになるコードが載っていた。
puppeteerで非ヘッドレスモードで表示されているウインドウのサイズを動的に変更できた
上記のISSUEで紹介されているコードを一部変更して動く以下のコードができた。
puppeteerでAPIは結局のところ、用意されておらず。
browser._connection
でchromeと通信するためのコネクションを使って、直接DevTools Protocolをしゃべる事で、ウインドウサイズの 変更を行っている模様。
DevTools Protocolでウインドウのサイズを変更する際に必要なwindowsIdを以下のように2段階で取得している模様。
const { targetInfos: [{ targetId }] } = await browser._connection.send( 'Target.getTargets' ) const { windowId } = await browser._connection.send( 'Browser.getWindowForTarget', { targetId } )
これらをまとめて以下のようなコードで、動的にpuppeteerで表示されたChromiumのウインドウのサイズを 変えることができた。
const puppeteer = require('puppeteer') const run = async () => { const browser = await puppeteer.launch({ headless: false, args: ['--disable-infobars', ] }) const page = await browser.newPage() await page.goto('https://www.google.com/') await page.waitFor(1000) const { targetInfos: [{ targetId }] } = await browser._connection.send( 'Target.getTargets' ) const { windowId } = await browser._connection.send( 'Browser.getWindowForTarget', { targetId } ) console.log(`windowId = ${windowId}`) const myresize = async (c, wid, w, h) => { await page.setViewport({ width: w, height: h }) await c.send('Browser.setWindowBounds', { bounds: { height: h, width: w }, windowId: wid }) } // Resize. myresize(browser._connection, windowId, 1300, 600) await page.goto('https://kjunichi.github.io/') await page.waitFor(3000) // Resize. myresize(browser._connection, windowId, 640, 480) //await page.goto('https://github.com/kjunichi/') await page.goto('https://www.apple.com/') await page.waitFor(6000) await browser.close() } run()
実行結果
実行委結果を以下のYouTubeのチャンネルに動画としてアップした。