ON-BLOG

CGのこと、あれこれ書いてます。

Pyside勉強 その四

いままでのツールで一点おかしな部分があります。
それはウィンドウがMAYA画面の後ろにいってしまう点です。
なんでこんなことがおきるんでしょうか?

その原因はMAYAのウィンドウがPySideで
作成されているのが、起因になっています。
前の記事でも書いてますが
PySideは継承してUIを作成していきます、
そのため、MAYAウィンドウを継承せずに
UIを生成すると、MAYAウィンドウと生成UIが
並列になることになる為、裏側にUIがいってしまいます。
こんな感じでUIが立ち上がるが
f:id:tommy_on:20190417231628p:plain
MAYAのウィンドウの適当なところをクリックすると
f:id:tommy_on:20190417231706p:plain
裏にいってしまい消えてしまいます。

これを回避するため、MAYAウィンドウを継承してあげます。
やりかたは簡単です。

from maya.app.general import mayaMixin

をインポートして、クラスの第一引数に

mayaMixin.MayaQWidgetBaseMixin

を指定してあげるだけです。

こんな感じ↓

#-'''- coding: utf-8 -'''-
from PySide.QtCore import *
from PySide.QtGui import *
from maya.app.general import mayaMixin

class Form(mayaMixin.MayaQWidgetBaseMixin,QDialog):

    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.edit = QLineEdit("Your Name???")
        self.button = QPushButton("Push!!!")
        layout = QVBoxLayout()
        layout.addWidget(self.edit)
        layout.addWidget(self.button)
        self.setLayout(layout)
	
        self.button.clicked.connect(self.greetings)

    def greetings(self):
        print ("Hello", self.edit.text())

if __name__ == '__main__':
    form = Form()
    form.show()

実行してもらえばわかりますが
ウィンドウが後ろにいかなくなっています。
これはQDialogがMayaウィンドウの子どもになっているからです。
簡単ですね。
以降のスクリプトではこの記述は、必須ですね。

以上です。

Pyside勉強 その参

次は
Pysideの肝になる継承を含んだ例です。
こちらも前前回の記事に載っているものですね。

#-'''- coding: utf-8 -'''-
from PySide.QtCore import *
from PySide.QtGui import *

class Form(QDialog):

    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.edit = QLineEdit("Your Name???")
        self.button = QPushButton("Push!!!")
        layout = QVBoxLayout()
        layout.addWidget(self.edit)
        layout.addWidget(self.button)
        self.setLayout(layout)
        self.button.clicked.connect(self.greetings)

    def greetings(self):
        print ("Hello", self.edit.text())

if __name__ == '__main__':
    form = Form()
    form.show()

起動するとこんな感じに
f:id:tommy_on:20190415231745p:plain
UIに文字を入力すると
入力した値が出力されるといった簡単なものです。
f:id:tommy_on:20190415232037p:plain
なんか返って来てますね。

では解説を…

class Form(QDialog):

クラスを定義しています。
クラスの説明はややこしそうなので、簡単に雛形として
処理をまとめたりできるもの。として認識してくれればと思います。
Class この部分でクラスを定義しています。
関数だとDefですね。
その後の文字列【Form】がクラス名です。
名前は予約語以外ならなんでもいいです。
(QDialog)この部分が肝です。
クラスを定義する際に、括弧内に入れるものはPysideの場合は
継承をうまい事使ってGUIを構築していきます。
今回の件でいうと、Pysideの【QDialog】(文字入力させたりメッセージ確認等を行う、一時的に開かれるウィンドウのことです。)
を継承しますよ。そして、色々カスタマイズしますよ!と宣言しています。

ちなみに、QDialogの親元はQWidgetでまたその親がQObjectになります。
こういう親子関係がベースになりますので結構重要です。
https://blog.qt.io/jp/2010/05/05/object/Qt をはじめよう! 第7回: Qt のオブジェクトモデルを理解しよう - Qt Japanese Blog
私はここではじめ学んだ気がします。
あとこの図も
http://www.ibe.kagoshima-u.ac.jp/edu/gengo2j/p8/hume.png


ちなみに、PySideは以下の構造になっています。
※一部抜粋
[Qt QObject]
>[Qt QLayout]
>>[Qt QBoxLayout]
>>[Qt QGridLayout]
>[Qt QWidget]
>>[Qt QDialog]
>>[Qt QLineEdit]


では続けて

 def __init__(self, parent=None):
	super(Form, self).__init__(parent)

クラス内の一番初めの2行です。
関数が記載されています。

この関数は __init__となってます。initializeの略ですが、Pythonでは
ダブルアンダースコアで括るルールがあるみたいです。
この処理自体は、クラスを実体化させる、コンストラクタになるのですが
意味合い的には、クラスの情報を初めに定義する。
という意味があり、実際クラスを実行すると
この関数が初めに実行されます。


括弧内ですが、self、parent=Noneとなってますね。
selfは言葉通り、自身。Formクラスのことを示しています。
class Form(QDialogを継承したクラスForm)を初期化しますよ。という意味です。
parent=Noneの意味は、引数parentのデフォルト値がNoneという意味になります。
そもそもこのオブジェクト(クラス)自身、新規のオブジェクトなので
当たり前ですが、親がいません。
なので、継承元のQDialogのコンストラク
QDialogの__init__(None)を実行する。という意味になります。

ちなみに【parent】は実はクラス継承とは関係がないみたいで
単純にオブジェクト間の親子関係を表すみたいです。
要はparent引数をNoneにした場合、QDialogが親で
【新規のオブジェクトを作成】ということになります。
※このあたり自信がないので、間違っていたら
 ご指摘いただけますと、嬉しいです。

●次の行

super(Form, self).__init__(parent)

こちらは上記の続きになります。
super() は親クラスを示すためのメソッドです。
__init__() メソッドは親クラスでも、子クラスでも定義されています。
このような場合、子クラスが初期化が有線されて上書きされます。
これをオーバーライドと言います。
親クラスのメソッドや変数を呼んだり参照したいケースでsuper()を使用します。

では中身をみてみます。superのプロパティですが以下のようになります。
super(クラス, インスタンス).メソッド
詳しくは以下のサイトを…
www.lifewithpython.com


なので、置き換えると
クラスFormの親【QDialog】の初期化を実行します。
ということになり、この後に続く、QDialogに
パーツを足したりできるようになります。

長くなったので、まとめます。

def __init__(self, parent=None):
	super(Form, self).__init__(parent)

クラスFormの初期化メソッドで
スーパークラスのQDialogの初期化を実行し、QDialogに
パーツを仕込めるようにした。ということになります。

補足になりますが、クラス【Form】はサブクラスとなり
QDialogがスーパークラスになります。
※サブクラスとは、継承したクラスから新しく作ったクラスで
 継承元のクラスがスーパークラス

続き

self.edit = QLineEdit("Your Name???")
self.button = QPushButton("Push!!!")

QDialogにテキスト入力と、ボタンを追加します。
特に説明はいらないかな?括弧の中はウィンドウに表示される文字列です。

次に上記で作成した、パーツをUIの組み込みんで行きます。

layout = QVBoxLayout()
layout.addWidget(self.edit)
layout.addWidget(self.button)
self.setLayout(layout)

まずは

layout = QVBoxLayout()

QVBoxLayoutとは、Vertical(垂直)のレイアウトを作成します。
という意味で、要は縦にパーツを配置していくレイアウトを宣言しています。

layout.addWidget(self.edit)
layout.addWidget(self.button)

垂直レイアウトに上記で記載した、テキスト入力パーツとボタンを追加。
という意味になり、UIを構築している状態になります。

self.setLayout(layout)】

上記でコマンドで、UI構築を終了して確定させます。
という意味なるので、UIがこれで構築できました。

self.button.clicked.connect(self.greetings)

def greetings(self):
      print ("Hello", self.edit.text())

この部分は前も記載しましたので、軽く。
ボタンが押されたら、関数【greetings】を
実行。という意味になり、この関数は
テキスト入力されて値を表示する。
といったものになります。


以上で説明は終わりになります。
このスクリプトはPysideもしくはPythonの基本が詰まってるいいスクリプトですね。

Pyside勉強 その弐

続き。

ハローワールドのスクリプトはこれぐらいにして
続いてプッシュボタンについて。
これも前回記載したサイトに載っているものです。
こちらはイキナリMAYAに落とし込んだコードです。↓

# -'''- coding: utf-8 -'''-
import sys
from PySide.QtCore import *
from PySide.QtGui import *

def sayHello():
	print "Hello World!"
button = QPushButton("Click me")
button.clicked.connect(sayHello)
button.show()

実行すると、こんな感じに
f:id:tommy_on:20190415001411p:plain
ボタンを押すと、Hello World!って出力される
シンプルなものです。


以下に説明を記載します。

# -'''- coding: utf-8 -'''-

このコマンドを記載することで、UTF8にエンコードしてくれます。
記載しない限りは2バイト文字は文字化けしますので
注意が必要です。
詳しくは、こちらを参照に…
qiita.com

次、

def sayHello():

このコマンドは、def文といい、関数になります。
 sayHelloは関数名になります。
この辺りはPythonの基本ですね。
一応note.nkmk.me

button = QPushButton("Click me")

前回のLabel同様に、UIパーツあたります。
QPushButtonはMAYAでいう、cmds.button()と同じです。
Pyside側のボタンを設定するメソッドです。
()中は、ボタンに表示される文字列です。
後から変更する場合は

button.setText("Push!Push!")

とします。
これでボタンの表示が変更されます。
f:id:tommy_on:20190415004859p:plain

言葉通りですが、ボタンが格納されている変数に対して
【setText】メソッドで中身を替えています。
ボタンにアイコンを仕込む時は

button.setIcon(QIcon('D:/button.jpg'))

とします。
setIconでアイコンをセットする命令を出し()内で
アイコンのファイルを指定しています。
f:id:tommy_on:20190415005101p:plain
※青い画像を指定しています。

色を変える時は、スタイルシートを使いますが
長くなるので後述します。

button.clicked.connect(sayHello)

ボタンが押された。という変更を取得し、押されたあと
関数【sayHello】に接続しています。
要はボタン押したら、関数実行しますね。って事。
MAYAだと、button(command=Function)としますが
それと同じです。
最後の一行は前と同じなので割愛。
ボタンというパーツを読み込み
クリックされたら、関数実行する。
というかなりシンプルなものです。


はい。
こんな感じです。
関数を使ったぐらいですが、これが肝なので
書いてみました。

以上です。

Pyside勉強 その壱

Pyside自体は今までも使ってきました。
が、その場しのぎでずっと使ってきました。

数日前に数見たらPysideで作ったスクリプトが20個ほど。
ただ、これらはいろんなサンプルから作っただけなので
基本で躓くことが多く、結構時間が掛かってしまってました。

ちょうど2017年の暮頃に、モチオさんが
SiShelf
github.com
を開発されていて、それに影響受けて私もやろうと
メモしたやつが見つかったので、それを再度まとめつつ
どうせなら、うやむやにしてた箇所を、一から学びなおしたいと思います。
すべて記事にすることは難しいですが、一先ず最近セレクターを
複数個作ったので、この勉強でのゴールはPysideでセレクターを作ることにします。



では、初めにいつものハローワールドから。
https://wiki.qt.io/Hello_World_in_PySide/jahttps://wiki.qt.io/Hello_World_in_PySide/ja
こちらに記載しているコードで行います。

import sys
from PySide.QtCore import *
from PySide.QtGui import *
app = QApplication(sys.argv)
label = QLabel("Hello World")
label.show()
app.exec_()
sys.exit()

MAYAで実行すると、エラーになります。
f:id:tommy_on:20190414230602p:plain

なんでおきているかというと、MAYAでは
起動時にPysideを起動しているので
多重起動になってますよ!
ってエラーになっているわけです。

なので、それを回避する為には、以下のようにします。

app = QApplication.instance()

文字通り、インスタンス化して起動させています。


起動すると、以下の感じになります。
f:id:tommy_on:20190414230741p:plain
無事起動できました。
起動はできましたが、一個一個が説明できないと
理解したことにならないと思うので


ここは一行ずつみていきます。
コード

import sys

Pythonインタプリタ、実行環境などの処理をまとめた
モジュールになります。
ファイル、パス操作など、基本的なコマンド多いので
Pythonではよく使います。

from PySide.QtCore import *
from PySide.QtGui import *

PysideのGUIやそのコネクション周りのモジュールを読み込む必要があります。
それらをアスタリスクで一括読み込みしています。
アスタリスクで読み込んでいるのは、手を抜いている証拠です苦笑

app = QApplication.instance()

前述したとおり。
インスタンスを作成しています。

label = QLabel("Hello World")

PysideのQLabelというテキストや画像を表示するためのモジュールを
使って括弧内の【Hello World】をテキストとして、描画する!
という設定を行いつつ、labelという変数に格納しています。

label.show()

前述した、ラベルと表示させるコマンドです。
MAYAのShowと同じ感じです。

app.exec_()

このコマンドで、アプリケーションを実行しています。
exec_()は実行コマンドなので、インスタンスを実行する。
ということです。

sys.exit()

スクリプトのプロセスを終了させます。
正直、上記の書き方では無くても動きます。

MAYAで動かす際には

from PySide.QtCore import *
from PySide.QtGui import *
label = QLabel("Hello World")
label.show()

このコマンド郡だけで動きます。
これは前述しましたが、MAYA上で既にPysideが動いていますので
スクリプトエディタ上では

app.exec_()
sys.exit()

この二つをスクリプト実行時に、裏で実行している為です。

こんな感じで一番初歩の部分から進めていき
10回ぐらい続けばいいなぁと思います。

以上です。

Jiggleについて

使おう使おうと思って、中々手が出なかった【Jiggle】デフォーマ。
今日というかさっき、テストしたので
メモ代わりに記載。

初めに【Jiggle】とは?
→揺れに当たる。
私はこれを見て、やりたいなぁと思いました。

これは直ジオメトリですが、骨などにも使えそうだなぁと。

そこでさっきやってみました。
まずは元がこれ

JiNoggle from tommy_on on Vimeo.

これにJiggleを設定したやつ。

Jiggle from tommy_on on Vimeo.

これはJiggle_pSphere1にJiggleデフォーマを設定し
f:id:tommy_on:20180706014140p:plain

次に適当な頂点位置にロケーター【Jiggle_locator1】を置いて
pointOnPolyConstraintを実行。
f:id:tommy_on:20180706014314p:plain
※このあたりは揺れの移動値が取れればなんでもいい。

Jiggle_pSphere1の子どもにロケータ【Jiggle_locator2】を配置。
そのロケータは上記のロケーター【Jiggle_locator1】に対して、ペアレントコンストしただけです。
f:id:tommy_on:20180706013755p:plain
このロケータがバインド骨とかをドリブンすれば
いいかな?と思います。

まぁ簡単ですが、とりあえず揺れたんでOKとします。
ちなみにJiggleの設定はこんな感じにしてます。
f:id:tommy_on:20180706014444p:plain
超適当です。

これをリグに仕込むのはこれから考えますが
Softbodyより軽ければ使いみちがあるかも。
あと、個人的には【SEN_A0527】さんのベルレのやつも気になってます。。。
ベルレを使った揺れ処理ノードを作ろう! - SEN_Aのじゆうちょう


とりあえず今回はここまで。
以上です。

膝パーツの組み方

よくある事例なんですが、いつも接続で?ってなるのでメモ。

画像みたいに、ひざにパーツがついてる時があります。
特にメカ系なんかに多いんですが…
f:id:tommy_on:20180529221831p:plain

これを足の骨だけでそのまま回すと
こうなるかと思います。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
f:id:tommy_on:20180529221849p:plain

このままだと、硬い物等で違和感が生じる為、骨を足して
処理を追加します。
※膝と同階層に
画像のように配置します。
f:id:tommy_on:20180529221900p:plain

ウェイトは繋がっていない限り、パーツに全振りします。
んで、この骨に対して接続を行います。
f:id:tommy_on:20180529221928p:plain

※デモの為、jointOrientをしていません汗・・・・
kneeの回転値Zを各【remapvalue】の【入力値】に接続し、出力値をnodeとしいう階層に接続してます。
この出力値は、追加した骨を拘束しており、ドリブンされています。
とりあえず、【TX】ノードの接続をみていきます。
f:id:tommy_on:20180529221941p:plain


基本的には画像の通り。
膝の回転値を変換して、最大は動かないように【0】にして最小の値を【-1】にしています。
このように他のチャンネルにも接続すると、

Knee_Rig from tommy_on on Vimeo.

このような挙動になります。
はじめはclampノードを使っていたんですが
clampだと、リミット時にカクッとなる為
代案を考えておりました。
いろんなデータを落としたりしてる中で、IKFKの切り替えで
【remapvalue】を使っている所を確認したので
試した所きれいにいったのでこれをつかってます。
先人達には感謝がつきませんね…



では… 

NameSpaceをすべて削除

リグ組んでたり、アニメーションを読み込みしてたりすると
偶にネームスペースが邪魔になることがある。
その都度一々、ネームスペースエディタを開いていたら
面倒なので、マクロを作りました。
※家用。

# -*- encoding: utf-8 -*-
import maya.cmds as cmds
def main():
    NameSpace = cmds.namespaceInfo(recurse=1,listOnlyNamespaces=1)
    NameSpace.remove(u'UI')
    NameSpace.remove(u'shared')
    if len(NameSpace) > 0:
        for i in NameSpace:
            cmds.namespace(mergeNamespaceWithRoot = True , removeNamespace = i)
        cmds.headsUpMessage( u'NameSpaceを削除しました', verticalOffset=20 )
    else:
        print "NotNameSpace"


今後は指定したやつだけ残す機能とか付けたい。

以上です。