Skip to content

Instantly share code, notes, and snippets.

@kingmax
Forked from ryusas/workspaceControl.py
Created December 14, 2018 03:37
Show Gist options
  • Save kingmax/1d5e6ef8aa84c75eb599977b07bf6f32 to your computer and use it in GitHub Desktop.
Save kingmax/1d5e6ef8aa84c75eb599977b07bf6f32 to your computer and use it in GitHub Desktop.

Revisions

  1. @ryusas ryusas created this gist Apr 18, 2017.
    70 changes: 70 additions & 0 deletions workspaceControl.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    # coding: utf-8
    u"""
    Maya 2017 workspaceControl の問題回避のサンプル。
    workspaceControl と workspaceControlState のゴミが残らないようにする。
    retain=False の場合でも何故か state のゴミが残ってしまうが、
    scriptJob で workspaceControl の削除を監視して state も同時に削除するようにする。
    retain=True の場合は、UI が閉じたとしても state は残って良いはずなので監視はしない。
    いずれにせよ、スタートアップの UI 再生時にエラーとなった場合は
    (そのツールをアンインストールしたり、何らかの問題が発生している場合)、
    UI が閉じられた(削除ではない)時に workspaceControl と state がともに削除されるようにする。
    exec を通しているのは、何故かそうするとグローバルスコープが汚れないため。
    """

    import maya.cmds as cmds


    def create(name, code, **kwargs):
    u"""
    workspaceControl を生成する。
    オプション引数の
    retain は False に、
    loadImmediately は True に
    デフォルトが置き換えられている。
    :param `str` name: 生成する workspaceControl の名前。
    :param `str` code: 中身のUIの生成コード。
    """
    ret = kwargs.pop('retain', kwargs.pop('ret', False)) # デフォルト変更: True -> False
    li = kwargs.pop('loadImmediately', kwargs.pop('li', True)) # デフォルト変更: False -> True
    code = _CODE_TEMPLATE % (name, ret, code)
    return cmds.workspaceControl(name, ui=code, retain=ret, loadImmediately=li, **kwargs)

    _CODE_TEMPLATE = """
    exec('''
    import maya.cmds as cmds
    name = '%s'
    retain = %r
    def deleteWSCtl(*a):
    if cmds.workspaceControl(name, ex=True):
    cmds.deleteUI(name)
    if cmds.workspaceControlState(name, ex=True):
    cmds.workspaceControlState(name, remove=True)
    try:
    if not retain:
    cmds.scriptJob(uid=(name, deleteWSCtl))
    %s
    except:
    from traceback import print_exc
    print_exc()
    def cleanup():
    if cmds.workspaceControl(name, q=True, vis=True):
    cmds.workspaceControl(name, e=True, vcc=deleteWSCtl)
    else:
    deleteWSCtl()
    cmds.evalDeferred(cleanup)
    ''')
    """

    211 changes: 211 additions & 0 deletions ws_test.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,211 @@
    # coding: utf-8
    u"""
    workspaceControl.py による問題回避のテスト。
    実行方法:
    import ws_test
    ws_test.TestWindow()
    """
    import re
    from weakref import WeakValueDictionary
    import maya.cmds as cmds
    import maya.mel as mel
    from workspaceControl import create as _wctl_create

    MAYA_VERSION = float(re.search(r'.+/(\d+(?:\.\d+)?)', cmds.internalVar(upd=True)).group(1)) #: Mayaのバージョン float 値。

    if MAYA_VERSION >= 2017.:
    _CREATING_WCTL = WeakValueDictionary()
    _INSTANCE_DICT = WeakValueDictionary()


    #------------------------------------------------------------------------------
    class DockableWindow(object):
    u"""
    ドッキング可能ウィンドウのラッパークラス。
    2017 以降の workspaceControl 向けの実装しかしていないが、
    発展させれば 2017 以降とそれ以外との差の吸収も可能。
    """
    UI_NAME = '' #: ユニークなUI名。
    WINDOW_TITLE = 'window' #: ウィンドウタイトル。
    WINDOW_WH = (500, 500) #: デフォルトのウィンドウサイズ。
    DOCK_AREA = 'right' #: ドッキングエリアの指定。
    WCTL_OPTS = None #: workspaceControl のドッキングに関するオプションを指定する辞書。DOCK_AREA 簡易指定に優先する。

    def __init__(self, root='', **kwargs):
    self.__name = root.split('|')[-1]
    if not root:
    root = self._createUI(**kwargs)
    self.__ui_root = root
    _INSTANCE_DICT[root] = self
    #------------------------
    trackDestruction(self)
    #------------------------

    def __repr__(self):
    return "<%s '%s'>" % (type(self).__name__, self.name())

    def __str__(self):
    return self.__ui_root

    def root(self):
    return self.__ui_root

    def name(self):
    if not self.__name:
    name = self.UI_NAME
    if not name:
    raise NotImplementedError('UI_NAME')
    while cmds.control(name, ex=True):
    name = _incrementName(name)
    self.__name = name
    return self.__name

    if MAYA_VERSION >= 2017.:
    try:
    MAIN_WORKAREA = mel.eval('$gWorkAreaForm=$gWorkAreaForm') #: メインの workspacePanel 。
    except RuntimeError:
    MAIN_WORKAREA = None
    try:
    MAIN_PANE = mel.eval('$gViewportWorkspaceControl=$gViewportWorkspaceControl') #: ビューポートの workspaceControl 。
    except RuntimeError:
    MAIN_PANE = None
    CHAN_LAYER_EDITOR = mel.eval('getUIComponentDockControl("Channel Box / Layer Editor", false)') #: チャンネルボックスの workspaceControl 。
    OUTLINER = mel.eval('getUIComponentDockControl("Outliner", false)') #: アウトライナーの workspaceControl 。
    SHELF = mel.eval('getUIComponentToolBar("Shelf", false)') #: シェルフの workspaceControl 。
    TIME_SLIDER = mel.eval('getUIComponentToolBar("Time Slider", false)') #: タイムスライダーの workspaceControl 。

    def _createUI(self, **kwargs):
    # workspaceControl コマンドのオプション引数を決定。
    wctl_opts = self.WCTL_OPTS
    if wctl_opts is None:
    dock_area = self.DOCK_AREA
    if dock_area:
    if dock_area == 'right':
    if self.CHAN_LAYER_EDITOR:
    wctl_opts = {'tabToControl': (self.CHAN_LAYER_EDITOR, -1)}
    elif dock_area == 'left':
    if self.OUTLINER:
    wctl_opts = {'dockToControl': (self.OUTLINER, 'left')}

    if wctl_opts is None:
    if self.MAIN_PANE:
    wctl_opts = {'dockToControl': (self.MAIN_PANE, dock_area)}
    elif self.MAIN_WORKAREA:
    wctl_opts = {'dockToPanel': (self.MAIN_WORKAREA, dock_area, True)}
    else:
    wctl_opts = {'dockToMainWindow': (dock_area, True)}
    else:
    wctl_opts = {}

    name = self.name()
    _CREATING_WCTL[name] = self

    cls = type(self)
    code = 'import %s; %s.%s._createWCContents(%s)' % (
    cls.__module__, cls.__module__, cls.__name__,
    ', '.join([('%s=%r' % kv) for kv in kwargs.items()]),
    )
    return _wctl_create(name, code, l=self.WINDOW_TITLE, iw=self.WINDOW_WH[0], ih=self.WINDOW_WH[1], **wctl_opts)

    @classmethod
    def _createWCContents(cls, **kwargs):
    u"""
    workspaceControl 内の UI を作成する。workspaceControl に uiScript として登録するもの。
    """
    # カレント親が workspaceControl 。
    root = cmds.setParent(q=True)

    # _createUI から呼び出されたなら、__init__ 途中であるが、一時的な辞書からインスタンスを取得できる。
    self = _CREATING_WCTL.pop(root, None)
    if self:
    # インスタンスが得られたなら、最初の生成中なので raise する。
    cmds.evalDeferred(lambda: cmds.workspaceControl(root, e=True, r=True))
    else:
    # インスタンスが得られないなら、Maya に再生成されているものなので、ここでインスタンスを生成する。
    self = cls(root=root)

    # UI が削除されるまでインスタンスが解放されないようにする。
    cmds.workspaceControl(root, e=True, vcc=self.onVisibilityChanged)

    self.createContents(**kwargs)

    else:
    # 2016 以前では dockControl コマンド等を使えば、バージョン違いを吸収できる。
    raise NotImplementedError('using dockControl for MAYA_VERSION < 2017')

    def onVisibilityChanged(self, *args):
    pass

    def createContents(self, **kwargs):
    raise NotImplementedError('createContents')

    def delete(self):
    cmds.deleteUI(str(self))

    @classmethod
    def instances(cls):
    return [x for x in _INSTANCE_DICT.values() if isinstance(x, cls)]

    @classmethod
    def deleteAll(cls):
    for x in cls.instances():
    x.delete()


    def _incrementName(name):
    m = _RE_TAIL_NUMBER.search(name)
    if m:
    i = m.group(0)
    return name[:-len(i)] + str(int(i) + 1)
    return name + '1'

    _RE_TAIL_NUMBER = re.compile(r'\d+$')


    #------------------------------------------------------------------------------
    from weakref import ref as _wref
    import traceback


    def trackDestruction(obj):
    s = repr(obj)
    def func():
    print('# DESTRUCT: ' + s)
    print('# BEGIN_TRACK: ' + s)
    return _registerFinalizer(obj, func)


    def _registerFinalizer(obj, func):
    r = _wref(obj, _finalizer)
    k = id(r)
    _finalize_refs[k] = (r, func)
    return k

    _finalize_refs = {}


    def _finalizer(r):
    if _finalize_refs:
    func = _finalize_refs.pop(id(r))[1]
    try:
    func()
    except Exception:
    traceback.print_exc()


    #------------------------------------------------------------------------------
    class TestWindow(DockableWindow):
    u"""
    テストアプリケーション。
    """
    UI_NAME = 'Test'
    WINDOW_TITLE = 'Test'

    def createContents(self, **kwargs):
    cmds.columnLayout()
    cmds.button()
    cmds.button()