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日金曜日

2019年10月24日木曜日

2017年2月16日木曜日

Motionbuilder:Python:LongNameでsort

オブジェクトをリストで取得した後で、NameやLongnameなどのパラメーターを使ってソートをする方法。

myList= sorted(myList, key=lambda x: x.LongName)

myList
オブジェクトなどを複数格納しているリストの例
テキトウに作ったものを用意

sorted(
単に
sorted( myList )
としてもソートはできるが中身がオブジェクトのリストの場合何を基準にソートされるかはわからない

key=
例えば
key=int
とするとintで比較してくれるようになる

key=lambda x: x.LongName
lambdaは無名関数というものらしい
今回のキモ(読み方はわからないが俺は「ランバダ」と呼んでいる)
上記は以下のように分解できるらしい
def key ( x ):
     return x.LongName
これを使うことでLongNameでソートすることができるようになる

スクリプトを効率良く書くにはlambda関数便利そうだけど
身につくまでは時間がかかりそうだ・・・

2016年10月25日火曜日

Python:関数と戻り値と勝手に代入

拾い物のスクリプトを見ていたら。変な記述を見つけた。
簡単にするとこんな感じ。

test = []
def Test1(pList):
    pList.append("test")
    print pList
    return pList
Test1(test)
print test

本来ならtest = Test1(test)
とでもすればよいのか?
一見代入していないように見えてprintはちゃんと"test"と表示される。

ほほーPythonでは勝手に代入できちゃうの?
と思って以下のように動作チェック。
結果は・・・
['test']
['test']
---
['test']
---
test
んーあれー?
appendを使った時だけうまくいってる。
よくわからんわ・・・
どうして?

2016年10月21日金曜日

MotionBuilder:Python:IncrementSave

MayaのIncrement Save機能がうらやましいので、作った。



15:import shutil
フォルダのコピーはosコマンドでは無いらしいのでこれを導入

16:import datetime
日時情報を入手するのに使う

18:lFileName = FBApplication().FBXFileName
現在のfbxファイルのパスを取得

23:if not os.path.isdir(path+"\\old"):
既にoldフォルダが存在するか確認

31:for obj in os.listdir(path):
フォルダのリストアップ

51:shutil.copy(fromPath+"\\"+fileName, toPath+"\\"+newFileName)
フォルダのコピーはosコマンドではない

56:FBMessageBox( "Error", "Already exist\n"+errorPath+"\n\nCheck backup folder\nor backup by hand\n\nThere is NOT also normal SAVE", "OK" )
英語に自信なし

63:fileTime = fileTime.strftime("%Y-%m%d-%H%M-%S")
時間の取り出し
表記が簡単で便利

以下参考にしたサイト
pythonでファイルのコピー・移動 - 心魅
Saving to a File - docs.autodesk.com
共通のパス名操作 - docs.python.jp
pythonでフォルダ内のファイル一覧をパス無しで取得する - Qiita
ファイルのタイムスタンプ取得 - Python Snippets
基本的な日付型および時間型 - docs.python.jp

2016年7月5日火曜日

2016年6月22日水曜日

Motionbuilder:Python:自作モジュールの導入

何度も同じ事を書きたくない!

というわけで自作モジュールを導入してみようかと思った。

参考にしたのは以下のサイト。


呼ばれる側
こちらは普通にPythonのClassについて調べればもうちょっと面白いことができそう。
クラス変数とかそのあたり。
呼ぶ側
こちらはなかなか苦労した

4:libPath = "PATH" #your module.py file's directory path
  作成した「呼ばれる側」のスクリプトを置いたディレクトリまでのパスを指定する。
  「呼ぶ側」と同じ場所に置いて呼び出した方がいろいろと便利かも。
  MotionBuilder:Python:スクリプト自体のパスを取得

5:if not libPath in sys.path : sys.path.append(libPath)
  Motionbuilderのパスライブラリに書き込んで・・・

6:import moduleCalled
  「呼ばれる側」ファイル名を指定

7:reload(moduleCalled)
  これは自動で作られる.pycファイルをリロードする命令。
  これをリロードしないといくら「呼ばれる側」.pyファイルを更新しても「呼ぶ側」に反映されない。
  但し、他にこのファイルをロードしているファイルがある場合、そちらでも呼び直さないといけないらしい・・・。のでこれは開発中の処理。

8:import moduleCalled
  .pycファイルがリロードされたのでもう一度読み直す

10:testmod.testclass()
  こんな感じで呼び出す。出力結果は"create testclass"

11:testmod.testclass().testmethod()
  クラスの中のメソッドの呼び出し。出力結果は"call testmethod"

以上。
とりあえず作った自作モジュール。選択関連の動作を自動化してみた。

2016年6月6日月曜日