non vorrei lavorare

昔はおもにプログラミングやガジェット系、今は?

RemacsのmacOSのGUI版が復活して動かせるようになってた

こんばんは、最近はkindle fireで兄弟でFPS系のゲームをやっていることが多くなってきました。@kjunichiです。

背景

abrakatabura.hatenablog.com

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もフツーに動く。

www.youtube.com

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で知った。

github.com

関連するrebuild.fmの回

関連記事

15年前の記事

12年前の記事

7年前の記事

blenderでgRPC(クライアント&サーバ)を動かした

こんばんは、子供達がなぜかプレデターブームです。紙とセロハンテープで多数のプレデターを作っています。@kjunichiです。

背景

blenderpythonにちょっかい出して遊んでいる。 gRPCがpythonでも動くことを知ったので、blender + gRPCをやってみることに。 今回は、macOS mojaveで取り組んだ。

準備

blenderにpipを入れる

以下のQiitaの記事のようにまずpipを入れる

qiita.com

クライアント編

.protoファイルから生成したモジュールを読み込むには

Googleの公式サンプルがそのままでは、.protoから生成したであろうモジュールが 見つからないエラーとなってしまった。

動的にモジュールのサーチパスを追加するには

以下のようにsysモジュールで対応できた

import sys
sys.path.append('/Users/junichi/work/grpc/examples/python/helloworld/tmp')

この対応以外はそのまま動いた。

blenderでgRPCクライアントを動かした

諸事情により、ポートを50051から50053に変更して動かしている。

サーバーをblenderpythonで以下のように動かしておく。

/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()

実行結果

f:id:kjw_junichi:20190318211402p:plain

サーバ編

Google純正のgrpcモジュールのthreadを使った方法だとblenderが即死

blenderGUIにかかわる処理を行わなければセーフなのだけれども、 例えば、クライアント編のようにダイアログを受信時に表示するなどしたとたん、 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())

実行結果

www.youtube.com

学んだこと

  • pythonのモジュールパスを動的に追加する方法。
  • マルチスレッドでGUI操作には要注意。
  • Pythonでもコルーチンがつかえる。
  • grpclibモジュールを使えば、BlenderでもgRPCのサーバを立てられ、GUI処理も普通に記述できる。
  • pythonスクリプト実行中に3D viewを更新する方法を知った。

課題

  • マルチスレッドで処理を行い、メインスレッドになんらかの方法で通信してGUI処理を行う方法を探す

参考資料

関連記事

1年前の記事

puppeteerで動的にウインドウの大きさを変更するには

こんばんは。先週は次男が発熱で実家に居て、週末に自宅に帰ってきた@kjunichiです。

背景

puppeteerで非ヘッドレスモード(Chromiumが画面に表示されるモード)でブラウズしている様子をキャプチャーする際にChromiumのウインドウのサイズを変更したくなった。

page.setViewportでは、ウィンドウのサイズは変わらない

page.setViewportでは、ウインドウのサイズは変わらなかった。puppeteerでpdf等のファイルの保存した際の画面サイズは このメソッドで変更されるが、画面に表示されているウインドウのサイズは変わらなかった。

Chrominumのウインドウのサイズを変更するには

以下のISSUEを見つけた

github.com

ここに、ヒントになるコードが載っていた。

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のチャンネルに動画としてアップした。

youtu.be

参考資料

moznion.hatenadiary.com

関連記事

Chrome DevTools Protocolつながり

10年前の記事