余分なGroupを作らずにフェースを切り離したい

こんにちは!Rhinoです。
運動不足が結構響いてきています。少々疲れ気味ですね( ノД`)シクシク…
皆さんは平気ですか?

今日はフェースを切り離すMayaのExtract Facesについて書いてみました。
モデリングしている際によく使う機能の1つだと思いますが、使った後にGroup化されてしまうのが気になりませんか?
こうなってしまうのはちょっと使いづらいですね。

ゲーム制作などでは余分なノードなどはエラーの原因になるためにもクリーンアップするのですが、Groupを逐一解除していくのも手間です。
そこで、Group化されないフェースの切り離しについて考えてみました。

# -*- coding: utf-8 -*-
import maya.cmds as cmds

def getAllFaces( obj ):
	result = []
	faces = cmds.polyListComponentConversion( obj, tf=True )
	result = cmds.ls( faces, fl=True )
	return result

selection = cmds.ls( sl=True, fl=True)
selFaces = cmds.filterExpand( selection, sm=34 )
objs = list( set( [ f.split(".")[0] for f in selFaces ] ) )

invertFaces = []
for o in objs:
	duplicatedObj = cmds.duplicate( o, n=o+"_detached" )[0]
	duplicatedObjAllFaces = getAllFaces( duplicatedObj )
	duplicatedObjFaces = [ duplicatedObj  + "." + f.split(".")[1] for f in selFaces if f.split(".")[0] == o ] 
	invertFaces.extend( list( set(duplicatedObjAllFaces) - set(duplicatedObjFaces) ) )
	
cmds.delete( selFaces )
cmds.delete( invertFaces )

解説

フェースを切り離すということは?

フェースを切り離すということはどういうことでしょうか?
元のオブジェクトを複製して、一方からは選択したフェースを削除、もう一方からは選択を反転したフェースを削除すれば切り離したものと同じ結果になると思いませんか?
図解してみますとこのようになるかと思います。

選択しているフェースのオブジェクトを取得する

まずはオブジェクトを複製する必要がありますね。
ですが、今選択ているのはフェースだと思います。
選択しているフェースを持ったオブジェクトを取得する必要がありますね。
そのオブジェクトを複製するというフローへと続いていくわけです。
選択しているフェースの取得はlsfilterExpandを使います。定番ですね。
ポリゴンフェースのフラグはsm=34となります。

selection = cmds.ls( sl=True, fl=True)
selFaces = cmds.filterExpand( selection, sm=34 )

ここで選択しているフェースがスクリプトではどういう表記になっているのかScript Editorでみましょう。

select -r は選択するための関数です。
次のpolySurface2.f[0]が選択しているフェースになります。
これはpolySurface2というオブジェクトの0番目のフェースという意味です。
少しややこしいのですが、固有のインデックスではなくフェースを削除したりすると他のフェースが0番になったりすることには注意が必要です。

これを踏まえてみると、polySurface2.f[0]を.(ピリオド)で切り離して得られるリストの最初の値がオブジェクトになりますよね。
文字列を切り離してリストに格納するにはsplit関数を使います。
フェースは複数選択されている場合がありますので、
polySurface2.f[0], polySurface2.f[1], polySurface2.f[2],…
などとオブジェクト名が重複してしまうこともあるでしょう。
重複を削除するにはset関数を使いました。
さらに内包表記というPythonの表記方法を使って簡略化して書いたものが次の1行です。

objs = list( set( [ f.split(".")[0] for f in selFaces ] ) )

オブジェクトを複製する

取得したオブジェクトのリストをForループで取り出しながら複製していきます。
複製にはduplicate関数を使いました。
nフラグに文字列を入力することで複製したオブジェクトに名前を付けることができます。
duplicate関数で複製したオブジェクトはリストで返ってくるのですが、今回はオブジェクトそれぞれを1つずづしか複製しませんので、最後に[0]とつけて複製したオブジェクトのリストの要素を取り出しています。

for o in objs:
	duplicatedObj = cmds.duplicate( o, n=o+"_detached" )[0]

選択しているフェースを反転する

複製したオブジェクト側では選択しているフェースを反転してあげる必要があります。
いくつかの方法があるかと思いますが、単純に引き算で考えてみました。
オブジェクトの全てのフェースからそのオブジェクトで選択しているフェースを引いてあげれば、反転したフェースが得られるというわけです。

$$選択を反転したフェース \quad = \quad 全てのフェース \quad – \quad 選択しているフェース$$
	duplicatedObjAllFaces = getAllFaces( duplicatedObj )

オブジェクトの全てのフェースを取得するためにgetAllFacesという関数を作成しました。これは与えられたオブジェクトからコンポーネントへ変換するためのpolyListComponentConversion関数を使って、そのオブジェクトが持っているすべてのフェースのリストを取得するという独自の関数です。
polyListComponentConversionを使うだけですと、
polySurface1.f[*]
と複数のフェースがワイルドカードでまとめられた表記になっていますので、
ls関数のflフラグを使ってばらしています。

def getAllFaces( obj ):
	result = []
	faces = cmds.polyListComponentConversion( obj, tf=True )
	result = cmds.ls( faces, fl=True )
	return result

もともと選択しているフェースのオブジェクトと、新しく複製したオブジェクトではオブジェクト名が異なりますので、付け替えてあげる必要があります。
その為、ここでも内包表記を使いました。

	duplicatedObjFaces = [ duplicatedObj  + "." + f.split(".")[1] for f in selFaces if f.split(".")[0] == o ] 

複製したオブジェクト側の全てのフェースから、もともと選択していたフェースの部分を取り除いたもの。
つまりは反転させたフェースのリストを予め作成しておいたinvertFacesという変数に格納します。

リスト同士の直接の引き算はできません。
そこで一度set型に変換し集合演算を行った後で、再びlist型に戻すことでリスト同士の引き算が可能になります。

	invertFaces.extend( list( set(duplicatedObjAllFaces) - set(duplicatedObjFaces) ) )

フェースを削除する

最後に不要なフェースを削除します。
この時Forループの中で逐一削除するのではなく、一度削除する対象をリストなどにすべて格納して、最後にまとめて削除するのがお勧めです。
その為、Forループの外で削除を行っています。

cmds.delete( selFaces )
cmds.delete( invertFaces )

注意点

今回のスクリプトは単純な内容となっております。
その為オブジェクト同士をペアレントした場合の切り離しなど、イレギュラーな場合では正しく動作しない点はご了承ください。

最後に

如何でしたでしょうか?
数年前に同じようなものをMelで書いたことがあったのですが、その時は凄く長いコードになったことを覚えています。苦労しながら書いた気がしますね( ノД`)シクシク…
そういう経験があったので、Pythonに移行して内包表記などで簡潔に書けるのを知った時の感動は忘れられないですね!
僕みたいなデザイナーでも書けるPythonという言語はとっつきやすいので、ぜひ皆さんも挑戦してみて下さい。
では、また!

コメント

タイトルとURLをコピーしました