ちょっと育児でバタバタしていたり、結構前に99%完成はしていたのに詳細は後述しますが
Mayaが落ちる致命的な挙動が解決できなかったりして、なかなか記事にできませんでしたが、
別のスクリプトを制作中にスクリプトのリファレンスを見ていて偶然その打開策を発見したので
ようやく記事にすることができました。
今回作ったのはSoftimageでは標準であった「選択しているオブジェクトの名前を
ディスプレイ上に表示する」という機能ですが、残念ながらMayaにはなかったので作ってみました。
ただ、Mirageさん(@MirageYM)が書いて下さったサンプルを元に拡張していっただけなんですけどもね・・・(;´∀`)
改めてMirageさん、サンプル大変参考になりました。ありがとうございました!
使い方は、まずこちらからファイルをDLしてもらい、Mayaのプラグインパスの通っている所(よく分からない方は C:\Users\ユーザー名\Documents\maya\2016\ja_JP\plug-ins など)にファイルを入れて下さい。
Mayaを起動して、メインメニュー>ウィンドウ>プラグインマネージャー を開いてその中のkkDisplaySelectedNameのロードのチェックをONにして下さい。
そうすると、メインメニュー>ディスプレイ の一番下にDisplay Selected Nameが追加されていると思います。
(※もしプラグインマネージャーに表示されていない場合はプラグインのファイルを置く場所がパス通ってないか、
もしくはプラグインのTypeIdが他のモノと被っている可能性があるのでコードの18行目を 0xF0F0F1 とは違うものに変えてみて下さい。)
さらにそこのチェックボックスをONにするとONになっている場合のみ、
選択したオブジェクトの名前がディスプレイ上に表示されるようになります。
モノによってはその表示される名前の色が見づらい場合があると思います。
その場合は先程のDisplay Selected Nameメニューの右側の四角いボックスをクリックしてもらうと
色変更のメニューが出てきますのでお好きな色に変更して下さい。
このDisplay Selected NameメニューのONOFFをショートカットで変更したい方は
ホットキーエディタに下記のように登録して下さい。
これだけでONOFFをトグルできます。
import maya.cmds as mc dsn = "MayaWindow|mainDisplayMenu|DisplaySelectedName" if(mc.menuItem(dsn, exists=True)): mc.menuItem(dsn, e=True, checkBox = not mc.menuItem(dsn, q=True, checkBox=True))
コードはDLして見てもらってもいいのですが、一応こちらにも載せておきます。
簡単に処理内容を言いますと、SelectionChangedイベントのscriptJobに、
「専用ノード生成しそれに選択したオブジェクトの名前をセットしてペアレントコンストレイントする」
というのを登録しています。
冒頭で書いたMayaが落ちてしまうのを回避できた解決策は
265行目のscriptJobの登録時にcompressUndoフラグをTrueにしたことです。(+UndoInfo)
これがないときは選択したオブジェクトがdeleteされていた場合にUndoすると削除されてもう存在しないモノに
名前を表示するノードを作ろうとしたためか落ちたり、逆にこのスクリプトの挙動がUndoに残ってしまい戻れなくなるなど起きていました。
一応今の状態でプリミティブのキューブ1000個全選択して、
名前を表示し終わるまでの時間を計測した所、平均0.8秒でした。
(※Maya2016 : CPU Corei7-4790K : GPU GeForce GTX 980 で計測)
undoInfoのopenChunk/closeChunkで多少早くはなっていますが、
もっと処理が早くなりそうな書き方をご存知の方いらっしゃいましたらご教授下さい。
あと、色々と自分でも試してはいますが、
不具合が出ても責任は取れませんので自己責任でお試し下さい。
(ちなみに2016でしか試せていませんがたぶん他のバージョンでも大丈夫かと…)
# -*- coding: utf-8 -*- import sys import maya.api.OpenMaya as om2 import maya.api.OpenMayaUI as om2u import maya.api.OpenMayaRender as om2r import maya.cmds as mc maya_useNewAPI = True #=============================================================== class kkDisplaySelectedNameNode( om2u.MPxLocatorNode ): kPluginNodeTypeName = "kkDisplaySelectedNameNode" # TypeIdは適当な番号 (0x00000000以上、0x0007ffff以下で指定。他のプラグインとかぶってはいけない) NodeId = om2.MTypeId( 0xF0F0F1 ) # ノードがDrawOverrideを使うためにはOverrideをMayaに登録しておく必要がある。以下2つはそのためのID classfication = 'drawdb/geometry/kkDisplaySelectedName' registrantId = 'kkDisplaySelectedNamePlugin' # ノードのアトリビュート。aStringにセットした文字列が描画される aString = om2.MObject() ColorR = om2.MObject() ColorG = om2.MObject() ColorB = om2.MObject() #=============================================================== def __init__( self ): om2u.MPxLocatorNode.__init__( self ) #=============================================================== def draw( self, view, path, style, status ): pass #=============================================================== def isBounded( self ): return True #=============================================================== def boundingBox( self ): return om2.MBoundingBox( om2.MPoint( -0.5, -0.5, -0.5 ), om2.MPoint( 0.5, 0.5, 0.5 ) ) #=============================================================== # creator @staticmethod def nodeCreator(): return kkDisplaySelectedNameNode() #=============================================================== # initializer @staticmethod def nodeInitializer(): fnTypedAttr = om2.MFnTypedAttribute() fnNumericAttr1 = om2.MFnNumericAttribute() fnNumericAttr2 = om2.MFnNumericAttribute() fnNumericAttr3 = om2.MFnNumericAttribute() kkDisplaySelectedNameNode.aString = fnTypedAttr.create( "String", "str", om2.MFnData.kString ) kkDisplaySelectedNameNode.addAttribute( kkDisplaySelectedNameNode.aString ) kkDisplaySelectedNameNode.ColorR = fnNumericAttr1.create( "ColorR", "colR", om2.MFnNumericData.kFloat, 1.0 ) kkDisplaySelectedNameNode.addAttribute( kkDisplaySelectedNameNode.ColorR ) kkDisplaySelectedNameNode.ColorG = fnNumericAttr2.create( "ColorG", "colG", om2.MFnNumericData.kFloat, 1.0 ) kkDisplaySelectedNameNode.addAttribute( kkDisplaySelectedNameNode.ColorG ) kkDisplaySelectedNameNode.ColorB = fnNumericAttr3.create( "ColorB", "colB", om2.MFnNumericData.kFloat, 1.0 ) kkDisplaySelectedNameNode.addAttribute( kkDisplaySelectedNameNode.ColorB ) return True #=============================================================== class UserData( om2.MUserData ): """ DrawOverrideにユーザー定義の情報を供給する為のデータ構造 """ #=============================================================== def __init__( self ): om2.MUserData.__init__( self, False ) self.datas = [] #=============================================================== class api_annotationLoc2Override( om2r.MPxDrawOverride ): """ 注釈のDrawOverride """ #=============================================================== def __init__( self, obj ): om2r.MPxDrawOverride.__init__( self, obj, api_annotationLoc2Override.draw ) #=============================================================== @staticmethod def draw( context, data ): pass #=============================================================== def supportedDrawAPIs( self ): """ OpenGLとDirextX11をサポート """ return om2r.MRenderer.kOpenGL | om2r.MRenderer.kDirectX11 #=============================================================== def hasUIDrawables( self ): """ addUIDrawablesを使うために必要 """ return True #=============================================================== def isBounded( self, objPath, cameraPath ): return True #=============================================================== def boundingBox( self, objPath, cameraPath ): bbox = om2.MBoundingBox( om2.MPoint( -0.5, -0.5, -0.5 ), om2.MPoint( 0.5, 0.5, 0.5 ) ) return bbox #=============================================================== def disableInternalBoundingBoxDraw( self ): return True #=============================================================== def prepareForDraw( self, objPath, cameraPath, frameContext, oldData ): # ロケーターからアトリビュートを読んでUserDataとして返す if( objPath ): newData = None # oldDataがあった場合はoldDataをnewDataに入れて、UserDataのdatasを初期化する if( oldData ): newData = oldData newData.datas = [] else: newData = UserData() thisNode = objPath.node() fnNode = om2.MFnDependencyNode( thisNode ) strPlug = fnNode.findPlug( 'String', False ) colRPlug = fnNode.findPlug( 'ColorR', False ) colGPlug = fnNode.findPlug( 'ColorG', False ) colBPlug = fnNode.findPlug( 'ColorB', False ) # asStringやasFloatがget関数 newData.datas.append( strPlug.asString() ) newData.datas.append( colRPlug.asFloat() ) newData.datas.append( colGPlug.asFloat() ) newData.datas.append( colBPlug.asFloat() ) return newData return None #=============================================================== def addUIDrawables( self, objPath, drawManager, frameContext, data ): # UI描画。MUIDrawManagerを使うことで簡単なライン描画とかもできる if( data ): drawManager.beginDrawable() dColor = om2.MColor() dColor[0] = mc.optionVar(q = "kkDisplaySelectedName_ColorR") #data.datas[1] #Red dColor[1] = mc.optionVar(q = "kkDisplaySelectedName_ColorG") #data.datas[2] #Green dColor[2] = mc.optionVar(q = "kkDisplaySelectedName_ColorB") #data.datas[3] #Blue drawManager.setColor( dColor ) drawManager.setFontSize( om2r.MUIDrawManager.kDefaultFontSize ) drawManager.text( om2.MPoint( 0.5, 0.5 ), data.datas[0] ) drawManager.endDrawable() return True #=============================================================== # creator @staticmethod def creator( obj ): return api_annotationLoc2Override( obj ) #=============================================================== def changeColorUI(*args): if mc.window("dispSelName_ColorWin", exists=True): mc.deleteUI("dispSelName_ColorWin") mc.window("dispSelName_ColorWin", title='Colors' ) mc.columnLayout() mc.colorInputWidgetGrp("dispSelName_ColorInpWG", label='Color', rgb=(1, 1, 1), changeCommand = changeColorAttr ) mc.showWindow() def changeColorAttr(*args): changCol = mc.colorInputWidgetGrp("dispSelName_ColorInpWG", q=True, rgbValue=True) if mc.optionVar(exists = "kkDisplaySelectedName_ColorR") == True: mc.optionVar(intValue = ["kkDisplaySelectedName_ColorR", changCol[0]]) if mc.optionVar(exists = "kkDisplaySelectedName_ColorG") == True: mc.optionVar(intValue = ["kkDisplaySelectedName_ColorG", changCol[1]]) if mc.optionVar(exists = "kkDisplaySelectedName_ColorB") == True: mc.optionVar(intValue = ["kkDisplaySelectedName_ColorB", changCol[2]]) try: annotateList = om2.MGlobal.getSelectionListByName("kkDisplaySelectedNameNode*") for x in range(annotateList.length()): annotateDepNode = om2.MFnDependencyNode().setObject( annotateList.getDependNode(x) ) colR = annotateDepNode.findPlug('ColorR', False ) colG = annotateDepNode.findPlug('ColorG', False ) colB = annotateDepNode.findPlug('ColorB', False ) colR.setFloat( changCol[0] ) colG.setFloat( changCol[1] ) colB.setFloat( changCol[2] ) except: pass #=============================================================== # initialize the script plug-in def initializePlugin( obj ): mplugin = om2.MFnPlugin( obj ) try: # Overrideを使うためにはオーバーロードされた専用のredisterNode関数を使う必要がある mplugin.registerNode( kkDisplaySelectedNameNode.kPluginNodeTypeName, kkDisplaySelectedNameNode.NodeId, kkDisplaySelectedNameNode.nodeCreator, kkDisplaySelectedNameNode.nodeInitializer, om2.MPxNode.kLocatorNode, kkDisplaySelectedNameNode.classfication ) # DrawOverride登録 om2r.MDrawRegistry.registerDrawOverrideCreator( kkDisplaySelectedNameNode.classfication, kkDisplaySelectedNameNode.registrantId, api_annotationLoc2Override.creator ) # ディスプレイメニューに追加 チェックボックスはデフォルトOFF mc.menuItem("DisplaySelectedName", label="Display Selected Name", checkBox=False, parent="MayaWindow|mainDisplayMenu" ) mc.menuItem(optionBox = True, command = changeColorUI, parent="MayaWindow|mainDisplayMenu" ) mc.scriptJob( event = ["SelectionChanged", createAnnotation] , compressUndo=True) # optionVarに文字カラー用の変数を作成 mc.optionVar(intValue = ["kkDisplaySelectedName_ColorR", 1]) mc.optionVar(intValue = ["kkDisplaySelectedName_ColorG", 1]) mc.optionVar(intValue = ["kkDisplaySelectedName_ColorB", 1]) except: sys.stderr.write( "Failed to register node: %s" % kkDisplaySelectedNameNode.kPluginNodeTypeName ) raise #=============================================================== # uninitialize the script plug-in def uninitializePlugin( obj ): mplugin = om2.MFnPlugin( obj ) try: mplugin.deregisterNode( kkDisplaySelectedNameNode.NodeId ) om2r.MDrawRegistry.deregisterDrawOverrideCreator( kkDisplaySelectedNameNode.classfication, kkDisplaySelectedNameNode.registrantId ) if mc.menuItem("MayaWindow|mainDisplayMenu|DisplaySelectedName", exists=True): mc.deleteUI("MayaWindow|mainDisplayMenu|DisplaySelectedName", menuItem=True) mc.undoInfo(stateWithoutFlush=False) mc.delete( mc.ls( 'annotationDSN_*' ) ) mc.undoInfo(stateWithoutFlush=True) # optionVarから文字カラー用の変数を削除 mc.optionVar(remove="kkDisplaySelectedName_ColorR") mc.optionVar(remove="kkDisplaySelectedName_ColorR") mc.optionVar(remove="kkDisplaySelectedName_ColorR") jobList = mc.scriptJob(listJobs=True) jobNum = 0 for job in jobList : # ジョブリストから登録したジョブの内容を検索する # 単純にそのインデックスを使えばいいと思ったら # インデックスとジョブ番号が違うことがあるのでスライスで取得する if 'conditionTrue = ["SomethingSelected", createAnnotation]' in job : jobNum = job [0 : job .find(":")] # 上の処理でjobNumに数値が入っていたらそのスクリプトジョブを停止させる if jobNum > 0: print("killing_jobNum__%s " %jobNum) mc.scriptJob( kill=jobNum, force=True) print(" Unload >> old_kDisplaySelName Plugin ") except: sys.stderr.write( "Failed to deregister node: %s" % kkDisplaySelectedNameNode.kPluginNodeTypeName ) raise #======================================================================================================================= def createAnnotation( *args ): try: # Undoのチャンクを開く. これをすることで速度が早くなる mc.undoInfo(openChunk=True) # すでに annotationDSN_ が付いているものがある場合、削除しておく annotationList = mc.ls( "annotationDSN_*" ) if len( annotationList ) > 0: mc.delete( annotationList ) if mc.menuItem("MayaWindow|mainDisplayMenu|DisplaySelectedName", q=True, checkBox=True): # 選択しているリスト selList = om2.MGlobal.getActiveSelectionList() if selList.length() > 0: # Transformのみのフィルターをする selListIter = om2.MItSelectionList(selList, om2.MFn.kTransform) while not selListIter.isDone(): mObj2 = selListIter.getDependNode() selDagFn = om2.MFnDagNode( mObj2 ) if not "annotationDSN" in selDagFn.name(): ant = mc.createNode("kkDisplaySelectedNameNode") antDagPath = om2.MGlobal.getSelectionListByName( ant ).getDagPath(0) antDagFn = om2.MFnDagNode( antDagPath ) antParent = om2.MFnDependencyNode().setObject( antDagFn.parent(0) ) antStrPlug = antDagFn.findPlug( 'String', False ) antStrPlug.setString( selDagFn.name() ) dgmf = om2.MDGModifier() dgmf.renameNode(antDagFn.parent(0), "annotationDSN_1") dgmf.doIt() mc.parentConstraint(selDagFn.fullPathName(), antParent.name(), skipRotate="none") selListIter.next() # 選択をオブジェクトに戻す om2.MGlobal.setActiveSelectionList(selList) except Exception as e: print(e.args) pass finally: # Undoのチャンクを閉じる mc.undoInfo(closeChunk=True)
コメントをお書きください
aaa (火曜日, 22 1月 2019 10:28)
2018で動かず。
ブログ管理者 (月曜日, 29 4月 2019 01:23)
aaaさん、ご返信遅くなり大変申し訳無いです。
2018では試せてないのですが、2019では問題なく動くのを確認できました。
もし宜しければ、もう少し動かなかったaaaさんの実行環境の状況(Mayaの2018のUpdateのバージョンいくつだとか、プリファレンス>ディスプレイのレンダリングエンジンの設定など)を教えてもらえますでしょうか?