2019年12月24日火曜日

QtDesignerと.uiと.pyそしてMotionbuilder

この投稿は
Windows用にPyQt使ってたけど
Motionbuilderで使いたいので
PySideで書き直してみた
という最初からPySide&Motionbuilderでやっとけよという内容です

結論から言うと
Motionbuilderでも使うならPySide使え
特に考えなくPyQtを使っていたのですが。
MotionbuilderのPythonにPySideが入っていて、PyQt4のリファレンスがほぼ無い。
というわけで使うならPySideがよさそう。

PySide 1.2.1 Reference
PyQt's Modules
以下、最初にPyQt4でuiを作成したプロジェクトをPySideに変更、その後Motionbuilderに対応させた例。
(Motionbuilderへの変更はほぼ無いに等しいけど)

PySideの導入は以下のサイトを参考にした
Python + PySide + QtDesigner で 実行ファイルを作るまで

備忘録のために作業の流れ。

1.QtDesignerで.uiを作る
2.PyQt用に.uiを.pyにコンバートする
3.実行側のpythonをPyQtを使って作る
4.PySide用に.uiを.pyにコンバートする
5.実行側のpythonをPySideを使って作る
6.PySideを使ったpythonをMotionbuilderで動くように書き換える
Motionbuilderで使うだけなら1と6だけやればいい

1.QtDesignerで.uiを作る

詳細はどこか別のサイトを参考にしてください。ここで言えるのは。
・後でアクセスしやすい名前を付けておけ
・.uiは中身はXMLなのでPyQtでもPySideでも使える
黄色い部分の名前をわかりやすくしておくとスクリプトからアクセスしやすい。
できた.ui

2.PyQt用に.uiを.pyにコンバートする

pyuic4 testWindow_UI.ui -o testWindow_UIPyQt.py
コマンドラインから.uiを.pyにコンバート。

ちなみにuiだけでいいじゃんと思っている(自分のような)人間に説明すると。
・変換した.pyに機能を追加していけば.uiという外部ファイルが不要になる
・exeにする時にも同様
・あれこれレイアウトを変更しつつ作業する時には.uiをロードして使うのが便利

自分の作業工程としては
・.uiのままレイアウト変更しつつ作業
・.pyにしてimportしてexeにしたときに外部ファイル無しで完成させる
なので実行側のpythonは.uiと.pyの両方に対応している。

3.実行側のpythonをPyQtを使って作る

作ったのがこちら
fromUi = False
このフラグがTrueの時は.uiからloadする。Falseの時は.pyからimportする。
exe化する時はFalse。
ui = None
これがuiデータの親になるのでglobalに出しておくと便利
def CB_Add():
ボタンを押した時のコールバック用
ui.listWidget.addItem(QtGui.QListWidgetItem(testList[ui.listWidget.count()]))
リストに追加する時にはQtGui.QListWidgetItemに変換して入れてやらなくてはならない。面倒。
あとlistWidget.countではなく.count()なのが地味に面倒。毎回間違える。
# ui.listWidget.removeItemWidget(ui.listWidget.item(i))
リストの項目を消す用とおぼしきremoveItemWidget()というのがあるが、どうやらうまく動作しないらしい。
del itemを使えという記事もあったが、表示から消えるだけでリスト内には残っているため後の処理で不便。
ここでは一度全部消してから書き直している。
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
このあたりは自分的にはオマジナイの意味しかない。(理解してない)
ui.toolButton_0.clicked.connect(CB_Add)
ui部品を取り出す例。名前はQtDesignerで指定した名前。(ラベルとは違う)

4.PySide用に.uiを.pyにコンバートする

pyside-uic.exe testWindow_UI.ui -o testWindow_UIPySide.py

5.実行側のpythonをPySideを使って作る

from PySide import QtGui, QtCore, QtUiTools # ☆変更
PySideにはuicが無い。(別のところにある)
# AttributeError: 'PySide.QtGui.QListWidget' object has no attribute 'isItemSelected'
        if i == ui.listWidget.currentRow():
        # ui.listWidget.removeItemWidget(ui.listWidget.item(i))
        continue
PySideではisItemSelected()ではなくcurrentRow()を使う。
また、removeItemWidget()は相変わらず使えない。
ui = QtUiTools.QUiLoader().load(os.path.join(localPath, fileName))
window.setCentralWidget(ui)
.uiファイルのロード方法が異なる。
ui.toolButton_0.clicked.connect(CB_Add)
ui要素の呼び出しはPyQt同様

6.PySideを使ったpythonをMotionbuilderで動くように書き換える

if __name__ == '__builtin__': # ☆変更
MotionbuilderのPython Editorから実行すると__name__が__main__ではなく__builtin__になるuiPath = os.path.abspath("E:/PATH/testWindow_UI.ui")
Motionbuilderだとcwdが実行したファイルと違う&cwdにあっても他のファイルを参照できない。ので、絶対パスでロードする。
別途パスを通してもよいが、毎度やるのは面倒なのでパスは決め打ち。

PyQt > PySideのスイッチは割と楽だった。
PySideをMotionbuilder用に書き換えるのもそれほど苦ではない。
けどまあ結論から言うとMotionbuilderでも使うならPySideかな。
Posted on 16:55 | Categories:

Python:PyQt4:ViewとWidget

QtDesigner見てたら同じ項目がViewとWidgetってやつであるんだけど。
ちょっといじってみた結果だけど。
View難しいからWidget使うことにする
View使えたら便利そうなんだけどね。

2019年12月23日月曜日

Motionbuilder:python:ElementTree:MotionbuilderでXMLを扱ってみる

XMLからデータを取得してあーだこーだしなければならなくなったのでElementTreeを使ってみた。
文字コード関係とかどうも拾ってきたサンプルがうまく動かないことが多かったので、

Motionbuilderで使ってみたという前提

で書く。
マニュアルはここ

実行すると以下のXMLファイルが作られる。
(コマンドログの一番上のフォルダにtest.xmlで出力されている)
import xml.etree.ElementTree as ET
XMLを扱うにはlxmlとxml.etree.elementtreeというライブラリがあるっぽいが、導入が簡単だったのでxml.etree.elementtreeを使ってみた。
違いはXMLの整形機能の内容くらいらしいが、xml.etree.elementtreeにはXML整形出力機能が弱い。
導入できるならlxmlを使った方がいいかもしれない。サンプルも微妙にlxmlの方が多い印象。

ゼロからXMLを作成

def make_xml():
あんまりやらないとは思うけど一応。

xml全体はTreeと表現される。Treeの中にElementを入れてゆく。
ビジュアル的に<tag></tag>で囲われたものがElement。


r = ET.Element('root', {'name':'rootName'})
rootとなるElementから作成開始してElementにattributeやtextや子Elementを追加してゆく。

i.text = 'あああ'.decode('utf-8') 
2バイト文字の扱いが面倒。
xml内ascii <> python内utf-8
なので取り出す時、代入する時に毎回decodeとencodeが必要。

tree = ET.ElementTree(r)
Elementの組み立てが終わったら、Treeオブジェクトを生成する。

tree.write(fl, encoding='utf-8', xml_declaration=True)
実際に.xmlファイルに出力する際に2つの方法がある。
・ElementTreeのwrite機能を使って出力する
  >簡単だが全てが一行で出力される
  >lxmlだとwrite命令に整形機能が付いているらしい
・minidomを使う
  >なんか色々面倒だけどこれを使わないと見た目が悪い。
  >writeFunction(fl, root)で後述

XMLを読み込む

def read_xml(fl):
マニュアルを見ながら適当にいじればわかると思う。
代表的な機能をピックアップした。

attributeの値はテキスト
なので、取り出した値が数値だった場合float(value)とかで変換してやらなければいけない。
代入する時も同じ。str(value)する。

print 'itemA.text.encode("utf-8"):'+itemA.text.encode('utf-8')
前述した通り2バイト文字の扱いが特殊。(普段から文字コードいじってる人には普通だと思うけど)
ここでは取り出したitemA.textをprint出力するために変換している。
取り出したままだとasciiなのでちゃんと表示されない。

XMLを編集する

def edit_xml(fl):
これもMotionbuilder経由で使っていると需要ないかも。

c = ET.SubElement(root, 'childB')
Elementを追加したり

cp = copy.deepcopy(c)
コピーしたりできる。
注意点としては追加した時に戻り値でElementが来ないので、追加前に編集しておかないと検索が面倒くさい。

XMLを保存する

def writeFunction(fl, root):
基本のwriteを使うと一行になってしまう。のと、整形出力用のwritexmlがasciiエラーを出してしまうのでちょっと面倒だった。
このあたりはMotionbuilder特有かもしれない。他の環境では要検証。

open(fl, 'w').close()
一度ファイルを全部消している。これをしないと既に構築されているインデントや改行設定が二度書きされて2回改行とかが行われてしまう。
かなり強引なので、ほかにちゃんとしたやり方があるのではないかという疑念が拭えない。
writexmlの動作が
・ファイルの中を見て
・追加部分を書き込み
・整形
という順番なので、整形済みの既存の部分がもう一度整形にかけられてしまうようだ。
ツリー全体はスクリプト内に格納済みなのでファイル側は全消去しても問題ないという判断。

with codecs.open(fl, "w", "utf-8") as out:
サンプルを見ると一つ前の行で作ったdocumentをいきなりwritexmlに入れれば動作するようだが(なにしろutf-8でストリングに変換してdocumentを作っているので)、実際にはasciiエラーを出してしまうので、writexmlに入れる前にもう一度utf-8で念押ししている。


2019年12月20日金曜日