Pythonスクリプトで行いたい場合のお話。
ちょっと検索すればgetClosestPointを使うのが良いとすぐに分かります。
が、そのためにはMaya Python APIを使わなければいけません。
関連したサンプルコードも検索すればいくつか出てきます。
djx blog » get closest vertex in maya using python
【OpenMaya】ClosestPointは大事! | リグログ!
【Maya Python API2.0】 選択した頂点の反対側の最近接頂点を検索し、左右対称にする。
ある1つのメッシュに対して、複数の点でそれぞれ、メッシュの最近点を取得したいという場合があるので、今回はクラスを作ってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import maya.api.OpenMaya as om import numpy as np class ClosestVertexOnMesh: def __init__(self, target_polygon_name): self.closest_point = None self.closest_face_ID = None self.closest_vertex_ID = None self.closest_vertex_coordinate = None self.closest_distance = float('inf') selection_list = om.MSelectionList() selection_list.add(target_polygon_name) self.DAG_path = selection_list.getDagPath(0) # self.DAG_path_to_shape = self.DAG_path.extendToShape() self.target_shape = om.MFnMesh(self.DAG_path) selection_list.clear() def __get_vertices_id_and_Mcoordinate_from_face_id(self, face_ID): mesh_face_iterator = om.MItMeshPolygon(self.DAG_path) mesh_face_iterator.setIndex(face_ID) face_vertices_ID = mesh_face_iterator.getVertices() face_vertices_Mcoordinate = mesh_face_iterator.getPoints(om.MSpace.kWorld) ID_to_Mcoordinate = {} i = 0 for vertex_ID in face_vertices_ID: ID_to_Mcoordinate[vertex_ID] = face_vertices_Mcoordinate[i] i += 1 return ID_to_Mcoordinate def calculate(self, target_coordinate): target_MPoint = om.MPoint(target_coordinate[0], target_coordinate[1], target_coordinate[2]) closest_MPoint, self.closest_face_ID = self.target_shape.getClosestPoint(target_MPoint, om.MSpace.kWorld) self.closest_point = np.array(list(closest_MPoint)[:3]) candidate_vertices_ID_and_Mcoordinate = self.__get_vertices_id_and_Mcoordinate_from_face_id(self.closest_face_ID) for id, Mcoordinate in candidate_vertices_ID_and_Mcoordinate.items(): distance = target_MPoint.distanceTo(Mcoordinate) if distance < self.closest_distance: self.closest_distance = distance self.closest_vertex_ID = id self.closest_vertex_coordinate = np.array(list(Mcoordinate)[:3]) |
getClosestPointを使うためには、まずMFnMeshというクラスにメッシュを指定してあげる必要があります。
target_shape = om.MFnMesh('pSphere1Shape')
と出来たら簡単なのですが、APIを使う場合は残念ながらそう簡単ではありません。
OpenMaya.MFnMesh Class Referenceを見てみると、MDagPath型かMObject型で指定しろと書かれています。なんだそれ?となるわけですが、1つ1つ丁寧に遡っていけば解決します。
13 14 15 16 17 18 19 |
selection_list = om.MSelectionList() selection_list.add(target_polygon_name) self.DAG_path = selection_list.getDagPath(0) # self.DAG_path_to_shape = self.DAG_path.extendToShape() self.target_shape = om.MFnMesh(self.DAG_path) |
これで下準備してやります。
selection_list = om.MSelectionList(target_polygon_name)
と書きたいところですが、MSelectionListは引数なしのコンストラクタかコピーコンストラクタしか無いとのことで(OpenMaya.MSelectionList Class Reference)、2行に分けて書きます。適宜APIのReferenceを見ながらやると分かりやすいです。
MDagPathはこのようにMSelectionListを通して得るのが良いようです。
なお、これだとTransformノードのMDagPathが取得され、meshノード(shapeノード)にはならず、
第68回:AttributeとPlugについて語る | 読んで触ってよくわかる!Mayaを使いこなす為のAtoZ | AREA JAPAN
を読むと
.extendToShape()
でmeshノードのパスを取得出来ると書かれているのですが、Transformノードへのパスでも問題なく出来ました。
座標の指定にはMPointという型を使う必要があるそうで、
36 37 38 |
target_MPoint = om.MPoint(target_coordinate[0], target_coordinate[1], target_coordinate[2]) closest_MPoint, self.closest_face_ID = self.target_shape.getClosestPoint(target_MPoint, om.MSpace.kWorld) self.closest_point = np.array(list(closest_MPoint)[:3]) |
でtarget_coordinateをMPoint型に変換してから、getClosestPointを使っています。
API Referenceには
getClosestPoint(MPoint, space=MSpace.kObject) -> (MPoint, int)
Returns a tuple containing the closest point on the mesh to the
given point and the ID of the face in which that closest point lies.
と書かれているので、その通り書けばOKです。MSpace.kObjectというのもAPI Referenceを読めばわかります。どの座標系を使うかを指定するための定数で、ワールド座標が欲しければom.MSpace.kWorld(ただの定数値)と指定すれば良いです。
最後の
self.closest_point = np.array(list(closest_MPoint)[:3])
は、MPoint型をNumPy用に変換しているだけです。[:3]となっているのは、座標値としてx, y, z, wの4つが返ってくるからです。wはアフィン変換用の1なのだと思うのでが、自信がありません。
いずれにしても必要なのはwを除いた最初の3つです。
ここで注意すべきは、最初、getClosestPointは一番近い頂点の座標を返してくるものだと思っていたのですが、頂点ではなく、メッシュ上で本当に一番近い点が返ってきます。
それに加えて、その点が乗っているポリゴンのindexも取得出来ます(一番近い点がちょうど頂点にぴったり一致した場合には複数のポリゴンが該当する気がしますが、そんなことは滅多にないのだと思います)。
なので、最も近い頂点を取得したければ、ポリゴンのindexから、そのポリゴンを生成している頂点を取得して、長さを測れば良いわけです。
頂点の取得処理を
__get_vertices_id_and_Mcoordinate_from_face_id
にまとめました。
イテレータを使います。
5 つの基本ポリゴン API クラスとして日本語ヘルプもあるくらい、基本の基本だそうです。
24 25 26 27 |
mesh_face_iterator = om.MItMeshPolygon(self.DAG_path) mesh_face_iterator.setIndex(face_ID) face_vertices_ID = mesh_face_iterator.getVertices() face_vertices_Mcoordinate = mesh_face_iterator.getPoints(om.MSpace.kWorld) |
イテレータの初期化にはここでもMDagPath型でメッシュを指定する必要があります。
setIndexでポリゴンのIDを指定してgetVerticesで頂点のIDを、getPointsで頂点の座標を取得出来ます。
頂点座標の取得時には、どの座標系を使うかを指定する必要があり、再びom.MSpace.kWorldが出てきます。
Maya Python APIは最初はなかなか取っつきにくいイメージがありますが、丁寧に辿っていけばきちんと理解しながら書くことが出来ますし、実行速度がめちゃくちゃ速いです。
4,5000ポリゴンのメッシュに対してClosestVertexOnMesh.calculateを1,000回繰り返してみても5秒程度で終わりました。
※本記事内容は、国立研究開発法人 日本医療研究開発機構(AMED)の平成29年度 「未来医療を実現する医療機器・システム研究開発事業『術中の迅速な判断・決定を支援するための診断支援機器・システム開発』」採択課題である「術前と術中をつなぐスマート手術ガイドソフトウェアの開発」(代表機関名:東京大学、研究開発代表者名:齊藤延人)に、東京大学大学院情報理工学系研究科の学術支援専門職員として参画している瀬尾拡史が、研究開発として行っているものやその成果を一部含んでいます。