Maya PySide2 / PySide チュートリアル 初級編 ⑤ – データフォーマットを扱ってみる
このチュートリアルでは PySide でjsonやiniなどのデータフォーマットを利用し、UIの情報を書き換えたり、保存したりする方法を学びます
データフォーマットとは
複合的なデータを記録・伝送する際に、個々の要素の記法や長さ、順番など、データの記述方法を定めたものをデータフォーマット(データ形式)という。特に、ストレージに記録されるファイルの体裁を取るものはファイルフォーマット(ファイル形式)という。
引用 - IT用語辞典 e-Words - フォーマット 【format】
ゲーム業界ではいろいろなデータフォーマットを扱うことが多いかと思います。fbxをはじめ、pngやpsdなどこれらは画像データや2D情報を含んだデータですが、今回扱うデータフォーマットはPySideのUI情報を記録したデータフォーマットになります。
今回のチュートリアルではゲーム業界でもよく利用されるJSONFormat(以下json)とQtが提供しているIniFormat(以下ini)の二つを利用してみます
ザックリを解説していきます
{ "key" : "value" }
jsonはPythonでいうところのDictつまり、辞書型と呼ばれる形式でKeyとValueで管理します
[Section1] key1=value1 key2=value2 key3=value3 [Section2] key1=value1 key2=value2 key3=value3
iniはSection Key Valueで分類して、管理します
今回、jsonは規定の値を保持させ、iniは最後に閉じたときのUIの情報を保持するというような使い方をしてみます
まずはQtDeisgnerでpushButton_All05とpushButton_All1の二種類を用意します
ObjectName | setText | 説明 |
---|---|---|
pushButton_All05 | All 0.5 | RGBAの値をそれぞれ0.5にする |
pushButton_All1 | All 1 | RGBAの値をそれぞれ1にする |
metadata.jsonを用意しますC:\Users\<USERNAME>\Documents\maya\scripts\sample
に
metadetaというフォルダを作成し、その中にtextを作成し、metadata.jsonにリネームしてください
C:\Users\
作成したmetadata.jsonにスクリプトエディタやテキストエディタなどで以下のように記述します
{ "All 1": [ 1, 1, 1, 1 ], "All 0.5": [ 0.5, 0.5, 0.5, 0.5 ] }
keyはQPushButtonに設定したテキストの文字列で
valueはリストの要素のインデックスがRGBAの順に対応しています
iniに関しては自動で生成されるので作成しなくても問題ありません
Code
今回のサンプルコードです
# !/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import json import maya.cmds as cmds import maya.OpenMayaUI as OpenMayaUI try: from PySide2.QtWidgets import * from PySide2.QtCore import * from PySide2.QtUiTools import QUiLoader import shiboken2 as shiboken except ImportError: from PySide.QtGui import * from PySide.QtCore import * from PySide.QtUiTools import QUiLoader import shiboken ptr = OpenMayaUI.MQtUtil.mainWindow() mayaMainWindow = shiboken.wrapInstance(long(ptr), QMainWindow) class vtxMainWindow(QMainWindow): __currentPath = os.path.dirname(__file__) __uiFilePath = os.path.join(__currentPath, "ui", "vtxMain.ui") __geometries = [] __settingPath = os.path.join(__currentPath, "metadata", "setting.ini") __setting = QSettings(__settingPath, QSettings.IniFormat) __metaDataPath = os.path.join(__currentPath, "metadata", "metadata.json") with open(__metaDataPath) as f: __metadata = json.load(f) def __init__(self, parent=None): super(vtxMainWindow, self).__init__(parent) loader = QUiLoader() uiWidget = loader.load(self.__uiFilePath) self.setCentralWidget(uiWidget) self.resize(400, 270) self.setWindowTitle(".ui MainWindow") self.installEventFilter(self) self.centralWidget().listWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.__initSignalSlot() def __initSignalSlot(self): self.centralWidget().button_Apply.clicked.connect(self.setVertexColor) self.centralWidget().listWidget.itemClicked.connect(self.selectItem) self.centralWidget().checkBox_IsSelect.stateChanged.connect(self.selectItem) self.centralWidget().pushButton_All05.clicked.connect(self.setValues) self.centralWidget().pushButton_All1.clicked.connect(self.setValues) def setValues(self): values = self.__metadata[self.sender().text()] self.centralWidget().doubleSpinBox_R.setValue(values[0]) self.centralWidget().doubleSpinBox_G.setValue(values[1]) self.centralWidget().doubleSpinBox_B.setValue(values[2]) self.centralWidget().doubleSpinBox_A.setValue(values[3]) def setVertexColor(self): current = cmds.ls(sl=True) meshList = [] if self.centralWidget().checkBox_IsSelect.isChecked(): for item in self.centralWidget().listWidget.selectedItems(): meshList.append(item.text()) else: meshList = cmds.ls(sl=True) for mesh in meshList: cmds.select(mesh) cmds.polyColorPerVertex( r=self.centralWidget().doubleSpinBox_R.value(), g=self.centralWidget().doubleSpinBox_G.value(), b=self.centralWidget().doubleSpinBox_B.value(), a=self.centralWidget().doubleSpinBox_A.value() ) cmds.setAttr("%s.displayColors" % mesh, 1) cmds.select(current) def selectItem(self): if self.centralWidget().checkBox_IsSelect.isChecked(): cmds.select(cl=True) for item in self.centralWidget().listWidget.selectedItems(): cmds.select(item.text(), add=True) def eventFilter(self, object, event): if event.type() == QEvent.WindowActivate \ or event.type() == QEvent.FocusOut: self.widgetReload() else: pass super(vtxMainWindow, self).eventFilter(object, event) def widgetReload(self): self.centralWidget().listWidget.clear() for count, geometry in enumerate(self.geometries): self.centralWidget().listWidget.insertItem(count, geometry) @property def geometries(self): self.__geometries = [] meshlist = cmds.ls(type="mesh") for mesh in meshlist: self.__geometries.append(cmds.listRelatives(mesh, fullPath=True, parent=True, type="transform")[0]) return self.__geometries def closeEvent(self, event): self.saveSettings() super(vtxMainWindow, self).closeEvent(event) def showEvent(self, event): self.loadSettings() super(vtxMainWindow, self).showEvent(event) def loadSettings(self): widget = None for i in dir(self.centralWidget()): try: exec("widget = self.centralWidget().%s" % i) widgetType = type(widget) objectName = widget.objectName() value = self.__setting.value(objectName) if widgetType == QDoubleSpinBox: widget.setValue(value) elif widgetType == QCheckBox: widget.setChecked(value) except BaseException: pass self.restoreGeometry(self.__setting.value("geometry")) def saveSettings(self): widget = None for i in dir(self.centralWidget()): try: exec("widget = self.centralWidget().%s" % i) widgetType = type(widget) objectName = widget.objectName() if widgetType == QDoubleSpinBox: self.__setting.setValue(objectName, widget.value()) elif widgetType == QCheckBox: self.__setting.setValue(objectName, widget.isChecked()) except BaseException: pass self.__setting.setValue("geometry", self.saveGeometry()) def main(): app = QApplication.instance() mainWin = vtxMainWindow(parent=mayaMainWindow) mainWin.show() sys.exit() app.exec_()
まずはjsonとiniのパスや読み込み先をクラスに定義します
# !/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import json import maya.cmds as cmds # ~~~ 略 mayaMainWindow = shiboken.wrapInstance(long(ptr), QMainWindow) class vtxMainWindow(QMainWindow): __currentPath = os.path.dirname(__file__) __uiFilePath = os.path.join(__currentPath, "ui", "vtxMain.ui") __geometries = [] __metaDataPath = os.path.join(__currentPath, "metadata", "metadata.json") with open(__metaDataPath) as f: __metadata = json.load(f)
jsonを読み込むにはjsonモジュールを利用するのがやりやすいのでimportでjsonを呼び出し
metadata.jsonを読み込んでおきます
def __initSignalSlot(self): self.centralWidget().pushButton_All05.clicked.connect(self.setValues) self.centralWidget().pushButton_All1.clicked.connect(self.setValues) def setValues(self): values = self.__metadata[self.sender().text()] self.centralWidget().doubleSpinBox_R.setValue(values[0]) self.centralWidget().doubleSpinBox_G.setValue(values[1]) self.centralWidget().doubleSpinBox_B.setValue(values[2]) self.centralWidget().doubleSpinBox_A.setValue(values[3])
シグナル&スロットでそれぞれのQPushButtonを押した際にQDubleSpinBoxに値が入るように設定しますself.sender()
は何らかのシグナルでスロットが呼び出された際に、シグナルを送信したオブジェクトへのポインタを返すもので、ザックリ話すとclickedを押した際にどのWidgetが押されたかがわかる便利なものです
senderで.text()を取得することでここではQPushButton.text()を呼んでいると同じ事をしています
先ほどのjsonにはkeyにPushButtonのテキストを設定していたので、自動的に対応するvalueをインデックス番号で設定しています
class vtxMainWindow(QMainWindow): __currentPath = os.path.dirname(__file__) __uiFilePath = os.path.join(__currentPath, "ui", "vtxMain.ui") __geometries = [] __settingPath = os.path.join(__currentPath, "metadata", "setting.ini") __setting = QSettings(__settingPath, QSettings.IniFormat) __metaDataPath = os.path.join(__currentPath, "metadata", "metadata.json") with open(__metaDataPath) as f: __metadata = json.load(f)
setting.iniの保存先とQSettingsを設定しますQSettings.IniFormat
を引数に指定することで保存する際に自動的にIniFormatで保存してくれます
他にもレジストリに書き込むタイプなども指定できますがスタンドアロンでソフトウェアを作成したりしないのであればIniFormatが無難かと思います
def closeEvent(self, event): self.saveSettings() super(vtxMainWindow, self).closeEvent(event) def saveSettings(self): widget = None for i in dir(self.centralWidget()): try: exec("widget = self.centralWidget().%s" % i) widgetType = type(widget) objectName = widget.objectName() if widgetType == QDoubleSpinBox: self.__setting.setValue(objectName, widget.value()) elif widgetType == QCheckBox: self.__setting.setValue(objectName, widget.isChecked()) except BaseException: pass self.__setting.setValue("geometry", self.saveGeometry())
Windowを閉じる際に設定を保存するようにcloseEventにself.saveSettings()
呼び出すようにしますsaveSettings
ではcentralWidgetに設定されているWidgetで値を保持したいものだけを条件分岐し、
vtxMainWindowのgeometry情報だけ最後に保存できるように設定します
条件分岐はwidgetTypeでQDoubleSpinBoxとQCheckBoxの値を保存するようにしています
def showEvent(self, event): self.loadSettings() super(vtxMainWindow, self).showEvent(event) def loadSettings(self): widget = None for i in dir(self.centralWidget()): try: exec("widget = self.centralWidget().%s" % i) widgetType = type(widget) objectName = widget.objectName() value = self.__setting.value(objectName) if widgetType == QDoubleSpinBox: widget.setValue(value) elif widgetType == QCheckBox: widget.setChecked(value) except BaseException: pass self.restoreGeometry(self.__setting.value("geometry"))
設定を読み込むloadSettingsも同じく
Windowを表示する際に設定を読み込むようにshowEventにself.loadSettings()
呼び出すようにします
Maya PySide2 / PySide チュートリアルのこのパートでは、データフォーマットを扱う方法を学びました
前はMaya PySide2 / PySide チュートリアル 初級編 ④ – .uiとPythonのコーディングを組み合わせてみる
※おまけ
フォーマットは色々な形式で扱うことが多く、結局はテキストデータなので拡張子を変更して、プロジェクト独自や会社独自の形式で扱うことがあったりします
metaデータといいつつ実はjsonでしかなかったり、
yamlといった形式を扱うこともあるでしょうし、場合によってはfbxのデータの中身をいじったりすることもあるかもしれません
ディスカッション
コメント一覧
まだ、コメントがありません