😎

【Pyside2】テーマカラーが選べるGUI

2022/06/21に公開

今回はpyside2でテーマカラーが選べるguiを作成しました。作成の過程で勉強したことを簡単に共有できたらと思います。
https://www.youtube.com/watch?v=H4K5MJMrfDk

https://github.com/Kuru-teo/PythonCollection/tree/master/ThemeColorPyside2
※権利上配布データにはアイコンと一部QSSファイルが含まれていません。

【はじめに】

最近MayaやSubstance等のツール開発の勉強をしているのですが、ui周りで使い回せるスクリプトの雛形があったらいいなと思い作成しました。そのため汎用性のあるpyside2を使用しています。せっかくなのでちょっとモダンな見た目にできるよう勉強しました。

実行方法(Visual Studio Code)

  1. ThemeColorPyside2をダウンロード
  2. ThemeColorPyside2フォルダをプルジェクトフォルダに設定
  3. MyPyside2.pyを実行
    ※事前にpyside2のインストールが必要です。

検証環境

  • maya2020
  • Visual Studio Code 1.68.1
  • substance painter 7.2

内容

  1. 少し特殊なWidgetのモジュール(ドラッグで動くウィンドウ等)
  2. INIファイル、qss読み込み、同名windowの多重防止等、色替え等の基本的なgui機能
  3. pyside2だから様々なDCCツールで使いまわし可能

デスクトップアプリケーションで実行(配布しているものはこちらになります)

mayaで実行

Substance Painterで実行

トップウィンドウの取得やインスタンスの作成方法、また一部の関数やqssでそれぞれのツールに合わせた書き方に変える必要がありますが、ほぼ同じ見た目で実行することができます。
(ステータスバー部分はツールごとにかなり見た目が異なり、substance painterでは角丸部分まで侵食してしまっています。この場合はQSizeGripの方がいいかもしれません)

【Pyside2勉強サイト】

pyside2を学ぶにあたって以下サイトを利用しました。非常にわかりやすいサイトですので、より深く学びたい方はぜひ読んでみてください。
https://unpyside.com/
https://kiwamiden.com/
YouTubeのvideoIDが不正ですhttps://www.youtube.com/c/WandersonIsMe

【パッケージ解説】

パッケージ構成

ThemeColorPyside2
├── __init__.py
├── MyPyside2.py
├── Mypyside2Lib
│   ├── __init__.py
│   └── Mypyside2Lib.py
└── qssLib
│   ├── __init__.py
│   └── qssLib_***.qss
└── resource
    └── icon.png

MyPyside2Lib.py

各Widgetに機能を追加したクラスがまとめられています。ドラッグできるボタンや、トグルなどです。

qssLib

各Widgetのqssファイルを「qssLib_〇〇〇.qss」の命名規則で入れておきます。形状はQSS、色はクラスのメソッドで変更するという構造です。

resource

iconなどの画像を入れておきます。

MyPyside2.py

【色の追加】
グローバル変数colorDictにキーと色コードを指定することで、テーマカラーのプリセットとして追加することができます。

# -*- coding:utf-8 -*-
import importlib
import os
import glob
import time
import sys

from MyPyside2Lib.MyPyside2Lib import *

WindowObjName = "mm_test_window"
folderPath = os.path.dirname(__file__)
iniFileName = "UIsetting_MyPyside2"
#colorDict = {"色":[MainColor,SubColor,BG_MainColor,BG_SubColor,GP_BorderColor]}
colorDict = {"Green": ["#027373", "#038C7F", "#A9D9D0", "#F2E7DC", "#FFF9F5"],
             "Yellow": ["#BF8845", "#F2A341", "#4C594E", "#BF8845", "#F2DCB3"],
             "Purole": ["#C899F7", "#EE8BF0", "#9896E0", "#99C4F7", "#86E7F0"],
             "night": ["#B4BEC9", "#DEEFE7", "#202022", "#878787", "#CACACA"]}

【uiWidgetクラス・MainWindowクラス】
メインのMainWindowクラスは基底クラスにQmainWindowクラスを使用しているので、setCentralWidget()で設定する中央widgetが必要です。本スクリプトではuiWidgetクラスのインスタンスがそれにあたります。この部分をqtDesignerで作成したui等、必要に応じてカスタマイズしてみてください。

【それぞれのDCCツールで対応部分】
最後のwindowの立て方に関する記述は各ツールで少しずつ異なるので、それぞれのツールに合わせて編集してください。(親windowの取得など)

【レイアウトの仕組み】

疑問に感じたもの

pysideの勉強をしている最中、widgetをwindowに配置するときに各Widgetのスペースがどのように決まるのかということに疑問を持ちました。以下はwindowにボタンとラベルを配置したシンプルなコードです。

# _*_ coding: utf-8 _*_
import sys
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *

class Form(QDialog):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.setWindowTitle("My Form")
        self.setGeometry(400, 400, 650, 300)

        layout = QVBoxLayout()
        Button = QPushButton("Button")
        label = QLabel("Label")
        
        layout.addWidget(Button)
        layout.addWidget(label)

        self.setLayout(layout)


if __name__ == '__main__':
    # Create the Qt Application
    app = QApplication(sys.argv)
    # Create and show the form
    form = Form()
    form.show()
    # Run the main Qt loop
    sys.exit(app.exec_())

上記のコードを実行した結果は以下の通り

個人的に以下のような等しいスペース配分になると思っていたので少し引っ掛かりました。

layout内のWidgetのスペース処理

この辺りもしっかりQt公式ドキュメントで説明されていましたので、詳細を知りたい方はまずこちらをご覧ください。(Adding Widgets to a Layout)
https://doc.qt.io/qt-6/layout.html
簡単に解説しますと、レイアウトにウィジェットが追加された時、レイアウト処理は以下のように動作します。

  1. sizePolicyおよびsizeHintに従って一定量のスペースが割り当て
  2. いずれかのウィジェットにストレッチ係数が設定されていて、値が 0 より大きい場合は、そのストレッチ係数に比例してスペースが割り当て
  3. ウィジェットのいずれかにストレッチファクターがゼロに設定されている場合、他のウィジェットがスペースを必要としない場合にのみ、それらはより多くのスペースを取得
  4. 最小サイズ・最大サイズの割り当て。ストレッチ係数に従って配分を決定

それぞれの用語の意味は以下のサイトで丁寧に解説されています。
https://gihyo.jp/dev/feature/01/qt/0004?page=2

【sizeHint】
ウィジェットのデフォルトで決められる最適なサイズ

【sizePolicy】
ウィジェットが縦または横にどのように伸縮できるか

【ストレッチ係数】
最大サイズが決まっていないウィジェット同士の配分を決定

ボタンのデフォルトsizePolicy
horizontalPolicy = QSizePolicy::Minimum,
verticalPolicy =QSizePolicy::Fixed
ラベルのデフォルトsizePolicy
horizontalPolicy = QSizePolicy::Preferred,
verticalPolicy =QSizePolicy::Preferred

ボタンのverticalpolicyがFixedなのに対してラベルはPreferredとなっており、ラベル側のverticalのスペースがボタンの縦サイズ分以外図べ手割り当てられることとなります。だから図1のような見た目になるのですね。

また以下のようにsizePolicyを設定することで図2のようにすることができます。先程の処理手順の[3.ウィジェットのいずれかにストレッチファクターがゼロに設定されている場合、他のウィジェットがスペースを必要としない場合にのみ、それらはより多くのスペースを取得]にあたります。

Button = QPushButton("Button")
Button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
label = QLabel("Label")
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

例によって本データの利用は自己責任でお願いいたします。ここまで読んでいただきありがとうございました!

Discussion