読者です 読者をやめる 読者になる 読者になる

水深1mm

広く浅い個人的メモ。

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

変数については

sphinx python docstring instance attribute

とか

sphinx python docstring class attribute

でググれば出る

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が出力される
.
.
.
.
...で、出力されたのがコレ f:id:bonbonbe:20160922205723p:plain

どこから飛んでもこのページ...クッソ見づらい...見づらくない??

そもそも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の extensionssphinx.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が出力される
.
.
.
.
...で、出力されたのがコレ f:id:bonbonbe:20160922205730p:plain

出来た!!
.
.
.
.
...ただし親クラスのpublic、テメーはダメだ!!

この状態だとクラス別のページで親クラスのpublicも一覧に表示されてしまい、非常に見づらい。
というわけで、消す。

autosummaryの一覧から親クラスのpublicの表示を消す

恐らく他の方法がある

自分が調べる限り、conf.pyやディレクティブでなんやかんやできるという情報は見つからなかった。
ので、ソースとautosummaryのclassのtemplateを修正する。

修正するのは

  • \sphinx\ext\autosummary\generate.py
  • \sphinx\ext\autosummary\templates\autosummary\class.rst

の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: のフラグを追加するべきなのだろうが、今回は力尽きたのでこれで良しとした

―――
ここまで設定した後に(略)
.
.
.
.
...で、出力されたのがコレ f:id:bonbonbe:20160922205715p:plain

完成!! 第三部完!!

―――
(追記:10/1)

その他・気になったところ

  • クラス別ページの出力はされているのに、クラス一覧→クラス別ページに飛ばないことがあった。
    • automoduleディレクティブに:noindex:を追加することで解決

参考・気になったところとかは気が向いたら追加

QtDesignerで配置後にPython上でのWidgetのカスタマイズ

ファイル

https://drive.google.com/file/d/0B4fmLEFqtND0THd4TmlDR0lQems/view?usp=sharing

QtDesigner上でこんな感じのUIを作成したとして

f:id:bonbonbe:20160827132118p:plain

この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を均等に並べたい場合は レイアウトを使用する。

使い方

f:id:bonbonbe:20151212033439p:plain

このままだとWindowのサイズを変更したときにレイアウト・ボタンがついてこない。

Windowサイズを変更した時にレイアウトがついてくるようにするには レイアウトをドロップ後、Windowの適当なところで

右クリック > レイアウト > 水平に並べる or 垂直に並べる

をクリックするとWindowサイズの変更に伴ってレイアウトも変更されるようになる。

f:id:bonbonbe:20151212033449p:plain

画面上にレイアウトが複数ある場合には、上記のレイアウトの項目で選択した形で レイアウトが配置される。

Windowサイズに追従させたくないときは

右クリック > レイアウト > レイアウトを破棄

※MainWindowでもWidgetでも同じ。

いい感じに並べる

  • レイアウトでWidgetを並べたとき、Window(親)のサイズについて来てほしいけど微妙に間隔がほしかったりするとき。
  • Widgetの比率を指定してLayoutに追従してもらいたいとき。

// この方法は正しいやり方ではない。多分。

以下、手順。

  • Layout内に配置したWidgetのSizePolicyを縦・横Ignoredに変更 f:id:bonbonbe:20151212033453p:plain

  • LayoutのLayoutSpacing・LayoutStretchを設定 f:id:bonbonbe:20151212033456p:plain

後はLayoutを入れ子状態にすれば何とかなる。
// これMaya関係ないな

同一シーン内におけるGameObject・スクリプトの状態管理

(2016/10/30) 処理内容をかなり書き換えている(&↓がクッソひどい内容)ので新しくまとめる

続きを読む