LoginSignup
2

More than 1 year has passed since last update.

雰囲気で使うMedic

Last updated at Posted at 2020-11-30

一年ぶりまして。
今年もAdventCalendarの季節がやってまいりました!
今年のMaya Advent Calendarはというと例年と同様に大好評(?)のようで、安心しました(涙目)

さて、それでは今回はMedicという個人的に触ってるツールについてお話します。

Medicについて

Medicとは、Mayaで動作するオープンソースのデータチェックツールです。
Python、C++でテスト項目(Tester)を拡張でき、それらを一つのチェックリスト(Karte)として統合することでシーン全体または選択したノードに対して一括でデータチェックを走らせることができます。
(画像左:Karte画面、画像右:Tester画面)
image.pngimage.png

この記事では細かいことは置いておいて、TesterKarteというMedicの中でも重要な2つの要素について触れていきます。

Tester(テスター)

Testerはチェック項目本体です。
Testerは先述した通りPythonとC++で拡張できますが、私はC++ワカラナイのでここではPythonのTesterについて書きます。

Testerの書き方は基本的にここに書いてある情報しかありません。
(ググってみても第三者によるそれらしい情報のようなものは殆ど見つけられませんでした。)

これだけの情報だと少し心細さはありますが、Medicには最初からお手本となるような便利なTesterがそれなりのボリュームで用意されているため、それらのコードを読むことで大体の雰囲気を掴むことが出来ました。

実際のコードを見ていきましょう。

example1: anyLayer.py (code link)

image.png
これはシーン内にBaseAnimation, defaultRenderLayer, defaultLayerがある場合に検知するTesterです。
上画像の例では layer1 が存在するためエラー表示になっています。

このTesterのコードを例に、それぞれのブロックで何が行われているか解説していきます。

AnyLayer(medic.PyTester)

class AnyLayer(medic.PyTester):
    def __init__(self):
        super(AnyLayer, self).__init__()
    ...

Testerは必ずmedic.PyTesterを継承する必要があります。
このPyTesterの内容をオーバーライドすることで独自のTesterが出来上がるわけです。

Name

def Name(self):
    return "AnyLayer"

GUIのTester一覧画面に表示される文字列を設定する関数です。

Description

def Description(self):
    return "Check if any render, anim or display layer exists"

GUIに表示されるTesterの説明文を設定する関数です。

Match

def Match(self, node):
    return node.object().hasFn(OpenMaya.MFn.kDisplayLayer) or node.object().hasFn(OpenMaya.MFn.kAnimLayer) or node.object().hasFn(OpenMaya.MFn.kRenderLayer)

ここではTesterの対象となるノードの条件を記載します。
このコードの例ではkDisplayLayer, kAnimLayer, kRenderLayerがフィルタリングされます。

引数のnodeはMedic独自のMdNodeというノードが入っていて、node.object()と書くことでMObjectを呼び出しhasFunでノードのtypeを判定しています。

Medicではこのnodeがとても重要になってきます。
nodeの中身は詳しく知りたい方はこちらのコードをご覧ください。

test


def test(self, node):
    if node.name() not in ["BaseAnimation", "defaultRenderLayer", "defaultLayer"]:
        return medic.PyReport(node)

    return None

Matchでフィルタリングされたnodeはこのtestでデータチェックが行われます。
このコードの例ではnode.name()でノード名の文字列を取得し、その文字列が"BaseAnimation", "defaultRenderLayer", "defaultLayer"以外であればmedic.PyReport(node)を返します。
この「medic.PyReport(node)を返す」という部分がエラーを検出したということになり、検出されたノードはGUIのListViewに一覧されます。
エラーが検出されなかった場合はNoneを返します。

fix

def IsFixable(self):
    return True

def fix(self, report, params):
    node = report.node()
    if node.dg().isFromReferencedFile():
        return False

    if node.dg().isLocked():
        node.dg().setLocked(False)

    cmds.delete(node.name())

    return True

Medicではtestで検出したエラーを修正(fix)するロジックを書くこともできます。
この処理はtestと違って実際のデータを弄ることになるため、もしfixを作成する場合は気を付けて書く必要があります。

コードの例ではまずreport.node()でtestに引っかかったnodeを取得し→
そのnodeがリファレンスだった場合は処理を中断しFalseを返す、リファレンスでない場合は→
そのノード(レイヤー)がロックされているかどうかを調べ、Trueならロックを解除、Falseならそのまま→
cmds.delete(node.name())でノードを削除し→
fix処理が完了

といった処理内容になっています。

IsFixbleはこのTesterがエラーを修正できる能力を持っているかどうかを表すためのもの(GUIのfixボタンをenableにするためのもの)なので、もしfixを書くならセットでIsFixbleをオーバーライドしてTrueを返しましょう。

Create

def Create():
    return AnyLayer()

いまいち詳しいことは分かっていませんが、とりあえずCreateでこのTester本体のclassのインスタンスを返す必要があるようです。
深く考えず、おまじないだと思って忘れずに書きましょう。

testの補足:GUIのListViewから問題のあるコンポーネントを選択できるようにする

「transformノードの名前がルールと合っているかどうか」を調べるようなTesterだった場合は問題のノードはどれか分かるだけで問題ないと思います。
しかし、「とあるメッシュの全頂点のウェイト値がルール通りかどうか(小数点第3位以下が切り捨てられてるか 等)」を調べるようなTesterの場合、問題のあるメッシュと合わせて問題のあるコンポーネント(頂点やエッジ、面など)も選択できた方が便利だと思います。

実はMedicではそういった操作もサポートしています。
しかし、標準のTester、具体的にはpythonで書かれたTesterにはそういった処理がどれにも書かれていません。

それではどこに書かれているのかというと、C++で書かれたTesterの中に答えが書かれています。

少しだけコードを覗いてみましょう。

example2: nonManifoldVertex.cpp (github link)

このTesterのコードの37~66行目に書かれている内容がその答えになります。
一応コードを解説すると、

MItMeshVertex itvtx(node->getPath());

nodeの持つdagPathからMItMeshVertexイテレーターのitvtxを作成し、

MFnSingleIndexedComponent comp;
MObject comp_obj = comp.create(MFn::kMeshVertComponent);

MFnSingleIndexedComponent型のcompと、
comp.create(MFn::kMeshVertComponent)で生成したMObjectをもったオブジェクトcomp_objを生成し、

while (!itvtx.isDone())
{
    MIntArray faces;
    MIntArray edges;
    itvtx.getConnectedFaces(faces);
    itvtx.getConnectedEdges(edges);

    if ((edges.length() - faces.length()) > 1)
    {
        result = true;
        comp.addElement(itvtx.index());
    }

    itvtx.next();
}

全頂点をイテレーションして問題のある頂点がある場合はresultにtrueを代入すると同時に頂点のindexをcompに格納しています。

そして、resultがfalseであればreturn 0を返し、tureであればMdReport(Python TesterのPyReportに相当)を返します。

return new MdReport(node, comp_obj);

ここでMdReportの第2引数に注目すると、comp_objが渡されているのが分かると思います。
察しの良い人はお気づきかと思いますが、PyReportでも同様に第2引数に問題のあるコンポーネントのインデックスが格納されたMObjectを渡してあげるとGUIから問題のあるコンポーネントを選択できるようになります。

少し難点なのは、コンポーネントのインデックスが格納されたMObjectを生成する必要がある都合上、
OpenMayaをある程度理解して使える必要があることかなと思います。
(恐らくcmdsやpymelだと膨大な頂点数を処理する必要がある場合に速度面がネックになるからだと思います。)

今までcmdsやpymelで慣れてきた人にとっては少しハードルがあるかもしれませんが、頑張りましょう。

Testerまとめ

Testerとは要はMatchでテストしたいノードをフィルタリングし、testで実際にテストして問題を検出し、必要に応じてfixで修正するという役割を持ったモジュールです。

Medicにおいてもっともキモとなる部分ですね。

Karte(カルテ)

Karteは先ほど説明したTesterをひとまとめにするためのものです。
例えばモデリング工程であればモデリング工程用のTesterをひとまとめにし、セットアップ工程ではセットアップ工程用のTesterをひとまとめにするなど、そういった用途で使います。

Karteの作り方はTesterと比べると非常にシンプルです。

example3: all.karte (github link)

これがall.karteの全文です。
名前から分かる通り全てのTesterを表示します。

{
    "Name": "All",
    "Description": "All testers",
    "Testers": ["*"],
}

jsonのような構文になっていて、これをもとに目的に合わせて内容を書き換えることでKarteを作成します。
例えば以下のようなKarteの場合、

{
    "Name": "Modeling",
    "Description": "Modeling testers",
    "Testers": [
        "NonManifoldEdge",
        "NonManifoldVertex",
        "NonUniqueName",
        "ZeroLengthEdge"
    ]
}

こういう感じになります。
image.pngimage.png

Karteまとめ

全てのプロジェクト、全ての工程で全てのTesterが必要になるかというとそういうわけではないと思います。
そういった場合にこのKarteを使用して、必要なTesterだけをまとめたチェックリストを作りましょう。

まとめ

データチェックツールの類は各社で独自に用意されていることが多いと思います。
ただ、0から作るとなるとツールそのものを用意したりUIを用意したりそれらを更新するシステムを作ったりとそれなりにコストがかかると思います。

そこで、Medicはこれからチェックツールを用意しようとしている場合に検討する候補の一つとしておすすめです。

データチェックという非クリエイティブな作業をとことん自動化して、クリエイティブな作業に費やす時間を増やしたいですね!

明日の記事はUnPySideさんの記事です。お楽しみに!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2