リアルタイム系の3DCG制作に関するノウハウや実践的なTipsを共有するWiki形式のWEBサイトです。


kmjCopyWeightMatchName

名前の一致するメッシュにウェイトコピーを行うスクリプトです
複数メッシュをまとめて処理できます
バインド先が違うジョイント階層でも構造が同じであればコピーできます

用途

  • ウェイトコピーをしたいがメッシュが複数あって面倒な時

更新履歴

20191204 公開

注意事項

  • ターゲット側のメッシュも予めバインドしておく必要があります
  • デフォルト機能のウェイトコピーを使用しているためコピーの精度等はデフォルト機能と同じです
  • スキンクラスターが無い、ターゲットが見つからない場合はスクリプトエディターに出力されます

ダウンロード

Gumroadからもダウンロードできるようになりました!
KMJ COPYWEIGHT MATCH NAME -Gumroadページ-

実行方法

スクリプト本文をkmjCopyWeightMatchName.pyという名前で保存し、スクリプトフォルダにコピー、
スクリプトエディターに下記を入力して実行します。

import kmjCopyWeightMatchName
kmjCopyWeightMatchName.main()

スクリプト本文

# -*- coding: utf-8 -*-

###################################################
"""
■概要
kmjCopyWeightMatchName
・名前の一致するメッシュにウェイトコピーを行うスクリプトです
・複数メッシュをまとめて処理できます
・バインド先が違うジョイント階層でも構造が同じであればコピーできます

■実行方法
kmjCopyWeightMatchName.pyファイルをスクリプトフォルダにコピーして、スクリプトエディターに下記を入力して実行します。
###ここから
import kmjCopyWeightMatchName
kmjCopyWeightMatchName.main()
###ここまで

■注意事項
・ターゲット側のメッシュも予めバインドしておく必要があります
・デフォルト機能のウェイトコピーを使用しているためコピーの精度等はデフォルト機能と同じです
・スキンクラスターが無い、ターゲットが見つからない場合はスクリプトエディターに出力されます

■更新履歴
・2019/11/26 作成

■製作者
作成者:kmj
URL:https://seesaawiki.jp/realtime3dcg/

"""
###################################################

from functools import partial
import maya.cmds as cmds

class KMJ_CopyWeightMatchName(object):
    def __init__(self):
        self.window = 'kmjCopyWeightMatchName'
        self.title = 'kmjCopyWeightMatchName'
        self.size = (640, 680)
        self.copySkinWeightsInfluenceAssociationOption1_value = self.get_option_value('copySkinWeightsInfluenceAssociationOption1')
        self.copySkinWeightsInfluenceAssociationOption2_value = self.get_option_value('copySkinWeightsInfluenceAssociationOption2')
        self.copySkinWeightsInfluenceAssociationOption3_value = self.get_option_value('copySkinWeightsInfluenceAssociationOption3')
        self.copySkinWeightsNormalize_value = self.get_option_value('copySkinWeightsNormalize')
        self.copySkinWeightsSurfaceAssociationOption_value = self.get_option_value('copySkinWeightsSurfaceAssociationOption')
        self.sa_val_list = ["", "closestPoint", "rayCast", "closestComponent", "UV space"]

    def create(self):
        if cmds.window('kmjCopyWeightMatchName', exists=True):
            cmds.deleteUI('kmjCopyWeightMatchName', window=True)
        self.window = cmds.window(
            self.window,
            t=self.title,
            widthHeight=self.size
        )
        self.layout()
        cmds.showWindow()

    # レイアウト
    def layout(self):
        self.formLayout01 = cmds.formLayout(numberOfDivisions=100)
        # ソース側
        self.sourceMeshLabel = cmds.text(l='Source meshes:', align='left')
        self.sourceMeshList = cmds.textScrollList(
            numberOfRows=8,
            allowMultiSelection=True
        )
        cmds.textScrollList(
            self.sourceMeshList,
            edit=True,
            deleteKeyCommand=partial(self.removeItemCmd, self.sourceMeshList),
            selectCommand=partial(self.selectItem, self.sourceMeshList)   
        )
        self.buttonAddSource = cmds.button(l='Add Source Meshes', c=partial(self.addMeshesCmd, self.sourceMeshList))
        self.buttonSelectSource = cmds.button(l='Find Items by Selection', c=partial(self.selectItemByMeshCmd, self.sourceMeshList))
        self.buttonRemoveSource = cmds.button(l='Remove from List', c=partial(self.removeItemCmd, self.sourceMeshList))
        self.buttonClearSource = cmds.button(l='Clear', c=partial(self.clearListCmd,self.sourceMeshList))

        # ターゲット側
        self.targetMeshLabel = cmds.text(l='Target meshes:', align='left')
        self.targetMeshList = cmds.textScrollList(
            numberOfRows=8,
            allowMultiSelection=True
        )
        cmds.textScrollList(
            self.targetMeshList,
            edit=True,
            deleteKeyCommand=partial(self.removeItemCmd, self.targetMeshList),
            selectCommand=partial(self.selectItem, self.targetMeshList)   
        )
        self.buttonAddTarget = cmds.button(l='Add Target Meshes', c=partial(self.addMeshesCmd, self.targetMeshList))
        self.buttonSelectTarget = cmds.button(l='Find Items by Selection', c=partial(self.selectItemByMeshCmd, self.targetMeshList))
        self.buttonRemoveTarget = cmds.button(l='Remove from List', c=partial(self.removeItemCmd, self.targetMeshList))
        self.buttonClearTarget = cmds.button(l='Clear', c=partial(self.clearListCmd,self.targetMeshList))

        # Surface Associationラジオボタン
        self.columnLayout01 = cmds.columnLayout()
        self.radioButton01 = cmds.radioButtonGrp( label='Surface Association: ', labelArray4=['Closest point on surface', 'Ray cast', 'Closest component', 'UV space'], numberOfRadioButtons=4 ,vr=True)
        cmds.radioButtonGrp(self.radioButton01, e=True, sl=self.copySkinWeightsSurfaceAssociationOption_value)
        cmds.setParent('..')

        # Influence Associationプルダウン
        self.columnLayout02 = cmds.columnLayout()
        self.optionMenu01 = cmds.optionMenuGrp(l='Influence Association 1:')
        cmds.menuItem(self.optionMenu01, l='closestJoint')
        cmds.menuItem(self.optionMenu01, l='closestBone')
        cmds.menuItem(self.optionMenu01, l='oneToOne')
        cmds.menuItem(self.optionMenu01, l='label')
        cmds.menuItem(self.optionMenu01, l='name')
        cmds.optionMenuGrp(self.optionMenu01, e=True, sl=self.copySkinWeightsInfluenceAssociationOption1_value)
        self.optionMenu02 = cmds.optionMenuGrp(l='Influence Association 2:')
        cmds.menuItem(self.optionMenu02, l='None')
        cmds.menuItem(self.optionMenu02, l='closestJoint')
        cmds.menuItem(self.optionMenu02, l='closestBone')
        cmds.menuItem(self.optionMenu02, l='oneToOne')
        cmds.menuItem(self.optionMenu02, l='label')
        cmds.menuItem(self.optionMenu02, l='name')
        cmds.optionMenuGrp(self.optionMenu02, e=True, sl=self.copySkinWeightsInfluenceAssociationOption2_value)
        self.optionMenu03 = cmds.optionMenuGrp(l='Influence Association 3:')
        cmds.menuItem(self.optionMenu03, l='None')
        cmds.menuItem(self.optionMenu03, l='closestJoint')
        cmds.menuItem(self.optionMenu03, l='closestBone')
        cmds.menuItem(self.optionMenu03, l='oneToOne')
        cmds.menuItem(self.optionMenu03, l='label')
        cmds.menuItem(self.optionMenu03, l='name')
        cmds.optionMenuGrp(self.optionMenu03, e=True, sl=self.copySkinWeightsInfluenceAssociationOption3_value)
        cmds.setParent('..')

        # Normalizeチェックボックス       
        self.columnLayout03 = cmds.columnLayout()
        self.checkBox01 = cmds.checkBox(label='Normalize')
        cmds.checkBox(self.checkBox01, e=True, v=self.copySkinWeightsNormalize_value)
        cmds.setParent('..')
        
        # コピー実行ボタン
        self.copyButton = cmds.button(label=u'Copy', c=self.duplicateCopyWeight)
        
        # 閉じるボタン
        self.closeButton = cmds.button(label=u'Close', c=('cmds.deleteUI("' + self.window + '")'))

        # 配置
        cmds.formLayout(self.formLayout01, edit=True,\
            attachPosition=(
                (self.sourceMeshLabel, 'top', 0, 0),\
                (self.sourceMeshLabel, 'left', 5, 0),\
                (self.sourceMeshLabel, 'bottom', 0, 5),\
                (self.sourceMeshLabel, 'right', 5, 50),\
                
                (self.sourceMeshList, 'top', 0, 5),\
                (self.sourceMeshList, 'left', 5, 0),\
                (self.sourceMeshList, 'bottom', 5, 60),\
                (self.sourceMeshList, 'right', 5, 50),\

                (self.buttonAddSource, 'top', 1, 60),\
                (self.buttonAddSource, 'left', 5, 0),\
                (self.buttonAddSource, 'bottom', 1, 65),\
                (self.buttonAddSource, 'right', 5, 50),\

                (self.buttonSelectSource, 'top', 1, 65),\
                (self.buttonSelectSource, 'left', 5, 0),\
                (self.buttonSelectSource, 'bottom', 1, 70),\
                (self.buttonSelectSource, 'right', 5, 50),\

                (self.buttonRemoveSource, 'top', 1, 70),\
                (self.buttonRemoveSource, 'left', 5, 0),\
                (self.buttonRemoveSource, 'bottom', 1, 75),\
                (self.buttonRemoveSource, 'right', 5, 50),\

                (self.buttonClearSource, 'top', 1, 75),\
                (self.buttonClearSource, 'left', 5, 0),\
                (self.buttonClearSource, 'bottom', 1, 80),\
                (self.buttonClearSource, 'right', 5, 50),\
                
                (self.targetMeshLabel, 'top', 0, 0),\
                (self.targetMeshLabel, 'left', 5, 50),\
                (self.targetMeshLabel, 'bottom', 0, 5),\
                (self.targetMeshLabel, 'right', 5, 100),\
                
                (self.targetMeshList, 'top', 0, 5),\
                (self.targetMeshList, 'left', 5, 50),\
                (self.targetMeshList, 'bottom', 5, 60),\
                (self.targetMeshList, 'right', 5, 100),\

                (self.buttonAddTarget, 'top', 1, 60),\
                (self.buttonAddTarget, 'left', 5, 50),\
                (self.buttonAddTarget, 'bottom', 1, 65),\
                (self.buttonAddTarget, 'right', 5, 100),\

                (self.buttonSelectTarget, 'top', 1, 65),\
                (self.buttonSelectTarget, 'left', 5, 50),\
                (self.buttonSelectTarget, 'bottom', 1, 70),\
                (self.buttonSelectTarget, 'right', 5, 100),\

                (self.buttonRemoveTarget, 'top', 1, 70),\
                (self.buttonRemoveTarget, 'left', 5, 50),\
                (self.buttonRemoveTarget, 'bottom', 1, 75),\
                (self.buttonRemoveTarget, 'right', 5, 100),\

                (self.buttonClearTarget, 'top', 1, 75),\
                (self.buttonClearTarget, 'left', 5, 50),\
                (self.buttonClearTarget, 'bottom', 1, 80),\
                (self.buttonClearTarget, 'right', 5, 100),\
                
                (self.columnLayout01, 'top', 5, 80),\
                (self.columnLayout01, 'left', 5, 0),\
                (self.columnLayout01, 'bottom', 1, 85),\
                (self.columnLayout01, 'right', 5, 5),\
                
                (self.columnLayout02, 'top', 5, 80),\
                (self.columnLayout02, 'left', 5, 50),\
                (self.columnLayout02, 'bottom', 1, 85),\
                (self.columnLayout02, 'right', 5, 100),\
                
                (self.columnLayout03, 'top', 5, 90),\
                (self.columnLayout03, 'left', 5, 75),\
                (self.columnLayout03, 'bottom', 1, 95),\
                (self.columnLayout03, 'right', 5, 100),\

                (self.copyButton, 'top', 5, 95),\
                (self.copyButton, 'left', 30, 0),\
                (self.copyButton, 'bottom', 5, 100),\
                (self.copyButton, 'right', 30, 50),\

                (self.closeButton, 'top', 5, 95),\
                (self.closeButton, 'left', 50, 50),\
                (self.closeButton, 'bottom', 5, 100),\
                (self.closeButton, 'right', 50, 100)
            )
        )
    

    # コマンド
    # prefからウェイトコピーの設定を読み込み
    def get_option_value(self, option_name, *args):
        if cmds.optionVar(exists=option_name) == False:
            return 1    # 無ければ暫定で1を返す
        else:
            option_value = (cmds.optionVar(q=option_name))
            return option_value



    # selectCommand:選択アイテムの実体を選択
    def selectItem(self, meshListName, *args):
        selItem = cmds.textScrollList( meshListName, q=True, selectItem=True)
        cmds.select(cl=True)
        cmds.select(selItem)

    # Add Source(Target) Meshes:選択メッシュをリストに追加
    def addMeshesCmd(self, meshListName, *args):
        allItems = self.getAllItems(meshListName)
        selectMeshes = self.getSelectMeshes(meshListName)
        if allItems is not None:
            for selectMesh in selectMeshes:
                if selectMesh not in allItems:
                    cmds.textScrollList(meshListName, edit=True, append=selectMesh)
        else:
            cmds.textScrollList(meshListName, edit=True, append=selectMeshes)


    # Find Items by Selection:選択したメッシュをリスト上で選択
    def selectItemByMeshCmd(self, meshListName, *args):
        selectMeshes = self.getSelectMeshes(meshListName)
        cmds.textScrollList(meshListName, edit=True, deselectAll=True)
        cmds.textScrollList(meshListName, edit=True, selectItem=selectMeshes)

    # Clear:リストをクリア
    def clearListCmd(self, meshListName, *args):
        cmds.textScrollList(meshListName, edit=True, removeAll=True)
    
    # Remove from List:選択アイテムをリストから削除
    def removeItemCmd(self, meshListName, *args):
        selItem = cmds.textScrollList(meshListName, q=True, selectItem=True)
        if selItem is not None:
            cmds.textScrollList(meshListName, edit=True, removeItem=selItem)

    # リスト上のアイテムを全て取得
    def getAllItems(self, meshListName, *args):
        allItems = cmds.textScrollList(meshListName, q=True, allItems=True)
        return allItems

    # 選択メッシュを再帰的に取得
    def getSelectMeshes(self, meshListName, *args):
        meshes = []
        cmds.select((cmds.ls(sl=True)), hierarchy=True)
        transformNodes = cmds.ls(sl=True, long=True, type='transform')
        for transformNode in transformNodes:
            if (cmds.listRelatives(transformNode, shapes=True)) is not None:
                meshes.append(transformNode)
        return meshes

    # アンドゥインフォ設定
    def undoRecord(func):
        def wrapper(*args, **kwargs):
            cmds.undoInfo(openChunk=True)
            try:
                return func(*args, **kwargs)
            except Exception, e:
                raise
            finally:
                cmds.undoInfo(closeChunk=True)
        return wrapper


    # ウェイトコピー実行部
    @undoRecord
    def duplicateCopyWeight(self, *args):
        # UIの設定値を読み込み
        sourceMeshList = cmds.textScrollList(self.sourceMeshList, q=True, ai=True)
        targetMeshList = cmds.textScrollList(self.targetMeshList, q=True, ai=True)
        sa_val_idx = cmds.radioButtonGrp(self.radioButton01, q=True, sl=True)
        sa_val = self.sa_val_list[sa_val_idx]
        ia_val = []
        ia1_val = cmds.optionMenuGrp(self.optionMenu01, q=True, v=True)
        ia_val.append(ia1_val)
        ia2_val = cmds.optionMenuGrp(self.optionMenu02, q=True, v=True)
        if ia2_val != 'None':
            ia_val.append(ia2_val)
        ia3_val = cmds.optionMenuGrp(self.optionMenu03, q=True, v=True)
        if ia3_val != 'None':
            ia_val.append(ia3_val)
        normalize_val = cmds.checkBox(self.checkBox01, q=True, v=True)

        for targetMesh in targetMeshList:
            target_sc = self.get_skinCluster(targetMesh)
            if target_sc is not None:
                sourceMesh = self.name_matching(targetMesh, sourceMeshList)
                if sourceMesh is not None:
                    source_sc = self.get_skinCluster(sourceMesh)
                    if source_sc is not None:
                        print ("sourceMesh:" + str(sourceMesh) + ", targetMesh:" + str(targetMesh))
                        if sa_val_idx < 4:    # UV設定だった場合別処理
                            print("ss=" + str(source_sc[0]) + ", ds=" + str(target_sc[0]) + ", nr=" + str(normalize_val) + ", sa=" + str(sa_val) + ", ia=" + str(ia_val))
                            cmds.copySkinWeights(ss=source_sc[0], ds=target_sc[0], nm=True, nr=normalize_val, sa=sa_val, ia=(ia_val))
                        else:
                            sourceUVSet = self.get_currentUVSet(sourceMesh)
                            targetUVSet = self.get_currentUVSet(targetMesh)
                            print("ss=" + str(source_sc[0]) + ", ds=" + str(target_sc[0]) + ", nr=" + str(normalize_val) + ", sa=" + str(sa_val) + ", ia=" + str(ia_val))
                            cmds.copySkinWeights(ss=source_sc[0], ds=target_sc[0], nm=True, nr=normalize_val, uv=(str(sourceUVSet), str(targetUVSet)), ia=(ia_val))

                    else:
                        print(sourceMesh + " : skinCluster is not found")
                else:
                    print(targetMesh + " : source mesh is not found")
            else:
                print(targetMesh + " : skinCluster is not found")


    # UVセット取得
    def get_currentUVSet(self, obj, *args):
        currentUVSet = cmds.polyUVSet(obj, q=True, cuv=True)
        return currentUVSet


    # ヒストリからskinClusterを検索して返す
    def get_skinCluster(self, obj, *args):
        history = cmds.ls(cmds.listHistory(obj), type='skinCluster')
        if history == []:
            return None
        else:
            return history


    # ネームスペースやパスを削除した名前を取得
    def get_shortname(self, name, *args):
        name = name.rsplit("|", 1)
        name = name[-1].rsplit(":", 1)
        return name[-1]


    # ターゲットリストに同じ名前があれば返す(ひとつ目を見つけると終了)
    def name_matching(self, obj_name, searchobj_list, *args):
        obj_name_short = self.get_shortname(obj_name)
        for searchobj in searchobj_list:
            searchobj_short = self.get_shortname(searchobj)
            if obj_name_short == searchobj_short:
                return searchobj
        return None


def main():
    kmjCopyWeightMatchNameWindow = KMJ_CopyWeightMatchName()
    kmjCopyWeightMatchNameWindow.create()

ヘルプ

各項目の説明


共通部分はソース側のみ記載
Source meshesウェイトのコピー元メッシュ一覧
Add Source Meshesビューポートで選択しているメッシュを追加します
Find Items by Selectionビューポートで選択しているメッシュをリストにあればリスト上で選択します
Remove from Listリストで選択しているアイテムをリストから消去します
Clearリストをクリアします
Surface Association、Influence Association、NormalizeはデフォルトのCopy Skin Weightsと同じです。

自分でスクリプトを書きたい方は

MAYAのスクリプティングについて学ぶならこれらの書籍がおすすめです。
全部買うと高いので、目次やレビューをチェックして必要なものだけポチるのが良いと思います。

MELの本です。ちと古いですが基本であるMELから始めたい人には良いですね。
Python(cmds)の本です。ちょっと高いですかね……
PyMELの本です。PyMELの日本語本はこれ以外に無い……はず。
Python(cmds)の本です。プラグインのAmaterasu製作者の方の本です。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

※現在 ユーザーID保持者 のみがコメント投稿可能な設定になっています。

Wiki内検索

メニュー

Maya

管理人


管理者:kmj
Twitter:@kmj3dcg
ゲームグラフィックに携わり十数年、現在フリーランスのモデラーとして活動しています。
専門分野:モデリング、テクスチャ、ウェイト(キャラモデルがメインのゲームグラフィッカーです)
お問い合わせはこちらまでお願いいたします。

Amazonアソシエイト

皆様のご協力感謝しておりますm(_ _)m























管理人/副管理人のみ編集できます