Maya2017・2016の両方で動作するUIについて
Maya2016のスクリプトをMaya2017で動作するような対応を行った。
作業環境
- Window7 64bit
- Maya2017
動作確認を行ったスクリプトについて
- Maya2016で動作済み
- QtDesignderで作成した.uiファイルを.pyに変換して使用
- QtDesignder上でのSignal・Slotの設定は無し
- 直接のShibokenは無し
対応方法
Documents/maya/2017/scripts
にuserSetup.pyを作成して(すでに存在する場合は先頭に)
以下を記述する。
# -*- coding: utf-8 -*- import sys import PySide2 import PySide2.QtWidgets sys.modules['PySide'] = PySide2 import PySide.QtGui for key, value in PySide2.QtWidgets.__dict__.items(): setattr(sys.modules['PySide.QtGui'], key, value) setattr(PySide.QtGui.QApplication, 'UnicodeUTF8', -1)
PySide2をPySideとしてimportできるようするために参照を渡し、PySide.QtGuiには
PySide2.QtWidgets以下のクラスをアトリビュートとして追加している。
最後にその他の不足していた参照を後述の方法で検証し追加した。
この処理を追加すると、以降は見た目PySide・中身PySide2というような状態のモジュールを
importできるようになるので、Maya2016で動作していたスクリプトの変更を一切行わずに
Maya2017に対応できる。.uiを変換した.pyもそのまま。やったぜ。
Mayaウィンドウの後ろに行かないようにするための処理についてはshibokenではなく
MayaQWidgetBaseMixin
を使用していたためshiboken2の対応は必要ない状態だったので書いていないが、
同じような処理をuserSetup.pyに追加すればいけそうな気がする。
不足していた参照の追加
上記のスクリプト追加後の実行時に.uiファイルを変換した.pyファイル内部で
PySideに関するエラーが発生した場合には、
setattr(PySide.QtGui.QApplication, 'UnicodeUTF8', -1)
という形で参照を追加する必要がある。
Google先生に聞いてみてもPySide2のリファレンス的なものが見つからなかったので
元の.uiファイルをPySide2対応の.pyに一度変換してPySide(無印)と比較することでどのような値を
追加すればよいかの確認を行った。
変換方法
(作業環境ではないためあやふや。Maya2017はProgramFiles/Autodeskのヤツ。)
Maya2017/bin/mayapy.exe Maya2017/bin/pyside2-uic 対象の.uiファイル > 出力先.pyファイル
自作したスクリプトの処理部分でPySideのエラーが発生した場合の対応は不明。
PyCharm師匠にmayapy.exeのパス設定したらPySide2のHinting出してくれたりしないかな?
DaikinのPythonセミナー:ざっくりメモ
2016/11/10にあったDaikinのPythonセミナー行ってきた。
時間がなく、自動化とテストについてはほぼ話が聞けなかったので残念。
ツール開発の仕組み・ルールがきちんと組み立てられていてとても参考になった。
UI層・ライブラリ層・アプリケーション層というのは、一応自分の中で切り分けて考えて
実装を行っていたつもりだったが、パッケージを分けるまではやっていなかったので
そのあたり意識して作業しようと思った。
後日セミナーに使用した資料がダウンロードできるようになるらしいので確認する。
以下、セミナー内容のメモ。
聞いている最中に書いたので誤字脱字・聞き洩らし・解釈違い多々あり。
SphinxでのAPIドキュメント作成 まとめ
作業環境
環境構築
Sphinxのインストール
pip install sphinx
ドキュメント出力対象のプロジェクトと最終的なSphinxプロジェクト
https://drive.google.com/file/d/0B4fmLEFqtND0MGZRMWh2TlNNTlE/view?usp=sharing
PromotedTest
PromotedTest │ ButtonWrapper.py │ MainWindow.py │ ui_PromotedTest.py │ ViewWrapper.py │ WrapperBase.py │ __init__.py │ └─ui ui2py.py ui_PromotedTest.ui
ソースへdocstringの記述
記述位置
大体こんな感じ
# -*- coding:utf-8 -*- """ Module no docstring """ import os import sys class Hogehoge(object): """ Class no docstring """ def __init__(self, a, b): """ Method no docstring """ pass def huga(self, i, j): """ Method no docstring """ pass
変数については
とか
でググれば出る
Methodのdocstring書式
PyCharmの場合docstringで補完とかしてくれるのでドキュメントを出さないにしても書いておくと楽
引数の説明等々は以下の通り
def huga(self, a, b): """ 説明 :type a: 引数の型 :type b: 引数の型 :param a: 引数の説明 :param b: 引数の説明 :rtype: 戻り値の型 """ pass @property def value(self): """ 説明 :getter: getterの説明 :type: 戻り値の型 """ pass
setterは知らん
PyCharmのdocstringショートカット
def huga(self, a, b): pass
という形の関数があった場合に関数名・引数のそれぞれの変数部分でAlt+Enter押すと
何種類かのdocstringを挿入する項目が表示される
- Insert documentation string stub
- docstringのテンプレを追加
- Specify return type in docstring
- 関数の戻り値の型の記述を追加
- Specify type for reference in docstring
- 引数の型の記述を追加
このショートカットで上記の形式のdocstringになっていない場合には
Setting > Tools > Python Integrated Tools > docstring
の設定を reStructuredText に変更する必要がある
Sphinxでの作業
Sphinxではrstファイルを使用してhtmlなどのファイルを出力する
そのために、まずはSphinxのプロジェクト・rstファイルの作成をする必要がある
基本的にプロジェクトを作成するには
sphinx-quickstart
のコマンドを使用するが、
apiドキュメント用のプロジェクト作成コマンド
sphinx-apidoc
があったのでそれを使用する
プロジェクトの作成
コマンドラインから
sphinx-apidoc -F -f -o [OUTPUT] [PATH]
- -F
- フルサイズのSphinxプロジェクトの生成
- -f
- 強制的に上書き
- -o [OUTPUT]
- プロジェクト出力先の指定
- [PATH]
コマンド実行後、出力されたSphinxプロジェクトに移動して
make html
と実行するとhtmlファイルが生成される...はずだが、このままではパス諸々の設定が
正しくない場合があるので
conf.py
を編集して設定を変更する
conf.pyの編集
Sphinxプロジェクトから対象プログラムへのパスを通す
conf.pyの割と最初のほうにある
import os import sys sys.path.insert(0, os.path.abspath('.'))
のコメントアウトを外して
os.path.abspath('.')
の部分に対象となる
プログラムが読み込めるようなパスを指定する
ここに指定するパスはconf.pyの存在するディレクトリから見たパスなので
_DATA ├─SphinxProject └─TargetPackage
このように対象となるプログラムがSphinxプロジェクトのディレクトリと同階層にあった場合には
sys.path.insert(0, os.path.abspath('..'))
といった指定にする
―――
ここまで設定した後に
make html
を実行するとhtmlが出力される
.
.
.
.
...で、出力されたのがコレ
どこから飛んでもこのページ...クッソ見づらい...見づらくない??
そもそもSphinxでのドキュメント出力を勉強しようと思った切っ掛けが、
仕事でバカでかいパッケージの修正(ドキュメントなし)案件が来たので
- 開発用にprivateも含めたすべてのMethodが見たい
- Method・Attributeの一覧が見たい
- クラスの一覧→1クラス1ページでなんかよくあるドキュメントっぽくしたい
といったことがしたかった。
というわけで上記を実現するための処理を追加していく。
privateを含めたMethodの表示
conf.pyに autodoc_default_flags を追加して private-members を設定
autodoc_default_flags = [ 'members', 'private-members' ]
この設定はSphinxの拡張であるautodocに対してのデフォルトを設定する
ここに
special-members
と追加することで__init__等の特殊関数の表示もできる
autodocはrstファイルに書かれてる
- automodule
- autoclass
- automethod
とかそのへんのやつ
クラスの一覧→1クラス1ページで表示
Moduleごとにrstファイルの生成
クラスの一覧→1クラス1ページを実現するためにはまずモジュールごとの
rstファイルを作成する必要がある
そのために sphinx-apidoc 実行時にオプションを追加する
コマンドラインから
sphinx-apidoc -F -f -e -o [OUTPUT] [PATH]
- -e
- Moduleごとにrstファイルを生成
としてコマンドを実行
これでモジュールごとにrstファイルが生成される
一覧表示とクラス別ページの生成
モジュールごとに生成されたrstファイルに
autosummary
のディレクティブを追加することで
一覧表示とクラス別ページを同時に実現できる
conf.pyの修正
autosummaryはSphinxの拡張なので使用することを明示する必要がある
conf.pyの extensions に sphinx.ext.autosummary を追加する
extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.todo', 'sphinx.ext.viewcode', ]
また、
autosummary_generate = True
という記述も追加しておく必要がある
これがないとモジュール別→クラス別のページとして作成されない
モジュールごとのrstファイルの修正
モジュールごとに
PromotedTest.ButtonWrapper module ================================= .. automodule:: PromotedTest.ButtonWrapper :members: :undoc-members: :show-inheritance:
上記のような内容のrstファイルが生成されるので
PromotedTest.ButtonWrapper module ================================= .. automodule:: PromotedTest.ButtonWrapper :members: :undoc-members: :show-inheritance: .. autosummary:: :toctree: _gen ButtonWrapper
というように追加
この場合の
.. autosummary:: :toctree: _gen ButtonWrapper
ButtonWraperは
クラス名
なので注意
1Moduleに複数のクラスがある場合は表示させたいクラス名を全て記述する必要がある
:toctree: _gen
は(autosummary_generate = Trueの場合に)_genディレクトリに
rstファイル出力しますよ的なアレ
で、rstが生成されていないと別ページにならない
―――
ここまで設定した後に
make html
を実行するとhtmlが出力される
.
.
.
.
...で、出力されたのがコレ
出来た!!
.
.
.
.
...ただし親クラスのpublic、テメーはダメだ!!
この状態だとクラス別のページで親クラスのpublicも一覧に表示されてしまい、非常に見づらい。
というわけで、消す。
autosummaryの一覧から親クラスのpublicの表示を消す
恐らく他の方法がある
自分が調べる限り、conf.pyやディレクティブでなんやかんやできるという情報は見つからなかった。
ので、ソースとautosummaryのclassのtemplateを修正する。
修正するのは
の2つ
generate.pyのget_membersを以下のように修正
def get_members(obj, typ, include_public=[], no_inherited=True): items = [] for name in dir(obj): try: documenter = get_documenter(safe_getattr(obj, name), obj) except AttributeError: continue if documenter.objtype == typ: items.append(name) if no_inherited: import inspect items = set(items) for cls in inspect.getmro(obj)[1:]: items = items - set(dir(cls)) items = list(items) public = [x for x in items if x in include_public] return public, items
inspectでなんやかんやしてitemsから親クラスのmemberを削除している
また、クラス別ページのMethod一覧内にprivateも含めたかったので最後の内包表記ではじいていた処理を削除
class.rstは以下のように修正
{{ fullname }} {{ underline }} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} :show-inheritance: {% block methods %} .. automethod:: __init__ {% if all_methods %} .. rubric:: Methods .. autosummary:: {% for item in all_methods %} ~{{ name }}.{{ item }} {%- endfor %} {% endif %} {% endblock %} {% block attributes %} {% if all_attributes %} .. rubric:: Attributes .. autosummary:: {% for item in all_attributes %} ~{{ name }}.{{ item }} {%- endfor %} {% endif %} {% endblock %}
修正箇所は
- autoclassの下に:show-inheritance:を追加
- methodsをall_methodsに変更
- attributesをall_attributesに変更
の3箇所
:show-inheritance:の追加は親クラスのMember一覧については別ページで表示するため
methods・attributesを修正したのは上記のget_membersの修正によりmethod名・attribute名が
all~に入るようになったため
get_membersの変更を正しく行えばmethods・attributesからall~の変更は不要
また、autosummaryのtemplateの修正については別ファイルで行い、autosummaryディレクティブに
:template:
のフラグを追加するべきなのだろうが、今回は力尽きたのでこれで良しとした
―――
ここまで設定した後に(略)
.
.
.
.
...で、出力されたのがコレ
完成!! 第三部完!!
―――
(追記:10/1)
その他・気になったところ
- クラス別ページの出力はされているのに、クラス一覧→クラス別ページに飛ばないことがあった。
- automoduleディレクティブに:noindex:を追加することで解決
参考・気になったところとかは気が向いたら追加
QtDesignerで配置後にPython上でのWidgetのカスタマイズ
ファイル
https://drive.google.com/file/d/0B4fmLEFqtND0THd4TmlDR0lQems/view?usp=sharing
QtDesigner上でこんな感じのUIを作成したとして
このUIに対して
- ボタンを動かせるようにする
- GrapshicsView(以下View)にボタンが当たったら色を変える
という処理を追加する場合、ボタンとView用のWrapperクラスを作成する。
ButonWrapper
class ButtonWrapper(WrapperBase): collision = QtCore.Signal(WrapperBase) def __init__(self, widget, col): """ :type widget: PySide.QtGui.QWidget """ super(ButtonWrapper, self).__init__(widget) #: :type: QColor self.__color = col #: :type: WrapperBase self.__collision_target = None self.widget.mousePressEvent = self.__on_pressed self.widget.mouseMoveEvent = self.__on_moved self.__move_offset = None pass @property def color(self): return self.__color def __on_pressed(self, event): """ :type event: PySide.QtGui.QMouseEvent """ self.__move_offset = self.widget.mapFromGlobal(event.globalPos()) pass def __on_moved(self, event): """ :type event: PySide.QtGui.QMouseEvent """ pos = self.widget.window().mapFromGlobal(event.globalPos()) pos -= self.__move_offset self.widget.move(pos.x(), pos.y()) if self.__collision_target and self.__collision_target.widget.geometry().intersects(self.widget.geometry()): self.collision.emit(self) pass def set_collision_target(self, wrapper): """ :type wrapper: ButtonWrapper """ self.__collision_target = wrapper pass
ViewWrapper
class ViewWrapper(WrapperBase): def __init__(self, widget): """ :type widget: PySide.QtGui.QWidget """ super(ViewWrapper, self).__init__(widget) self.widget.enterEvent = self.__on_enter self.widget.leaveEvent = self.__on_leave pass def __on_enter(self, event): print "mouse enter." pass def __on_leave(self, event): print "mouse leave." pass def set_color(self, color): scene = QtGui.QGraphicsScene() scene.setBackgroundBrush(color) self.widget.setScene(scene) pass
基本的には、両方とも__init__のwidgetにはQtDesigner上で配置されたWidgeを指定し、
その中のVirtualFunctionを書き換えるような処理を行っている。
(ぶっちゃけこのVirtualFunctionを書き換えられれば良いのでWrapperクラスとか作らなくていい)
ButonWrapper
self.widget.mousePressEvent = self.__on_pressed self.widget.mouseMoveEvent = self.__on_moved
ViewWrapper
self.widget.enterEvent = self.__on_enter self.widget.leaveEvent = self.__on_leave
書き換えられるVirtualFunctionについてはPySideの公式Referenceを読めばわかる。
MayaのPySideのバージョンは1.2らしいので1.2.1のRefernceあたりを見ればいいと思う。
個人的には1.2.1のRefernceだと図でクラスをたどれないので1.0.1が好き。
本来であれば
- QWidgetなり継承してVirtualFunctionをオーバーライド、それをQtDesignerで格上げ先として
使えるようにする - QtCreator(C++)で最終的なuiファイルまで作成してそれをPythonで使う(出来るか不明)
が正しいのだろうけど自分の頭では無理だった・・・。
ついでにWidget同士の当たり判定について
処理部分はここ↓
ButtonWraper
if self.__collision_target and self.__collision_target.widget.geometry().intersects(self.widget.geometry()): self.collision.emit(self) pass
widgetからgeometry()でQRectを取得してそれをintersectsで判定している。
rect()から取得したQRectの場合、位置が来ないらしく正しい判定ができなかった。
UnityのButtonのクリック範囲をRectTransformで制限する方法
円形の制限はあったが矩形での制限がなかったのでメモ
using UnityEngine; using System.Collections; using UnityEngine.EventSystems; using UnityEngine.UI; public class CustomButton: Button, ICanvasRaycastFilter { [SerializeField] private RectTransform targetRectTransform = null; [SerializeField] private Camera cam = null; public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) { if (targetRectTransform == null) return true; return RectTransformUtility.RectangleContainsScreenPoint(targetRectTransform, sp, cam); } }
Buttonを継承したときにはSerializeFieldでもUI上にRectTransformの項目が表示されなかった。
何とかうまいことやれば出るのだろう。
とりあえずInspectorをDebugモードにしてComponentをセットして動作した...気がする。
(※↑最悪の手段)
カメラについてはCamera.mainでもいけるのかもしれない。
Maya PySide まとめ その2
レイアウトについて
QtDesignerを使用してUIを作成したときに、Widgetを均等に並べたい場合は レイアウトを使用する。
使い方
このままだとWindowのサイズを変更したときにレイアウト・ボタンがついてこない。
Windowサイズを変更した時にレイアウトがついてくるようにするには レイアウトをドロップ後、Windowの適当なところで
右クリック > レイアウト > 水平に並べる or 垂直に並べる
をクリックするとWindowサイズの変更に伴ってレイアウトも変更されるようになる。
画面上にレイアウトが複数ある場合には、上記のレイアウトの項目で選択した形で レイアウトが配置される。
Windowサイズに追従させたくないときは
右クリック > レイアウト > レイアウトを破棄
※MainWindowでもWidgetでも同じ。
いい感じに並べる
// この方法は正しいやり方ではない。多分。
以下、手順。
Layout内に配置したWidgetのSizePolicyを縦・横Ignoredに変更
LayoutのLayoutSpacing・LayoutStretchを設定
後はLayoutを入れ子状態にすれば何とかなる。
// これMaya関係ないな