Adobe Substance 3D PainterのQML extensionsについて

## 環境
* Windows
* Adobe Substance 3D Painter 10.1.2


## QML extensions

Substance 3D Painter では JavaScript APIで利用可能なUIコンポーネントが提供されています。これらのUIコンポーネントはQMLタイプで実装されているため、Qt for Python でも呼び出すことが可能です。


## Qt for Python からの利用例

スライダーとボタンを表示するだけのシンプルなウィンドウを例に説明します。なお、以下のコードは説明のわかりやすさを重視したものです。コーディングのベストプラクティスを示したものではありませんのでご注意ください。

### 利用例

![sp_qml_00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/cb3d5698-d2d5-c884-a53b-4c4349bfef22.jpeg)

```qml:QMLファイル
import AlgWidgets 2.0
import QtQuick.Layouts 1.3

import ClickSample 1.0


ColumnLayout {
    id: root

    ClickSample {
        id: click
    }

    AlgSlider {
        id: slider
        Layout.fillWidth: true
        text: "slider"
    }
    AlgButton {
        id: button
        Layout.fillWidth: true
        text: "button"
        onClicked: {
            click.echo_slider_value(slider.value);
        }
    }
}
```

```python:Pythonファイル
from PySide6 import QtCore, QtGui, QtQml, QtQuick, QtWidgets

import substance_painter.ui


class WindowSample(QtWidgets.QMainWindow):
    def __init__(self):
        main_window = substance_painter.ui.get_main_window()
        super().__init__(parent=main_window)

        central_widget = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(central_widget)
        layout.setContentsMargins(10, 10, 10, 10)
        self.setCentralWidget(central_widget)

        self.view = self.create_view()
        self.widget = QtWidgets.QWidget.createWindowContainer(self.view, self)
        layout.addWidget(self.widget)

    def create_view(self):
        view = QtQuick.QQuickView()
        view.setSource(QtCore.QUrl.fromLocalFile("[QMLファイルのパス]"))
        view.setColor(QtGui.QColor(50, 50, 50))
        view.setResizeMode(QtQuick.QQuickView.SizeRootObjectToView)
        return view


class ClickSample(QtQuick.QQuickItem):
    def __init__(self):
        super().__init__()

    @QtCore.Slot(float)
    def echo_slider_value(self, value):
        print(value)


QtQml.qmlRegisterType(ClickSample, "ClickSample", 1, 0, "ClickSample")

window = WindowSample()
window.show()
```

### Pythonのコードについて

Python側では主に下記の処理を行っています。

* ウィンドウの定義 (WindowSampleクラス)
* ボタンを押した際に実行されるロジックの定義 (ClickSampleクラス)
* Pythonで定義したクラスをQMLへ公開 (QtQml.qmlRegisterType)

#### ウィンドウの定義

WindowSampleでウィンドウの定義を行っています。
ここではQMLの読み込み ```create_view``` メソッドにフォーカスを当てて説明します。

QMLファイルで定義されたUIコンポーネントは下記の処理でウィンドウにセットできます。
```python
view = QtQuick.QQuickView()
view.setSource(QtCore.QUrl.fromLocalFile("[QMLファイルのパス]"))
```

また、視認性とビューサイズの調整のため下記の処理を入れています。
```python
view.setColor(QtGui.QColor(50, 50, 50))  # 視認性調整
view.setResizeMode(QtQuick.QQuickView.SizeRootObjectToView)  # ビューサイズ調整
```

上記の調整を入れない場合は下記の画像のようになります。

|視認性未調整|ビューサイズ未調整|
|:-:|:-:|
|<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/7977640a-5e60-ffd5-5e73-876b848acb0e.jpeg">|<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/0b0f0b32-b379-4e14-7f2a-0049b076f3b0.jpeg">|


#### Pythonで定義したクラスをQMLへ公開

Pythonで定義したクラスをQMLファイルで使用できるようにするためには設定が必要です。

```python
QtQml.qmlRegisterType(ClickSample, "ClickSample", 1, 0, "ClickSample")
```

注意点としてqmlRegisterTypeは同じクラスを複数回登録できてしまいます。
実運用では重複登録を避けるために、確認用のロジックを追加するか
起動時のスタートアップスクリプトに処理を回して1回しか実行されないようにするなどの検討が必要です。


### QMLのコードについて

QML側では主に下記の処理を行っています。

* UIコンポーネントの定義
* Python API のインスタンス化


#### Python API のインスタンス化

Python側で定義したClickSampleクラスを使用できるようにインスタンス化します。

```qml
import ClickSample 1.0
```

で読み込み

```qml
ClickSample {
    id: click
}
```

でインスタンス化します。インスタンス化すると ```<id名>.<メソッド名>``` でPythonのメソッドを呼び出せるようになります。


## 見た目の比較

| Substance Painter QML <br>(AlgSlider, AlgButton)| QWidget <br> (QSlider, QPushButton)| QML <br> (Slider, Button)|
|:-:|:-:|:-:|
|<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/cb3d5698-d2d5-c884-a53b-4c4349bfef22.jpeg">|<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/cbb40ab5-40f0-a438-3348-3d839ad01dcc.jpeg">|<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965005/b7179a36-1f54-7f88-4962-d338a33d0c6d.jpeg">|