作業中に検出した同じ名前のノードを自動的にリネームする

この記事は[Maya Advent Calendar 2019](https://qiita.com/advent-calendar/2019/maya)の15日目の記事になります。

先日の[zebraed](https://qiita.com/zebraed)氏の[pyside:MEventMessageを使ったcallback](https://qiita.com/zebraed/items/5b01b799d5f37a14b6d3)はご覧になられましたでしょうか？

OpenMayaでscriptJobと同じ機能を軽くシンプルに扱えるとの事で、自分も今後活用していこうと思います。

今回はscriptJobを使って書きますが...

- - -

# はじめに
ノード名が重複しているからと言ってMayaに致命的なエラーが発生することはありません。
しかし、melスクリプトを作成する際に正しい作法で処理を書かないと指定したオブジェクトが見つけられずにスクリプトが停止してしまう可能性があります。

- [Mayaエラー：行0：複数のオブジェクトが名前と一致します](
https://knowledge.autodesk.com/ja/search-result/caas/sfdcarticles/sfdcarticles/JPN/Maya-Error-line-0-More-than-one-object-matches-name.html)
- [Melの「More than one object matches name」の回避](http://tech-coyote.com/archives/632)

- ノード名の重複があっても正しく動作するツールであれば問題ないし、最終的にデータを綺麗に出来るならこの記事は読まなくても大丈夫だと思います。


# 何か起こる前に自動でリネーム出来てたら楽だよね

## 自動リネーム処理
### 1. リネームする

1. uuidからノードを取得する。
    - [【Maya】オブジェクト名によらないオブジェクト選択 _ UUIDを使う](https://note.com/it_ks/n/n0d15c7dee2e8?creator_urlname=it_ks)
2. ノード名に`|`(バーティカルライン)が含まれていた場合、ノード名が重複している。
3. 正規表現でベースとなるノード名を取得する。
    - 赤くマーキングされている範囲をベース名として取得する。<br>
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124610/230a18cb-1032-c8f4-6eeb-827d987a78a0.png)
4. リネームする
    - [cmds.rename](http://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/CommandsPython/rename.html)
        - 新しい名前の最後に「#」を 1 つ付けると、名前の変更コマンドは、新しい名前を固有にする番号で最後の「#」を置き換えます。

#### 出来たコードはこんな感じ
```python:
def rerename(node_uuid):
    for node in cmds.ls(node_uuid):
        if "|" in node:
            basename = re.search("^(.+)?\|(.+?)(\d+)?$", node).group(2)
            rename_name = cmds.rename(node, "{}#".format(basename))
```
[nonSameNameAlliance.py#L5-L9](https://github.com/teionn/nonSameNameAlliance/blob/a3c6b22c932fb9e82757de5c25d01838cc987d90/nonSameNameAlliance.py#L5-L9)



### 2. 手動リネーム時の処理

- NameChangedが実行されたときにリネーム処理を実行します。
    - 手動でリネームをする場合はオブジェクトを1つしか選択していないと思うので`ls`コマンドで選択しているオブジェクトを取得してUUIDに変換してリネーム処理を実行します。
    
```python
def renameEvent():
    for node_uuid in cmds.ls(cmds.ls(sl=True), uuid=True):
        rerename(node_uuid)
```

[nonSameNameAlliance.py#L11-L13](https://github.com/teionn/nonSameNameAlliance/blob/a3c6b22c932fb9e82757de5c25d01838cc987d90/nonSameNameAlliance.py#L11-L13)

### 3. 複製時の処理

- 手動で複製を行ったときは、複製されたオブジェクトに選択が移動するのでSelectionChangedが実行されたときにリネーム処理を実行します。
  - dag=Trueで階層を取得して、tr=Trueでトランスフォームノードのみ取得できるようにし、UUIDに変換してリネーム処理を実行します。

```python
def duplicateEvent():
    for node_uuid in cmds.ls(cmds.ls(sl=True, dag=True, tr=True), uuid=True):
        rerename(node_uuid)
```

[nonSameNameAlliance.py#L15-L17](https://github.com/teionn/nonSameNameAlliance/blob/a3c6b22c932fb9e82757de5c25d01838cc987d90/nonSameNameAlliance.py#L15-L17)

## スクリプトジョブに登録

```python
def main():
    try:
        if jobIds is not None:
            for jobId in jobIds:
                cmds.scriptJob(kill=jobId, force=True)
    except:pass
        
    jobIds = []
    jobIds.append(cmds.scriptJob(event=["NameChanged", renameEvent], protected=True))
    jobIds.append(cmds.scriptJob(event=["SelectionChanged", duplicateEvent], protected=True))
```
- スクリプトジョブに処理を登録すると、条件に合ったタイミングで登録された処理を実行してくれます。
- 今回はrenameEventとSelectionChangedそれぞれに処理を登録しています。

[nonSameNameAlliance.py#L19-L28](https://github.com/teionn/nonSameNameAlliance/blob/a3c6b22c932fb9e82757de5c25d01838cc987d90/nonSameNameAlliance.py#L19-L28)

### Maya起動時に自動でスクリプトジョブを実行する

```python:userSetup.py
import nonSameNameAlliance;nonSameNameAlliance.main()
```
nonSameNameAlliance.pyをMayaのScriptsフォルダ内に配置してuserSetup.pyに起動用の処理を追記します。
これでMaya起動時にscriptJobに処理が登録されて自動リネームされるようになります。

### ダウンロード
今回のソースコードはこちら
https://github.com/teionn/nonSameNameAlliance

## 注意点

1. シーン内で操作をした時に自動的にノードがリネームされてしまうので、基本的にモデラー向けの内容かと思います。
 

1. トランスフォームノードのみに実行されるようになっています。
  - 中間シェイプノードなどの全てのノードで効果を得たい場合は`tr=True`を消してください。

1. このスクリプトは大文字/小文字の判定は行いません。
Mayaは大文字/小文字違いのオブジェクトを同じ階層であっても保持することが出来てしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124610/c234139b-0912-81b6-3c87-fb26a966c702.png)

この状態のままUnityにアセットを持っていくと、オブジェクト`object_a`と`object_A`があった場合どちらかのオブジェクトが参照出来なくなってしまうらしいです。


- - -

明日の記事は[rateionn](https://qiita.com/rateionn)さんによる[ちゃんと Remove: Unknown Plugins してますか?]()というお話です。

あれ… 書くの自分やん… まだ記事できてないんですけど…

間に合ったらスクリプトを無償公開する予定なのでじゃんじゃんDLしてただけると嬉しいです。