Last active
April 20, 2019 11:14
-
-
Save ShikouYamaue/77d16b25d60ea77d0c5953585ae3c319 to your computer and use it in GitHub Desktop.
Sample Editor 3 MayaAPI2.0 + QTableView + QStandardItemModel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from maya import cmds | |
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin | |
import maya.api.OpenMaya as om2 | |
import maya.api.OpenMayaAnim as oma2 | |
import time | |
import imp | |
try: | |
imp.find_module('PySide2') | |
from PySide2.QtWidgets import * | |
from PySide2.QtGui import * | |
from PySide2.QtCore import * | |
except ImportError: | |
from PySide.QtGui import * | |
from PySide.QtCore import * | |
#時間計測用関数 | |
def timer(func): | |
def wrapper(*args, **kwargs): | |
global WINDOW | |
start = time.time()#開始時間 | |
func(*args, **kwargs)#関数実行 | |
end = time.time()#終了時間 | |
msg = func.__name__ + ' : ' + str(end - start) | |
print '-'*50 | |
print msg | |
print '-'*50 | |
WINDOW.time_label.setText(msg) | |
return wrapper | |
#ウィンドウ呼び出し | |
def main(): | |
global WINDOW | |
WINDOW = SampleEditor() | |
WINDOW.resize(600, 400) | |
WINDOW.show() | |
#サンプルエディタークラス | |
class SampleEditor(MayaQWidgetBaseMixin, QMainWindow): | |
#コンストラクタ | |
def __init__(self, parent=None): | |
super(self.__class__, self).__init__(parent) | |
self.setWindowTitle('Sample Editor API') | |
#ラッパーウイジェットとレイアウトを設定 | |
central_widget = QWidget() | |
self.setCentralWidget(central_widget) | |
self.main_layout = QVBoxLayout() | |
central_widget.setLayout(self.main_layout) | |
#ウェイト取得するボタン | |
self.get_button = QPushButton('Get Weights') | |
self.main_layout.addWidget(self.get_button) | |
self.get_button.clicked.connect(self.get_weights) | |
#ウェイトテーブルの変更を実際のウェイト値に反映するボタン | |
self.set_button = QPushButton('Set Weights') | |
self.main_layout.addWidget(self.set_button) | |
self.set_button.clicked.connect(self.set_weights) | |
#ウェイトテーブルビュー | |
self.table_view = QTableView() | |
self.main_layout.addWidget(self.table_view) | |
#時間計測結果のお知らせ | |
self.time_label = QLabel('Execution time :') | |
self.main_layout.addWidget(self.time_label) | |
#選択コンポーネントのウェイトデータを取得してテーブルに反映 | |
def get_weights(self): | |
self.get_skin_data()#まずはウェイトデータの取得 | |
self.table_setup()#テーブルにデータセット | |
#QTableWidgetにデータ設定 | |
@timer | |
def table_setup(self): | |
#StandardItemModel作成 | |
self.weight_model = QStandardItemModel() | |
self.weight_model.dataChanged.connect(self.update_weights) | |
#モデルにデータをセット | |
for row, weights in enumerate(self.weights_list): | |
items = [] | |
for column, w in enumerate(weights): | |
item = QStandardItem(str(w)) | |
items.append(item) | |
self.weight_model.appendRow(items) | |
#選択管理モデルを作る | |
self.sel_model = QItemSelectionModel(self.weight_model) | |
#モデルをビューに設定 | |
self.table_view.setModel(self.weight_model) | |
self.table_view.setSelectionModel(self.sel_model) | |
#選択頂点、ウェイト、インフルエンス、スキンクラスタを取得 | |
@timer | |
def get_skin_data(self): | |
#現在選択しているオブジェクト、コンポーネントをAPIで取得 | |
sList = om2.MGlobal.getActiveSelectionList() | |
#選択リストのイテレータ | |
iter = om2.MItSelectionList(sList) | |
#イテレーションしてメッシュのDagPathとコンポーネントをセットで取得 | |
while not iter.isDone(): | |
meshDag, component = iter.getComponent() | |
iter.next()#nextでイテレーションを進める | |
#今回は1メッシュのみサポート(書式をわかりやすくするため) | |
self.meshDag = meshDag | |
#MFnSkinMeshクラス、頂点アレイ、スキンクラスタ名を取得する関数呼び出し | |
self.skinFn, self.skin_cluster = self.om_get_skin_cluster(self.meshDag) | |
self.vertexArray= self.convert_to_vertex(self.meshDag, component) | |
#指定の頂点をコンポーネントとして取得する | |
singleIdComp = om2.MFnSingleIndexedComponent()#SingleIDを格納するクラス | |
self.vertexComp = singleIdComp.create(om2.MFn.kMeshVertComponent)#頂点コンポーネント(SingleID)を格納する函 | |
singleIdComp.addElements(self.vertexArray)#頂点アレイを格納 | |
#インフルエンスを取得して情報整理 | |
infDags = self.skinFn.influenceObjects()#インフルエンスを全取得 | |
self.infIndices = om2.MIntArray( len(infDags), 0 )#インフルエンスIDを格納するMIntArrayを用意、インフルエンスの数のIntArray | |
for x in xrange(len(infDags)): | |
self.infIndices[x] = int(self.skinFn.indexForInfluenceObject(infDags[x])) | |
#インフルエンス名をフルパスで取得 | |
self.full_path_influences = [infDags[x].fullPathName() for x in range(len(self.infIndices))] | |
#ショート名に変換(列ヘッダー表示用) | |
self.influences = [inf.split('|')[-1] for inf in self.full_path_influences] | |
#ウェイト取得 | |
self.weights = self.skinFn.getWeights(self.meshDag , self.vertexComp) | |
#ウェイトデータ(1次元配列)を頂点ごとの2次元配列にリシェイプ | |
self.weights_list = self.conv_weight_shape(len(self.infIndices), self.weights[0]) | |
#頂点番号名を生成(行ヘッダー表示用) | |
mesh_name = self.meshDag.fullPathName().split('|')[-1] | |
self.vertices = [mesh_name + '.vtx[' + str(vid) + ']'for vid in self.vertexArray] | |
#ウェイトをバーテックス単位の2次元配列にリシェイプする関数 | |
def conv_weight_shape(self, shape, weights): | |
return [[weights[i+j*shape] for i in xrange(shape)] for j in xrange(len(weights)/shape)] | |
#ウェイトテーブルが更新されたらクラス内変数で保有しているウェイト値も更新する | |
def update_weights(self, top_left, bottom_right): | |
row_start = top_left.row() | |
row_end = bottom_right.row() + 1 | |
column_start = top_left.column() | |
column_end = bottom_right.column() + 1 | |
for row in range(row_start, row_end): | |
for column in range(column_start, column_end): | |
new_value = float(self.weight_model.item(row, column).data(0)) | |
self.weights_list[row][column] = new_value | |
#コンポーネントやメッシュリストをバーテックスリストに変換する | |
def convert_to_vertex(self, meshDag, component): | |
cmpType = None | |
#選択されているコンポーネントタイプを取得 | |
if component.hasFn(om2.MFn.kMeshVertComponent): | |
cmpType = "vtx" | |
elif component.hasFn(om2.MFn.kMeshEdgeComponent): | |
cmpType = "edge" | |
elif component.hasFn(om2.MFn.kMeshPolygonComponent): | |
cmpType = "face" | |
if cmpType: | |
compFn = om2.MFnSingleIndexedComponent(component) | |
#コンポーネントを頂点情報に変換してIDリストに格納する | |
meshFn = om2.MFnMesh(meshDag) | |
#頂点選択はそのまま取得 | |
if "vtx" == cmpType: | |
vtxArray = compFn.getElements() | |
#エッジを頂点IDに変換する | |
elif "edge" == cmpType: | |
eid = compFn.getElements() | |
eSet = [] | |
for e in eid: | |
evid = meshFn.getEdgeVertices(e) | |
eSet.extend(evid) | |
vids = list(set(eSet)) | |
vtxArray = om2.MIntArray() | |
[vtxArray.append(id) for id in vids] | |
#フェースを頂点IDに変換する | |
elif "face" == cmpType: | |
fid = compFn.getElements() | |
fSet = [] | |
for f in fid: | |
vid = meshFn.getPolygonVertices(f) | |
fSet.extend(vid) | |
vids = list(set(fSet)) | |
vtxArray = om2.MIntArray() | |
[vtxArray.append(id) for id in vids] | |
#メッシュ選択の場合 | |
else: | |
vids = range(meshFn.numVertices) | |
vtxArray = om2.MIntArray() | |
[vtxArray.append(id) for id in vids] | |
return vtxArray | |
#スキンクラスタ名とMFnSkinClusterクラスを返す | |
def om_get_skin_cluster(self, dagPath=None): | |
skinCluster = cmds.ls(cmds.listHistory(dagPath.fullPathName()), type='skinCluster') | |
clusterName = skinCluster[0] | |
sellist = om2.MGlobal.getSelectionListByName(clusterName) | |
skinNode = sellist.getDependNode(0) | |
skinFn = oma2.MFnSkinCluster( skinNode ) | |
return skinFn, clusterName | |
#ウェイト値をスキンメッシュに反映 | |
@timer | |
def set_weights(self): | |
#変更されたウェイト値をMDoubleArrayにキャスト | |
new_weights = om2.MDoubleArray() | |
for weights in self.weights_list: | |
new_weights += weights | |
#書き込み | |
self.skinFn.setWeights(self.meshDag, self.vertexComp, self.infIndices, new_weights, False) | |
#実行 | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment