スクリプト プラグインと独自のプラグインの記述方法について説明します。
ビデオのキャプション: VRED Pro の Python スクリプト作成に関するチュートリアル シリーズへようこそ。Christopher といいます。今日は、スクリプト プラグインとその作成方法についてお話ししたいと思います。
前回のチュートリアルで学習したように、VRED は、独自の Python スクリプトを追加してシーンをカスタマイズすることができる、さまざまなインタフェースを備えています。いくつかの簡単なスクリプトを使用して、制作に役立つユーザ インタラクションとツールを実装することができます。すべての同僚が利用したり、顧客に配布したりする必要がある特殊なツールを提供する場合は、ツールをスクリプト プラグインとして実装することを検討する必要があります。
スクリプト プラグインは特別な種類の Python スクリプトです。これらは VRED シーンから独立しており、使用するには特別なスクリプト プラグイン ディレクトリにインストールする必要があります。スクリプト プラグインにアクセスするには、VRED のメニュー バーで「Script」エントリを開きます。このエントリには、インストールされているすべてのスクリプト プラグインがリストされます。各プラグインは、対応するエントリをクリックすることで、有効と無効を切り替えることができます。通常、スクリプト プラグインは VRED に統合され、VRED の一部のように使用できるユーザ インタフェースを提供します。これは、PySide ウィジェットが既定で VRED のウィンドウ スタイルを使用しているためです。このチュートリアルでは、スクリプト プラグインを一緒に実装し、その過程で、ユーザ インタフェースを構築し、新しいツールを実装する方法を学習します。
このビデオでは、ビューポイントの操作に役立つ簡単なプラグインを実装します。まず、指定できるディレクトリにすべてのビューポイントをレンダリングするのに役立つツールを実装します。次に、ビューポイントをランダムに選択して、カメラを設定します。
これらの関数を実装するには、最初にプラグイン クラスを作成する必要があります。このクラス設定は、基本的に、開発するすべてのプラグインで同じになります。まず、「マイ ユーザ ドキュメント」、「Autodesk」、「VRED 13.3」、「ScriptPlugins」の下に新しいディレクトリを作成します。このディレクトリには「ViewpointPlugin」という名前を付け、作成するすべてのプラグイン ファイルを格納します。このディレクトリがコンピュータ上に存在しない場合は作成し、VRED がそこからスクリプト プラグインの読み込みを試みるようにできます。
次のサンプルでは、Python 拡張機能をインストールした Visual Studio Code を使用して作業します。新しく作成したディレクトリで Visual Studio Code を開き、viewpoint_plugin.py
と viewpoint_plugin.ui
の 2 つのファイルを作成することができます。最初のファイルには Python コードが含まれ、2 番目のファイルにはユーザ インタフェース レイアウトが含まれます。スクリプトの先頭で、PySide 名前空間から必要なモジュール、つまり Qt コア、Qt GUI、および Qt ウィジェットを読み込みます。プラグイン ウィジェットの生成に役立つ uiTools も読み込みます。
次に、UI ツールを使用して、フォームとウィジェット ベースをロードします。ここでは、先ほど作成した UI ファイルを参照します。次に、先ほど生成したフォームとベースから継承する実際のプラグイン クラスを追加します。init コンストラクタの開始は常に同じで、作成するすべてのプラグインにコピーすることができます。
クラスの下で、コンストラクタのパラメータとして「VRED プラグイン ウィジェット」を使用して、プラグインをインスタンス化できます。説明が速すぎてすべてを理解できなくても心配はいりません。これらは多くの特殊なプラグイン コードですが、常に同じです。プラグインを独自に開発する場合は、VRED のプラグインのサンプルからこのプラグイン構造をコピーすることができます。またこのチュートリアルは、いつでも巻き戻したり、[一時停止]ボタンをクリックして一時停止することができます。
Python 側での作業はこれで完了しましたが、ユーザ インタフェースも定義する必要があります。これは、viewpoint_plugin.ui
ファイルで行います。レイアウト ファイルは、ユーザ インタフェースの定義に XML 表記を使用します。このチュートリアルでは、いくつかのボタンを追加します。複雑な操作はありません。ここで重要なのは、2 つの押しボタンを追加して名前を付けることです。この名前は、後でボタンを参照するために Python スクリプトで使用されます。このような QT レイアウト ファイルを作成して書き出すためのエディタを提供する「QT Designer」を参照することをお勧めします。このチュートリアルでは手動で行いますが、大規模なプロジェクトでは非常に手間がかかります。
ファイルを保存したら、VRED に戻ってスクリプト プラグインを再ロードします。[スクリプト]メニューに別のエントリが表示されています。このエントリをクリックすると、新しいプラグイン インタフェースが表示されます。開いたり閉じたりできる UI は既に存在しますが、現在はまだ機能がありません。
すべてのビューポイントをレンダリングする最初のツールを実装しましょう。「render viewpoints」という新しい関数を定義します。最初に、ファイル ダイアログ モジュールを使用して、ユーザにディレクトリを要求します。ユーザがディレクトリを選択する場合は問題ありません。ただし、ユーザが操作をキャンセルした場合は、そのことを確認し、何も行わずに関数から戻る必要があります。次のステップでは、使用可能なすべてのビューポイントのリストに対して処理を反復し、ビューポイントごとにアクティブにします。その後、ファイル名を生成し、ビューポイントをファイルにレンダリングします。ディレクトリ、ユーザの選択、およびビューポイントの名前から、レンダリングを保存する場所を VRED に指示するファイル パスを生成できます。レンダリングが完了したら、ファイルが保存されたディレクトリを開くかどうかをユーザに尋ねるメッセージ ダイアログを表示するのもよいアイデアです。これは、ユーザの選択を返すことができる「QMessageBox」を使用して実現できます。[OK]と[キャンセル]ボタンが表示され、ユーザが[OK]をクリックすると、既定の Python の機能を使用してディレクトリが開きます。
今すぐテストを行っても、スクリプトの重要な部分がないため、実際には何も起こりません。レンダリング関数を押しボタンに接続する必要があります。そのためには、UI レイアウトで指定した名前を使用して、参照するボタンの clicked シグナルに接続します。これで、準備が完了しました。スクリプト プラグインを再ロードして新しいプラグインを開始すると、実装をテストできます。予想したとおり、すべてのビューポイントがレンダリングされ、レンダリングが完了した後にレンダリング ディレクトリを開くかどうかを尋ねられます。
ここからは、2 番目の関数を実装します。今回は、テキストではなく、アイコンをボタンに追加します。そこで、UI レイアウト ファイルの 2 番目の押しボタンからテキスト ノードを削除し、Python ファイルに戻ります。setIcon
関数を使用してボタンにアイコンを直接設定すれば、アイコンを追加できます。この関数は、アイコン イメージを使用して作成する入力として QIcon パラメータを必要とします。アイコンは、プラグイン ディレクトリに格納される .PNG イメージです。
また、サイズが大きすぎてユーザ インタフェースに収まらないことがないよう、アイコンのサイズを設定する必要があります。もちろん、この押しボタンを関数に接続する必要もあります。今回は、事前に実行して関数 selectRandomViewpoint
を呼び出します。この関数をプラグイン クラスに追加し、実装のための記述を開始します。このツールは、ランダムなビューポイントを選択してアクティブにするだけなので、実行時間は大幅に短くなります。プラグインを再ロードすると、テキスト ボタンがイメージ ボタンに変わり、クリックするとランダムなビューポイントが選択されます。うまくできました。
ここから、使用したい機能を自由に実装できます。独自のツールを設計し、独自のルック & フィールを与えることができます。HTML ユーザ インタフェースを入力とするスクリーンプレートを使用するスクリプト プラグインを実装することもできます。注意しなければならない点は、VRED がすべての Python スクリプトを単一のスクリプト プラグインとして扱うことです。そのため、プラグインを複数のファイルに分割することが難しくなります。この問題を回避するには、使用する追加の Python モジュールを内部の VRED Python ライブラリ ディレクトリに移動します。このディレクトリ内のすべての Python モジュールは、VRED 内の任意のスクリプトからロードできます。
もう 1 つの注意事項: スクリプトにいくつかの VRED モジュールを明示的に読み込む必要があることに気付いたかもしれません。この操作は、API バージョン 1 のすべてのモジュールに行う必要があります。API バージョン 2 のモジュールは、VRED 自身によって自動的に追加されます。
このチュートリアルを終えると、スクリプト プラグインを使用して独自のツールを実装できるようになります。初めての場合は、VRED プラグインのサンプルを開始点として使用し、独自の実装を入力することをお勧めします。次に、Qt Designer ツールを使用して、独自のユーザ インタフェースの作成を開始できます。
今日の説明はこれで終わりです。ご視聴いただき、ありがとうございました。またお会いしましょう。
これは、「チュートリアル 4: スクリプト プラグインの記述方法」ビデオに付属する Python サンプル スクリプトです。
以下のファイルが含まれた ZIP ファイルをダウンロードするには、ここをクリックしてください。
ここに render_viewpoints_simple.py のサンプル コードがあります。
import vrFileIO
import vrFileDialog
import vrRenderSettings
from PySide2 import QtCore, QtWidgets
from shiboken2 import wrapInstance
def vredMainWindow():
main_window_ptr = getMainWindow()
return wrapInstance(int(main_window_ptr), QtWidgets.QMainWindow)
class CustomDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
boxlayout = QtWidgets.QVBoxLayout(self)
self.lineedit = QtWidgets.QLineEdit()
boxlayout.addWidget(self.lineedit)
self.button = QtWidgets.QPushButton("Set Label")
self.button.clicked.connect(self.buttonClicked)
boxlayout.addWidget(self.button)
self.label = QtWidgets.QLabel()
boxlayout.addWidget(self.label)
self.setLayout(boxlayout)
def buttonClicked(self):
self.label.setText(self.lineedit.text())
self.lineedit.setText("")
def renderViewpoints():
renderDirectory = vrFileDialog.getExistingDirectory("Select a render directory:", vrFileIO.getFileIOBaseDir())
if not renderDirectory:
print("No directory where to save the renderings!")
return
viewpoints = vrCameraService.getAllViewpoints()
for viewpoint in viewpoints:
name = viewpoint.getName()
vrRenderSettings.setRenderFilename("{}.jpg".format(name))
vrRenderSettings.startRenderToFile(False)
dialog = CustomDialog(vredMainWindow())
dialog.show()
次の3つのサンプル ファイルがあります。
viewpoint_plugin.py
viewpoint_plugin.ui
from PySide2 import QtCore, QtGui, QtWidgets
import os
import random
import threading
# Import vred modules in a try-catch block to prevent any errors
# Abort plugin initialization when an error occurs
importError = False
try:
import vrController
import vrFileIO
import vrMovieExport
import vrFileDialog
import vrRenderSettings
except ImportError:
importError = True
pass
import uiTools
# Load a pyside form and the widget base from a ui file that describes the layout
form, base = uiTools.loadUiType('viewpoint_plugin.ui')
class vrViewpointPlugin(form, base):
"""
Main plugin class
Inherits from fhe form and the widget base that was generated from the ui-file
"""
def __init__(self, parent=None):
"""Setup and connect the plugins user interface"""
super(vrViewpointPlugin, self).__init__(parent)
parent.layout().addWidget(self)
self.parent = parent
self.setupUi(self)
self.setupUserInterface()
# Initialize some class variables that we need for our loop function
self.loopCounter = 0
self.loopRunning = False
def setupUserInterface(self):
"""Setup and connect the plugins user interface"""
self._render_all.clicked.connect(self.renderViewpoints)
self._random_viewpoint.clicked.connect(self.selectRandomViewpoint)
self._random_viewpoint.setIcon(QtGui.QIcon("icon_random_viewpoint.png"))
self._random_viewpoint.setIconSize(QtCore.QSize(32,32))
self._loop_viewpoints.clicked.connect(self.loopViewpoints)
def renderViewpoints(self):
"""
Open a directory dialog and then render all viewpoints to that directory
"""
print("[Viewpoint Plugin] Render all viewpoints...")
renderDirectory = vrFileDialog.getExistingDirectory("Select a render directory:", vrFileIO.getFileIOBaseDir())
if not renderDirectory:
print("No directory where to save the renderings!")
return
viewpoints = vrCameraService.getAllViewpoints()
for viewpoint in viewpoints:
name = viewpoint.getName()
viewpoint.activate()
print("{}/{}.jpg".format(renderDirectory, name))
vrRenderSettings.setRenderFilename("{}/{}.jpg".format(renderDirectory, name))
vrRenderSettings.startRenderToFile(False)
msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle("Finished Rendering Viewpoints")
msgBox.setInformativeText("Finished Rendering Viewpoints. Do you want to open the render directory?")
msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
ret = msgBox.exec_()
if ret == QtWidgets.QMessageBox.Ok:
os.startfile(renderDirectory)
def selectRandomViewpoint(self):
""" Select a random viewpoint from all viewpoints """
print("[Viewpoint Plugin] Select a random viewpoint...")
viewpoints = vrCameraService.getAllViewpoints()
randomViewpoint = random.choice(viewpoints)
randomViewpoint.activate()
def loopViewpoints(self):
"""
Loops through all viewpoints once. Stops looping when the button is pressed again
"""
# When a loop is already running, then cancel the loop
if self.loopRunning:
print("[Viewpoint Plugin] Stop loop...")
self.__setLoopViewpointLabel("Loop Viewpoints")
self.loopRunning = False
return
# Otherwise start a new loop
if self.loopCounter == 0 and not self.loopRunning:
print("[Viewpoint Plugin] Loop all viewpoints...")
self.__setLoopViewpointLabel("Stop Loop")
viewpoints = vrCameraService.getAllViewpoints()
self.loopCounter = len(viewpoints) - 1
self.loopRunning = True
threading.Timer(1.0, self.__loopNextViewpoint).start()
def __loopNextViewpoint(self):
"""
Loops through all viewpoints once. Stops looping when the button is pressed again
"""
if self.loopCounter == 0 or not self.loopRunning:
self.loopRunning = False
self.loopCounter = 0
return
viewpoints = vrCameraService.getAllViewpoints()
viewpoints[self.loopCounter].activate()
self.loopCounter = self.loopCounter - 1
threading.Timer(2.0, self.__loopNextViewpoint).start()
def __setLoopViewpointLabel(self, labelText):
""" Change the text of the "loop" button """
self._loop_viewpoints.setText(labelText)
# Actually start the plugin
if not importError:
viewpointPlugin = vrViewpointPlugin(VREDPluginWidget)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>vrViewpointPlugin</class>
<widget class="QWidget" name="vrViewpointPluginGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Viewpoint Plugin</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="_render_all">
<property name="text">
<string>Render All Viewpoints</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_random_viewpoint">
<property name="text">
<string>Render All Viewpoints</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_loop_viewpoints">
<property name="text">
<string>Loop Viewpoints</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>