dothikoのカクカクワールド2D REBOOT

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか色々。

mypaint 2.0.0-alphaへの追従で四苦八苦

Mypaintの自作ブランチにupstreamのmergeを長期間怠っていたら、大変な作業量が待っていました…備忘録的に

目に付いた主な変更点

  • ビルドシステムがSConsからsetup.pyへ移行している
    • scons debug=trueに相当するのは、python setup.py build --debug
    • この結果、build/以下にlib.linux-x86_64-3.6 等のディレクトリが出来て、何かカッコいい。
  • scons cleanしておく必要がある(重要)
  • mypaintbrush 2.0が必要らしいので、gitから持ってきてmake installした。これが多分正しい。
    • エラーがでたので手当り次第に処置したけど、実はよくわかっていない…
  • python2.7でビルドするとlib/gettext.pyのfrom gettext import gettextでImportErrorが起きるように見える。
    • git cloneしたバニラなmypaint 2.0でも起きた
      • しかしpython3で一旦ビルド成功後は、python2.7でビルドし動かしても動いたりして二度と再現されない。要追跡。
    • いずれにせよ、そろそろpython3に本格的に乗り換えないとなぁ…という感じ。他の自作スクリプトも随時書き換えて行かねば。
  • update-alternativesで3をデフォにしてもいいけど、python-moinmoinが3非対応だったりして別の部分でつらい

_mypaintlib.soが見つからないエラーが出る

前述の通り、cleanしておかないとSConsで作られた古いmypaintlib.pyが lib/に残りこのエラーが起きる。削除すること。また、当然ながら以前のlib/_mypaintlib.so自体も削除しておくのが良い。

ビルドスクリプトの追加修正

独自のcppモジュールを追加するには、lib/mypaintlib.iのみならず、 setup.pyの576行目近辺

    mypaintlib = Extension(
        'lib._mypaintlib',
        [
            'lib/mypaintlib.i',
            'lib/fill.cpp',
            'lib/gdkpixbuf2numpy.cpp',
            'lib/pixops.cpp',
            'lib/fastpng.cpp',
            'lib/brushsettings.cpp',
            'lib/pyramidfill.cpp', # XXX for pyramid-fill
            'lib/opencv_util.cpp', # XXX for adjust tool
        ],

のように改変。(以前、SConstructにやっていたのと同じ)

Python3対応

  • SWIG(cpp)においてPyInt_AsLongはpython3のAPIでは存在しないので、PyLong_AsLongに変更する。(lib/pyramidfill.cpp)
  • from _future_ import division, print_functionはスクリプト先頭に置かねばならない。
    • git mergeでの自作部分とのコンフリクト検出時に不用意に済ませるとpython3ではこれがエラーとなる

importの厳密化

例えばgui/drawwindow.py でimport sizechangepopupとしてもエラーになるようになっている。 これは、

from . import sizechangepopup 
# もしくは
import gui.sizechangepopup as sizechangepopup # 若干美しくない気がする

と書かなければならない。これが物凄い多いので、普段からディレクトリを明示する等して徹底しておくべきだったorz

with_wait_cursor

このデコレータはgui.drawwindowから、gui.widgetsに移管されている。

python2のpropertyとタプルの分解で苦労した話

検索ワードが思いつかず苦労しました。アンパックというのですね。

foo, bar = (100, 200)

みたいなやつ。

例えばこんなクラスがあったとします

class _Node:
       
    def __init__(self, x=0.0, y=0.0):
        self._v = np.array( (x, y, 1) )

    @property
    def x(self):
        return self._v[0]

    @x.setter
    def x(self, v):
        self._v[0] = v

    @property
    def y(self):
        return self._v[1]

    @y.setter
    def y(self, v):
        self._v[1] = v

numpy.arrayを隠蔽して、簡単に行列とかかけられつつ 普段は感覚的に.x, .yとかで位置を変更できるという感じの。

こいつにこれをやっていました

n = Node()
n.x, n.y = (100, 101)

これをやるとダメ。

python2ではインスタンスnのプロパティにアンパックではなく、同名のxやyという属性が作成されてしまい、内部値は二重に存在している状態で、二度とプロパティでアクセスできなくなるのでございました。

なおpython3では普通に動くのでありました

ってこの話、遠い昔に見たような気がしなくもなく…(^^;

また一つpython3に乗り換える理由が増えた気がします

python-OpenCV2のfillPolyで苦労した話

python-opencv2にて、普通に [[x1, y1], [x2, y2], [x3, y3] ....] 的な配列を突っ込んだら、np.int32だろうが何だろうが以下のエラーが出続けた話。

OpenCV Error: Assertion failed (p.checkVector(2, CV_32S) >= 0) in fillPoly, file /build/opencv-L2vuMj/opencv-3.2.0+dfsg/modules/imgproc/src/drawing.cpp, line 2373

ググってたどり着いたのはやはりstackoverflow、本当に有り難い…のだけど、既に閉じてしまって今検索しても何故か見つからない。悲しい (`;ω;´)

そんなわけで書いておきますが要するにreshapeして

[ [[x1, y1] ], [ [x2, y2]], [[x3, y3]] ... ] 

のような超変な形に変えて、しかもfillPolyのptsに与えるやつをlistでくくる*1。これで動きました。

以下、動いたコードをコピペ

        pix = 255
        buf = np.zeros(bufshape, 'uint8')
        points = np.array(points).reshape((-1,1,2)).astype(np.int32)
        cv2.fillPoly(buf, pts=[points], color=pix)

このくだらねー処理に、何時間費やしたことか…

*1:これをしないと、Assertionは出なくなるがポリゴンフィルはされずに点だけ描画される

絶対に許せないmodemmanager@xubuntu 18.04.1

「絶対に許せない!」と言えばプリキュアの定番セリフですが(そうか?)

絶対に許せない現象が発生してしまいました

というのもワタクシ、実はお絵かきの左手デバイスに赤外線テレビリモコンが使えるのでは?という思いつきを胸に実際に使ってみていまして、その受信装置はarduino(の複製) + MCP2221でUSB変換してPCに接続→PC側でuinputを使うソフトがキーコードを発信というようになっているのですね。

そしてそれは、systemdでシステム起動時に自動起動するようになっております。

この状況下でubuntuっていうかxubuntuを18.04.1にアップデートして以来、

  • システム起動時にリモコンソフトが起動するが、機能しない
  • どーもオチているようである(systemdからは落ちたと認識されていないらしい)
  • systemctl restart ir_remocon.serviceすると動作するが、サスペンド→復帰するとまた動かない。
  • ソフト単体で試したら、たしかに復帰すると変換ソフトが超長いリモコンコードを受信し、アサーションを発して落ちている。
  • そこでリモコンコード異常が発生したらポート閉じて再度開く…と言う仕組みにしたら、「device reports readiness to read but returned no data」と言われるようになり、やはりまともに受信できない
  • そこでソフトを一旦落とし、再起動するとこれは動く。
  • acpidで/etc/pm/にスクリプトを置いてresumeでsystemctl restart ir_remocon.serviceするようにしてみたが、駄目

毎回愚直にターミナルからsystemctl restartしてたんですが、いい加減なんとかしなければ…と追ってみたのですが、上記のように自分には想定外の不可思議な現象が起きておりました。

んで/var/log/syslog していてようやく気づきました。

Dec  5 01:08:16 mydesktop ModemManager[649]: <info>  Creating modem with plugin 'Generic' and '1' ports
Dec  5 01:08:16 mydesktop ModemManager[649]: <warn>  Could not grab port (tty/ttyACM0): 'Cannot add port 'tty/ttyACM0', unhandled serial type'

誰だコイツ > ModemManager

いつの間に入ったのか今までは悪さをしなかったのかわかりませんが、どうもコイツが全てのシリアルポートを塞いでいるっぽいですな…そしてシステム起動・復帰時に先に塞いでいるので我がir_remocon.serviceが失敗すると

そう考えると device reports readiness〜エラーとも辻褄が合う。

そこでバッサリapt-get remove modemmanagerしました。

どうもサスペンド復帰しても大丈夫になりました、「今のところは」 あとプラセボ効果かもですが…デスクトップ描画も16.04のときのサックリサクサクに戻った気がするのですよ。気の所為かもですがね。

まぁ、本当にmodemmanagerが悪かったのかは実際の所不明w

systemdでstopした時にpythonのfinallyが呼ばれない問題について

systemdでsystemctl stop hoge.serviceすると、基本的にSIGTERM -> SIGKILLが送られてdaemonプロセスが終了します。

しかしながら、pythonのfinally節はSIGTERMで終了した場合は呼びだされません。 そのため、pythondaemonを作った時、finallyで必ず呼ばれることを期待して記述された終了処理は呼ばれずに終わるという…

これでハメられた…まぁ具体的にはRaspberry PiのGPIOをお手軽にRPi.GPIOでadd_event_detectしていたのですが、サービス化していたので何も考えずにstopしたら、テストでRuntimeErrorを吐くようになってしまいました。

finallyでcleanup()を呼んでたので、SIGTERMで終了時は呼ばれなかった結果、プロセス終了後も握り続けているのか?こうなった

dothiko@raspberrypi:~/python/usbcamcap $ ./usbcamcap.py -p 12345 -f camsconf.json 
- GPIO pin 4 as Button, 3 as LED.
./usbcamcap.py:296: RuntimeWarning: This channel is already in use, continuing anyway.  Use GPIO.setwarnings(False) to disable warnings.
  gp.setup(LED, gp.OUT)
Traceback (most recent call last):
  File "./usbcamcap.py", line 439, in <module>
    camera = Camera(args.cameras, confs)
  File "./usbcamcap.py", line 204, in __init__
    self.init_gpio()
  File "./usbcamcap.py", line 298, in init_gpio
    gp.add_event_detect(BTN, gp.BOTH, callback=self._gpio_callback)  
RuntimeError: Failed to add edge detection

ちな、このusbcamcap.pyというのは自作のUSBカメラキャプチャスクリプトで、カメラを握り続けてhttpでリクエストした時に画像を送るものです。んでもってこの中で同時にGPIOでボタンを見ていて、ボタンが押されるとシャットダウンするようにしています。 普段はラズパイを監視カメラとして使い、整備・移動する時はボタンでシャットダウンして電源断という目論見です。

最近のネットカメラはゴミばかりあまり気に入ったものがなく、スマホでアプリというものばかり。それもお手軽で良いのでしょうが外部のサーバや専用アプリを必要としたりして、しかもそれがOSのバージョンに相性があったりするなど、使い勝手が実に良くない。 もはや自分的には監視カメラはラズパイで自作する他無いという感じです。

話がそれてしまいました。

ともかく、どうすれば直るのか悩みましたがあっさり諦めてリブートしました(^^;

んで調べた結果、対策は2つあって

  • serviceファイルのservice節でKillSignal=SIGINTを記述して、syetemdのシグナルを変える。
  • python側でSIGTERMを捕捉してsys.exit()を呼び出す

後者の方はsignalモジュールでSIGTERMにハンドラを付けて、その中からsys.exit()をコールするわけです。これでsys.exit()がfinallyを呼んでくれるので問題がなくなります。

以下参考用のテストプログラム

import time
import signal
import sys

def termed(signum, frame):
    print("SIGTERM!")
    sys.exit(0)

def main():
    signal.signal(signal.SIGTERM, termed)
    try:
        while True:
            print('loop')
            time.sleep(3)
    finally:
        print("CALL FINALLY")

if __name__ == '__main__':
    main()

まぁどっちでもいいんですけど、後でうっかりパターンを考えるとSIGTERMを捕捉したほうがいいかな?

ubuntu 18.04.1でapt upgradeに失敗

正確には何をしたらおかしくなったのか分かりませんが… また、実際焦っていたのでエラーメッセージなども保存していない。スミマセン。

  • まず普通のデスクトップアプリ「ソフトウェアの更新」で更新しようとした
  • しかし、幾つかファイルがダウンロードできずに更新に失敗
  • そこでサーバを日本からメインに変更(これが悪かったか?)
  • やはり失敗
  • ターミナルからapt update & apt upgrade
  • libgs9-commonのバージョンが合わないのでインストールが出来ないという内容のメッセージが出る。
  • このバージョン番号が末尾が18.04.2なのですが、何かが間違ってないっすかねぇ?
  • apt --fix-broken installしろと出たのでやってみる
  • 無駄無駄無駄ァーッ という感じでやっぱlibgs9がアカンのでダメとなる
  • 以降、apt removeもできなくなる (apt --fix-broken installしろと出続ける)

というわけで困ったのですが、検索して得た以下の情報を元にaptのステータスをいじったところナントカ治りました(汗 そのURLを貼りたいのですがこれまた失念…stackoverflowだったと思ったのですが、ubuntuフォーラムかもしれない。 ともかく。

cp /var/lib/dpkg/status /var/lib/dpkg/status.bak # 念の為、今のステータスファイルを保存
cp /var/backups/dpkg.status.*.gz /var/lib/dpkg # すべての連番ファイル圧縮バックアップがコピーされる
gunzip -d /var/lib/dpkg/dpkg.status.*.gz # バックアップを展開する。
cp -f /var/lib/dpkg/dpkg.status.n /var/lib/dpkg/status # nは任意の番号。もっとも大きい番号のが直前のようだ。
# この状態で、以前は実行不可能だったaptが実行可能になっている。

という情報を得たのでやってみたのですが、apt update->upgradeすると、結局libgs9で同じことに。 そこでふとひらめきました。

「statusファイルを復帰させたらそこでapt remove libgs9-commonする」

という。 あとバージョン番号がなんか18.04.2という、ん〜まだリリース前じゃね?みたいな雰囲気のファイルだったので、もしかしてと思い、upgradeではなくdist-upgradeにしてみました。 結局、やったことは

statusファイル復帰
↓
apt remove libgs9-common
↓
apt update
↓
apt dist-upgrade

そしたら無事終了しました! まぁ偶然サーバのほうで何か修正が入っただけかもですが(^^;

ではでは〜

100均で見かけた電子工作向きっぽい容器

昔は自作の電子工作機器を収納するといえばブルジョワ都会人ならタカチ等のちゃんとしたケース*1、田舎だとせいぜいタッパー類似の食品密閉ケースがありがちなパターンでした。

でも密閉ケースは軟質樹脂で接着もほぼ無理なら塗装もほぼ出来なかった*2んですね…

そんでもって今時は廉価で塗装も可能な硬質スチロール樹脂の容器が簡単に入手できる時代となってますね、100均のお陰で*3

そこで今回は中々良いケースを見つけてきたのでそれを紹介する記事なのでした。

今回探してきた条件は3つ

  • ユニバーサル基板+センサー類が入る大きさ
  • 透明で硬質樹脂
  • 蓋がある

まぁ、蓋はぶっちゃけ無くても良かったのですが(板を貼ってもいいし、同じトレイを2つ向かい合わせでくっつけてもよい)、あるほうが面倒でなくてありがたいですね。

まずはサナダ精工のクリアケース ミニ

f:id:dothiko:20180919232956j:plain:w320

蓋が主張し過ぎの気もしないでもないですが、逆に蓋を底面にしても良いかと思われます。

f:id:dothiko:20180919233124j:plain:w320

ユニバーサル基板*4を収納してみたところはこちら。残念ながら、「ケースを横置きで、基板を縦に入れる」には1mmほど基板が大きいので入りません。まぁ多少削るか、別タイプのユニバーサル基板を使えばいいだけですけどね。

以前はLEDライトケースにこだわって居ましたが別にコレでいいかも…あっ、LEDライトケースには電池ボックス&スイッチ付属という超大きなメリットがありますね。

次は和泉化成のクリアケース ロングです。

f:id:dothiko:20180919233602j:plain:w320

なんとこれ、蓋が一体型で可動式なのですよ〜

f:id:dothiko:20180919233629j:plain:w320

これまた、ケース横置きで基板横置きしか無理です。

f:id:dothiko:20180919233652j:plain:w320

これはもう1mmとかいうレベルではなく完全に無理ということで。まぁ、ケース自体が長いのでいかようにもなるかと。

実は前も書いたかもですが、トイレ照明(&将来的には換気扇も)のセンサー + 赤外線リモコンでの完全自動化を目論んでいまして、タカチ的なABS樹脂ケースは幾つか買い込んであるものの、貧乏性が災いして可能な限り使いたくねえっ!という感じなのでした(^^;

まぁ、センサーや赤外線LED用の窓を綺麗に四角く切り抜くのがめんどいということと、失敗とかすると無駄に穴のあいたケースになってしまったりして悲しいですからね…*5

双方ともスチロール樹脂ゆえ、塗装も接着剤も簡単に効くのはよい*6のですが、耐衝撃性に弱く多少のことで傷つきやすい割れやすい、消しゴムやそのカスが長時間ひっつくと可塑剤のガスで溶けるなどの問題点があります…そのへんを考慮して場所を選んで使えば良いかと思われます。

ではでは〜

*1:ブルジョワの特注とかは無しでおながいしますw

*2:染めQなら行けるのかなぁ?

*3:特にセリアが好きですが、これらの製品は別段セリア限定でもない模様

*4:秋月電子で売っている「片面ガラスコンポジット・ユニバーサル基板 Cタイプ」

*5:250円とかだから別にいいじゃんという感じかもだけど、秋月中毒患者としてはこの250円で他のパーツが買えるとか思ってしまうのですな

*6:なお、今時はセメダインのスーパーXやコニシのウルトラ多用途SUなどの優秀なシリコーン系接着剤もあるので、溶剤系接着剤にこだわらなければ樹脂の選択肢も広がるのですが