PySideでIDLEイベントを捕まえるには??

 そもそもの話 PySideにはIDLEイベントがなくて、どうしよう?ってなったんですが、ググったら頭いい人がいました。

 emit SIGNAL when GUI Thread is idle in Qt?

IDLEイベントというのは、Windowプログラムが「現在お手隙な状態です」っていうタイミングを示すイベントって意味です。
要するに、GUIがなにもしてないので、なんか仕事ありませんか?って言ってくれる感じです。

 
 元はC++のコードなんですが、ざっくりPySide向けに書き直してみたんですが、これでいいのか良くわかんないんです…
 なんせイベントを上げたら次のaboutToBlockが発生するので、この実装を使うと実際には1コア占有しちゃう。
 IDLEで発行したイベントはカウントしない様にして欲しかったりするんですけどね…

python3x: parameterとして出てくる*argsと**kwargsって何を意味するの?

これも掲題の記事を見つけたメモ。

python3x: parameterとして出てくる*argsと**kwargsって何を意味するの?

 タプルの場合は引数の頭に*(アスタリスク1個)、辞書の場合は引数の頭に**(アスタリスク2個)付けていると、自動的に引数を展開して扱ってくれるんだけど、その辺の話。
 意外と必要になった時に、どうすんだっけ?って暫し考えることがあるので(特に辞書の時に…)メモっておく。。

Qtでスレッドを使う前に知っておこう

スレッド関連を調べていて、掲題の記事を見つけたのでメモ。

Qtでスレッドを使う前に知っておこう

内容としては、How To Really, Truly Use QThreads; The Full Explanationというブログ記事で語られる方法が最近のスレッド利用時の基本的なお作法になってますよ。的な話。

C++記事なんで、Python(PySide)で書き下してみたのが、こちら。
※全部のシグナルがこれで正しい設定なのか若干わかってないので、参考程度に。

いっぱいスレッド回そうと思ったら、こんな感じ?
※同時に表示されるタイミングがあるので、セマフォを使って順に処理するようにした。あと、処理終了時にウィンドウを閉じる設定も追加。

裏でスレッドを回しつつPySideのGUIの動作を滑らかにしたい

冒頭が一番最後、結論的な話で始めるけれど…

 実際に実装を書いて実行してみると、やっぱり場合により動作がおかしくて、「アプリケーションが停止しました」ってアプリが落ちたりする。
 ログを取ってみると、どうやら裏のプロセス実行が早すぎて表のUI処理が終わらないうちに次々と裏から要求が来てしまう。
 その為に表示処理が多重に呼び出されて、呼び出された関数が多すぎてスタックを食いつぶしているっぽい…という結論になった。

 ここにはまだ書いてないけど、裏から送られる処理を1つずつ順に片付けて、表の情報反映に必要な関数実行が多重化しないように制限を加えたら、安定して動くようになった。

 プロセス処理は、いろいろPythonを使ってると意識しなくてよかったことを意識させられてしまう。
 Pythonではなかなかしんどい部類の処理かもしれない。

うまく行かず、考え直してみた

 いろいろやってみて(後述)、PC替えたら正常に動作しないことが判明。
 調べてみると、PySide(というかPyQt?)は別スレッドから動かすことを許していない模様。
 自分のPCは4スレッドしか回らないのだけど、PCのスペックが高くて10~20スレッドが平気で動くハイスペックなPCだと、「Warning: QPixmapがどうたら」のようなメッセージがstdoutかstderrに吐かれてしまった…無念。

 
 どうにも、他の方法を検討する必要がある。
 とりあえず、ディレクトリ取る程度だとさほど処理を食わないのでスレッドでも平気だが、その後にファイルを読み込んだり、更に内容を読み取るような場合は別プロセス起動を考えないと、処理スレッドに移る度に数秒停滞という事が起こってしまう。

 感覚的には、処理毎に10ms程度のオーダーで処理を完了するような形にまとめないと、GUIが思うように動かなくなると思う。

 別スレッドで処理するサンプルをメモっておく。

いろいろやった事柄(時系列的には、ここがはじまり。)

参考ページ1
参考ページ2

 最初は参考ページ1で頑張っていたけど、どうにもならなくて…
 いろいろ試した上に、結局Pythonの並列処理モジュールを使わないとダメだってこと(※)が参考ページ2をベースにいろいろ試してみておおまかに理解できたと思う。
 ※掲題のGUIをスムーズに動かすって事が、厳しいってこと。

なにが引っ掛かるのか

 端的に言えば、PythonのGILの仕組みのせいで(いくらスレッドを複数回したところで)実際にはそれは並列処理ではないので、バトンリレーの要領でタスク切替が行われなければ無駄にスレッドが空回りしてしまう。

 処理A –> 処理B –> (処理A) .. みたいな流れをしたいのに、
 実際には、処理A ——-(不要な間)————> 処理B —(不要な間)–> (処理A) —(不要な間)–> … のようなことが起こる。

 不要な間の時には、処理が無駄な空回りをしているか、やって欲しくない処理を延々続けていたりする。
 無駄な空回りをしていると当然その間ほかのスレッドの処理は停滞してしまうので、GUIに至っては応答なし状態が続き、最悪アプリケーションエラーで不意に落ちることも。。

Pythonの並列処理モジュール

 この辺のモジュールのお世話になった。
 threading — スレッドベースの並列処理(@Python Doc)
 queue — 同期キュークラス

 簡単に言うと、並列処理モジュールを仲介させることでスムーズにスレッド間の処理が切り替わってくれるようになるので、無駄にスレッドが空回りしてしまう状況を回避できる。(ノンプリエンティブ・マルチタスクのイメージ。なるべく短い時間で処理を切り上げ、ほかに処理を渡す…という書き方。高速に切り替わるほどに、滑らかに処理されるようになる。)

 処理A –> 処理B –> (処理A) .. みたいな流れを作り出せる。

 今回は、「処理A -(queue)-> 処理B -(queue)-> (処理A)」 .. みたいに、Queueでバトンを渡す感じに作った。
 でもlistWidgetの都合…なのか、登録中にやたらpaint処理が走るせいで、ウィンドウが白くなっていることが多くてあまりよろしくない感じ。
 描画タイミングとかを考えると、登録方法にも手を入れないとダメっぽい…

その後

 いろいろゴチャゴチャ書いてしまったけど、結局のところ今回の件の場合はセマフォで解決(※)という話だった。

 ※実際は解決どころか、ここからがドツボ本番で…PCを変えたらWarning連発で、記事冒頭の考え直しの項に進みます。
  ただ処理的にダメって話ではなくて、それじゃまだ対策が足りない…という話だったんですが。

 参考ページ3

globでジェネレータ実装を使いたい

 Pythonのモジュールにglobという、ディレクトリリストを取得する機能を提供するものがある。

 とか書くと、Cドライブのルートディレクトリのディレクトリリストが取得できる。

 …で、普段はそんなに必要にならない気もするが、UI的に特にレスポンスを重視する場合、globの返答すら遅いことにイライラさせないようにしたい。なんていう要求もあったりする。
 そういう場合に欲しくなるのが、ジェネレータ実装。

 ジェネレータ実装を知るには、イテレータを知る方が早いと思う。
 イテレータは、検索すると「プログラミング言語において配列やそれに類似する集合的データ構造(コレクションあるいはコンテナ)の各要素に対する繰り返し処理の抽象化」という定義になる…小難しい。
 でも、そこにも書かれているように「配列やそれに類似する集合的データ」これを「各要素に対する繰り返し処理の抽象化」するもの。これがイテレータなので、Python的には「for文のinの後ろにくっ付けられるもの」がイテレータということ。

 イテレータは、配列のように順番に並べられたデータ集合から順番に1個ずつ取り出す機能。
 この「順番に1個ずつ取り出す」というのがポイント。ジェネレータ実装はイテレータの派生のようなもので、「まだ用意されてないデータ集合」がさもあるかのように「順番に1個ずつ取り出す」機能を提供できる。

 for文説明などに出てくるなんてことない処理だけど、このrange関数がジェネレータ。
 [0,1,2,3,4,5,6,7,8,9]と書かないで range(10)と書くことで [0,1,2,3,4,5,6,7,8,9]を生成している range関数。
 簡素に書くとこんな感じ。

 yield i の行は、return i みたいなもの。

yieldは、「yield行を実行した時点で一旦処理をその場で中断し、return文のごとく呼び出し先に値を返す。」という動作をしていて、イテレーションを提供するfor文などの繰り返し処理の中で、次の処理が必要になるとyieldの次行から処理を復帰させる。
なので、イテレーションを繰り返し実行してくれるステートメント内では、次々と処理復帰が行われて1回毎に必要な処理だけを実行することが出来る。
予めすべての要素を作る必要がないので、非常にレスポンスが良い処理を提供できるようになる。

 ジェネレータは、値を1つずつ生成しては yieldで値を返してくる。数千~数万件処理しないと関数から応答が返らないような状況があったとしても、見つけた傍から1つずつ応答を返してくれるので、素早い応答が可能になる。

 話をはじめに戻すと…

 このように指定すると、変数 hoge にジェネレータが生成され、print hoge.next()と実行する度に、値が取得できることが確認できる。

イテレータオブジェクトのnextメソッドは、for文の場合には自動的に呼び出してくれるようになっているので実行している意識はないが、内部的にはnextメソッドが呼ばれている。
英語だが、この記事を読むと詳細を知ることが出来る。

 これを使えば大量のディレクトリリストを処理する場合でも、逐一応答を返せるのでレスポンスの良い実装を書けそう。

そういえば、

 ググってたら「Pythonで再帰的にファイル・ディレクトリを探して出力する」というページを見つけた。
 自前で glob 的な処理を作る場合に参考になるかも。

 さらに、os.walkをググったら、こんなのも。
 「Pythonで、os.walk()を使って、特定のディレクトリを除いたファイル一覧を取得する
 まずos.walkの動きを理解しないとどうにもならないけど、不要な探索を省けるなんて素晴らしい。

気を付けないといけないのは、os.walkの吐き出した dirs への代入の際は、dirs[:] のようにスライスを使った記述をしておかないとNGって部分ですな。
もしくは、dirs.remove(‘削除したい名称’)のように書くか..

REPL ?

めも。

REPLとは「Read(読み取り) – Evel(評価) – Print((主に評価結果の)表示) – Loop(以上の繰り返し)」の略称。
BASICやPythonのコマンドモードみたいなやつのこと。

QTableWidgetで複数セル消そうとしたら無限ループ

イベントハンドリングしている場合に、無限ループにはまったのでメモ。

Set data om multiple selected items in TableView

要は modelの更新がトリガーだから、model.blockSignals(True)して更新せよ。という話だった。
(更新終わったら、model.blockSignals(False)に。)

あと、再描画は自前になるので widgetのrepaint呼んどく。

PySideのウィンドウが閉じる際のイベントをキャッチできない場合

思い出したので、めも。

Widget’s “destroyed” signal is not fired (PyQT)

終了時に保存処理とか入れてるつもりなのに、なんか知らないうちにプログラム終わって保存できてねーっすケド?!みたいな状況があったりする場合の対処法。
「windowクラスの setAttributeメソッドで、QtCore.Qt.WA_DeleteOnCloseを設定しておきなさい。」という話。

AETemplate(Python版)

めも。

AETemplateのPython版記述。
Mayaインストール先の devkit\other\pymel\examples\AETemplates.py がサンプル。
コードを見た通り、pymelが使われている。

How to add custom attribute to AETemplate with python?
OpenMaya/src/mayaToCorona/mtco_devmodule/scripts/Corona/AETemplate/AECoronaSurfaceTemplate.py

別の実装を見ても分かる通り、pymel.core.ui.AETemplateを継承して基本になるクラスを定義。
※devkit内の実装には _applyLocalizationメソッドが入っていて、表示文字列のローカライズ対応も考慮してる。

このクラスを継承して、テンプレート実装を書いていく。
※devkit内の実装では テンプレート適用時の初期化処理と、アトリビュートエディタのUI実装が別クラスになっているけど、別にそんな必要はないらしい。AECoronaSurfaceTemplate.pyでは、UI実装とテンプレート適用時の初期化処理はひとつのクラス内で処理している。

テンプレートの初期化実装は、こんな感じ。
mel.beginScrollLayout ~ self.endScrollLayout メソッドまでが、mel版のAETemplateの「global proc AE~Template( string $nodeName )」というプロシージャ定義に対応。
LocalizedTemplate.__init__ メソッドは、ローカライズ文言の読み込み。
mel.AEswatchDisplay メソッドは、名前通りシェーダの系統の冒頭に表示される画像のスウォッチ表示のテンプレート呼び出し。

UI実装は、ノードによりけりになると思うので、参考になるのかいまいち不明。
お作法に従うなら、初期化処理で読んでいる buildbodyメソッドを __init__メソッドとは分けて書いておこう。ということくらいか。
※書き方はAETemplate(mel版)の書き方を覚えないと例が少なくて大変だと思う。melで覚えたら、Python側はそれに対応する形で書かれている感じ。

AETemplateの基礎

めも。

Intro to AETemplates
Google翻訳版

英語なんで、掻い摘んで列挙しておく。

AETemplateの基礎的なことが書かれている。

まず、最低限の定義内容。

上記のままだと、実際のノードを表示した場合に、アトリビュートが馬鹿みたいに大量に列挙されて非常に見づらい状態になる。
それを避けるには、必要なものに限って表示させる必要がある。

“transform”ノードタイプの場合の、基礎的な定義内容。

「stringAttr, enumAttr, numAttr」のコントロールを追加してみる。
※個々の項目名の項目が、アトリビュートエディタの冒頭に追加される。
※当たり前だけど、追加するアトリビュートが存在しなければ項目追加もされない。(例の場合、stringAttr, enumAttr, numAttrという名称のアトリビュートが存在する前提。)

追加部分をカスタマイズする方法の例。
※ stringAttrを “TXT:”という項目名に変更。
※ numAttrを “NUM:”という項目名に変更。

enumのコントロールのカスタマイズはやや複雑で、editorTemplate -callCustom フラグで動作を指定する必要がある。

対応するプロシージャは、こんな感じで。

 根本的には、ノードを自分で作った場合に威力を発揮する機能なので、まずはノードを作ってみないとピンとこない気がする。
 あとは、どうしてもビルトインでMayaのUIに既存ノードの編集を楽にする機能を盛り込みたい場合か…

 テンプレートの定義自体は、起動時にstartup処理の一環として読み込まれるものなので、定義を変更した場合は再起動しないとダメかも。
 (※melのrehashで再定義できれば…と思ったけど、どうもダメっぽい。)

そもそもAETemplateとは

 素朴な疑問というか、本当の初心者には「なんのことやら?」という部分。
 これを知るには「ノード」の概念を知る必要があります。(これについては、マヤ道!!を読むのも良いですね。)

 Mayaの管理している情報は、基本的にノード(Node)と呼ばれる情報の単位を持っています。
 名前、位置、向き、サイズ、色、形状…等々、Mayaが扱う情報はいろいろありますが、これらを利用するニーズに合わせた情報単位に構成したものをノードとして管理、使用します。
 例えば画面にポリゴンを表示する時も、transform&meshノードが作成されますし、ポリゴンもMayaで機械的に生成したものであれば、polyPlane, polySphere, polyBoxのように利用した機能に対応するノードが付随します。これらがノードです。

 AETemplateは、これらノードが保持している多数のアトリビュートのうち、ユーザーが編集に必要な要素をMayaのUI上で確認したり編集したりする為に必要な設定です。
 polyPlaneには、AEpolyPlaneTemplate.melというテンプレートがあります。
定義は、以下の通り。

 
 幅、高さ、分割数(幅・高さ)、UV生成の設定、texture要素の抑止、polyPrimitiveテンプレートの参照指定が入っています。
 ※polyPrimitiveテンプレートには、axis項目が定義されています。

 このように、ユーザがノード編集に必要な項目を定義するのがAETemplateです。
 この設定は melで書かれているので、ユーザー側でカスタマイズすることも出来ますし、新しく自分でノードを作成(pluginを用意)した場合にはその為の定義を新しく書くことも出来るようになっています。

AETemplateフォルダの .res.melファイル

めも。

startupフォルダにある、~.mel と ~.res.melの関係とか?【わかってないところアリ版】

要は、displayString コマンドがキモ。「m_AEtransformMain.kTransformAttributes」みたいな定義済名称をキーに、それに対応する項目名の表記を辞書式に取り出す為の仕組み。
uiRes 関数が定義されていて、実処理内からはこれで辞書から表示文字列を取り出す。

listHistoryは、基本的にDGノードの連なりを列挙するもの

 ヘルプには、小難しく「コンストラクション ヒストリがあるすべてのノードを返します。コンストラクション ヒストリは、クリエータとして定義されているノードの特定アトリビュートへの接続、および NURBS カーブ ノードのカーブなど、ノードのメイン データの結果から構成されます。」とか書かれていて、非常に判り辛い。。

 おおまかに、下記のように考えた方が良いのでは??(厳密さが必要になったら、いろいろあるとは思うけど)

DGは、ディペンデンシグラフ
※対義語的に、DAG(directed acyclic graph : 有向非循環グラフ)があって、要はTransformノードのこと。

 
 ちなみに、listHistoryは Transformノードのコネクションは追ってくれない。