お手軽スクショ Maya SSSS(super screen shot system)

[Maya Advent Calendar 2021](https://qiita.com/advent-calendar/2021/maya)

一年ぶりでございます。もはや恒例になりました。

昨年はさくっとアドカレ埋まったのですが、今年は埋まりが悪いようですね。


今回も、今年調査して自分的ホットになったネタを紹介していきます。

今年はスクリーンショットでございます。

スーパーって書いてるけど大したことはないです。ただ、Sを重ねたかっただけです(　´_ゝ｀)
<br>
<br>
<br>

:::note warn
この記事でのMayaは全部2020でやってます。2022では確認していないのでそこはご了承ください。
:::


# 使用用途
さて、そもそもどんなときにスクリーンショットを使うのかというところから。

基本的にはデータのサムネとして使いたいときに行います。

例えば、アセットブラウザ。

UE4でのサムネ表示みたいな感じですね。

![advcale_mayapy_2021_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153835/11c09784-e5f1-eea2-a58f-7b440e34a742.png)

shotgrid(名前変わってややこしい)でもアセットの登録時に使えることでしょう。

ちょっとした変わり種だと、リグのpickerの背景というのもあります。

![advcale_mayapy_2021_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153835/687985d7-0ba6-b428-74d2-686c726f7f03.png)

正直なところなくても代用が効くけどあるとそれっぽくなるってやつですね。

前の記事とかでも言ってるので重複になりますけど、

ツールは「アフォーダンスがある」、「情報がわかりやすい」と受け入れやすいのでUIにも凝る意味があるんですよね。
<br>
<br>
<br>

# Mayaでのスクリーンショット
さて、前置きはこんなもので。Mayaでのスクリーンショットを取る方法を見ていきましょう。

まず、プログラム的な知識がない場合、レンダリングすることが思い浮かぶのではないかと思います。

方法としては全然ありです。私も昔考えるのめんどかったときはバッチでレンダリング回して作成してました。

それ以外での方法はコマンドによるものです。

レンダリングも究極的には全部スクリプトで自動化できますが、

設定がめんどいのとコマンドでサクッと回したいので今回はコマンドによるスクリーンショットにフォーカスを当てます。
<br>
<br>
<br>

## mayaPython
``` python
from maya import cmds
```
```python:
cmds.modelEditor(panel, e=True, cpt=save_path)
```
or

```python:
cmds.refresh(fe='png', fn=save_path)
```

save_pathは任意の保存先のパス、panelはモデルパネル名になります。
<br>

「っえ！二つあるの！？」とびっくりしました。

というか、普通にスクリーショットっぽいコマンド名で検索しても引っ掛からないので、

そもそも存在してないと思った方もいるんじゃないかと思います。

かくいう自分も方々探しまくったのと、偶然に他の目的で引いたコマンドリファレンスにフラグが存在したのを見つけた次第です。

Autodeskさん不親切…

ちょっと脱線しましたが一個ずつ解説します。
<br>

```python:
cmds.modelEditor(panel, e=True, cpt=save_path)
```

capture(cpt)がスクショのフラグです。

フラグの名前だけみればわかりやすいんですが、cmds.modelEditorのフラグは↓こんだけあります。

![advcale_mayapy_2021_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153835/26e5d813-4378-16bc-d55c-b7b51da335e1.png)

わかんないって！
<br>

次。

```python:
cmds.refresh(fe='png', fn=save_path)
```

こちらはビューの描画をリフレッシュするときのコマンドになります。

このコマンドには、filename(fn)という引数が存在しています。

リファレンス曰く、

```
Specify the name of a file in which to save a snapshot of the viewports, or just the current one if the currentView flag is set.
ビューポートのスナップ ショットを保存するファイルの名前を指定します。currentView フラグが設定されている場合は、現在の 1 つを指定します。
```

これは気づかないですね…

ちなみに、fileExtension(fe)でpngを指定していますが、jpgとかgifとかもできます。

さらには、fileExtension(fe)を指定しなくともパスで拡張子をちゃんと記述しておけばそちらで認識されます。

対象のビューですが、直前までアクティブだったものが対象になります。

なので、コマンドを叩く前に撮りたい対象のビューをアクティブにするコマンドを叩くのを忘れないように。

```python:
cmds.setFocus(panel)
```

つまるところ、

```python:
cmds.setFocus(panel)
cmds.refresh(fn=save_path)
```

と書けば、<font color="Green">cmds.modelEditor</font>側と同じになるということです。
<br>

## openMaya
openMayaで実行する方法もあるので一応ご紹介。

[[Maya] Viewport をキャプチャする](http://mukaimame.blog111.fc2.com/blog-entry-1221.html)

**もってぃさん**ありがとう！

**もってぃさん**元気でやってるかな…閑話休題
<br>

## 余談：panel名の取得について
```python:
cmdsm.getPanel(wf=True)
```
これでアクティブなものをとれます。

ただし、scriptEditorとかでこれを実行すると、必然的にアクティブになっているのがscriptEditorのパネルになってしまうので注意です。


カメラから取得したいときはこれ。

```python:
# ---------------------------------------------------------
# get showing modelPanel name
# ---------------------------------------------------------
# @param <str>cam : cam name
# @return <obj>activePanel : showing model panel
def get_viewpanel(cam='persp'):
    panels = cmds.getPanel(typ='modelPanel')
    if panels:
        active_panel = None
        for pnl in panels:
            panel_cam = cmds.modelPanel(pnl, q=True, cam=True)
            if panel_cam == cam:
                active_panel = pnl
                break
    else:
        active_panel = None
    return active_panel
```

引数camにカメラ名を入れるとそのカメラで映しているpanel名が取れます。
<br>
<br>
<br>

## 撮ったけど…
さて、とりあえずこれで撮れることがわかりました。

ところがどっこい、この方法でpng出力すると…

![advcale_mayapy_2021_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153835/0a834fed-79d4-5295-251e-6b907a076fe9.png)

こんな感じで背景が透過してしまいます。サムネとしては見辛い…

もとはこんな感じ

![advcale_mayapy_2021_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153835/35de3698-e212-dda3-4745-87e458d254f3.jpeg)

「いやでも容量的に画質的にもpngの方がいいし、pngで撮りたい！」

またしても痒い所に手が届かないところで悩まされます(’A`)
<br>

というわけで、実はこの後からが本当の本題になります。
<br>
<br>
<br>

# pysideでのスクリーンショット
はい。というわけでまた困ったときのpysideでございます。

ご明察のとおり、pysideでもスクリーンショットを撮ることができます。


```python:
from PySide2 import QtGui
ss = QtGui.QPixmap.grabWindow(widget.winId())
ss.save(save_path)
```

widgetはpysideの<font color="Green">QtWidgets.QWidget</font>です。

panelをどのようにQtWidgets.QWidgetに変換するかは[Maya UIにひとてま](https://qiita.com/mono-g/items/3228028203f046ed6ecc) この記事で解決してるので割愛します。

QtGui.QPixmap.grabWindow関数を使ってwidgetのスクリーンショットをメモリに保存します。

QtGui.QPixmapオブジェクトになっていますのでついでに大きさも調整してしまいましょう。

あとはそれをファイルに出力することで完了です。
<br>

汎用的にするとこんな感じ。

```python:
import maya.OpenMayaUI as omui
from PySide2 import QtGui
from PySide2 import QtWidgets
try:
    import shiboken2
except ImportError:
    from PySide2 import shiboken2
# ---------------------------------------------------------
# take screenshot by maya UI pointer
# ---------------------------------------------------------
# @param <str>ui_str     : maya UI name
# @param <str>save_path  : save path
# @param <int>height     : image height
# @return <bool>result : result 
def take_ss_in_maya_ui(ui_str, save_path, height):
    tmp_ptr = omui.MQtUtil.findControl(ui_str)
    if not tmp_ptr:
        tmp_ptr = omui.MQtUtil.findLayout()
    if not tmp_ptr:
        tmp_ptr = omui.MQtUtil.findWindow()
    ui_ptr = tmp_ptr
    if not ui_ptr:
        return False

    widget = shiboken2.wrapInstance(long(ui_ptr), QtWidgets.QWidget)
    ss = QtGui.QPixmap.grabWindow(widget.winId())
    ss = ss.scaledToHeight(height)
    try:
        ss.save(save_path)
        result = True
    except Exception:
        result = False
    return result
```
.e.g.

```python:
take_ss_in_maya_ui('modelPanel4', 'D:/hoge/ss.png', 512)
```

exceptのとこはもっとエラー振り分けた方がいいんですが、撮るだけならこれでまぁいいかなと。

ui_strにはpanel名を入力します。

heightで高さの解像度を指定して、縦横比率そのままにすることで幅も決定してます。

逆にしてもいいですし、両方指定できるようにしてもいいのでそこはご自由にということで。


ちなみに、これで撮ったものはmayaのコマンドで撮ったものと若干違います。

![advcale_mayapy_2021_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153835/35de3698-e212-dda3-4745-87e458d254f3.jpeg)![advcale_mayapy_2021_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153835/f7da9b05-bd71-58c1-d1d0-3e2e734d03b3.png)

上がmayaのコマンド、下がpysideです。

若干panelの範囲が違っているのがわかると思います。

個人的にはUIも微妙に映っている方がスクショっぽいのでいいかなとも思うのですが、そこは用途次第ですかね。
<br>
<br>

さてさて、これの利点はpanelのスクショだけでなく、mayaのありとあらゆるウィンドウをスクショできるところにあります。

ui_strにmaya内のウィンドウ名を入れるだけ。あら簡単。

これ一個書いとけば万々歳というわけですね。やっぱpysideステキ。
<br>
<br>
<br>

# おわに
以上がスクリーンショットの方法になります。

ビューをただスクリーンショットすることから始まったのに、

結果的にスーパー汎用的なスクショ関数が出来てしまったのが個人的に激熱でした。
<br>

こんな感じで妥協しないで突き詰めていった結果、想定を超えるものができると脳汁ドバドバするのでオススメです。
<br>
<br>
<br>
<br>
ということでここまで。よいお年を(:3ﾉｼ　)ﾍ
<br>
<br>

*　※免責事項※
本記事内で公開しているコードの安全性について、当方は一切の保証を与えておりません。
これらのコードを使用したことによって引き起こる損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。*