MObjectの不穏な挙動
MayaPythonAPI2.0にて、Maya2019で確認
取得済みのMObjectを、新規シーンを作成してから参照すると別のオブジェクトが参照されてしまう模様
内部の参照インデックスなどの関係だと思う
しかもとれるものが毎回変わってしまうし、たまにMayaも落ちる
以下その挙動再現用のスクリプト
下記スクリプトを順に実行していくとpolycubeを参照したはずのMObjectはperspを参照したりtopを参照したりしている
しかも一度に実行すると正常にpolyCube1が取れたりする
(多分だけど、内部インデックスみたいな参照情報が更新される前に取得できてしまう)
シーンの状態によっては強制終了しかねないスクリプトなので実行は自己責任でお願いします。
from maya.api import OpenMaya as om2 from maya import cmds # 適当なオブジェクトを作成 temp_obj= cmds.polyCube() # dependency node として取得 temp_sel = om2.MGlobal.getSelectionListByName(temp_obj[0]) dependency = temp_sel.getDependNode(0) mfn_dependency = om2.MFnDependencyNode(dependency) # 名前を取得 print(mfn_dependency.name()) # 新規シーンを作成 cmds.file(new=True, force=True) # 取得済みだった MObjectから名前を取得(シーンが立ち上がり切ってから実行する) print(mfn_dependency.name())
実行結果(試すたびに変わる)
pCube1 top
怖いね
Maya2018以降のAPI2.0ヘルプ見づらいので2017使ってるけど検索が分かりづらい
Maya2018以降のAPI2.0のヘルプページがヘッダーでかすぎて見づらいので、
調べたい内容があるときは2017のAPIドキュメントみている。
2017のドキュメントの見方とか、今更な話題だと思うけど
みやすさの関係でまだ全然使うので一応。
2020のドキュメントを許容できる人はみなくてもいい内容です。
MayaPythonAPI2.0ドキュメント2017 help.autodesk.com
MayaPythonAPI2.0ドキュメント2018 help.autodesk.com
MayaPythonAPI2.0ドキュメント2019 help.autodesk.com
MayaPythonAPI2.0ドキュメント2020 help.autodesk.com
Maya2018以降からヘッダーでかくなっていってる問題
みづらい。ヘッダーに関してはスマホからの閲覧を意識しているんだろうか。
サイドバー形式だった2017から2018→2019と
ヘッダーがでかくなり2020でちょっと縮んでいる。
一方で、Maya2017以前のドキュメントだと検索方法が分かりづらい。
2018以降だとその非常に大きいヘッダー内にAPI2.0ドキュメント内の検索があるのだが、
2017以前はデフォルトだとドキュメント全体の検索になっている。
巨大なヘッダーが好ましくない人は、
機能を探すだけなら2017以前で検索しても良いと思う。
(あんまり更新されてないんで)
2017のAPIドキュメントでAPI2.0のサイト内のみから検索する
2017ではハンバーガーメニューをクリックすることで格納されている検索ウィンドウを表示できる
そのまま検索するとMayaのドキュメント全部から検索される仕様
(と思っていたので自分は長いこと検索機能使ってなかった)
矢印をクリックして項目を変更
まだAPI2.0ドキュメントのみからの検索になっていない
さらにその横にフィルタという項目があるのでPythonに変更
これでAPI2.0のドキュメントからの検索になる。
2017で検索わかりづらいのも2020までのドキュメントが見づらいのもUI問題。
反面教師にしたい。
flipメモ lifespan
fluid source で作成した flip solverはデフォルトでlifespanの設定が出来ない
flip solver / particle motion / behavior
- Age particles
寿命を設定 - reap particles
そもそもこれをオンにしないとkillできないっぽい
volume source / particles
- life expectancy
寿命を設定
flipメモ lifespan
fluid source で作成した flip solverはデフォルトでlifespanの設定が出来ない
flip solver / particle motion / behavior
- Age particles
寿命を設定 - reap particles
そもそもこれをオンにしないとkillできないっぽい
volume source / particles
- life expectancy 寿命を設定
flipメモ lifespan
fluid source で作成した flip solverはデフォルトでlifespanの設定が出来ない
flip solver / particle motion / behavior - Age particles
- reap particles :そもそもこれをオンにしないとkillできないっぽい
volume source / particles - life expectancy :
Maya上でRayを飛ばして衝突点を取得する
Houdiniでやれ
リファレンス
MayaPythonAPI2.0 MFnMesh
MayaPythonAPI2.0 MItMeshVertex
モデルの用意
サンプル用の適当なメッシュを用意している
# -*- coding: utf-8 -*- from __future__ import unicode_literals from __future__ import print_function from __future__ import absolute_import from maya import cmds from maya.api import OpenMaya as om2 def create_bridge(): """ それっぽいモデルの作成 :return :[piers_names], plank_name :rtype: list[list[str],str] """ piers_num = 20 piers_radius = 0.2 piers_height = 2 plank_margin = 1 y_offset = 3 piers = [] for i in [-1, 1]: for j in range(piers_num): z_pos = (float(j) / (piers_num - 1) - 0.5) * piers_num created_cylinder = cmds.polyCylinder(radius=piers_radius, height=piers_height) cmds.xform(created_cylinder, translation=(i, piers_height / 2 + y_offset, z_pos)) piers.append(created_cylinder) plank = cmds.polyCube(height=0.5, depth=piers_num + plank_margin, width=2 + plank_margin) cmds.xform(plank, translation=(0, piers_height + y_offset, 0)) return [piers, plank] def create_terrain(): """ ぼこぼこした地面の作成 :return: terrain_name :rtype: str """ max_height = 3 base_plane = cmds.polyPlane(width=30, height=30, subdivisionsX=10, subdivisionsY=10) cmds.polyMoveVertex(base_plane, translateY=max_height, random=1) cmds.xform(base_plane, translation=(0, -max_height/2, 0)) cmds.polySmooth(base_plane, divisions=2) return base_plane piers, plank = create_bridge() terrain = create_terrain()
衝突点に対して頂点を移動
橋の法線方向が-Y方向のメッシュの頂点を取得し、rayで判定した位置に対して移動している
# -*- coding: utf-8 -*- from __future__ import unicode_literals from __future__ import print_function from __future__ import absolute_import from maya import cmds from maya.api import OpenMaya as om2 def get_face_from_direction_y_minus(mesh_dag): """ 指定のメッシュからYマイナス方向のフェースを構成する頂点番号を取得する :param mesh_dag: dag path from mesh node :type mesh_dag: om2.MDagPath :return : vertex indices :rtype : list[int] """ mit_mesh = om2.MItMeshPolygon(mesh_dag) got_indices = [] while True: if mit_mesh.isDone(): break current_normal = mit_mesh.getNormal() if current_normal[1] < 0: got_indices += mit_mesh.getVertices() # MItMeshPolygon の next は無意味引数を求められる(バグ) mit_mesh.next(0) return got_indices def ray_transform(ray_src, ray_dest, vertex_indices): """ rayを飛ばして接点に頂点を移動 今回はレイの方向は特に指定せず下方向で。 :type ray_src: om2.MDagPath :type ray_dest: om2.MDagPath :type vertex_indices: list[int] :return: """ ray_direction = om2.MFloatVector(0, -1, 0) src_mit_vtx = om2.MItMeshVertex(ray_src) dest_mfn_mesh = om2.MFnMesh(ray_dest) for index in vertex_indices: src_mit_vtx.setIndex(index) src_position = src_mit_vtx.position(om2.MSpace.kWorld) src_position = om2.MFloatPoint(src_position) # 被衝突メッシュ側の関数として用意されている衝突判定関数 # また、似たような関数であるallIntersectionを利用すると貫通先の複数の衝突の取得もできる hit_point, hit_ray_param, hit_face, hit_triangle, hit_bary1, hit_bary2 = dest_mfn_mesh.anyIntersection( src_position, ray_direction, om2.MSpace.kWorld, 1000, True) # APIで移動した場合は戻すが出来ないので今回のようなスクリプトで実行するだけなら大概はxformで移動 # 速度を重視する場合はcmdsプラグインとして移動前の状態を保管してctrl+z用の関数として作成する # src_mit_vtx.setPosition(om2.MPoint(hit_point), om2.MSpace.kWorld) # SPI2.0での頂点移動 cmds.xform("{}.vtx[{}]".format(str(ray_src), index), t=[v for v in hit_point][0:3], worldSpace=True) # 衝突判定の取得情報 """ print("source_point:{}".format(src_position)) # 衝突した座標 print("hit_point:{}".format(hit_point)) # 衝突した座標 print("hit_ray_param:{}".format(hit_ray_param)) # 衝突点までの距離 print("hit_face:{}".format(hit_face)) # 衝突したフェースのindex print("hit_triangle:{}".format(hit_triangle)) # 衝突した三角形フェースの相対index(ポリゴンが保持しているindex) print("hit_bary1:{}".format(hit_bary1)) # 衝突点が三角フェースのどの辺に位置するかどうかの比率 print("hit_bary2:{}".format(hit_bary2)) # 衝突点が三角フェースのどの辺に位置するかどうかの比率 """ terrain_sel = om2.MGlobal.getSelectionListByName(terrain[0]) """:type transform_sel: om2.MSelectionList""" terrain_dag = terrain_sel.getDagPath(0) """:type transform_dag: om2.MDagPath""" for pier in piers: pier_sel = om2.MGlobal.getSelectionListByName(pier[0]) """:type transform_sel: om2.MSelectionList""" pier_dag = pier_sel.getDagPath(0) """:type transform_dag: om2.MDagPath""" y_direction_face_vertices = get_face_from_direction_y_minus(pier_dag) ray_transform(pier_dag, terrain_dag, y_direction_face_vertices)
補足
注意点
anyIntersectionの以下の引数はドキュメント通りの名前だと受け入れてくれない
(arg=~を書かないで指定する)
- raySource
- rayDirection
- space
- maxParam
- testBothDirections
補足
実務ではカメラからの衝突したメッシュの取得に利用したことはある
接地を行うなどの特殊なコンストレイントプラグインなんかを作ると楽しいかもしれない
モデリングに使うならHoudiniでやったほうが他の取り回しも効いていいと思う
画面外のウィンドウを画面内に移動
リモートワークで複数種のレイアウトのウィンドウを行き来してたらアトリビュートエディタが画面外に消えてしまったのでそのための対応。
特に移動したいもののフィルタリングとかしてなく、すべてのウィンドウを移動するので環境によっては弊害がでてしまうかも。
# Maya 消えたウィンドウの呼び出し from PySide2 import QtCore, QtWidgets, QtGui active_window = QtWidgets.QApplication.activeWindow() children = active_window.children() for child in children: if type(child) == QtWidgets.QWidget: child.move(0,0)
補足1・ウィンドウ種別ごとのフィルタリング
windowごとにフィルタリングして動作させたい場合はtitleで判定するしかないっぽい?
object name はついてるもとのついてないものがあったりする。(大体ついてない)
継承クラス用意したりはもちろんしてくれてない。
from __future__ import unicode_literals from PySide2 import QtCore, QtWidgets, QtGui active_window = QtWidgets.QApplication.activeWindow() children = active_window.children() for child in children: if type(child) == QtWidgets.QWidget: print "-"*20 print "object name: {}".format(child.objectName()) print "widget class: {}".format(type(child)) print "window title : {}".format(child.windowTitle())
上述のようにTitleででとるのが妥当な予感
補足2・cmdsでアクティブなウィンドウ取れなかった
front_window = cmds.window(frontWindow=True)
cmds.showWindow(front_window)
てっきり上記のcmdsだけでアクティブウィンドウを取得できると思ったら自分の環境(Maya2019)だと正常に取得できなかった。
(昔この方法で取得した記憶があるのでバージョンのバグだと思う。詳細は調べてない)