[maya]ウェイトのリストを高速に取得してみる。

基本は

cmds.skinPercent( skincluster, vtx, transform=bone, query=True)
経過時間 = 00h:03m:16s

だけどgetAttrでとろうとするとこうなる
参考 http://www.marcuskrautwurst.com/2012/12/skinweights-savingloading-in-pymel.html

経過時間 = 00h:03m:38s  ちょっと遅いんかい。

#get
skincluster="skinCluster1"
vert=8155
influence='joint1'

bones=pm.listConnections(str(skincluster) + ".matrix")

print("bones= "+str(bones))

boneInt=0
boneslen=len(bones)
for b in range(0,boneslen):
    bonename=bones[b]
    if influence in str(bonename):
        print("found hit")
        boneInt=b
print("boneInt= "+str(boneInt))   
evalStr=str(skincluster)+'.weightList['+str(vert)+'].weights['+str(boneInt)+']'
resultVal=pm.getAttr(evalStr)
print("resultVal= "+str(resultVal))

setAttrするにもこう

#set
skincluster="skinCluster1"
vert=8155
influence='joint1'
weight=1

bones=pm.listConnections(str(skincluster) + ".matrix")

print("bones= "+str(bones))

boneInt=0
boneslen=len(bones)
for b in range(0,boneslen):
    bonename=bones[b]
    if influence in str(bonename):
        print("found hit")
        boneInt=b
print("boneInt= "+str(boneInt)) 

evalStr=str(skincluster)+'.weightList['+str(vert)+'].weights['+str(boneInt)+']'
#print("evalStr= "+str(evalStr))
#pm.setAttr('%s.weightList[%s].weights[%s]'%(skincluster,vert,skinData[each].influence),weights)
pm.setAttr(evalStr,weight)

om2でとるとこうなる。参考URL https://jamesbdunlop.github.io/om2/2018/03/29/IOSkins.html

import logging
import time, os
#import simplejson as json
import maya.api.OpenMaya as om2
import maya.cmds as cmds
#from OM2core.plugs.utils import getPlugValue, findPlugOnNode

def findPlugOnNode(node, plugName):
    """
    :param node: MObjectHandle
    :return: MPlug
    """
    if not node.isValid():
        raise Exception("Node is no longer valid!")

    mFn = om2.MFnDependencyNode(node.object())
    for x in xrange(mFn.attributeCount()):
        attr = om2.MFnAttribute(mFn.attribute(x))
        if attr.name == plugName:
            attr = mFn.attribute(plugName)
            plug = om2.MPlug(node.object(), attr)
            return plug

    raise Exception("PlugName {} is not valid!".format(plugName))


def getPlugValue(plug):
    """
    :param plug: MPlug. The node plug.
    :return: The value of the passed in node plug or None
    """
    pAttribute = plug.attribute()
    apiType = pAttribute.apiType()

    # Float Groups - rotate, translate, scale; Com2pounds
    if apiType in [om2.MFn.kAttribute3Double, om2.MFn.kAttribute3Float, om2.MFn.kCompoundAttribute]:
        result = []
        if plug.isCompound:
            for c in xrange(plug.numChildren()):
                result.append(getPlugValue(plug.child(c)))
            return result
    # Distance
    elif apiType in [om2.MFn.kDoubleLinearAttribute, om2.MFn.kFloatLinearAttribute]:
        return plug.asMDistance().asCentimeters()
        # Angle
    elif apiType in [om2.MFn.kDoubleAngleAttribute, om2.MFn.kFloatAngleAttribute]:
        return plug.asMAngle().asDegrees()
        # TYPED
    elif apiType == om2.MFn.kTypedAttribute:
        pType = om2.MFnTypedAttribute(pAttribute).attrType()
        # Matrix
        if pType == om2.MFnData.kMatrix:
            return om2.MFnMatrixData(plug.asMObject()).matrix()
            # String
        elif pType == om2.MFnData.kString:
            return plug.asString()
            # MATRIX
    elif apiType == om2.MFn.kMatrixAttribute:
        return om2.MFnMatrixData(plug.asMObject()).matrix()
        # NUMBERS
    elif apiType == om2.MFn.kNumericAttribute:
        pType = om2.MFnNumericAttribute(pAttribute).numericType()
        if pType == om2.MFnNumericData.kBoolean:
            return plug.asBool()
        elif pType in [om2.MFnNumericData.kShort, om2.MFnNumericData.kInt, om2.MFnNumericData.kLong,
                       om2.MFnNumericData.kByte]:
            return plug.asInt()
        elif pType in [om2.MFnNumericData.kFloat, om2.MFnNumericData.kDouble, om2.MFnNumericData.kAddr]:
            return plug.asDouble()
    # Enum
    elif apiType == om2.MFn.kEnumAttribute:
        return plug.asInt()

    return None

def _iterForSkinCluster(node):
    """
    :param node: MObject for the source connection
    :return: MObject
    """
    if node.apiType() == om2.MFn.kSkinClusterFilter:
        return om2.MObjectHandle(node)

    iterDg = om2.MItDependencyGraph(node,
                                    om2.MItDependencyGraph.kDownstream,
                                    om2.MItDependencyGraph.kPlugLevel)
    while not iterDg.isDone():
        currentItem = iterDg.currentNode() # use currentItem for 2017 and below
        if currentItem.hasFn(om2.MFn.kSkinClusterFilter):
            return om2.MObjectHandle(currentItem)

        iterDg.next()
def _findSkinCluster(mesh=None):
    """
    Returns a skinCluster attached to the kMesh or kNurbsCurve
    @:param mesh: MObjectHandle. Not the shape! Use the transform.
    :return: MObject
    """
    if not mesh.isValid():
        logger.warning("Destination is no longer valid!")
        return

    dagPath = om2.MDagPath()
    geo = dagPath.getAPathTo(mesh.object())

    ## Does it have a valid number of shapes?
    if geo.numberOfShapesDirectlyBelow() != 0:
        ## Fetch the shape of the geo now.
        shapeMobj = geo.extendToShape().node()
        mFn_shape = om2.MFnDependencyNode(shapeMobj)
        apiType = shapeMobj.apiType()
        if apiType == om2.MFn.kMesh:
            ## Look at the inMesh attr for the source
            inMesh_attr = mFn_shape.attribute('inMesh')
        elif apiType == om2.MFn.kNurbsCurve:
            inMesh_attr = mFn_shape.attribute('create')
        else:
            logger.warning("This type of om2.MFn node is not supported! int: {}".format(apiType))
            return

        inMesh_plug = om2.MPlug(shapeMobj, inMesh_attr)
        getSource = inMesh_plug.source().node()

        ## Now use the iterForSkinCluster() function to find the skinCluster in the connected network.
        skinClusterNode_MObjH = _iterForSkinCluster(getSource)

        if skinClusterNode_MObjH is not None:
            return skinClusterNode_MObjH
        else:
            return None

    return None

def _findInfluences(skinClusterMobjH=None):
    """
    Returns all the valid influences from the .matrix attribute on the skinCluster node.
    @:param mesh: MObjectHandle for the skinCluster. Using the handles here may be playing it a little too safe. But meh.
    :return: MObject
    """
    if not skinClusterMobjH.isValid():
        logger.warning("Skincluster is no longer valid! Did it get deleted?")
        return

    skClsMFnDep = om2.MFnDependencyNode(skinClusterMobjH.object())
    mtxAttr = skClsMFnDep.attribute("matrix")
    matrixPlug = om2.MPlug(skinClusterMobjH.object(), mtxAttr)

    ## Get a list of all the valid connected indices in the matrix array now.
    indices = matrixPlug.getExistingArrayAttributeIndices()
    influences = []
    for idx in indices:
        name = om2.MFnDependencyNode(matrixPlug.elementByLogicalIndex(idx).source().node()).absoluteName()
        influences.append(str(om2.MNamespace.stripNamespaceFromName(name)))

    return influences

def fileExists(filePath):
    if filePath is not None and os.path.isfile(filePath):
        logger.info("File exists!")
        return True

    return False

def writetoJSON(filePath, data):
    with open(filePath, 'w') as outfile:
        outfile.write(json.dumps(data))

    return True

def readFromJSON(filePath):
    with open(filePath) as infile:
        data = json.load(infile)
        return data

    return None

def _fetchSkinWeights(geoList=None, skipZeroWeights=True):
    """
    If you send in a list of geo, we'll use that. Else we assume we're working off selected.
    :param geoList: list() of string names for geometry to fetch data for
    :param skipZeroWeights: if you want to avoid saving all 0.0 weight data
    :return:
    """
    geo = om2.MSelectionList()
    if geoList is not None:
        for eachGeo in geoList:
            geo.add(eachGeo)
    else: #Assume selected
        for eachGeo in cmds.ls(sl=True):
            geo.add(eachGeo)

    weightData = {}
    for x in range(geo.length()):
        geoMObjH = om2.MObjectHandle(geo.getDependNode(x))
        geoName = om2.MFnDependencyNode(geoMObjH.object()).name()
        skinClusterMObjH = _findSkinCluster(geoMObjH)
        if skinClusterMObjH is None:
            logger.warning("Skipping {} has no skinCluster!".format(geoName))
            continue

        skName = str(om2.MFnDependencyNode(skinClusterMObjH.object()).name())

        influences = _findInfluences(skinClusterMObjH)
        ## Add the data to the dict
        weightData[geoName] = {}
        weightData[geoName][skName] = {}
        weightData[geoName][skName]['influences'] = influences
        weightData[geoName][skName]['maxInf'] = cmds.skinCluster(skName, q=True, maximumInfluences=True)
        weightData[geoName][skName]['weights'] = {}

        ## Fetch the weights now
        weightPlug = findPlugOnNode(skinClusterMObjH, 'weightList')
        weightCount = weightPlug.getExistingArrayAttributeIndices()
        for x in range(len(weightCount)):
            # .weightList[x]
            p = weightPlug.elementByLogicalIndex(weightCount[x])
            # .weights
            c = p.child(0)

            ## Now fetch the list of idx numbers that should relate to the inf [23, 66, 99]
            w = c.getExistingArrayAttributeIndices()

            ## For each idx we're going to build a tuple (idx, infName, weightVal)
            weightList = list()
            for i in range(len(w)):
                childPlug = c.elementByLogicalIndex(w[i])
                weightValue = getPlugValue(childPlug)

                if skipZeroWeights and weightValue == 0.0:
                    continue

                idx = w[i]
                weightList.append((idx, weightValue))

            weightData[geoName][skName]['weights'][str(x)] = weightList
            print("weightData["+str(geoName)+"]["+str(skName)+"][weights]["+str(x)+"]"+"= "+str(weightList))
    return weightData

weightData=_fetchSkinWeights(None,False)
#print("weightData= "+str(weightData))
_Weights=weightData[dupMesh][skincluster]["weights"][str(vert)]
print("_Weights= "+str(_Weights))


boneInt=0
        bones = pm.listConnections(str(skincluster) + ".matrix")
        boneslen=len(bones)
        for b in range(0,boneslen):
            bonename=bones[b]
            if influence in str(bonename):
                #print("found hit")
                boneInt=b


print("boneInt= "+str(boneInt))
weight=0
for _Weight in _Weights:
     #_Weight=(0, 0.8388942003250122)
     boneNum=_Weight[0]
     if(boneNum==boneInt):
           weight=_Weight[1]
print("weight= "+str(weight))

経過時間 = 00h:07m:10s om2なのに遅い。。。。
データ参照するためにfor分2個使うせいだな。
print消しても
経過時間 = 00h:04m:17s  おそい。。。

もっと早く取りたい

skincluster.getPointsAffectedByInfluence

インフルエンス骨基準で頂点のウェイトを問い合わせ回数を減らすことで早くなる。

getData = skincluster.getPointsAffectedByInfluence(InfluenceJoint)  

で取れるみたい。

詳しくはこちら。

https://www.marcuskrautwurst.com/2012/12/skinweights-savingloading-in-pymel.html


こんな感じで minidomに保存してそこからロードして使う。


import xml.dom.minidom as minidom

class SkinWeight_To_MiniDom():
    def getMeshVertex(self,objName):
        vtx = cmds.ls(objName+'.vtx[*]', fl=True)    
        return vtx
    def _showProgressDialog(self):
        self.WindowRef._showProgressDialog()#---------------------ProgressBar Starting--------------------------
        
    def setProgress(self,count):
        self.WindowRef.setProgress(count)

    def fnFindSkinCluster(self,mesh):
        skincluster = None
        for each in pm.listHistory(mesh):  
            if type(each)==pm.nodetypes.SkinCluster:   
                skincluster = each
        return skincluster
    def fnSaveSkinning(self,mesh,path): 
        print("SkinWeight_To_MiniDom---------fnSaveSkinning("+mesh+" path: "+path+")")
        # 次は、スキンウェイトの保存機能から始めましょう。
        ##skinClusterクラスには、influenceObjects()と呼ばれる関数があり、
        ##skinclusterにバインドされているすべてのジョイントを返します。
        ##次に、基本的に各ジョイントを反復処理し、各インフルエンスに対して関数getPointsAffectedByInfluence()を実行します。
        ##これにより、各頂点とその重みを含むリストが返されます。
        ##これらすべての値を適切なリストに保存し、minidomモジュールを利用してxmlファイルに保存できるようにします。
        
        ## データを収集します
        skincluster = self.fnFindSkinCluster(mesh)
     
        ## スキンクラスターが存在するかどうかを確認します
        if skincluster!=None:
            print(u"スキンクラスターがありました!")
            ## XMLxml_documentを準備します
            xml_doc = minidom.Document()
            xml_header = xml_doc.createElement("influences") 
            xml_doc.appendChild(xml_header)   

            ## ジョイントID/名前テーブルを書き出す  
            for each in skincluster.getInfluence():
                getData = skincluster.getPointsAffectedByInfluence(each)  
                tmpJoint= xml_doc.createElement(each)
                vertData = []
                if len(getData[0])>0:
                    ## すべての頂点IDを収集し、vertDataリストに保存します
                    for each in getData[0][0]:
                        vertData.append(each.currentItemIndex())
                    ## 次に、vertDataリストを「vertices」属性値として保存します
                    tmpJoint.setAttribute("vertices",str(vertData))
                    tmpJoint.setAttribute("weights",str(getData[1]))
                    xml_header.appendChild(tmpJoint)
         
            ## Save XML
            file = open(path, 'w')
            file.write(xml_doc.toprettyxml())
            pm.warning("Saved '%s.skinning' to '%s'"%(mesh,path))
        else:
            print(u"スキンクラスターがありません!")
            pm.warning('No skincluster connected to %s'%mesh)
 

    def fnLoadSkinning(self,mesh,path):  
        
        ##次に、いくつかの変数を使用して関数を設定し、メッシュがすでにスキンされている場合は、そのスキンクラスターを削除するようにします。
        ##後で新しいジョイントを作成し、スキンウェイトファイルに基づいてすべてのジョイントを自動的に追加します。
        
        ## 変数
        jointData = []
        skinData = []

        ### メッシュがすでにスキンされている場合は、メッシュをクリーンアップします
        skincluster = fnFindSkinCluster(mesh)
        if skincluster!=None:
            pm.runtime.DetachSkin(skincluster)
        
        ## 次に、xmlファイルを解析して、すべてのデータを整理しましょう。カスタムクラスcSkinningに基づいてオブジェクトを作成し、
        ## skinDataというリストに保存します。
        
        
        ## ドキュメントを解析し、XML形式に変換して、メモリにロードします
        xml_doc = minidom.parse(path)
        xml_doc.toxml()
         
        ## ルートノードを取得
        joints = xml_doc.childNodes[0].childNodes
    
        ## すべてのデータを収集し、cSkinningオブジェクトをskinDataリストに保存します
        for joint in joints:
            if joint.localName!=None:
                vertices = []
                weights = []
                jointData.append(joint.localName)   
                vertices = eval(joint.attributes.item(1).nodeValue)
                weights = eval(joint.attributes.item(0).nodeValue)
                skinData.append(cSkinning(vertices,joint.localName,weights))
        
        ##skinDataからアイテムをPrintすると、
        ##item.id、item.influence、またはitem.weightsを印刷すると、そのクラスに格納されているデータを確認できます。
        ##インフルエンスとウェイトはコヒーレントテーブルであるため、.weightsのインデックス15はインフルエンス15のウェイトを表すことがわかります。
        ##次に、スキンクラスターを作成し、すべてのジョイントを追加します。ウェイト属性を適用する前に、
        ##normalizeWeights属性を0に設定する必要があります。そうしないと、Mayaはウェイトを追加するたびにスキンウェイトを正規化します。
        ##また、skinPercentを使用してスキンをバインドする場合は、マヤの標準スキンウェイトを削除する必要があります。

        
        skincluster = pm.animation.skinCluster(mesh,jointData)
        skincluster.setNormalizeWeights(0)
        pm.skinPercent(skincluster, mesh, nrm=False, prw=100)
        
        ##ウェイトを適用する前に、最後に行う必要があることが1つあります。MayaはIDに基づいてWeightを保存します。新しいスキンクラスターを追加したため、
        ##これらのIDは古いIDと一致しなくなりました。ここでのこのコードは、skinDataリストでそれらを更新するだけです。
        
        tmpJoint = []
        for storedJoint in jointData:
            for skinnedJoint in skincluster.influenceObjects():
                if skinnedJoint==storedJoint:
                    tmpJoint.append(skincluster.indexForInfluenceObject(storedJoint))
             
        for each in range(0,len(skinData)):  
            skinData[each].influence = tmpJoint[each]
        
        ##素晴らしい、今ここに楽しい部分があります。setAttrを使用してスキンウェイトを追加しましょう。それが終わったら、スキンウェイトの正規化を再度オンにすることを忘れないでください。
        
        for each in range(0,len(skinData)):  
            for vertex in range(0,len(skinData[each].id)):
                vert = skinData[each].id[vertex]
                weights = skinData[each].weights[vertex]   
                pm.setAttr('%s.weightList[%s].weights[%s]'%(skincluster,vert,skinData[each].influence),weights)
                
        ##約30kの頂点を持つメッシュの場合、約3秒かかりました。かなり許容できる約6秒の読み込み。スクリプトはMaya2012でテストされ、
        ##Maya 2013でこれを機能させるのに問題があると聞きましたが、残念ながらまだ調査する時間がありませんでした。聞いてくれてありがとう。
        
        #--------------------------------------------------

class cSkinning(): 
    def __init__(self,id,infl,weights):
        
        ##これでデータを保存できますが、ロードすることもできます。これはもう少し苦痛であることが判明しました。
        ##skinPercentを使用すると、スキンウェイトの設定が非常に遅くなる可能性があります。したがって、この場合、 
        ##setAttrを使用して値を直接設定しました。これはかなり高速です。
        ##少しずつ見ていきましょう。まず、データを効率的に保存するためのクラスを設定します。
    
        self.id = id  
        self.influence = infl
        self.weights = weights



SkinWeight_To_MiniDomClass=SkinWeight_To_MiniDom()
BaseMesh="Mesh"
#cmds.select(BaseMesh)
SkinWeight_To_MiniDomClass.fnSaveSkinning(BaseMesh,"baseMesh_SkinWeight_"+BaseMesh+".txt")

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です