# -*- coding: utf-8 -*-
u"""
`.DagNode` クラスでサポートする機能の中核。
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from ...common import *
from .cyobject import _newNodeObjByMPath
from .objectref import _getObjectRef
from ._api2mplug import mplug_get_nums, mplug_get_xformmatrix
from ..datatypes.boundingbox import _newBB
from ..datatypes.matrix import _newM, ImmutableMatrix
from ..datatypes.quaternion import _newQ
from ..datatypes.vector import _newV
from ..datatypes.transformation import _newX
from ..datatypes import E
import maya.api.OpenMaya as _api2

__all__ = []

_REVERSE_RO = E.REVERSE_ORDER

_MFn = _api2.MFn
_2_MDagPath = _api2.MDagPath
_2_MFnDagNode = _api2.MFnDagNode
_MX = _api2.MTransformationMatrix
_MM = _api2.MMatrix
_MQ = _api2.MQuaternion
_ME = _api2.MEulerRotation
_MP = _api2.MPoint
_MV = _api2.MVector
if MAYA_VERSION >= (2016, 5):
    _2_MItDag = _api2.MItDag

_MFn_kTransform = _MFn.kTransform
_MFn_kShape = _MFn.kShape
_MFn_kJoint = _MFn.kJoint
_2_MDagPath_getAllPathsTo = _2_MDagPath.getAllPathsTo
_2_MDagPath_getAPathTo = _2_MDagPath.getAPathTo
_MSpace_kTransform = _api2.MSpace.kTransform


#------------------------------------------------------------------------------
class DagNodeMixin(object):
    u"""
    `.DagNode` クラスでサポートする機能の中核。
    """
    __slots__ = tuple()

    def isIntermediateObject(self):
        u"""
        インターミディエイトオブジェクトかどうか。

        :rtype: `bool`
        """
        return self.mfn().isIntermediateObject

    def objectColor(self):
        u"""
        設定されているオブジェクトカラーを得る。

        :rtype: `int`
        """
        return self.mfn().objectColor

    def useObjectColor(self):
        u"""
        オブジェクトカラーが使用される設定になっているかどうか。

        :rtype: `bool`
        """
        return self.mfn().useObjectColor

    def isVisible(self):
        u"""
        可視性を得る。

        :rtype: `bool`
        """
        return self._mpath().isVisible()

    def isTemplated(self):
        u"""
        テンプレート化されているかどうか。

        :rtype: `bool`
        """
        return self._mpath().isTemplated()

    def partialPath(self):
        u"""
        パーシャルパス名を得る。

        `~.CyObject.name` と同じ。

        :rtype: `str`
        """
        return self._mpath().partialPathName()

    def fullPath(self):
        u"""
        フルパス名を得る。

        :rtype: `str`
        """
        return self._mpath().fullPathName()

    def isRoot(self):
        u"""
        ルートノードかどうか。

        :rtype: `bool`
        """
        return self._mpath().length() == 1

    def root(self):
        u"""
        DAGパスに沿ったルートノードを得る。

        自身がルートノードなら同じ参照が返される。

        API の :mayaapi2:`MDagPath` の dagRoot() とは異なり、
        見えない『ワールド』ノードではなく、
        シーン上のルートノードが得られる。

        :rtype: `.Transform` or None
        """
        orig = self._mpath()
        d = orig.length() - 1
        return _newNodeObjByMPath(_2_MDagPath(orig).pop(d)) if d else self

    def allRoots(self):
        u"""
        このノードインスタンスを階層下に持つ全ルートノードのリストを得る。

        自身がルートノードなら同じ参照が返される。

        :rtype: `list`
        """
        orig = self._mpath()
        if orig.length() == 1:
            # ルートノードは他にインスタンスが在ることは有り得ないようだ。
            return [self]

        # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。
        if orig.isInstanced():  # indirect も含んだ判定。
            mpaths = []
            for x in _2_MDagPath_getAllPathsTo(orig.node()):
                # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。
                x = _2_MDagPath(x).pop(x.length() - 1)
                if x not in mpaths:
                    mpaths.append(x)
            return [_newNodeObjByMPath(x) for x in mpaths]
        else:
            return [_newNodeObjByMPath(_2_MDagPath(orig).pop(orig.length() - 1))]

    def leaves(self):
        u"""
        階層下のリーフノードのリストを得る。

        アンダーワールドノード（カーブオンサーフェース等）に
        下ることはされない。

        :rtype: `list`
        """
        leaves = []
        orig = self._mpath()
        mpaths = [orig]
        while mpaths:
            queue = mpaths
            mpaths = []
            for mpath in queue:
                num = mpath.childCount()
                if num:
                    mpaths.extend([_2_MDagPath(mpath).push(mpath.child(i)) for i in range(num)])
                else:
                    leaves.append(mpath)

        if len(leaves) == 1 and leaves[0] is orig:
            return [self]
        return [_newNodeObjByMPath(x) for x in leaves]

    def siblings(self):
        u"""
        自分以外の兄弟ノードのリストを返す。

        :rtype: `list`
        """
        parent = self.mpath().pop()
        num = parent.childCount()
        if num > 1:
            get = parent.child
            mobjs = [get(i) for i in range(num)]
            mnode = self.mnode_()
            return [_newNodeObjByMPath(_2_MDagPath(parent).push(x)) for x in mobjs if x != mnode]
        return []

    def siblingIndex(self):
        u"""
        兄弟におけるインデックスを得る。

        :rtype: `int`
        """
        parent = self.mpath().pop()
        num = parent.childCount()
        if num > 1:
            return _indexOfArr(self.mnode_(), parent.child, num)
        return 0

    def siblingIndices(self):
        u"""
        DAGパス中の全ノードの兄弟インデックスリストを得る。

        :rtype: `list`

        .. note::
            ノードリストをソートするキー関数として利用すると、
            DAG階層の深さ優先ソートとなる。

            幅優先なら `lengthAndSiblingIndices` を
            兄弟順保証しない幅優先なら `pathLength` を使うと良い。
        """
        result = []
        mpath = self._mpath()
        pathLen = mpath.length()

        if self.mfn_().inUnderWorld:
            while pathLen:
                parent = _2_MDagPath(mpath).pop()
                num = parent.childCount()
                if num > 1:
                    result.append(_indexOfArr(mpath.node(), parent.child, num))
                else:
                    result.append(0)

                if _2_MFnDagNode(parent).inModel:
                    mpath = parent
                    pathLen -= 1
                else:
                    # 着目している親が『ワールド』ノードの場合、次はそれをスキップ。
                    mpath = parent.pop()
                    pathLen -= 2
        else:
            while pathLen:
                parent = _2_MDagPath(mpath).pop()
                num = parent.childCount()
                if num > 1:
                    result.append(_indexOfArr(mpath.node(), parent.child, num))
                else:
                    result.append(0)
                mpath = parent
                pathLen -= 1

        result.reverse()
        return result

    def lengthAndSiblingIndices(self):
        u"""
        DAGパスを構成するノード数と、兄弟インデックスリストのペアを得る。

        :rtype: (int, `list`)

        .. note::
            ノードリストをソートするキー関数として利用すると、
            DAG階層の幅優先ソートとなる。

            兄弟順保証が不要なら `pathLength` を、
            深さ優先なら `siblingIndices` を利用する良い。
        """
        k = self.siblingIndices()
        return len(k), k

    def pathLength(self):
        u"""
        DAGフルパスの長さを得る。

        カーブオンサーフェースと親シェイプとの間に存在する
        シーン上では見えない『ワールド』ノードはカウントされないため、
        API の :mayaapi2:`MDagPath` の length() とは異なる。

        :rtype: `int`

        .. note::
            ノードリストをソートするキー関数として利用すると、
            DAG階層の兄弟順保証無しの幅優先ソートとなる。

            兄弟順保証するなら `lengthAndSiblingIndices` を
            深さ優先なら `siblingIndices` を利用すると良い。
        """
        orig = self._mpath()
        num = orig.length()
        if num >= 3 and self.mfn_().inUnderWorld:
            #return num - 1
            # 二重以上の UnderWorld が無いなら num - 1 で良いのだが、念のため not inModel の数だけ削減する。
            if num < 5:
                return num - 1
            mpath = _2_MDagPath(orig).pop()
            mfn = _2_MFnDagNode(mpath)
            while mfn.inUnderWorld:
                if not mfn.inModel:
                    num -= 1
                mpath.pop()
                mfn = _2_MFnDagNode(mpath)
        return num

    def numParents(self, indirect=False):
        u"""
        このノードインスタンスを子に持つ親ノードの数を得る。

        :param `bool` indirect:
            インスタンスポイント以外も含まれる親のパス全てを得る。
        :rtype: `int`
        """
        return self.numInstances() if indirect else self.mfn().parentCount()

    def parents(self, indirect=False):
        u"""
        このノードインスタンスを子に持つ親ノードのリストを得る。

        :param `bool` indirect:
            インスタンスポイント以外も含まれる親のパス全てを得る。
        :rtype: `list`
        """
        # ルートノードなら親は無し。
        orig = self._mpath()
        if orig.length() < 2:
            return []

        # このパス上の親を取得。
        mfn = self.mfn_()
        if self.isShape():
            parent = self._transform()
        else:
            mpath = _2_MDagPath(orig).pop()
            if mfn.inUnderWorld:
                if not _2_MFnDagNode(mpath).inModel:
                    mpath.pop()
            parent = _newNodeObjByMPath(mpath)

        # インスタンスポイント以外の全パスを得るなら、このノードの全パスの親を得る。
        if indirect:
            # インスタンスが1つなら親も1つ。
            if not orig.isInstanced():  # indirectも含んだ判定。
                return [parent]
            idx = orig.instanceNumber()
            return [
                parent if i == idx else _newNodeObjByMPath(_getParentPath(x))
                for i, x in enumerate(_2_MDagPath_getAllPathsTo(self.mnode_()))
            ]

        # インスタンスポイントにおける親ノードを得るなら mfn から得る。
        else:
            # インスタンス数が複数でもインスタンスポイントでなければ、親は1つとなる。
            num = mfn.parentCount()
            if num == 1:
                return [parent]

            # 親が複数の場合、このパス上でない親は getAPathTo に任せる。
            mobjs = [mfn.parent(i) for i in range(num)]
            mnode = parent.mnode_()
            return [
                parent if x == mnode else _newNodeObjByMPath(_2_MDagPath_getAPathTo(x))
                for x in mobjs]

    def parent(self, step=1):
        u"""
        DAGパスに沿った親ノードを得る。

        アンダーワールドノード（カーブオンサーフェース等）から
        親シェイプを得ることもできる。

        :param `int` step:
            遡るノード数。
            API の :mayaapi2:`MDagPath` の pop とは異なり、
            カーブオンサーフェースと親シェイプとの間に存在する
            シーン上では見えない『ワールド』ノードはカウントしない。
        :rtype: `.DagNode` or None
        """
        if step > 0:
            self.checkValid()
            if step == 1 and self.isShape():
                # shape の親を得るなら _transform メソッドを呼ぶ。
                return self._transform()
            orig = self._mpath_()
            if self.mfn_().inUnderWorld:
                if step < orig.length() - 1:
                    mpath = _2_MDagPath(orig).pop()
                    mfn = _2_MFnDagNode(mpath)
                    while mfn.inUnderWorld:
                        if mfn.inModel:
                            if step == 1:
                                break
                            step -= 1
                        mpath.pop()
                        mfn = _2_MFnDagNode(mpath)
                    step -= 1
                    if step:
                        mpath.pop(step)
                    return _newNodeObjByMPath(mpath)
            else:
                if step < orig.length():
                    return _newNodeObjByMPath(_2_MDagPath(orig).pop(step))

    def numChildren(self, shapes=False, intermediates=False):
        u"""
        DAGパス上の子ノードの数を得る。

        :param `bool` shapes:
            シェイプも含めるかどうか。
        :param `bool` intermediates:
            shapes=True のとき、さらに
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `int`
        """
        if self.isTransform():
            if shapes and intermediates:
                return self._mpath().childCount()
            num = 0
            orig = self._mpath()
            get = orig.child
            for i in range(orig.childCount()):
                mobj = get(i)
                if (
                    (shapes or not mobj.hasFn(_MFn_kShape)) and
                    (intermediates or not _2_MFnDagNode(mobj).isIntermediateObject)
                ):
                    num += 1
            return num
        return 0

    def children(self, shapes=False, intermediates=False):
        u"""
        DAGパス上の子ノードのリストを得る。

        シェイプからアンダーワールドノード（カーブオンサーフェース等）を
        得ることはできないため、その場合は `underWorldNodes` を利用すること。

        :param `bool` shapes:
            シェイプも含めるかどうか。
        :param `bool` intermediates:
            shapes=True のとき、さらに
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `list`
        """
        if not self.isTransform():
            return []

        if shapes:
            return self.__getShapes(intermediates, True)

        orig = self._mpath()
        get = orig.child
        return [
            _newNodeObjByMPath(_2_MDagPath(orig).push(o))
            for o in [get(i) for i in range(orig.childCount())]
            if not o.hasFn(_MFn_kShape) and
            (intermediates or not _2_MFnDagNode(o).isIntermediateObject)
        ]

    def child(self, idx, shapes=False, intermediates=False):
        u"""
        インデックス指定で子ノードを1つ得る。

        シェイプからアンダーワールドノード（カーブオンサーフェース等）を
        得ることはできないため、その場合は `underWorldNodes` を利用すること。

        :param `int` idx:
            インデックス。
            shapes や intermediates オプションによって、
            同じインデックスでも得られるものが変わることがある。
            無効なインデックスを指定するとエラーではなく None となる。
        :param `bool` shapes:
            シェイプも含めるかどうか。
        :param `bool` intermediates:
            shapes=True のとき、さらに
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `.DagNode` or None
        """
        if not self.isTransform():
            return

        orig = self._mpath()
        get = orig.child

        # シェイプを含める場合はキャッシュと同期する。
        if shapes:
            # インターミディエイトを含める場合は、直接インデックス指定で得て、
            # それがシェイプの場合にシェイプとの同期を処理する。
            if intermediates:
                # まず、直接得て、シェイプでなければそれを返す。
                try:
                    mobj = get(idx)
                except:
                    return
                if not mobj.hasFn(_MFn_kShape):
                    return _newNodeObjByMPath(_2_MDagPath(orig).push(mobj))

                # シェイプだった場合は、キャッシュ用のシェイプインデックスを調べて同期する。
                k0 = 0  # not intermediate
                k1 = 0  # all
                for i in range(orig.childCount()):
                    mobj = get(i)
                    if mobj.hasFn(_MFn_kShape):
                        if i == idx:
                            return self._Node_c__getShapeWithCache(k0, k1, mobj)
                        if not _2_MFnDagNode(mobj).isIntermediateObject:
                            k0 += 1
                        k1 += 1

            # インターミディエイトを含めない場合は、どのみち直接インデックス指定では得られないので、
            # 子をトラバースして、インデックスを調べつつ、それがシェイプなら同期する。
            else:
                k0 = 0  # not intermediate
                k1 = 0  # all
                for i in range(orig.childCount()):
                    mobj = get(i)
                    if _2_MFnDagNode(mobj).isIntermediateObject:
                        if mobj.hasFn(_MFn_kShape):
                            k1 += 1
                    else:
                        if not idx:
                            if mobj.hasFn(_MFn_kShape):
                                return self._Node_c__getShapeWithCache(k0, k1, mobj)
                            else:
                                return _newNodeObjByMPath(_2_MDagPath(orig).push(mobj))
                        if mobj.hasFn(_MFn_kShape):
                            k0 += 1
                            k1 += 1
                        idx -= 1

        # シェイプを含めないなら、キャッシュとの同期を考える必要はない。
        else:
            for i in range(orig.childCount()):
                mobj = get(i)
                if (
                    (shapes or not mobj.hasFn(_MFn_kShape)) and
                    (intermediates or not _2_MFnDagNode(mobj).isIntermediateObject)
                ):
                    if not idx:
                        return _newNodeObjByMPath(_2_MDagPath(orig).push(mobj))
                    idx -= 1

    def isParentOf(self, node, exact=False, underWorld=True):
        u"""
        指定ノードの親かどうか検査する。

        直接の親に限らない上位ノードかどうかチェックする場合は
        `isAncestorOf` が利用できる。

        :type node: `.DagNode`
        :param node: チェックするノード。
        :param exact:
            インスタンス関係は考慮せず、DAGパスに沿った判定のみとするかどうか。
        :param `bool` underWorld:
            アンダーワールド（カーブオンサーフェース）の親シェイプも判定する。
        :rtype: `bool`
        """
        if exact:
            mpath = node.mpath().pop()
            if underWorld and not _2_MFnDagNode(mpath).inModel:
                mpath.pop()
            return mpath == self._mpath()

        else:
            mnode = self.mnode()
            node_fn = node.mfn()
            for i in range(node_fn.parentCount()):
                if node_fn.parent(i) == mnode:
                    return True
            if underWorld and node_fn.inUnderWorld:
                mpath = node.mpath_().pop()
                return not _2_MFnDagNode(mpath).inModel and mpath.pop().node() == mnode
            return False

    def isAncestorOf(self, node, exact=False, underWorld=True):
        u"""
        指定ノードの上位ノードかどうか検査する。

        直接の親かどうかをチェックする場合は
        `isParentOf` が利用できる。

        :type node: `.DagNode`
        :param node: チェックするノード。
        :param exact:
            インスタンス関係は考慮せず、DAGパスに沿った判定のみとするかどうか。
        :param `bool` underWorld:
            アンダーワールド（カーブオンサーフェース）の親シェイプも判定する。
        :rtype: `bool`
        """
        if exact:
            if underWorld or (self.mfn().inUnderWorld == node.mfn().inUnderWorld):
                self_mpath = self._mpath_()
                node_mpath = node._mpath_()
                n = node_mpath.length() - self_mpath.length()
                return n > 0 and self_mpath == _2_MDagPath(node_mpath).pop(n)
            return False

        else:
            self_fn = self.mfn()
            if self_fn.isParentOf(node.mnode()):
                return True
            if underWorld and not self_fn.inUnderWorld:
                node_fn = node.mfn_()
                if node_fn.inUnderWorld:
                    mnode = _2_MDagPath_getAPathTo(node_fn.dagRoot()).pop().node()
                    return self.mnode_() == mnode or self_fn.isParentOf(mnode)
            return False

    def inUnderWorld(self):
        u"""
        アンダーワールド（カーブオンサーフェース等）かどうか。

        :rtype: `bool`
        """
        return self.mfn().inUnderWorld

    def parentWorld(self):
        u"""
        アンダーワールド（カーブオンサーフェース等）における親空間シェイプを返す。

        本メソッドによって、カーブオンサーフェースから
        親のサーフェースシェイプを得ることができる。

        :rtype: `.Shape` or None
        """
        mfn = self.mfn()
        if mfn.inUnderWorld:
            mpath = self.mpath_().pop()
            root = mfn.dagRoot()  # シーン上には見えない『ワールド』ノード。
            while root != mpath.node():
                mpath.pop()
            return _newNodeObjByMPath(mpath.pop())

    if MAYA_VERSION >= (2016, 5):
        def hasUnderWorldNodes(self):
            u"""
            シェイプ下にアンダーワールドノード（カーブオンサーフェース等）を持つかどうか。

            :rtype: `bool`
            """
            shape = self.shape()
            if shape:
                it = _2_MItDag()
                it.reset(shape._mpath())
                it.traverseUnderWorld = True
                next(it)
                return not it.isDone() and not _2_MFnDagNode(it.getPath()).inModel
            return False

        def underWorldNodes(self):
            u"""
            シェイプ下のアンダーワールドノード（カーブオンサーフェース等）のリストを得る。

            :rtype: `list`
            """
            shape = self.shape()
            if shape:
                it = _2_MItDag()
                it.reset(shape._mpath())
                it.traverseUnderWorld = True
                next(it)
                if not it.isDone():
                    mpath = it.getPath()
                    if not _2_MFnDagNode(mpath).inModel:
                        get = mpath.child
                        return [_newNodeObjByMPath(_2_MDagPath(mpath).push(get(i))) for i in range(mpath.childCount())]
            return []

    def numShapes(self, intermediates=False):
        u"""
        :mayanode:`transform` の持つ :mayanode:`shape` ノード数を得る。

        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `int`
        """
        if self.isTransform():
            if intermediates:
                num = 0
                orig = self._mpath()
                get = orig.child
                for i in range(orig.childCount()):
                    if get(i).hasFn(_MFn_kShape):
                        num += 1
                return num
            else:
                return self._mpath().numberOfShapesDirectlyBelow()
        return 0

    def shape(self, idx=0, intermediates=False):
        u"""
        :mayanode:`shape` ノードを得る。

        自身がシェイプの場合は自身が、
        :mayanode:`transform` の場合はそのシェイプが得られる。

        :param `int` idx:
            得たいシェイプのインデックス。
            自身がシェイプの場合は無視される。

            intermediates オプションによって、
            同じインデックスでも得られるものが変わることがある。
            無効なインデックスを指定するとエラーではなく None となる。

            Python的な負のインデックスも指定可能。
        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `.Shape` or None
        """
        if self.isShape():
            return self
        elif self.isTransform():
            self.checkValid()
            return self._shape(idx, intermediates)

    def shapes(self, intermediates=False):
        u"""
        :mayanode:`transform` の持つ :mayanode:`shape` ノードのリストを得る。

        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `list`
        """
        return self.__getShapes(intermediates) if self.isTransform() else []

    def __getShapes(self, intermediates=False, others=False):
        u"""
        子ノード群を得つつシェイプをキャッシュするための共通ルーチン。

        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :param `bool` others:
            シェイプ以外の子も含めるかどうか。
            ただし、キャッシュされるのはシェイプのみ。
        :rtype: `list`
        """
        # 子の MObject を収集。
        orig = self._mpath()
        get = orig.child
        allMObjs = [get(i) for i in range(orig.childCount())]
        if others:
            if intermediates:
                flagMObjs = [(o.hasFn(_MFn_kShape), o) for o in allMObjs]
                mfns = [_2_MFnDagNode(o) for f, o in flagMObjs if f]
            else:
                flagMFns = [(o.hasFn(_MFn_kShape), _2_MFnDagNode(o)) for o in allMObjs]
                mfns = [x for f, x in flagMFns if f]
        else:
            mfns = [_2_MFnDagNode(o) for o in allMObjs if o.hasFn(_MFn_kShape)]
        allIdxDict = dict([(x.name(), i) for i, x in enumerate(mfns)])

        # 既存のキャッシュを取得。
        oldCache0, oldCache1 = self._CyObject__data['shape']

        # 現在のシェイプ構成に合わせて、既存のキャッシュから可能な限り引き継ぐ。
        cache1 = {}
        for shape in oldCache0.values():
            if shape.isValid():
                i = allIdxDict.pop(shape.nodeName_(), None)
                if i is not None:
                    cache1[i] = shape
        for shape in oldCache1.values():
            if shape.isValid():
                i = allIdxDict.pop(shape.nodeName_(), None)
                if i is not None:
                    cache1[i] = shape

        # キャッシュから引き継げなかったシェイプで、戻り値に必要なものだけを生成する。
        if allIdxDict:
            wref = _getObjectRef(self).weakref()
            if intermediates:
                # all
                for i in allIdxDict.values():
                    shape = _newNodeObjByMPath(_2_MDagPath(orig).push(mfns[i].object()))
                    shape._CyObject__data['transform'] = wref
                    cache1[i] = shape
            else:
                # not intermediate
                for i in allIdxDict.values():
                    if not mfns[i].isIntermediateObject:
                        shape = _newNodeObjByMPath(_2_MDagPath(orig).push(mfns[i].object()))
                        shape._CyObject__data['transform'] = wref
                        cache1[i] = shape

        # all を参照しながら not intermediate キャッシュを生成。
        cache0 = {}
        k0 = 0
        for i in range(len(mfns)):
            shape = cache1.get(i)
            if shape and not shape.mfn_().isIntermediateObject:
                cache0[k0] = shape
                k0 += 1

        # 新しいキャッシュをセット。
        self._CyObject__data['shape'] = [cache0, cache1]

        # オプションに応じた list を返す。
        if others:
            def count():
                cnt[0] += 1
                return cnt[0]
            cnt = [-1]
            if intermediates:
                return [
                    (cache1[count()] if f else _newNodeObjByMPath(_2_MDagPath(orig).push(o)))
                    for f, o in flagMObjs]
            else:
                return [
                    (cache0[count()] if f else _newNodeObjByMPath(_2_MDagPath(orig).push(x.object())))
                    for f, x in flagMFns if not x.isIntermediateObject]
        else:
            if intermediates:
                return [cache1[i] for i in sorted(cache1)]
            else:
                return [cache0[i] for i in sorted(cache0)]

    def transform(self):
        u"""
        :mayanode:`transform` ノードを得る。

        自身が :mayanode:`transform` の場合は自身が、
         :mayanode:`shape` の場合はその親が得られる。

        :rtype: `.Transform` or None
        """
        if self.isTransform():
            return self
        elif self.isShape():
            self.checkValid()
            return self._transform()

    # _shape を少し隠しているように、これも少し隠すものとする。
    def _transform(self):
        u"""
        `~.CyObject.checkValid` を省略して、シェイプから :mayanode:`transform` ノードを得る。

        :rtype: `.Transform` or None
        """
        # キャッシュが古い可能性があるので、MDagPath は毎回取得し直す。
        mpath = _2_MDagPath(self._CyObject__data['mpath'])
        try:
            mpath.pop()
        except RuntimeError:
            return

        # キャッシュが在ればそれを再利用、無ければ新規生成する。
        obj = self._CyObject__data['transform']
        if obj:
            obj = obj()
            if obj and obj.isValid() and obj._CyObject__data['mpath'] == mpath:
                return obj
        obj = _newNodeObjByMPath(mpath)

        # transform の子ノードを調べ、キャッシュキーを決定する。
        k0 = 0  # not intermediate
        k1 = 0  # all
        get = mpath.child
        shape_mnode = self._CyObject__data['mnode']
        for i in range(mpath.childCount()):
            mnode = get(i)
            if mnode == shape_mnode:
                break
            if mnode.hasFn(_MFn_kShape):
                k1 += 1
                if not _2_MFnDagNode(mnode).isIntermediateObject:
                    k0 += 1

        # 相互キャッシュを生成。
        obj._CyObject__data['shape'][0][k0] = self
        obj._CyObject__data['shape'][1][k1] = self
        self._CyObject__data['tranform'] = _getObjectRef(obj).weakref()
        return obj

    def isInstanceable(self):
        u"""
        インスタンス可能かどうか。

        :rtype: `bool`
        """
        return self.mfn().isInstanceable

    def isInstanced(self, indirect=True):
        u"""
        インスタンスされているかどうか。

        :param `bool` indirect:
            直接インスタンス化されたポイント以外も含めるかどうか。
        :rtype: `bool`
        """
        # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。
        if indirect:
            return self._mpath().isInstanced()  # indirect も含んだ判定で mfn より少し速い。
        else:
            return self.mfn().isInstanced(False)

    def instanceIndex(self):
        u"""
        インスタンス番号を得る。

        :rtype: `int`

        .. note::
            この番号は並び順の変更やインスタンス数の増減によって動的に変わる。
            また、使用されているワールドスペースプラグのインデックスも
            それに合わせて変わる。
        """
        return self._mpath().instanceNumber()

    def numInstances(self, indirect=True):
        u"""
        インスタンス数を得る。

        :param `bool` indirect:
            直接インスタンス化されたポイント以外も含めるかどうか。
        :rtype: `int`
        """
        # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。
        mfn = self.mfn()
        if indirect and mfn.inUnderWorld:
            orig = self._mpath_()
            if orig.isInstanced():  # indirect も含んだ判定。
                # inUnderWorld を抜けるまで上昇してから instanceCount を得る。
                mpath = _2_MDagPath(orig).pop()
                mfn = _2_MFnDagNode(mpath)
                while mfn.inUnderWorld:
                    mpath.pop()
                    mfn = _2_MFnDagNode(mpath)
                return mfn.instanceCount(indirect)
            return 1
        else:
            # inUnderWorld だとインスタンスコピーはできないのでこれで良い。
            return mfn.instanceCount(indirect)

    def instance(self, idx):
        u"""
        インスタンス番号からインスタンスを得る。

        :param `int` idx: インスタンス番号。
        :rtype: `.DagNode` or None
        """
        if self._mpath().instanceNumber() == idx:
            return self

        # 0 番の場合 getAPathTo で得られるようだ。
        if not idx:
            mnode = self.mnode_()
            mpath = _2_MDagPath_getAPathTo(mnode)
            if not mpath.instanceNumber():  # 念のためチェック。
                mpath = _2_MDagPath(_2_MDagPath_getAllPathsTo(mnode)[idx])
            return type(self)(mpath)

        # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。
        arr = _2_MDagPath_getAllPathsTo(self.mnode_())
        if 0 <= idx < len(arr):
            return type(self)(_2_MDagPath(arr[idx]))

    def instances(self, noSelf=False):
        u"""
        インスタンスのリストを返す。

        :param `bool` noSelf: 自身を含めないようにする。
        :rtype: `list`
        """
        orig = self._mpath()
        if orig.isInstanced():  # indirect も含んだ判定。
            cls = type(self)
            idx = orig.instanceNumber()
            # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。
            if noSelf:
                return [
                    cls(_2_MDagPath(x))
                    for i, x in enumerate(_2_MDagPath_getAllPathsTo(self.mnode_()))
                    if i != idx]
            else:
                return [
                    self if i == idx else cls(_2_MDagPath(x))
                    for i, x in enumerate(_2_MDagPath_getAllPathsTo(self.mnode_()))]
        return [] if noSelf else [self]

    def boundingBox(self, ws=False):
        u"""
        バウンディングボックスを得る。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.BoundingBox`
        """
        if ws:
            bb = self.mfn().boundingBox.transformUsing(self._mpath_().exclusiveMatrix())
            return _newBB(bb)
        return _newBB(self.mfn().boundingBox)

    def getMatrix(self, ws=False, p=False, inv=False):
        u"""
        ノードのトランスフォーメーションのマトリックスを得る。

        :param `bool` ws: ワールド空間で得るかどうか。
        :param `bool` p: 親のマトリックスを得るかどうか。
        :param `bool` inv: 逆行列を得るかどうか。
        :rtype: `.Matrix`
        """
        # それぞれプラグから得る方法もあるが、MDagPath から得た方が速い。
        if ws:
            if p:
                if inv:
                    return _newM(self._mpath().exclusiveMatrixInverse())
                else:
                    return _newM(self._mpath().exclusiveMatrix())
            else:
                if inv:
                    return _newM(self._mpath().inclusiveMatrixInverse())
                else:
                    return _newM(self._mpath().inclusiveMatrix())
        else:
            if p:
                # 親のファンクションセットを得るより MDagPath のまま計算した方が速い。
                mpath = _2_MDagPath(self._mpath()).pop()
                if inv:
                    return _newM(mpath.exclusiveMatrix() * mpath.inclusiveMatrixInverse())
                else:
                    return _newM(mpath.inclusiveMatrix() * mpath.exclusiveMatrixInverse())
            elif self.isTransform():
                if inv:
                    return _newM(self.mfn().transformationMatrix().inverse())
                else:
                    return _newM(self.mfn().transformationMatrix())
            else:
                return _newM(_MM())

    getM = getMatrix  #: `getMatrix` の別名。

    def getTransformation(self, ws=False):
        u"""
        ノードのトランスフォーメーション情報を得る。

        :mayanode:`transform` 派生ノードの
        アトリビュート ``xm`` の値を得ることと同じだが、
        オプションでワールドスペースの値を得ることもできる。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Transformation`
        """
        if self.isTransform():
            x = mplug_get_xformmatrix(self.mfn().findPlug('xm', True))
            if ws:
                # Transformation に Matrix を乗じることで、
                # ピボットなどの各基準位置もワールド空間で一致させる。
                mpath = self._mpath_()
                if mpath.length() > 1:
                    x *= _newM(self._mpath_().exclusiveMatrix())
            return x
        else:
            if ws:
                return _newX(dict(m=_newM(self._mpath().exclusiveMatrix(), ImmutableMatrix)))
            else:
                return _newX({})

    getX = getTransformation  #: `getTransformation` の別名。

    def getScaling(self, ws=False):
        u"""
        ノードのスケーリングの値を得る。

        ws=False の場合は、
        単に scale アトリビュートから得られ、
        :mayanode:`joint` ノードのローカルマトリックスには含まれる
        inverseScale の影響は無視される。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Vector`
        """
        if ws:
            return _newV(_MP(_MX(self._mpath().inclusiveMatrix()).scale(_MSpace_kTransform)))
        if self.isTransform():
            return _newV(_MP(mplug_get_nums(self.mfn().findPlug('s', True))))
        return _newV(_MP(1., 1., 1.))

    getS = getScaling  #: `getScaling` の別名。

    def getShearing(self, ws=False):
        u"""
        ノードのシアーの値を得る。

        ws=False の場合は、
        単に shear アトリビュートから得られ、
        :mayanode:`joint` ノードのローカルマトリックスには含まれる
        inverseScale の影響は無視される。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Vector`
        """
        if ws:
            return _newV(_MP(_MX(self._mpath().inclusiveMatrix()).shear(_MSpace_kTransform)))
        if self.isTransform():
            return _newV(_MP(mplug_get_nums(self.mfn().findPlug('sh', True))))
        return _newV(_MP())

    getSh = getShearing  #: `getShearing` の別名。

    def getQuaternion(self, ws=False, ra=False, r=True, jo=True):
        u"""
        ノードの回転のクォータニオンを得る。

        デフォルトでは rotateAxis を含んでいない回転となり、
        マトリックスからの分解結果とは一致しないが、
        Maya の機能（Local Axis 表示やコンストレイン）が
        ノードの回転方向とする基準と一致する。

        ws=False の場合は、
        3種の回転アトリビュートのみから合成された結果が得られ、
        :mayanode:`joint` ノードのローカルマトリックスには含まれる
        inverseScale の影響は無視される。

        :param `bool` ws: ワールド空間で得るかどうか。
        :param `bool` ra: rotateAxis を含めるかどうか。
        :param `bool` r: rotate を含めるかどうか。
        :param `bool` jo: jointOrient を含めるかどうか。
        :rtype: `.Quaternion`

        .. note::
            ws=True の場合の親までの非一様 scale や shear による影響や
            ws=False の場合の jointOrient は、 
            :mayacmd:`xform` コマンドや :mayaapi2:`MTransformationMatrix`
            では考慮されないが、本メソッドでは考慮されている。

            ws=True の結果は Legacy Viewport での Local Axis 表示と常に一致するが、
            上位階層に 非一様 scale や shear が含まれる場合、
            orientConstraint や parentConstraint の結果とは一致しない場合がある。
            これは、コンストレイン側で考慮が足りていないからである。
            ただし、segmentScaleCompensate で常に非一様 scale が打ち消されていれば一致する。

            ws=False の結果は、上位階層に非一様 scale や shear が無いか、
            segmentScaleCompensate で常に非一様 scale が打ち消されていれば、
            親の getQ(ws=True, ra=True) を乗じた結果は getQ(ws=True) と一致する。

        .. warning::
            以下のオプションの組み合わせはエラーになる。

            - ws=False, ra=True, r=False, jo=True
            - ws=True, ra=True, r=False
            - ws=True, ra=True, jo=False
            - ws=True, r=True, jo=False
        """
        if ws:
            if (not(r and jo)) if ra else (r and not jo):
                raise ValueError('getQuaternion: unsupported option combination.')

            if self.isTransform():
                # Legacy Viewport の Local Axis と同じになる。
                # nw ではこのノードのscaleとshearを取り除いているがそれだと Local Axis には一致しない。
                # いずれにせよ、非一様scaleが残っている場合のコンストレインの結果とは一致しない。
                mfn = self.mfn()
                if ra or r or (jo and mfn.object().hasFn(_MFn_kJoint)):
                    q = _MQ().setValue(self._mpath().inclusiveMatrix().homogenize())
                    if not ra:
                        findPlug = mfn.findPlug
                        v = mplug_get_nums(findPlug('ra', True))
                        q = _ME(-v[0], -v[1], -v[2], ZYX).asQuaternion() * q
                        if not r:
                            v = mplug_get_nums(findPlug('r', True))
                            q = _ME(-v[0], -v[1], -v[2], _REVERSE_RO[findPlug('ro', True).asShort()]).asQuaternion() * q
                else:
                    q = _MQ().setValue(self._mpath().exclusiveMatrix().homogenize())
            else:
                # transform でなければ、ローカルオプションによる差はない。
                q = _MQ().setValue(self._mpath().inclusiveMatrix().homogenize())

        else:
            if ra and not r and jo:
                raise ValueError('getQuaternion: unsupported option combination.')

            if self.isTransform():
                # ssc=on であれば
                # (this.getQ() * parent.getQ(ws=True, ra=True)) が
                # this.getQ(ws=True) と同じになるようにしている。
                mfn = self.mfn()
                if ra:
                    findPlug = mfn.findPlug
                    q = _ME(mplug_get_nums(findPlug('ra', True))).asQuaternion()
                    if r:
                        q *= _ME(mplug_get_nums(findPlug('r', True)), findPlug('ro', True).asShort()).asQuaternion()
                        if jo and mfn.object().hasFn(_MFn_kJoint):
                            q *= _ME(mplug_get_nums(findPlug('jo', True))).asQuaternion()
                elif r:
                    findPlug = mfn.findPlug
                    q = _ME(mplug_get_nums(findPlug('r', True)), findPlug('ro', True).asShort()).asQuaternion()
                    if jo and mfn.object().hasFn(_MFn_kJoint):
                        q *= _ME(mplug_get_nums(findPlug('jo', True))).asQuaternion()
                elif jo and mfn.object().hasFn(_MFn_kJoint):
                    q = _ME(mplug_get_nums(mfn.findPlug('jo', True))).asQuaternion()
                else:
                    return _newQ(_MQ())
            else:
                # transform でなければ、ローカルオプションによる差はない。
                return _newQ(_MQ())

        if q[3] < 0.:
            q.negateIt()
        return _newQ(q)

    getQ = getQuaternion  #: `getQuaternion` の別名。

    def getJOQ(self, ws=False):
        u"""
        ノードの jointOrient までのクォータニオンを得る。

        `getQuaternion` の r=False 指定と同じ。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Quaternion`
        """
        return self.getQ(ws=ws, r=False)

    def getTranslation(self, ws=False, at=2):
        u"""
        ノードの位置を得る。

        デフォルトでは回転ピボットの位置となり、
        マトリックスからの分解結果とは一致しないが、
        Maya の機能（Local Axis 表示位置やコンストレイン）が
        ノードの位置とする基準と一致する。

        :param `bool` ws: ワールド空間で得るかどうか。
        :param `int` at:
            どのアトリビュートに相当する位置を得るか。

            - 0 だとローカル原点（親の matrix 位置）。
            - 1 だと translate の位置。
            - 2 だと rotatePivot の位置。
            - 3 だと scalePivot の位置。
            - 4 以上だと matrix の位置。

        :rtype: `.Vector`
        """
        # -sp s sh sp spt -rp ra r jo rp rpt -is t
        if not self.isTransform():
            at = 4

        if at >= 3:
            if ws:
                m = self._mpath().inclusiveMatrix()
            else:
                m = self.mfn().transformationMatrix()
            if at >= 4:
                return _newV(_MP(m[12], m[13], m[14]))
            p = _MP(mplug_get_nums(self.mfn_().findPlug('sp', True)))
            p *= m
            return _newV(p)

        if at < 1:
            if ws:
                m = self._mpath().exclusiveMatrix()
                return _newV(_MP(m[12], m[13], m[14]))
            else:
                return _newV(_MP())

        mfn = self.mfn()
        findPlug = mfn.findPlug
        p = _MP(mplug_get_nums(findPlug('t', True)))

        if at >= 2:
            v = _MV(mplug_get_nums(findPlug('rp', True)))
            v += _MV(mplug_get_nums(findPlug('rpt', True)))
            if mfn.object().hasFn(_MFn_kJoint) and findPlug('ssc', True).asBool():
                s = mplug_get_nums(findPlug('is', True))
                v *= _MM([
                    1. / s[0], 0., 0., 0.,
                    0., 1. / s[1], 0., 0.,
                    0., 0., 1. / s[2], 0.,
                    0., 0., 0., 1.,
                ])
            p += v

        if ws:
            m = self._mpath_().exclusiveMatrix()
            p *= m
        return _newV(p)

    getT = getTranslation  #: `getTranslation` の別名。


#------------------------------------------------------------------------------
def _indexOfArr(obj, get, num):
    for i in range(num):
        if get(i) == obj:
            return i


def _getParentPath(orig):
    mpath = _2_MDagPath(orig).pop()
    if not _2_MFnDagNode(mpath).inModel:
        mpath.pop()
    return mpath


u'''
def _searchShapeCache(cache, key, mnode):
    for k in list(cache):
        shape = cache[k]
        if not shape.isValid():
            # 無効だったら削除。
            del cache[k]
        elif shape._CyObject__data['mnode'] == mnode:
            # 目的のものなら、キャッシュを作り直し。
            del cache[k]
            cache[key] = shape
            return shape
'''


# -*- coding: utf-8 -*-
u"""
`.DagNode` クラスでサポートする機能の中核。
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from ...common import *
from .cyobject import _newNodeObjByMPath
from .objectref import _getObjectRef
from ._api2mplug import mplug_get_nums, mplug_get_xformmatrix
from ..datatypes.boundingbox import _newBB
from ..datatypes.matrix import _newM, ImmutableMatrix
from ..datatypes.quaternion import _newQ
from ..datatypes.vector import _newV
from ..datatypes.transformation import _newX
from ..datatypes import E
import maya.api.OpenMaya as _api2

__all__ = []

_REVERSE_RO = E.REVERSE_ORDER

_MFn = _api2.MFn
_2_MDagPath = _api2.MDagPath
_2_MFnDagNode = _api2.MFnDagNode
_MX = _api2.MTransformationMatrix
_MM = _api2.MMatrix
_MQ = _api2.MQuaternion
_ME = _api2.MEulerRotation
_MP = _api2.MPoint
_MV = _api2.MVector
if MAYA_VERSION >= (2016, 5):
    _2_MItDag = _api2.MItDag

_MFn_kTransform = _MFn.kTransform
_MFn_kShape = _MFn.kShape
_MFn_kJoint = _MFn.kJoint
_2_MDagPath_getAllPathsTo = _2_MDagPath.getAllPathsTo
_2_MDagPath_getAPathTo = _2_MDagPath.getAPathTo
_MSpace_kTransform = _api2.MSpace.kTransform


#------------------------------------------------------------------------------
class DagNodeMixin(object):
    u"""
    `.DagNode` クラスでサポートする機能の中核。
    """
    __slots__ = tuple()

    def isIntermediateObject(self):
        u"""
        インターミディエイトオブジェクトかどうか。

        :rtype: `bool`
        """
        return self.mfn().isIntermediateObject

    def objectColor(self):
        u"""
        設定されているオブジェクトカラーを得る。

        :rtype: `int`
        """
        return self.mfn().objectColor

    def useObjectColor(self):
        u"""
        オブジェクトカラーが使用される設定になっているかどうか。

        :rtype: `bool`
        """
        return self.mfn().useObjectColor

    def isVisible(self):
        u"""
        可視性を得る。

        :rtype: `bool`
        """
        return self._mpath().isVisible()

    def isTemplated(self):
        u"""
        テンプレート化されているかどうか。

        :rtype: `bool`
        """
        return self._mpath().isTemplated()

    def partialPath(self):
        u"""
        パーシャルパス名を得る。

        `~.CyObject.name` と同じ。

        :rtype: `str`
        """
        return self._mpath().partialPathName()

    def fullPath(self):
        u"""
        フルパス名を得る。

        :rtype: `str`
        """
        return self._mpath().fullPathName()

    def isRoot(self):
        u"""
        ルートノードかどうか。

        :rtype: `bool`
        """
        return self._mpath().length() == 1

    def root(self):
        u"""
        DAGパスに沿ったルートノードを得る。

        自身がルートノードなら同じ参照が返される。

        API の :mayaapi2:`MDagPath` の dagRoot() とは異なり、
        見えない『ワールド』ノードではなく、
        シーン上のルートノードが得られる。

        :rtype: `.Transform` or None
        """
        orig = self._mpath()
        d = orig.length() - 1
        return _newNodeObjByMPath(_2_MDagPath(orig).pop(d)) if d else self

    def allRoots(self):
        u"""
        このノードインスタンスを階層下に持つ全ルートノードのリストを得る。

        自身がルートノードなら同じ参照が返される。

        :rtype: `list`
        """
        orig = self._mpath()
        if orig.length() == 1:
            # ルートノードは他にインスタンスが在ることは有り得ないようだ。
            return [self]

        # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。
        if orig.isInstanced():  # indirect も含んだ判定。
            mpaths = []
            for x in _2_MDagPath_getAllPathsTo(orig.node()):
                # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。
                x = _2_MDagPath(x).pop(x.length() - 1)
                if x not in mpaths:
                    mpaths.append(x)
            return [_newNodeObjByMPath(x) for x in mpaths]
        else:
            return [_newNodeObjByMPath(_2_MDagPath(orig).pop(orig.length() - 1))]

    def leaves(self):
        u"""
        階層下のリーフノードのリストを得る。

        アンダーワールドノード（カーブオンサーフェース等）に
        下ることはされない。

        :rtype: `list`
        """
        leaves = []
        orig = self._mpath()
        mpaths = [orig]
        while mpaths:
            queue = mpaths
            mpaths = []
            for mpath in queue:
                num = mpath.childCount()
                if num:
                    mpaths.extend([_2_MDagPath(mpath).push(mpath.child(i)) for i in range(num)])
                else:
                    leaves.append(mpath)

        if len(leaves) == 1 and leaves[0] is orig:
            return [self]
        return [_newNodeObjByMPath(x) for x in leaves]

    def siblings(self):
        u"""
        自分以外の兄弟ノードのリストを返す。

        :rtype: `list`
        """
        parent = self.mpath().pop()
        num = parent.childCount()
        if num > 1:
            get = parent.child
            mobjs = [get(i) for i in range(num)]
            mnode = self.mnode_()
            return [_newNodeObjByMPath(_2_MDagPath(parent).push(x)) for x in mobjs if x != mnode]
        return []

    def siblingIndex(self):
        u"""
        兄弟におけるインデックスを得る。

        :rtype: `int`
        """
        parent = self.mpath().pop()
        num = parent.childCount()
        if num > 1:
            return _indexOfArr(self.mnode_(), parent.child, num)
        return 0

    def siblingIndices(self):
        u"""
        DAGパス中の全ノードの兄弟インデックスリストを得る。

        :rtype: `list`

        .. note::
            ノードリストをソートするキー関数として利用すると、
            DAG階層の深さ優先ソートとなる。

            幅優先なら `lengthAndSiblingIndices` を
            兄弟順保証しない幅優先なら `pathLength` を使うと良い。
        """
        result = []
        mpath = self._mpath()
        pathLen = mpath.length()

        if self.mfn_().inUnderWorld:
            while pathLen:
                parent = _2_MDagPath(mpath).pop()
                num = parent.childCount()
                if num > 1:
                    result.append(_indexOfArr(mpath.node(), parent.child, num))
                else:
                    result.append(0)

                if _2_MFnDagNode(parent).inModel:
                    mpath = parent
                    pathLen -= 1
                else:
                    # 着目している親が『ワールド』ノードの場合、次はそれをスキップ。
                    mpath = parent.pop()
                    pathLen -= 2
        else:
            while pathLen:
                parent = _2_MDagPath(mpath).pop()
                num = parent.childCount()
                if num > 1:
                    result.append(_indexOfArr(mpath.node(), parent.child, num))
                else:
                    result.append(0)
                mpath = parent
                pathLen -= 1

        result.reverse()
        return result

    def lengthAndSiblingIndices(self):
        u"""
        DAGパスを構成するノード数と、兄弟インデックスリストのペアを得る。

        :rtype: (int, `list`)

        .. note::
            ノードリストをソートするキー関数として利用すると、
            DAG階層の幅優先ソートとなる。

            兄弟順保証が不要なら `pathLength` を、
            深さ優先なら `siblingIndices` を利用する良い。
        """
        k = self.siblingIndices()
        return len(k), k

    def pathLength(self):
        u"""
        DAGフルパスの長さを得る。

        カーブオンサーフェースと親シェイプとの間に存在する
        シーン上では見えない『ワールド』ノードはカウントされないため、
        API の :mayaapi2:`MDagPath` の length() とは異なる。

        :rtype: `int`

        .. note::
            ノードリストをソートするキー関数として利用すると、
            DAG階層の兄弟順保証無しの幅優先ソートとなる。

            兄弟順保証するなら `lengthAndSiblingIndices` を
            深さ優先なら `siblingIndices` を利用すると良い。
        """
        orig = self._mpath()
        num = orig.length()
        if num >= 3 and self.mfn_().inUnderWorld:
            #return num - 1
            # 二重以上の UnderWorld が無いなら num - 1 で良いのだが、念のため not inModel の数だけ削減する。
            if num < 5:
                return num - 1
            mpath = _2_MDagPath(orig).pop()
            mfn = _2_MFnDagNode(mpath)
            while mfn.inUnderWorld:
                if not mfn.inModel:
                    num -= 1
                mpath.pop()
                mfn = _2_MFnDagNode(mpath)
        return num

    def numParents(self, indirect=False):
        u"""
        このノードインスタンスを子に持つ親ノードの数を得る。

        :param `bool` indirect:
            インスタンスポイント以外も含まれる親のパス全てを得る。
        :rtype: `int`
        """
        return self.numInstances() if indirect else self.mfn().parentCount()

    def parents(self, indirect=False):
        u"""
        このノードインスタンスを子に持つ親ノードのリストを得る。

        :param `bool` indirect:
            インスタンスポイント以外も含まれる親のパス全てを得る。
        :rtype: `list`
        """
        # ルートノードなら親は無し。
        orig = self._mpath()
        if orig.length() < 2:
            return []

        # このパス上の親を取得。
        mfn = self.mfn_()
        if self.isShape():
            parent = self._transform()
        else:
            mpath = _2_MDagPath(orig).pop()
            if mfn.inUnderWorld:
                if not _2_MFnDagNode(mpath).inModel:
                    mpath.pop()
            parent = _newNodeObjByMPath(mpath)

        # インスタンスポイント以外の全パスを得るなら、このノードの全パスの親を得る。
        if indirect:
            # インスタンスが1つなら親も1つ。
            if not orig.isInstanced():  # indirectも含んだ判定。
                return [parent]
            idx = orig.instanceNumber()
            return [
                parent if i == idx else _newNodeObjByMPath(_getParentPath(x))
                for i, x in enumerate(_2_MDagPath_getAllPathsTo(self.mnode_()))
            ]

        # インスタンスポイントにおける親ノードを得るなら mfn から得る。
        else:
            # インスタンス数が複数でもインスタンスポイントでなければ、親は1つとなる。
            num = mfn.parentCount()
            if num == 1:
                return [parent]

            # 親が複数の場合、このパス上でない親は getAPathTo に任せる。
            mobjs = [mfn.parent(i) for i in range(num)]
            mnode = parent.mnode_()
            return [
                parent if x == mnode else _newNodeObjByMPath(_2_MDagPath_getAPathTo(x))
                for x in mobjs]

    def parent(self, step=1):
        u"""
        DAGパスに沿った親ノードを得る。

        アンダーワールドノード（カーブオンサーフェース等）から
        親シェイプを得ることもできる。

        :param `int` step:
            遡るノード数。
            API の :mayaapi2:`MDagPath` の pop とは異なり、
            カーブオンサーフェースと親シェイプとの間に存在する
            シーン上では見えない『ワールド』ノードはカウントしない。
        :rtype: `.DagNode` or None
        """
        if step > 0:
            self.checkValid()
            if step == 1 and self.isShape():
                # shape の親を得るなら _transform メソッドを呼ぶ。
                return self._transform()
            orig = self._mpath_()
            if self.mfn_().inUnderWorld:
                if step < orig.length() - 1:
                    mpath = _2_MDagPath(orig).pop()
                    mfn = _2_MFnDagNode(mpath)
                    while mfn.inUnderWorld:
                        if mfn.inModel:
                            if step == 1:
                                break
                            step -= 1
                        mpath.pop()
                        mfn = _2_MFnDagNode(mpath)
                    step -= 1
                    if step:
                        mpath.pop(step)
                    return _newNodeObjByMPath(mpath)
            else:
                if step < orig.length():
                    return _newNodeObjByMPath(_2_MDagPath(orig).pop(step))

    def numChildren(self, shapes=False, intermediates=False):
        u"""
        DAGパス上の子ノードの数を得る。

        :param `bool` shapes:
            シェイプも含めるかどうか。
        :param `bool` intermediates:
            shapes=True のとき、さらに
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `int`
        """
        if self.isTransform():
            if shapes and intermediates:
                return self._mpath().childCount()
            num = 0
            orig = self._mpath()
            get = orig.child
            for i in range(orig.childCount()):
                mobj = get(i)
                if (
                    (shapes or not mobj.hasFn(_MFn_kShape)) and
                    (intermediates or not _2_MFnDagNode(mobj).isIntermediateObject)
                ):
                    num += 1
            return num
        return 0

    def children(self, shapes=False, intermediates=False):
        u"""
        DAGパス上の子ノードのリストを得る。

        シェイプからアンダーワールドノード（カーブオンサーフェース等）を
        得ることはできないため、その場合は `underWorldNodes` を利用すること。

        :param `bool` shapes:
            シェイプも含めるかどうか。
        :param `bool` intermediates:
            shapes=True のとき、さらに
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `list`
        """
        if not self.isTransform():
            return []

        if shapes:
            return self.__getShapes(intermediates, True)

        orig = self._mpath()
        get = orig.child
        return [
            _newNodeObjByMPath(_2_MDagPath(orig).push(o))
            for o in [get(i) for i in range(orig.childCount())]
            if not o.hasFn(_MFn_kShape) and
            (intermediates or not _2_MFnDagNode(o).isIntermediateObject)
        ]

    def child(self, idx, shapes=False, intermediates=False):
        u"""
        インデックス指定で子ノードを1つ得る。

        シェイプからアンダーワールドノード（カーブオンサーフェース等）を
        得ることはできないため、その場合は `underWorldNodes` を利用すること。

        :param `int` idx:
            インデックス。
            shapes や intermediates オプションによって、
            同じインデックスでも得られるものが変わることがある。
            無効なインデックスを指定するとエラーではなく None となる。
        :param `bool` shapes:
            シェイプも含めるかどうか。
        :param `bool` intermediates:
            shapes=True のとき、さらに
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `.DagNode` or None
        """
        if not self.isTransform():
            return

        orig = self._mpath()
        get = orig.child

        # シェイプを含める場合はキャッシュと同期する。
        if shapes:
            # インターミディエイトを含める場合は、直接インデックス指定で得て、
            # それがシェイプの場合にシェイプとの同期を処理する。
            if intermediates:
                # まず、直接得て、シェイプでなければそれを返す。
                try:
                    mobj = get(idx)
                except:
                    return
                if not mobj.hasFn(_MFn_kShape):
                    return _newNodeObjByMPath(_2_MDagPath(orig).push(mobj))

                # シェイプだった場合は、キャッシュ用のシェイプインデックスを調べて同期する。
                k0 = 0  # not intermediate
                k1 = 0  # all
                for i in range(orig.childCount()):
                    mobj = get(i)
                    if mobj.hasFn(_MFn_kShape):
                        if i == idx:
                            return self._Node_c__getShapeWithCache(k0, k1, mobj)
                        if not _2_MFnDagNode(mobj).isIntermediateObject:
                            k0 += 1
                        k1 += 1

            # インターミディエイトを含めない場合は、どのみち直接インデックス指定では得られないので、
            # 子をトラバースして、インデックスを調べつつ、それがシェイプなら同期する。
            else:
                k0 = 0  # not intermediate
                k1 = 0  # all
                for i in range(orig.childCount()):
                    mobj = get(i)
                    if _2_MFnDagNode(mobj).isIntermediateObject:
                        if mobj.hasFn(_MFn_kShape):
                            k1 += 1
                    else:
                        if not idx:
                            if mobj.hasFn(_MFn_kShape):
                                return self._Node_c__getShapeWithCache(k0, k1, mobj)
                            else:
                                return _newNodeObjByMPath(_2_MDagPath(orig).push(mobj))
                        if mobj.hasFn(_MFn_kShape):
                            k0 += 1
                            k1 += 1
                        idx -= 1

        # シェイプを含めないなら、キャッシュとの同期を考える必要はない。
        else:
            for i in range(orig.childCount()):
                mobj = get(i)
                if (
                    (shapes or not mobj.hasFn(_MFn_kShape)) and
                    (intermediates or not _2_MFnDagNode(mobj).isIntermediateObject)
                ):
                    if not idx:
                        return _newNodeObjByMPath(_2_MDagPath(orig).push(mobj))
                    idx -= 1

    def isParentOf(self, node, exact=False, underWorld=True):
        u"""
        指定ノードの親かどうか検査する。

        直接の親に限らない上位ノードかどうかチェックする場合は
        `isAncestorOf` が利用できる。

        :type node: `.DagNode`
        :param node: チェックするノード。
        :param exact:
            インスタンス関係は考慮せず、DAGパスに沿った判定のみとするかどうか。
        :param `bool` underWorld:
            アンダーワールド（カーブオンサーフェース）の親シェイプも判定する。
        :rtype: `bool`
        """
        if exact:
            mpath = node.mpath().pop()
            if underWorld and not _2_MFnDagNode(mpath).inModel:
                mpath.pop()
            return mpath == self._mpath()

        else:
            mnode = self.mnode()
            node_fn = node.mfn()
            for i in range(node_fn.parentCount()):
                if node_fn.parent(i) == mnode:
                    return True
            if underWorld and node_fn.inUnderWorld:
                mpath = node.mpath_().pop()
                return not _2_MFnDagNode(mpath).inModel and mpath.pop().node() == mnode
            return False

    def isAncestorOf(self, node, exact=False, underWorld=True):
        u"""
        指定ノードの上位ノードかどうか検査する。

        直接の親かどうかをチェックする場合は
        `isParentOf` が利用できる。

        :type node: `.DagNode`
        :param node: チェックするノード。
        :param exact:
            インスタンス関係は考慮せず、DAGパスに沿った判定のみとするかどうか。
        :param `bool` underWorld:
            アンダーワールド（カーブオンサーフェース）の親シェイプも判定する。
        :rtype: `bool`
        """
        if exact:
            if underWorld or (self.mfn().inUnderWorld == node.mfn().inUnderWorld):
                self_mpath = self._mpath_()
                node_mpath = node._mpath_()
                n = node_mpath.length() - self_mpath.length()
                return n > 0 and self_mpath == _2_MDagPath(node_mpath).pop(n)
            return False

        else:
            self_fn = self.mfn()
            if self_fn.isParentOf(node.mnode()):
                return True
            if underWorld and not self_fn.inUnderWorld:
                node_fn = node.mfn_()
                if node_fn.inUnderWorld:
                    mnode = _2_MDagPath_getAPathTo(node_fn.dagRoot()).pop().node()
                    return self.mnode_() == mnode or self_fn.isParentOf(mnode)
            return False

    def inUnderWorld(self):
        u"""
        アンダーワールド（カーブオンサーフェース等）かどうか。

        :rtype: `bool`
        """
        return self.mfn().inUnderWorld

    def parentWorld(self):
        u"""
        アンダーワールド（カーブオンサーフェース等）における親空間シェイプを返す。

        本メソッドによって、カーブオンサーフェースから
        親のサーフェースシェイプを得ることができる。

        :rtype: `.Shape` or None
        """
        mfn = self.mfn()
        if mfn.inUnderWorld:
            mpath = self.mpath_().pop()
            root = mfn.dagRoot()  # シーン上には見えない『ワールド』ノード。
            while root != mpath.node():
                mpath.pop()
            return _newNodeObjByMPath(mpath.pop())

    if MAYA_VERSION >= (2016, 5):
        def hasUnderWorldNodes(self):
            u"""
            シェイプ下にアンダーワールドノード（カーブオンサーフェース等）を持つかどうか。

            :rtype: `bool`
            """
            shape = self.shape()
            if shape:
                it = _2_MItDag()
                it.reset(shape._mpath())
                it.traverseUnderWorld = True
                next(it)
                return not it.isDone() and not _2_MFnDagNode(it.getPath()).inModel
            return False

        def underWorldNodes(self):
            u"""
            シェイプ下のアンダーワールドノード（カーブオンサーフェース等）のリストを得る。

            :rtype: `list`
            """
            shape = self.shape()
            if shape:
                it = _2_MItDag()
                it.reset(shape._mpath())
                it.traverseUnderWorld = True
                next(it)
                if not it.isDone():
                    mpath = it.getPath()
                    if not _2_MFnDagNode(mpath).inModel:
                        get = mpath.child
                        return [_newNodeObjByMPath(_2_MDagPath(mpath).push(get(i))) for i in range(mpath.childCount())]
            return []

    def numShapes(self, intermediates=False):
        u"""
        :mayanode:`transform` の持つ :mayanode:`shape` ノード数を得る。

        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `int`
        """
        if self.isTransform():
            if intermediates:
                num = 0
                orig = self._mpath()
                get = orig.child
                for i in range(orig.childCount()):
                    if get(i).hasFn(_MFn_kShape):
                        num += 1
                return num
            else:
                return self._mpath().numberOfShapesDirectlyBelow()
        return 0

    def shape(self, idx=0, intermediates=False):
        u"""
        :mayanode:`shape` ノードを得る。

        自身がシェイプの場合は自身が、
        :mayanode:`transform` の場合はそのシェイプが得られる。

        :param `int` idx:
            得たいシェイプのインデックス。
            自身がシェイプの場合は無視される。

            intermediates オプションによって、
            同じインデックスでも得られるものが変わることがある。
            無効なインデックスを指定するとエラーではなく None となる。

            Python的な負のインデックスも指定可能。
        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `.Shape` or None
        """
        if self.isShape():
            return self
        elif self.isTransform():
            self.checkValid()
            return self._shape(idx, intermediates)

    def shapes(self, intermediates=False):
        u"""
        :mayanode:`transform` の持つ :mayanode:`shape` ノードのリストを得る。

        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :rtype: `list`
        """
        return self.__getShapes(intermediates) if self.isTransform() else []

    def __getShapes(self, intermediates=False, others=False):
        u"""
        子ノード群を得つつシェイプをキャッシュするための共通ルーチン。

        :param `bool` intermediates:
            インターミディエイトオブジェクトも含めるかどうか。
        :param `bool` others:
            シェイプ以外の子も含めるかどうか。
            ただし、キャッシュされるのはシェイプのみ。
        :rtype: `list`
        """
        # 子の MObject を収集。
        orig = self._mpath()
        get = orig.child
        allMObjs = [get(i) for i in range(orig.childCount())]
        if others:
            if intermediates:
                flagMObjs = [(o.hasFn(_MFn_kShape), o) for o in allMObjs]
                mfns = [_2_MFnDagNode(o) for f, o in flagMObjs if f]
            else:
                flagMFns = [(o.hasFn(_MFn_kShape), _2_MFnDagNode(o)) for o in allMObjs]
                mfns = [x for f, x in flagMFns if f]
        else:
            mfns = [_2_MFnDagNode(o) for o in allMObjs if o.hasFn(_MFn_kShape)]
        allIdxDict = dict([(x.name(), i) for i, x in enumerate(mfns)])

        # 既存のキャッシュを取得。
        oldCache0, oldCache1 = self._CyObject__data['shape']

        # 現在のシェイプ構成に合わせて、既存のキャッシュから可能な限り引き継ぐ。
        cache1 = {}
        for shape in oldCache0.values():
            if shape.isValid():
                i = allIdxDict.pop(shape.nodeName_(), None)
                if i is not None:
                    cache1[i] = shape
        for shape in oldCache1.values():
            if shape.isValid():
                i = allIdxDict.pop(shape.nodeName_(), None)
                if i is not None:
                    cache1[i] = shape

        # キャッシュから引き継げなかったシェイプで、戻り値に必要なものだけを生成する。
        if allIdxDict:
            wref = _getObjectRef(self).weakref()
            if intermediates:
                # all
                for i in allIdxDict.values():
                    shape = _newNodeObjByMPath(_2_MDagPath(orig).push(mfns[i].object()))
                    shape._CyObject__data['transform'] = wref
                    cache1[i] = shape
            else:
                # not intermediate
                for i in allIdxDict.values():
                    if not mfns[i].isIntermediateObject:
                        shape = _newNodeObjByMPath(_2_MDagPath(orig).push(mfns[i].object()))
                        shape._CyObject__data['transform'] = wref
                        cache1[i] = shape

        # all を参照しながら not intermediate キャッシュを生成。
        cache0 = {}
        k0 = 0
        for i in range(len(mfns)):
            shape = cache1.get(i)
            if shape and not shape.mfn_().isIntermediateObject:
                cache0[k0] = shape
                k0 += 1

        # 新しいキャッシュをセット。
        self._CyObject__data['shape'] = [cache0, cache1]

        # オプションに応じた list を返す。
        if others:
            def count():
                cnt[0] += 1
                return cnt[0]
            cnt = [-1]
            if intermediates:
                return [
                    (cache1[count()] if f else _newNodeObjByMPath(_2_MDagPath(orig).push(o)))
                    for f, o in flagMObjs]
            else:
                return [
                    (cache0[count()] if f else _newNodeObjByMPath(_2_MDagPath(orig).push(x.object())))
                    for f, x in flagMFns if not x.isIntermediateObject]
        else:
            if intermediates:
                return [cache1[i] for i in sorted(cache1)]
            else:
                return [cache0[i] for i in sorted(cache0)]

    def transform(self):
        u"""
        :mayanode:`transform` ノードを得る。

        自身が :mayanode:`transform` の場合は自身が、
         :mayanode:`shape` の場合はその親が得られる。

        :rtype: `.Transform` or None
        """
        if self.isTransform():
            return self
        elif self.isShape():
            self.checkValid()
            return self._transform()

    # _shape を少し隠しているように、これも少し隠すものとする。
    def _transform(self):
        u"""
        `~.CyObject.checkValid` を省略して、シェイプから :mayanode:`transform` ノードを得る。

        :rtype: `.Transform` or None
        """
        # キャッシュが古い可能性があるので、MDagPath は毎回取得し直す。
        mpath = _2_MDagPath(self._CyObject__data['mpath'])
        try:
            mpath.pop()
        except RuntimeError:
            return

        # キャッシュが在ればそれを再利用、無ければ新規生成する。
        obj = self._CyObject__data['transform']
        if obj:
            obj = obj()
            if obj and obj.isValid() and obj._CyObject__data['mpath'] == mpath:
                return obj
        obj = _newNodeObjByMPath(mpath)

        # transform の子ノードを調べ、キャッシュキーを決定する。
        k0 = 0  # not intermediate
        k1 = 0  # all
        get = mpath.child
        shape_mnode = self._CyObject__data['mnode']
        for i in range(mpath.childCount()):
            mnode = get(i)
            if mnode == shape_mnode:
                break
            if mnode.hasFn(_MFn_kShape):
                k1 += 1
                if not _2_MFnDagNode(mnode).isIntermediateObject:
                    k0 += 1

        # 相互キャッシュを生成。
        obj._CyObject__data['shape'][0][k0] = self
        obj._CyObject__data['shape'][1][k1] = self
        self._CyObject__data['tranform'] = _getObjectRef(obj).weakref()
        return obj

    def isInstanceable(self):
        u"""
        インスタンス可能かどうか。

        :rtype: `bool`
        """
        return self.mfn().isInstanceable

    def isInstanced(self, indirect=True):
        u"""
        インスタンスされているかどうか。

        :param `bool` indirect:
            直接インスタンス化されたポイント以外も含めるかどうか。
        :rtype: `bool`
        """
        # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。
        if indirect:
            return self._mpath().isInstanced()  # indirect も含んだ判定で mfn より少し速い。
        else:
            return self.mfn().isInstanced(False)

    def instanceIndex(self):
        u"""
        インスタンス番号を得る。

        :rtype: `int`

        .. note::
            この番号は並び順の変更やインスタンス数の増減によって動的に変わる。
            また、使用されているワールドスペースプラグのインデックスも
            それに合わせて変わる。
        """
        return self._mpath().instanceNumber()

    def numInstances(self, indirect=True):
        u"""
        インスタンス数を得る。

        :param `bool` indirect:
            直接インスタンス化されたポイント以外も含めるかどうか。
        :rtype: `int`
        """
        # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。
        mfn = self.mfn()
        if indirect and mfn.inUnderWorld:
            orig = self._mpath_()
            if orig.isInstanced():  # indirect も含んだ判定。
                # inUnderWorld を抜けるまで上昇してから instanceCount を得る。
                mpath = _2_MDagPath(orig).pop()
                mfn = _2_MFnDagNode(mpath)
                while mfn.inUnderWorld:
                    mpath.pop()
                    mfn = _2_MFnDagNode(mpath)
                return mfn.instanceCount(indirect)
            return 1
        else:
            # inUnderWorld だとインスタンスコピーはできないのでこれで良い。
            return mfn.instanceCount(indirect)

    def instance(self, idx):
        u"""
        インスタンス番号からインスタンスを得る。

        :param `int` idx: インスタンス番号。
        :rtype: `.DagNode` or None
        """
        if self._mpath().instanceNumber() == idx:
            return self

        # 0 番の場合 getAPathTo で得られるようだ。
        if not idx:
            mnode = self.mnode_()
            mpath = _2_MDagPath_getAPathTo(mnode)
            if not mpath.instanceNumber():  # 念のためチェック。
                mpath = _2_MDagPath(_2_MDagPath_getAllPathsTo(mnode)[idx])
            return type(self)(mpath)

        # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。
        arr = _2_MDagPath_getAllPathsTo(self.mnode_())
        if 0 <= idx < len(arr):
            return type(self)(_2_MDagPath(arr[idx]))

    def instances(self, noSelf=False):
        u"""
        インスタンスのリストを返す。

        :param `bool` noSelf: 自身を含めないようにする。
        :rtype: `list`
        """
        orig = self._mpath()
        if orig.isInstanced():  # indirect も含んだ判定。
            cls = type(self)
            idx = orig.instanceNumber()
            # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。
            if noSelf:
                return [
                    cls(_2_MDagPath(x))
                    for i, x in enumerate(_2_MDagPath_getAllPathsTo(self.mnode_()))
                    if i != idx]
            else:
                return [
                    self if i == idx else cls(_2_MDagPath(x))
                    for i, x in enumerate(_2_MDagPath_getAllPathsTo(self.mnode_()))]
        return [] if noSelf else [self]

    def boundingBox(self, ws=False):
        u"""
        バウンディングボックスを得る。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.BoundingBox`
        """
        if ws:
            bb = self.mfn().boundingBox.transformUsing(self._mpath_().exclusiveMatrix())
            return _newBB(bb)
        return _newBB(self.mfn().boundingBox)

    def getMatrix(self, ws=False, p=False, inv=False):
        u"""
        ノードのトランスフォーメーションのマトリックスを得る。

        :param `bool` ws: ワールド空間で得るかどうか。
        :param `bool` p: 親のマトリックスを得るかどうか。
        :param `bool` inv: 逆行列を得るかどうか。
        :rtype: `.Matrix`
        """
        # それぞれプラグから得る方法もあるが、MDagPath から得た方が速い。
        if ws:
            if p:
                if inv:
                    return _newM(self._mpath().exclusiveMatrixInverse())
                else:
                    return _newM(self._mpath().exclusiveMatrix())
            else:
                if inv:
                    return _newM(self._mpath().inclusiveMatrixInverse())
                else:
                    return _newM(self._mpath().inclusiveMatrix())
        else:
            if p:
                # 親のファンクションセットを得るより MDagPath のまま計算した方が速い。
                mpath = _2_MDagPath(self._mpath()).pop()
                if inv:
                    return _newM(mpath.exclusiveMatrix() * mpath.inclusiveMatrixInverse())
                else:
                    return _newM(mpath.inclusiveMatrix() * mpath.exclusiveMatrixInverse())
            elif self.isTransform():
                if inv:
                    return _newM(self.mfn().transformationMatrix().inverse())
                else:
                    return _newM(self.mfn().transformationMatrix())
            else:
                return _newM(_MM())

    getM = getMatrix  #: `getMatrix` の別名。

    def getTransformation(self, ws=False):
        u"""
        ノードのトランスフォーメーション情報を得る。

        :mayanode:`transform` 派生ノードの
        アトリビュート ``xm`` の値を得ることと同じだが、
        オプションでワールドスペースの値を得ることもできる。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Transformation`
        """
        if self.isTransform():
            x = mplug_get_xformmatrix(self.mfn().findPlug('xm', True))
            if ws:
                # Transformation に Matrix を乗じることで、
                # ピボットなどの各基準位置もワールド空間で一致させる。
                mpath = self._mpath_()
                if mpath.length() > 1:
                    x *= _newM(self._mpath_().exclusiveMatrix())
            return x
        else:
            if ws:
                return _newX(dict(m=_newM(self._mpath().exclusiveMatrix(), ImmutableMatrix)))
            else:
                return _newX({})

    getX = getTransformation  #: `getTransformation` の別名。

    def getScaling(self, ws=False):
        u"""
        ノードのスケーリングの値を得る。

        ws=False の場合は、
        単に scale アトリビュートから得られ、
        :mayanode:`joint` ノードのローカルマトリックスには含まれる
        inverseScale の影響は無視される。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Vector`
        """
        if ws:
            return _newV(_MP(_MX(self._mpath().inclusiveMatrix()).scale(_MSpace_kTransform)))
        if self.isTransform():
            return _newV(_MP(mplug_get_nums(self.mfn().findPlug('s', True))))
        return _newV(_MP(1., 1., 1.))

    getS = getScaling  #: `getScaling` の別名。

    def getShearing(self, ws=False):
        u"""
        ノードのシアーの値を得る。

        ws=False の場合は、
        単に shear アトリビュートから得られ、
        :mayanode:`joint` ノードのローカルマトリックスには含まれる
        inverseScale の影響は無視される。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Vector`
        """
        if ws:
            return _newV(_MP(_MX(self._mpath().inclusiveMatrix()).shear(_MSpace_kTransform)))
        if self.isTransform():
            return _newV(_MP(mplug_get_nums(self.mfn().findPlug('sh', True))))
        return _newV(_MP())

    getSh = getShearing  #: `getShearing` の別名。

    def getQuaternion(self, ws=False, ra=False, r=True, jo=True):
        u"""
        ノードの回転のクォータニオンを得る。

        デフォルトでは rotateAxis を含んでいない回転となり、
        マトリックスからの分解結果とは一致しないが、
        Maya の機能（Local Axis 表示やコンストレイン）が
        ノードの回転方向とする基準と一致する。

        ws=False の場合は、
        3種の回転アトリビュートのみから合成された結果が得られ、
        :mayanode:`joint` ノードのローカルマトリックスには含まれる
        inverseScale の影響は無視される。

        :param `bool` ws: ワールド空間で得るかどうか。
        :param `bool` ra: rotateAxis を含めるかどうか。
        :param `bool` r: rotate を含めるかどうか。
        :param `bool` jo: jointOrient を含めるかどうか。
        :rtype: `.Quaternion`

        .. note::
            ws=True の場合の親までの非一様 scale や shear による影響や
            ws=False の場合の jointOrient は、 
            :mayacmd:`xform` コマンドや :mayaapi2:`MTransformationMatrix`
            では考慮されないが、本メソッドでは考慮されている。

            ws=True の結果は Legacy Viewport での Local Axis 表示と常に一致するが、
            上位階層に 非一様 scale や shear が含まれる場合、
            orientConstraint や parentConstraint の結果とは一致しない場合がある。
            これは、コンストレイン側で考慮が足りていないからである。
            ただし、segmentScaleCompensate で常に非一様 scale が打ち消されていれば一致する。

            ws=False の結果は、上位階層に非一様 scale や shear が無いか、
            segmentScaleCompensate で常に非一様 scale が打ち消されていれば、
            親の getQ(ws=True, ra=True) を乗じた結果は getQ(ws=True) と一致する。

        .. warning::
            以下のオプションの組み合わせはエラーになる。

            - ws=False, ra=True, r=False, jo=True
            - ws=True, ra=True, r=False
            - ws=True, ra=True, jo=False
            - ws=True, r=True, jo=False
        """
        if ws:
            if (not(r and jo)) if ra else (r and not jo):
                raise ValueError('getQuaternion: unsupported option combination.')

            if self.isTransform():
                # Legacy Viewport の Local Axis と同じになる。
                # nw ではこのノードのscaleとshearを取り除いているがそれだと Local Axis には一致しない。
                # いずれにせよ、非一様scaleが残っている場合のコンストレインの結果とは一致しない。
                mfn = self.mfn()
                if ra or r or (jo and mfn.object().hasFn(_MFn_kJoint)):
                    q = _MQ().setValue(self._mpath().inclusiveMatrix().homogenize())
                    if not ra:
                        findPlug = mfn.findPlug
                        v = mplug_get_nums(findPlug('ra', True))
                        q = _ME(-v[0], -v[1], -v[2], ZYX).asQuaternion() * q
                        if not r:
                            v = mplug_get_nums(findPlug('r', True))
                            q = _ME(-v[0], -v[1], -v[2], _REVERSE_RO[findPlug('ro', True).asShort()]).asQuaternion() * q
                else:
                    q = _MQ().setValue(self._mpath().exclusiveMatrix().homogenize())
            else:
                # transform でなければ、ローカルオプションによる差はない。
                q = _MQ().setValue(self._mpath().inclusiveMatrix().homogenize())

        else:
            if ra and not r and jo:
                raise ValueError('getQuaternion: unsupported option combination.')

            if self.isTransform():
                # ssc=on であれば
                # (this.getQ() * parent.getQ(ws=True, ra=True)) が
                # this.getQ(ws=True) と同じになるようにしている。
                mfn = self.mfn()
                if ra:
                    findPlug = mfn.findPlug
                    q = _ME(mplug_get_nums(findPlug('ra', True))).asQuaternion()
                    if r:
                        q *= _ME(mplug_get_nums(findPlug('r', True)), findPlug('ro', True).asShort()).asQuaternion()
                        if jo and mfn.object().hasFn(_MFn_kJoint):
                            q *= _ME(mplug_get_nums(findPlug('jo', True))).asQuaternion()
                elif r:
                    findPlug = mfn.findPlug
                    q = _ME(mplug_get_nums(findPlug('r', True)), findPlug('ro', True).asShort()).asQuaternion()
                    if jo and mfn.object().hasFn(_MFn_kJoint):
                        q *= _ME(mplug_get_nums(findPlug('jo', True))).asQuaternion()
                elif jo and mfn.object().hasFn(_MFn_kJoint):
                    q = _ME(mplug_get_nums(mfn.findPlug('jo', True))).asQuaternion()
                else:
                    return _newQ(_MQ())
            else:
                # transform でなければ、ローカルオプションによる差はない。
                return _newQ(_MQ())

        if q[3] < 0.:
            q.negateIt()
        return _newQ(q)

    getQ = getQuaternion  #: `getQuaternion` の別名。

    def getJOQ(self, ws=False):
        u"""
        ノードの jointOrient までのクォータニオンを得る。

        `getQuaternion` の r=False 指定と同じ。

        :param `bool` ws: ワールド空間で得るかどうか。
        :rtype: `.Quaternion`
        """
        return self.getQ(ws=ws, r=False)

    def getTranslation(self, ws=False, at=2):
        u"""
        ノードの位置を得る。

        デフォルトでは回転ピボットの位置となり、
        マトリックスからの分解結果とは一致しないが、
        Maya の機能（Local Axis 表示位置やコンストレイン）が
        ノードの位置とする基準と一致する。

        :param `bool` ws: ワールド空間で得るかどうか。
        :param `int` at:
            どのアトリビュートに相当する位置を得るか。

            - 0 だとローカル原点（親の matrix 位置）。
            - 1 だと translate の位置。
            - 2 だと rotatePivot の位置。
            - 3 だと scalePivot の位置。
            - 4 以上だと matrix の位置。

        :rtype: `.Vector`
        """
        # -sp s sh sp spt -rp ra r jo rp rpt -is t
        if not self.isTransform():
            at = 4

        if at >= 3:
            if ws:
                m = self._mpath().inclusiveMatrix()
            else:
                m = self.mfn().transformationMatrix()
            if at >= 4:
                return _newV(_MP(m[12], m[13], m[14]))
            p = _MP(mplug_get_nums(self.mfn_().findPlug('sp', True)))
            p *= m
            return _newV(p)

        if at < 1:
            if ws:
                m = self._mpath().exclusiveMatrix()
                return _newV(_MP(m[12], m[13], m[14]))
            else:
                return _newV(_MP())

        mfn = self.mfn()
        findPlug = mfn.findPlug
        p = _MP(mplug_get_nums(findPlug('t', True)))

        if at >= 2:
            v = _MV(mplug_get_nums(findPlug('rp', True)))
            v += _MV(mplug_get_nums(findPlug('rpt', True)))
            if mfn.object().hasFn(_MFn_kJoint) and findPlug('ssc', True).asBool():
                s = mplug_get_nums(findPlug('is', True))
                v *= _MM([
                    1. / s[0], 0., 0., 0.,
                    0., 1. / s[1], 0., 0.,
                    0., 0., 1. / s[2], 0.,
                    0., 0., 0., 1.,
                ])
            p += v

        if ws:
            m = self._mpath_().exclusiveMatrix()
            p *= m
        return _newV(p)

    getT = getTranslation  #: `getTranslation` の別名。


#------------------------------------------------------------------------------
def _indexOfArr(obj, get, num):
    for i in range(num):
        if get(i) == obj:
            return i


def _getParentPath(orig):
    mpath = _2_MDagPath(orig).pop()
    if not _2_MFnDagNode(mpath).inModel:
        mpath.pop()
    return mpath


u'''
def _searchShapeCache(cache, key, mnode):
    for k in list(cache):
        shape = cache[k]
        if not shape.isValid():
            # 無効だったら削除。
            del cache[k]
        elif shape._CyObject__data['mnode'] == mnode:
            # 目的のものなら、キャッシュを作り直し。
            del cache[k]
            cache[key] = shape
            return shape
'''


# -*- coding: utf-8 -*- `.DagNode` クラスでサポートする機能の中核。 #------------------------------------------------------------------------------ `.DagNode` クラスでサポートする機能の中核。 インターミディエイトオブジェクトかどうか。          :rtype: `bool` 設定されているオブジェクトカラーを得る。          :rtype: `int` オブジェクトカラーが使用される設定になっているかどうか。          :rtype: `bool` 可視性を得る。          :rtype: `bool` テンプレート化されているかどうか。          :rtype: `bool` パーシャルパス名を得る。          `~.CyObject.name` と同じ。          :rtype: `str` フルパス名を得る。          :rtype: `str` ルートノードかどうか。          :rtype: `bool` DAGパスに沿ったルートノードを得る。          自身がルートノードなら同じ参照が返される。          API の :mayaapi2:`MDagPath` の dagRoot() とは異なり、         見えない『ワールド』ノードではなく、         シーン上のルートノードが得られる。          :rtype: `.Transform` or None このノードインスタンスを階層下に持つ全ルートノードのリストを得る。          自身がルートノードなら同じ参照が返される。          :rtype: `list` # ルートノードは他にインスタンスが在ることは有り得ないようだ。 # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。 # indirect も含んだ判定。 # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。 階層下のリーフノードのリストを得る。          アンダーワールドノード（カーブオンサーフェース等）に         下ることはされない。          :rtype: `list` 自分以外の兄弟ノードのリストを返す。          :rtype: `list` 兄弟におけるインデックスを得る。          :rtype: `int` DAGパス中の全ノードの兄弟インデックスリストを得る。          :rtype: `list`          .. note::             ノードリストをソートするキー関数として利用すると、             DAG階層の深さ優先ソートとなる。              幅優先なら `lengthAndSiblingIndices` を             兄弟順保証しない幅優先なら `pathLength` を使うと良い。 # 着目している親が『ワールド』ノードの場合、次はそれをスキップ。 DAGパスを構成するノード数と、兄弟インデックスリストのペアを得る。          :rtype: (int, `list`)          .. note::             ノードリストをソートするキー関数として利用すると、             DAG階層の幅優先ソートとなる。              兄弟順保証が不要なら `pathLength` を、             深さ優先なら `siblingIndices` を利用する良い。 DAGフルパスの長さを得る。          カーブオンサーフェースと親シェイプとの間に存在する         シーン上では見えない『ワールド』ノードはカウントされないため、         API の :mayaapi2:`MDagPath` の length() とは異なる。          :rtype: `int`          .. note::             ノードリストをソートするキー関数として利用すると、             DAG階層の兄弟順保証無しの幅優先ソートとなる。              兄弟順保証するなら `lengthAndSiblingIndices` を             深さ優先なら `siblingIndices` を利用すると良い。 #return num - 1 # 二重以上の UnderWorld が無いなら num - 1 で良いのだが、念のため not inModel の数だけ削減する。 このノードインスタンスを子に持つ親ノードの数を得る。          :param `bool` indirect:             インスタンスポイント以外も含まれる親のパス全てを得る。         :rtype: `int` このノードインスタンスを子に持つ親ノードのリストを得る。          :param `bool` indirect:             インスタンスポイント以外も含まれる親のパス全てを得る。         :rtype: `list` # ルートノードなら親は無し。 # このパス上の親を取得。 # インスタンスポイント以外の全パスを得るなら、このノードの全パスの親を得る。 # インスタンスが1つなら親も1つ。 # indirectも含んだ判定。 # インスタンスポイントにおける親ノードを得るなら mfn から得る。 # インスタンス数が複数でもインスタンスポイントでなければ、親は1つとなる。 # 親が複数の場合、このパス上でない親は getAPathTo に任せる。 DAGパスに沿った親ノードを得る。          アンダーワールドノード（カーブオンサーフェース等）から         親シェイプを得ることもできる。          :param `int` step:             遡るノード数。             API の :mayaapi2:`MDagPath` の pop とは異なり、             カーブオンサーフェースと親シェイプとの間に存在する             シーン上では見えない『ワールド』ノードはカウントしない。         :rtype: `.DagNode` or None # shape の親を得るなら _transform メソッドを呼ぶ。 DAGパス上の子ノードの数を得る。          :param `bool` shapes:             シェイプも含めるかどうか。         :param `bool` intermediates:             shapes=True のとき、さらに             インターミディエイトオブジェクトも含めるかどうか。         :rtype: `int` DAGパス上の子ノードのリストを得る。          シェイプからアンダーワールドノード（カーブオンサーフェース等）を         得ることはできないため、その場合は `underWorldNodes` を利用すること。          :param `bool` shapes:             シェイプも含めるかどうか。         :param `bool` intermediates:             shapes=True のとき、さらに             インターミディエイトオブジェクトも含めるかどうか。         :rtype: `list` インデックス指定で子ノードを1つ得る。          シェイプからアンダーワールドノード（カーブオンサーフェース等）を         得ることはできないため、その場合は `underWorldNodes` を利用すること。          :param `int` idx:             インデックス。             shapes や intermediates オプションによって、             同じインデックスでも得られるものが変わることがある。             無効なインデックスを指定するとエラーではなく None となる。         :param `bool` shapes:             シェイプも含めるかどうか。         :param `bool` intermediates:             shapes=True のとき、さらに             インターミディエイトオブジェクトも含めるかどうか。         :rtype: `.DagNode` or None # シェイプを含める場合はキャッシュと同期する。 # インターミディエイトを含める場合は、直接インデックス指定で得て、 # それがシェイプの場合にシェイプとの同期を処理する。 # まず、直接得て、シェイプでなければそれを返す。 # シェイプだった場合は、キャッシュ用のシェイプインデックスを調べて同期する。 # not intermediate # all # インターミディエイトを含めない場合は、どのみち直接インデックス指定では得られないので、 # 子をトラバースして、インデックスを調べつつ、それがシェイプなら同期する。 # not intermediate # all # シェイプを含めないなら、キャッシュとの同期を考える必要はない。 指定ノードの親かどうか検査する。          直接の親に限らない上位ノードかどうかチェックする場合は         `isAncestorOf` が利用できる。          :type node: `.DagNode`         :param node: チェックするノード。         :param exact:             インスタンス関係は考慮せず、DAGパスに沿った判定のみとするかどうか。         :param `bool` underWorld:             アンダーワールド（カーブオンサーフェース）の親シェイプも判定する。         :rtype: `bool` 指定ノードの上位ノードかどうか検査する。          直接の親かどうかをチェックする場合は         `isParentOf` が利用できる。          :type node: `.DagNode`         :param node: チェックするノード。         :param exact:             インスタンス関係は考慮せず、DAGパスに沿った判定のみとするかどうか。         :param `bool` underWorld:             アンダーワールド（カーブオンサーフェース）の親シェイプも判定する。         :rtype: `bool` アンダーワールド（カーブオンサーフェース等）かどうか。          :rtype: `bool` アンダーワールド（カーブオンサーフェース等）における親空間シェイプを返す。          本メソッドによって、カーブオンサーフェースから         親のサーフェースシェイプを得ることができる。          :rtype: `.Shape` or None # シーン上には見えない『ワールド』ノード。 シェイプ下にアンダーワールドノード（カーブオンサーフェース等）を持つかどうか。              :rtype: `bool` シェイプ下のアンダーワールドノード（カーブオンサーフェース等）のリストを得る。              :rtype: `list` :mayanode:`transform` の持つ :mayanode:`shape` ノード数を得る。          :param `bool` intermediates:             インターミディエイトオブジェクトも含めるかどうか。         :rtype: `int` :mayanode:`shape` ノードを得る。          自身がシェイプの場合は自身が、         :mayanode:`transform` の場合はそのシェイプが得られる。          :param `int` idx:             得たいシェイプのインデックス。             自身がシェイプの場合は無視される。              intermediates オプションによって、             同じインデックスでも得られるものが変わることがある。             無効なインデックスを指定するとエラーではなく None となる。              Python的な負のインデックスも指定可能。         :param `bool` intermediates:             インターミディエイトオブジェクトも含めるかどうか。         :rtype: `.Shape` or None :mayanode:`transform` の持つ :mayanode:`shape` ノードのリストを得る。          :param `bool` intermediates:             インターミディエイトオブジェクトも含めるかどうか。         :rtype: `list` 子ノード群を得つつシェイプをキャッシュするための共通ルーチン。          :param `bool` intermediates:             インターミディエイトオブジェクトも含めるかどうか。         :param `bool` others:             シェイプ以外の子も含めるかどうか。             ただし、キャッシュされるのはシェイプのみ。         :rtype: `list` # 子の MObject を収集。 # 既存のキャッシュを取得。 # 現在のシェイプ構成に合わせて、既存のキャッシュから可能な限り引き継ぐ。 # キャッシュから引き継げなかったシェイプで、戻り値に必要なものだけを生成する。 # all # not intermediate # all を参照しながら not intermediate キャッシュを生成。 # 新しいキャッシュをセット。 # オプションに応じた list を返す。 :mayanode:`transform` ノードを得る。          自身が :mayanode:`transform` の場合は自身が、          :mayanode:`shape` の場合はその親が得られる。          :rtype: `.Transform` or None # _shape を少し隠しているように、これも少し隠すものとする。 `~.CyObject.checkValid` を省略して、シェイプから :mayanode:`transform` ノードを得る。          :rtype: `.Transform` or None # キャッシュが古い可能性があるので、MDagPath は毎回取得し直す。 # キャッシュが在ればそれを再利用、無ければ新規生成する。 # transform の子ノードを調べ、キャッシュキーを決定する。 # not intermediate # all # 相互キャッシュを生成。 インスタンス可能かどうか。          :rtype: `bool` インスタンスされているかどうか。          :param `bool` indirect:             直接インスタンス化されたポイント以外も含めるかどうか。         :rtype: `bool` # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。 # indirect も含んだ判定で mfn より少し速い。 インスタンス番号を得る。          :rtype: `int`          .. note::             この番号は並び順の変更やインスタンス数の増減によって動的に変わる。             また、使用されているワールドスペースプラグのインデックスも             それに合わせて変わる。 インスタンス数を得る。          :param `bool` indirect:             直接インスタンス化されたポイント以外も含めるかどうか。         :rtype: `int` # NOTE: inUnderWorld だと (MDagPath|MFnDagNode).isInstanced は正常動作するが MFnDagNode.instanceCount は常に 1 となる。 # indirect も含んだ判定。 # inUnderWorld を抜けるまで上昇してから instanceCount を得る。 # inUnderWorld だとインスタンスコピーはできないのでこれで良い。 インスタンス番号からインスタンスを得る。          :param `int` idx: インスタンス番号。         :rtype: `.DagNode` or None # 0 番の場合 getAPathTo で得られるようだ。 # 念のためチェック。 # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。 インスタンスのリストを返す。          :param `bool` noSelf: 自身を含めないようにする。         :rtype: `list` # indirect も含んだ判定。 # NOTE: getAllPathsTo で得たものをそのまま使うとクラッシュすることがあるので複製。 バウンディングボックスを得る。          :param `bool` ws: ワールド空間で得るかどうか。         :rtype: `.BoundingBox` ノードのトランスフォーメーションのマトリックスを得る。          :param `bool` ws: ワールド空間で得るかどうか。         :param `bool` p: 親のマトリックスを得るかどうか。         :param `bool` inv: 逆行列を得るかどうか。         :rtype: `.Matrix` # それぞれプラグから得る方法もあるが、MDagPath から得た方が速い。 # 親のファンクションセットを得るより MDagPath のまま計算した方が速い。 #: `getMatrix` の別名。 ノードのトランスフォーメーション情報を得る。          :mayanode:`transform` 派生ノードの         アトリビュート ``xm`` の値を得ることと同じだが、         オプションでワールドスペースの値を得ることもできる。          :param `bool` ws: ワールド空間で得るかどうか。         :rtype: `.Transformation` # Transformation に Matrix を乗じることで、 # ピボットなどの各基準位置もワールド空間で一致させる。 #: `getTransformation` の別名。 ノードのスケーリングの値を得る。          ws=False の場合は、         単に scale アトリビュートから得られ、         :mayanode:`joint` ノードのローカルマトリックスには含まれる         inverseScale の影響は無視される。          :param `bool` ws: ワールド空間で得るかどうか。         :rtype: `.Vector` #: `getScaling` の別名。 ノードのシアーの値を得る。          ws=False の場合は、         単に shear アトリビュートから得られ、         :mayanode:`joint` ノードのローカルマトリックスには含まれる         inverseScale の影響は無視される。          :param `bool` ws: ワールド空間で得るかどうか。         :rtype: `.Vector` #: `getShearing` の別名。 ノードの回転のクォータニオンを得る。          デフォルトでは rotateAxis を含んでいない回転となり、         マトリックスからの分解結果とは一致しないが、         Maya の機能（Local Axis 表示やコンストレイン）が         ノードの回転方向とする基準と一致する。          ws=False の場合は、         3種の回転アトリビュートのみから合成された結果が得られ、         :mayanode:`joint` ノードのローカルマトリックスには含まれる         inverseScale の影響は無視される。          :param `bool` ws: ワールド空間で得るかどうか。         :param `bool` ra: rotateAxis を含めるかどうか。         :param `bool` r: rotate を含めるかどうか。         :param `bool` jo: jointOrient を含めるかどうか。         :rtype: `.Quaternion`          .. note::             ws=True の場合の親までの非一様 scale や shear による影響や             ws=False の場合の jointOrient は、              :mayacmd:`xform` コマンドや :mayaapi2:`MTransformationMatrix`             では考慮されないが、本メソッドでは考慮されている。              ws=True の結果は Legacy Viewport での Local Axis 表示と常に一致するが、             上位階層に 非一様 scale や shear が含まれる場合、             orientConstraint や parentConstraint の結果とは一致しない場合がある。             これは、コンストレイン側で考慮が足りていないからである。             ただし、segmentScaleCompensate で常に非一様 scale が打ち消されていれば一致する。              ws=False の結果は、上位階層に非一様 scale や shear が無いか、             segmentScaleCompensate で常に非一様 scale が打ち消されていれば、             親の getQ(ws=True, ra=True) を乗じた結果は getQ(ws=True) と一致する。          .. warning::             以下のオプションの組み合わせはエラーになる。              - ws=False, ra=True, r=False, jo=True             - ws=True, ra=True, r=False             - ws=True, ra=True, jo=False             - ws=True, r=True, jo=False # Legacy Viewport の Local Axis と同じになる。 # nw ではこのノードのscaleとshearを取り除いているがそれだと Local Axis には一致しない。 # いずれにせよ、非一様scaleが残っている場合のコンストレインの結果とは一致しない。 # transform でなければ、ローカルオプションによる差はない。 # ssc=on であれば # (this.getQ() * parent.getQ(ws=True, ra=True)) が # this.getQ(ws=True) と同じになるようにしている。 # transform でなければ、ローカルオプションによる差はない。 #: `getQuaternion` の別名。 ノードの jointOrient までのクォータニオンを得る。          `getQuaternion` の r=False 指定と同じ。          :param `bool` ws: ワールド空間で得るかどうか。         :rtype: `.Quaternion` ノードの位置を得る。          デフォルトでは回転ピボットの位置となり、         マトリックスからの分解結果とは一致しないが、         Maya の機能（Local Axis 表示位置やコンストレイン）が         ノードの位置とする基準と一致する。          :param `bool` ws: ワールド空間で得るかどうか。         :param `int` at:             どのアトリビュートに相当する位置を得るか。              - 0 だとローカル原点（親の matrix 位置）。             - 1 だと translate の位置。             - 2 だと rotatePivot の位置。             - 3 だと scalePivot の位置。             - 4 以上だと matrix の位置。          :rtype: `.Vector` # -sp s sh sp spt -rp ra r jo rp rpt -is t #: `getTranslation` の別名。 #------------------------------------------------------------------------------ def _searchShapeCache(cache, key, mnode):     for k in list(cache):         shape = cache[k]         if not shape.isValid():             # 無効だったら削除。             del cache[k]         elif shape._CyObject__data['mnode'] == mnode:             # 目的のものなら、キャッシュを作り直し。             del cache[k]             cache[key] = shape             return shape