Skip to content

Instantly share code, notes, and snippets.

@deathponta
Created February 5, 2018 13:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deathponta/97063d8ac47470afc76d6c771d841e5d to your computer and use it in GitHub Desktop.
Save deathponta/97063d8ac47470afc76d6c771d841e5d to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
"""
このツールは、Translate , Rotate , Scale アトリビュートでポーズを付けているものにのみ効果を発揮する。
カスタムアトリビュートの値は残念ながら対応していない。
[x]選択したオブジェクトのTRSの現在の値を保持する。
[x]ペースト時は強制的にオブジェクトにキーを打つ
[x]リストをダブルクリックすると、ペースト対象となるオブジェクトを選択
[x]poseがコピーされたら、追加されたList内要素を選択
[x]poseが削除されたら、List内の下の要素を選択、無い場合は上を選択、上も無い場合は選択しない。
[x]セーブで poses データを外部ファイルに保存する。json形式。
[x]ロードで、poses データを外部ファイルから読み込み、poses変数に格納する。
[x]リスト内の項目をリネームできる。
"""
import maya.cmds as cmds
import maya.mel as mel
import os
from functools import partial
import re
import json
class TRS_PoseLibrary(object):
# これは poseList 内のデータです。二次元配列として[ "リストに表示する名前" , "キーデータ" ]を扱います
poses = [[]]
del poses[0]
def __init__(self):
btnWidth = 60
win = 'TRS_PoseLibrary'
if(cmds.window(win, ex=True)):
cmds.deleteUI(win)
w = cmds.window(win, tlb=True)
cmds.columnLayout(p=w)
cmds.rowLayout( nc=2 )
cmds.button( l=u'Load', w=btnWidth , c=partial(self.openFileDialog), ann=u'poseLibファイルからポーズリストを読み込み。' )
cmds.button( l=u'Save', w=btnWidth , c=partial(self.saveFileDialog), ann=u'poseLibファイルに現在のポーズを保存。' )
cmds.setParent('..')
cmds.text( l=u'-- poseList --' )
cmds.textScrollList( 'tsl_poseList' ,ams=False , numberOfRows=6 , doubleClickCommand=partial(self.selectPasteTgt,'') , deleteKeyCommand=partial(self.deletePose,''))
cmds.rowLayout( nc=4 )
cmds.button( l=u'Copy' , w=btnWidth, c=partial(self.copyPose) , ann=u'選択したオブジェクトの現在のTRSを保持します。' )
cmds.button( l=u'Paste' , w=btnWidth , c=partial(self.pastePose ) , ann=u'選択した項目のポーズをシーン内のオブジェクトに貼り付けます。\nターゲットオブジェクトは選択しなくていいです。' )
cmds.button( l=u'Delete', w=btnWidth , c=partial(self.deletePose) , ann=u'選択した項目のポーズを削除します。')
cmds.button( l=u'Rename', w=btnWidth , c=partial(self.renamePose) , ann=u'選択した項目のポーズをリネームします。(リストの表示上だけなり)')
cmds.setParent('..')
cmds.showWindow()
# poseList に現在選択しているオブジェクトのキーを保存する
def copyPose(self,v):
# 現在のシーン名とフレームを取得(poseList に表示する文字列)
poseListName = '%s [ %s F ]'%( cmds.file( q=True , sceneName=True , shortName=True ) , cmds.currentTime(q=True) )
# オブジェクトの絶対パスとPRSのアトリビュートを attr に保管(改行有り)
selObjs = cmds.ls(sl=True,long=True)
# データ変数定義(まずは選択ノード数追加。その後選択ノード名、選択TRSアトリビュート情報を追加)
datas = '%s,'%(len(selObjs))
for obj in selObjs:
datas = '%s%s'%(datas,obj+',') # オブジェクト名追加
datas = '%s%s'%(datas,self.getAttrKey(obj)) # アトリビュート追加
self.poses.append( [ poseListName , datas ] )
self.listRefresh()
# 追加した項目を選択(最後の行を選択))
cmds.textScrollList( 'tsl_poseList' , e=True , selectIndexedItem=len(self.poses) )
# poseList で選択した項目を削除
def deletePose(self,v):
# 削除確認
res = cmds.confirmDialog( title='Confirm', message='delete pose?', button=['Yes','No'], defaultButton='Yes', cancelButton='No', dismissString='No' )
if res == 'No':
return
# リストが選択されていなかったらリターン
if cmds.textScrollList( 'tsl_poseList' , q=True , selectItem=True ) == None:
return
# アクティブインデックスを取得し、対応する項目をグローバル変数から削除
selIdx = int(cmds.textScrollList( 'tsl_poseList' , q=True , selectIndexedItem=True )[0]) - 1
del self.poses[selIdx]
self.listRefresh()
# 削除された項目の下を選択。無かったら上を選択。無かったら何も選択しない。
if len(self.poses) == 0:
return
try:
cmds.textScrollList( 'tsl_poseList' , e=True , selectIndexedItem=selIdx+1 )
except:
cmds.textScrollList( 'tsl_poseList' , e=True , selectIndexedItem=len(self.poses) )
# 配列の要素とList表示をあわせる
def listRefresh(self):
# 2次元配列の名前に当たる部分だけ取り出す(リスト表示用)
poseNames = []
for name in self.poses:
poseNames.append( name[0] )
cmds.textScrollList( 'tsl_poseList' , e=True , removeAll=True )
cmds.textScrollList( 'tsl_poseList' , e=True , append=poseNames )
def getAttrKey(self, obj):
at = ['']*9
t = cmds.currentTime(q=True)
# 小数点第2以下は切り上げます
at[0] = round( cmds.getAttr(obj+'.tx', t=t) , 2 )
at[1] = round( cmds.getAttr(obj+'.ty', t=t) , 2 )
at[2] = round( cmds.getAttr(obj+'.tz', t=t) , 2 )
at[3] = round( cmds.getAttr(obj+'.rx', t=t) , 2 )
at[4] = round( cmds.getAttr(obj+'.ry', t=t) , 2 )
at[5] = round( cmds.getAttr(obj+'.rz', t=t) , 2 )
at[6] = round( cmds.getAttr(obj+'.sx', t=t) , 2 )
at[7] = round( cmds.getAttr(obj+'.sy', t=t) , 2 )
at[8] = round( cmds.getAttr(obj+'.sz', t=t) , 2 )
attrs = ''
for a in at:
attrs = '%s%s%s'%(attrs,a,',')
return attrs
def pastePose(self,v):
# poseList で選択した項目のdataを流し込む
poseIdx = int( cmds.textScrollList( 'tsl_poseList' , q=True , selectIndexedItem=True )[0]) - 1
paramList = self.poses[poseIdx][1].split(',') # data をアトリビュート毎に分解
# 配列内の削除とか行うので、temp変数として逃がす
tempParamList = paramList
# 処理を行う回数 = datasの0番目の数値
count = int(tempParamList[0])
del tempParamList[0] # 処理回数用の数値は用済みなので削除
print u'ポーズをペーストしました',
# ノード名数分処理
for i in range(count):
obj = tempParamList[0]
txa = float(tempParamList[1])
tya = float(tempParamList[2])
tza = float(tempParamList[3])
rxa = float(tempParamList[4])
rya = float(tempParamList[5])
rza = float(tempParamList[6])
sxa = float(tempParamList[7])
sya = float(tempParamList[8])
sza = float(tempParamList[9])
# 上記アトリビュートをセットしたら、セットし終わったものは削除
del tempParamList[0:10]
# TRSアトリビュートに対して値をセットしキーを打つ
try:
cmds.setAttr(obj+'.tx', txa)
cmds.setKeyframe(obj, at='tx', v=txa)
except:pass
try:
cmds.setAttr(obj+'.ty', tya)
cmds.setKeyframe(obj, at='ty', v=tya)
except:pass
try:
cmds.setAttr(obj+'.tz', tza)
cmds.setKeyframe(obj, at='tz', v=tza)
except:pass
try:
cmds.setAttr(obj+'.rx', rxa)
cmds.setKeyframe(obj, at='rx', v=rxa)
except:pass
try:
cmds.setAttr(obj+'.ry', rya)
cmds.setKeyframe(obj, at='ry', v=rya)
except:pass
try:
cmds.setAttr(obj+'.rz', rza)
cmds.setKeyframe(obj, at='rz', v=rza)
except:pass
try:
cmds.setAttr(obj+'.sx', sxa)
cmds.setKeyframe(obj, at='sx', v=sxa)
except:pass
try:
cmds.setAttr(obj+'.sy', sya)
cmds.setKeyframe(obj, at='sy', v=sya)
except:pass
try:
cmds.setAttr(obj+'.sz', sza)
cmds.setKeyframe(obj, at='sz', v=sza)
except:pass
# poseLibファイルを保存(list → json)
def saveFileDialog(self,v):
print self.poses,
# JSONで書き出しを行わう
d = os.path.dirname(cmds.file(q=True, exn=True))
f = cmds.fileDialog(m=1, dm=d+'\/*.poseLib' , title=u'poseLib ファイルを保存します')
if f == "":
print u"保存をキャンセルしました。",
return
fp = open(f, 'w')
# リストを辞書に変換してJson形式で保存
dictPoses = dict( self.poses )
json.dump( dictPoses , fp , ensure_ascii=False , indent=4 )
# poseLibファイルをロード(json → list)
def openFileDialog(self,v):
d = os.path.dirname(cmds.file(q=True, exn=True))
f = cmds.fileDialog(m=0, dm=d+'\/*.poseLib' , title=u'poseLib ファイルを読み込みます')
if f == "":
print u"読み込みをキャンセルしました。",
return
fp = open(f, 'r')
# 読み込んだJsonを2次元配列に変換
jsonData = json.load(fp)
tempPoses = jsonData.items() # [('aaaa','1')] この時点ではタプルが含まれているので、下でlistに変換
for i in range(len(tempPoses)):
tempPoses[i] = list(tempPoses[i])
self.poses = tempPoses
self.listRefresh()
# リストの項目をWクリックした際に、ペースト対象となるオブジェクトを選択する( ポーズを作成したいときとかに便利)
def selectPasteTgt(self,v):
# 選択されているIndexからposesの値を取得し・・・
poseIdx = int( cmds.textScrollList( 'tsl_poseList' , q=True , selectIndexedItem=True )[0]) - 1
paramList = self.poses[poseIdx][1].split(',') # data をアトリビュート毎に分解
# アルファベットの含む要素を、ノード名として収集し・・・
pasteTgtObj = []
for p in paramList:
if re.search( '[a-z]|[A-Z]' , p ) :
pasteTgtObj.append( p )
# 選択する
cmds.select( pasteTgtObj )
# リストの項目をリネームする
def renamePose(self,v):
poseIdx = int( cmds.textScrollList( 'tsl_poseList' , q=True , selectIndexedItem=True )[0]) - 1
poseName = self.poses[poseIdx][0] # data をアトリビュート毎に分解
# 入力ダイアログを表示
result = cmds.promptDialog(
title=u'Rename Object',
message=u'Enter Name(日本語は駄目):',
button=[u'OK', u'Cancel'],
defaultButton=u'OK',
cancelButton=u'Cancel',
dismissString=u'Cancel',
text=poseName
)
# 決定したら、poses に入力内容を反映
if result == 'OK':
renameText = cmds.promptDialog(query=True, text=True)
self.poses[poseIdx][0] = renameText
self.listRefresh()
# 開発中用 インポートして使用した場合は下記は実行されない
if __name__ == '__main__' :
TRS_PoseLibrary()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment