Skip to content

Instantly share code, notes, and snippets.

@ryusas
Created April 18, 2017 06:57
Show Gist options
  • Save ryusas/626483f760c95b90622219d550c4985b to your computer and use it in GitHub Desktop.
Save ryusas/626483f760c95b90622219d550c4985b to your computer and use it in GitHub Desktop.
A simple example to avoid the issue of Maya 2017 workspaceControl.
# 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)
''')
"""
# 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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment