스크립트 플러그인 및 사용자 고유의 쓰기 방법에 대해 알아봅니다.
동영상 캡션: 안녕하세요. VRED Pro를 위한 Python 스크립팅 튜토리얼 시리즈에 오신 것을 환영합니다. 저는 크리스토퍼라고 하며, 오늘은 스크립트 플러그인을 소개하고 이를 직접 작성하는 방법에 대해 알아보겠습니다.
지난 튜토리얼에서 배운 내용처럼 VRED는 고유한 Python 스크립트를 추가하여 장면을 사용자화할 수 있는 다양한 인터페이스를 제공합니다. 간단한 몇 가지 스크립트를 통해 제작 과정에서 도움이 되는 사용자 상호 작용 및 도구를 구현할 수 있습니다. 모든 동료 또는 고객이 사용할 수 있는 전문 도구를 제공하려면 스크립트 플러그인을 작성하는 것을 고려하는 것이 좋습니다.
스크립트 플러그인은 특수한 Python 스크립트 유형입니다. 이러한 플러그인은 VRED 장면과는 독립적이며, 사용하려면 특수 스크립트 플러그인 디렉토리에 설치되어야 합니다. 설치된 모든 스크립트 플러그인이 나열된 VRED의 메뉴 막대에서 "스크립트(Script)" 항목을 열어 스크립트 플러그인에 액세스할 수 있습니다. 해당 항목을 클릭하여 각 플러그인을 활성화 및 비활성화할 수 있습니다. 스크립트 플러그인은 일반적으로 VRED에 통합된 사용자 인터페이스를 제공하며 마치 VRED의 일부인 것처럼 느껴집니다. PySide 위젯이 기본적으로 VRED 윈도우 스타일을 사용하기 때문입니다. 이 튜토리얼에서는 스크립트 플러그인을 함께 구축하고 그 과정에서 사용자 인터페이스를 구축하고 새 도구를 구축하는 방법을 배웁니다.
이 동영상에서는 뷰포인트를 사용할 때 유용한 간단한 플러그인을 구축해 보겠습니다. 먼저, 디렉토리를 지정해 모든 뷰포인트를 렌더링할 수 있는 도구를 구축합니다. 두 번째로, 뷰포인트를 임의로 선택하고 카메라를 이 뷰포인트로 설정하겠습니다.
이러한 기능을 구현하려면 먼저 플러그인 클래스를 생성해야 합니다. 이 클래스 설정은 기본적으로 우리가 개발하는 각 플러그인에 대해 동일합니다. 먼저 "내 문서", "Autodesk", "VRED13.3", "ScriptPlugins" 아래에 새 디렉토리를 만듭니다. 이 디렉토리는 "ViewpointPlugin"이라고 하며 우리가 생성하는 모든 플러그인 파일이 여기에 포함됩니다. 이 디렉토리가 시스템에 없을 경우 생성할 수 있으며 VRED는 여기서 스크립트 플러그인을 읽습니다.
다음 예제에서는 Python 익스텐션이 설치된 상태에서 Visual Studio Code를 사용합니다. 새로 만든 디렉토리에서 Visual Studio Code를 열고 두 개의 파일 viewpoint_plugin.py와 viewpoint_plugin.ui를 만들 수 있습니다. 첫 번째 파일에는 Python 코드가 포함되며, 두 번째 파일에는 사용자 인터페이스 레이아웃이 포함됩니다. 스크립트 맨 위에서, PySide 네임스페이스에서 필요한 모듈을 가져옵니다. QtCore, QtGui 및 QtWidgets입니다. 플러그인 위젯을 생성하는 데 도움이 되는 uiTools도 가져옵니다.
다음으로 UI 도구를 사용하여 양식 및 위젯 베이스를 로드할 수 있습니다. 여기서는 방금 생성한 UI 파일을 참조합니다. 그런 다음 양식에서 상속한 실제 플러그인 클래스와 방금 생성한 베이스를 추가할 수 있습니다. init 생성자의 시작은 항상 동일하며 작성한 모든 플러그인에 대해 이를 복사하면 됩니다.
클래스 아래에서 "VRED 플러그인 위젯"을 생성자의 매개변수로 사용하여 플러그인을 인스턴스화할 수 있습니다. 너무 빨리 진행되어 모든 사항을 잘 이해하지 못하셨더라도 걱정할 필요가 없습니다. 이것은 특수 플러그인 코드 중 하나로, 항상 동일합니다. 직접 개발하려는 경우 VRED의 플러그인 예제에서 이 플러그인 구조를 복사할 수 있습니다. 물론 언제든지 이 튜토리얼 앞쪽으로 돌아가거나 일시 중지 버튼을 눌러 가면서 시청하셔도 좋습니다.
이제 Python 쪽에서는 완료되었지만 사용자 인터페이스도 정의해야 합니다. 이 작업은 viewpoint_plugin.ui 파일에서 수행됩니다. 레이아웃 파일은 xml로 사용자 인터페이스를 정의합니다. 이 튜토리얼에서는 몇 개의 버튼만 추가하므로 그다지 복잡하지 않습니다. 여기서 중요한 점은 두 개의 누름 버튼을 추가하고 이름을 지정하는 것입니다. 이 이름은 나중에 Python 스크립트에서 버튼을 참조하는 데 사용됩니다. 편집기를 통해 이러한 QT 레이아웃 파일을 만들고 내보내는 "QT Designer"를 확인하는 것이 좋습니다. 이 튜토리얼에서는 이 작업을 직접 수행해도 괜찮지만 대규모 프로젝트에서는 매우 지루합니다.
파일을 저장할 때 VRED로 다시 전환하고 스크립트 플러그인을 다시 로드할 수 있습니다. 이제 "스크립트(Script)" 메뉴에 또 다른 항목이 있는 것을 볼 수 있습니다. 이 항목을 클릭하면 새 플러그인 인터페이스가 표시됩니다. 이미 열고 닫을 수 있는 UI가 있지만 지금은 기능이 여전히 누락되어 있습니다.
모든 뷰포인트를 렌더링하는 첫 번째 도구를 구현해 보겠습니다. "render viewpoints"라는 새 함수를 정의합니다. 먼저 파일 대화상자 모듈을 사용하여 사용자에게 디렉토리를 요청할 수 있습니다. 사용자가 디렉토리를 선택하면 괜찮지만 사용자가 작업을 취소할 경우 이를 파악하여 아무 작업도 하지 않고 함수에서 돌아오게 해야 합니다. 다음 단계에서는 사용할 수 있는 모든 뷰포인트 목록을 반복하고 모든 뷰포인트에서 활성화합니다. 그런 다음 파일 이름을 생성하고 뷰포인트를 파일에 렌더링합니다. 사용자가 선택한 디렉토리 및 뷰포인트 이름에서 렌더링을 저장할 위치를 VRED에 알려주는 파일 경로를 생성할 수 있습니다. 렌더링이 완료되면 메시지 대화상자를 표시하여 사용자에게 파일이 저장된 디렉토리를 열지를 묻는 것도 좋습니다. 이 작업은 사용자 선택 사항을 반환할 수 있는 "QMessageBox"를 통해 수행할 수 있습니다. 여기에는 확인(OK) 및 취소(CANCEL) 버튼이 표시되고 사용자가 확인(OK) 버튼을 클릭하면 기본 Python 방식으로 디렉토리를 엽니다.
스크립트 중 중요한 부분이 빠진 상태이기 때문에 지금 바로 테스트할 경우에는, 아무것도 나타나지 않습니다. 누름 버튼에 렌더 함수를 연결해야 합니다. 이 작업은 UI 레이아웃에서 지정한 이름과 함께 참조하는 버튼의 clicked 신호에 연결하는 방법으로 수행할 수 있습니다. 이제, 계속할 수 있습니다. 스크립트 플러그인을 다시 로드하고 새 플러그인을 시작하여 구현을 테스트할 수 있습니다. 예상대로 모든 뷰포인트가 렌더링되고, 렌더링이 완료된 후 렌더 디렉토리를 열지 묻는 메시지가 표시됩니다.
두 번째 함수를 구현해 보겠습니다. 이번에는 버튼에 텍스트 대신 아이콘을 추가하려고 합니다. UI 레이아웃 파일의 두 번째 누름 버튼에서 텍스트 노드를 삭제하고 다시 Python 파일로 돌아갑니다. setIcon 함수를 사용하여 버튼에 직접 아이콘을 설정하여 추가할 수 있습니다. 이 함수에는 아이콘 이미지를 사용해 생성하는 입력으로 QIcon 매개변수가 필요합니다. 아이콘은 플러그인 디렉토리에 저장된 PNG 이미지일 뿐입니다.
아이콘 크기도 너무 커지지 않게 사용자 인터페이스에 맞춰 설정해야 합니다. 물론 이 누름 버튼을 함수에 연결해야 합니다. 이번에는 작업을 미리 수행하고 selectRandomViewpoint 함수를 호출합니다. 이 함수를 플러그인 클래스에 추가하고 이에 대한 구현을 작성합니다. 이 도구는 임의의 뷰포인트를 선택하고 활성 상태로 설정하면 되기 때문에 훨씬 간단합니다. 플러그인을 다시 로드하면 텍스트 버튼이 이미지 버튼으로 변경되고 이 버튼을 클릭하면 임의의 뷰포인트가 선택됩니다. 좋습니다!
이제 여러분이 생각한 모든 기능을 자유롭게 구현할 수 있습니다. 자체적으로 도구를 설계하고 고유한 모양과 느낌을 지정할 수 있을 것입니다. HTML 사용자 인터페이스가 포함된 장면 플레이트를 입력으로 사용하는 스크립트 플러그인을 구현할 수도 있습니다. 주의해야 할 사항은, VRED는 모든 Python 스크립트를 단일 스크립트 플러그인으로 처리한다는 점입니다. 따라서 플러그인을 여러 파일로 분할하기가 어렵습니다. 사용하는 추가 Python 모듈을 내부 VRED Python 라이브러리 디렉토리로 이동하면 이 문제를 해결할 수 있습니다. 이 디렉토리에 있는 모든 Python 모듈은 VRED의 모든 스크립트에서 로드할 수 있습니다.
스크립트에서 일부 VRED 모듈을 명시적으로 가져와야 한다는 점에도 주의해야 합니다. 이 사항은 API 버전 1의 모든 모듈에 대해 수행해야 합니다. API 버전 2의 모듈은 VRED 자체에서 자동으로 추가됩니다.
이 튜토리얼을 마친 후에는 스크립트 플러그인을 사용하여 자체 도구를 구현할 수 있습니다. 이제 막 시작하는 경우 VRED 플러그인 예제를 시작점으로 사용하여 사용자 고유의 구현 사항으로 채우는 것이 좋습니다. 그런 다음 QT Designer 도구를 사용하여 자체 사용자 인터페이스를 만들 수 있습니다.
오늘은 이것으로 마치겠습니다. 시청해 주셔서 감사합니다. 다음에 뵙겠습니다.
튜토리얼 4: 스크립트 플러그인을 작성하는 방법 동영상과 함께 제공되는 예제 Python 스크립트입니다.
이러한 파일을 압축하여 다운로드하려면 여기를 클릭하십시오.
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()
세 가지 예제 파일이 있습니다.
![]()
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>