튜토리얼 5: 웹 인터페이스를 사용하여 Python 스크립트를 통해 원격으로 VRED를 제어하는 방법

웹 인터페이스를 사용하여 Python 스크립트를 통해 원격으로 VRED를 제어하는 방법에 대해 알아봅니다.

예제 스크립트 다운로드

스크립트 예제로 이동

튜토리얼의 PDF 다운로드

동영상 캡션: 안녕하세요. VRED Pro를 위한 Python 튜토리얼에 오신 것을 환영합니다. 저는 크리스토퍼라고 하며, 오늘은 VRED의 웹 인터페이스를 사용하여 Python 스크립트를 통해 원격으로 VRED를 제어하는 방법을 보여 드리겠습니다.

여러분은 VRED의 웹 인터페이스 및 사용 방법에 이미 익숙하실 것입니다. 활성화된 경우 VRED는 웹 앱을 호스트하는 웹 서버를 시작하여 브라우저를 통해 VRED를 원격으로 제어합니다. 카메라를 이동하거나, 라이브 비디오 스트림을 생성하고, 정의된 변형 세트를 트리거할 수 있으므로 설계 검토 또는 고객 프레젠테이션에 적합합니다.

웹 인터페이스를 활성화하려면 기본 설정(Preferences)으로 이동하여 웹 인터페이스(WebInterface)로 스크롤해야 합니다. 여기에서 웹 인터페이스를 활성화할 수 있습니다. 변경 사항을 적용하면 기본적으로 로컬 호스트에서 웹 인터페이스를 사용할 준비가 된 것입니다. 그러나 iPad를 사용하여 액세스할 수 있도록 하려면 호스트 액세스를 "제한 없음"으로 설정하거나 해당 IP 주소를 사용하여 호스트 목록에 iPad를 추가해야 합니다.

테스트를 위해서는 호스트 액세스를 "제한 없음(no restriction)"으로 설정하는 것도 좋지만 보안을 위해서는 항상 호스트 목록을 사용해야 합니다. 동일한 네트워크에 있는 경우 사용 중인 디바이스의 IP 주소를 입력하기만 하면 웹 인터페이스에 연결할 수 있습니다. 일부 다른 설정이 있지만 이 튜토리얼에서 이러한 모든 설정을 모두 다루지는 않습니다. 웹 인터페이스 기본 설정에 대한 온라인 설명서에 모든 설정이 설명되어 있습니다.

현재 iPad를 사용하여 VRED 장면에 연결되었으며 손가락으로 장면을 제어하고 변형 세트 간에 전환할 수 있습니다. 스냅샷 생성, 설정 변경 등을 수행할 수 있습니다. 간단한 프레젠테이션을 수행하려는 경우에는 이 기능만으로도 충분합니다. 그러나 VRED 스트리밍 앱을 사용하면 터미널에서 실행되는 Python 스크립트를 직접 입력할 수도 있습니다. 여기에 새 장면을 생성하고 장면 그래프에 상자를 추가하는 예제가 이미 있습니다.

터미널을 사용하여 장면에서 Python 코드를 실행할 수 있습니다. 터미널 예제에서와 같이 Python API를 직접 사용하여 형상을 만들거나, 스크립트 편집기에 정의한 함수 또는 가져온 Python 모듈의 함수와 같이 장면에서 액세스할 수 있는 함수를 호출합니다. 여기서 터미널은 VRED에서 사용하는 경우 스크립트 편집기와 유사하게 작동합니다. 입력한 모든 항목은 직접 실행되고 장면과 상호 작용합니다.

Python 터미널을 사용하여 기본 스트리밍 앱을 통해 VRED를 제어하는 방법을 알아보았습니다. 사용자화 Python 스크립트를 호출하는 또 다른 유용한 방법은 변형 세트를 사용하는 것입니다. “Scripts” 또는 원하는 다른 이름으로 변형 세트 그룹을 생성하고 실행하려는 Python 코드가 포함된 변형 세트를 추가하면 됩니다. 이렇게 하면 특정 작업에 대한 버튼만 포함하도록 사용자화 인터페이스를 프로그래밍할 필요가 없습니다. 기본 스트리밍 앱에서 이미 이 작업을 처리하여 변형 세트에 메뉴 항목을 생성합니다.

물론, VRED와 상호 작용하는 웹 인터페이스를 자체적으로 구축할 수도 있습니다. 이 작업을 수행할 때는 원격 앱을 완전히 사용자화하고 원하는 대로 정확하게 스타일을 지정할 수 있습니다.

먼저 끝점을 사용하여 VRED와 상호 작용하는 방법을 보여 드리겠습니다. 끝점은 실행하려는 Python 코드가 포함된 웹 인터페이스에 대한 GET 요청입니다. 여기에서는 변형 세트를 선택하는 방법과 사용자화 웹 앱 내에서 다른 Python 함수를 트리거하는 방법에 대한 예제를 보여 드리겠습니다. 다른 쿼리 Python 함수를 웹 인터페이스에서 제공하는 끝점에 전송할 수 있습니다.

첫 번째 예제에서는 Python 끝점을 사용합니다. 그러면 Python 명령을 VRED로 전송하고 반환되는 값은 없습니다. Python 명령은 "value" 매개변수와 함께 제공되며 URL 인코딩 상태여야 합니다. 버튼을 클릭하면 메시지가 끝점으로 전송되어 터미널에서 메시지를 출력할 수 있습니다. 같은 방법을 사용하여 변형 세트를 선택합니다. 여기에서는 장면에 정의된 변형 세트를 사용하여 검은색 금속 재질을 선택합니다.

웹 인터페이스에서 데이터를 반환할 수도 있는 다른 끝점이 있습니다. 이 끝점을 "pythoneval2"라고 합니다. Python 끝점과 마찬가지로 명령을 VRED로 보낼 수 있지만 이제 반환 값 또한 관리해야 합니다. 이 경우 모든 뷰포인트를 요청하고 콘솔에 출력되는 뷰포인트 배열을 수신합니다. GET 요청이 반환하는 데이터 형식을 확인해야 합니다. 이 경우, JavaScript 배열로 평가할 수 있는 문자열입니다.

하지만 사용자화 웹 인터페이스를 구축하는 최신 접근 방식은 오토데스크에서 버전 2021에 도입한 웹 API를 사용하는 것입니다. 웹 API는 웹 앱에 통합된 JavaScript 응용프로그램으로, Python 끝점을 사용하지 않고도 VRED의 Python API에 대한 완전한 액세스를 지원합니다. 웹 API는 Python API 버전 2의 모든 모듈 및 함수를 지원합니다.

따라서 API 버전 1을 호출해도 이전 예제에서와 같이 제공된 끝점을 계속 사용할 것입니다. 웹 인터페이스에서 스크립트를 가져와 앱에 웹 API를 추가할 수 있습니다. API를 가져올 수 있는 새 JavaScript 모듈을 생성합니다. 그런 다음 주석과 함수를 추가하여 모든 주석을 다시 가져올 수 있습니다. 또한 주석을 VRED에 추가할 때마다 신호 이벤트에 연결합니다. 이 설정은 비교적 간단한 설정이며, 일반적으로 이 설정은 정식 웹 앱에 포함됩니다. 여기서는 일반적인 개념만 설명합니다.

함수를 HTML 문서로 표시하려면 전역 범위에 추가해야 합니다. 이 작업은 윈도우 객체에 함수를 추가하는 방법으로 수행합니다. HTML 문서에서 새 JavaScript 파일을 유형 모듈로 링크하면 이제 버튼 중 하나를 클릭하여 함수를 호출할 수 있습니다.

예제를 통해 Python 버전과 유사하게 서비스를 참조하고 함수를 호출하여 JavaScript 함수를 호출할 수 있는 것을 확인할 수 있습니다. 또한 약속 및 오류 처리와 같은 멋진 JavaScript 기능을 사용하여 비동기 방식으로 수신한 데이터를 처리할 수도 있습니다.

이 예제를 실행하려면 로컬 웹 서버를 사용하여 파일을 제공해야 합니다. 이는 로컬로 실행하는 경우 교차 원점 리소스 정책이 JavaScript 파일에 대한 액세스를 허용하지 않기 때문입니다. 이를 위해 노드와 함께 설치한 HTTP Server 도구를 사용합니다. 물론 원하는 모든 서버를 사용할 수 있습니다.

로컬 웹 서버가 제공하는 주소의 페이지를 방문할 수 있습니다. 버튼을 클릭하면 주석이 장면에 추가된 것을 볼 수 있고 모든 주석 목록을 요청하면 콘솔에 새 주석도 표시됩니다. 또한 오토데스크는 VRED의 예제 디렉토리에 웹 앱의 예제를 포함했습니다. 사용자화 웹 앱을 구축하는 데 관심이 있다면 이 예제가 좋은 시작점이 될 것입니다. 가장 간단한 예제인 stream js 예제로 시작할 수 있습니다. 그러나 Stream 앱은 react.js 앱을 기반으로 하며 약간 더 복잡합니다.

웹 인터페이스 및 해당 웹 API는 VRED와의 상호 작용에 있어 완전히 새로운 가능성을 제공합니다. 이제, 완벽하게 사용자화된 스트리밍 앱을 쉽게 구축하고 엔진을 기반으로 완전히 새로운 도구를 구축할 수 있습니다.

오늘은 이것으로 마치겠습니다. 동영상이 도움이 되셨기를 바랍니다. 다음에 또 뵙겠습니다.


샘플 Python 코드

튜토리얼 5: 웹 인터페이스를 사용하여 Python 스크립트를 통해 원격으로 VRED를 제어하는 방법 동영상과 함께 제공되는 예제 Python 스크립트입니다.

팁:

이러한 파일을 압축하여 다운로드하려면 여기를 클릭하십시오.

custom-api.js

import { api } from 'http://localhost:8888/api.js';

// Connect event listener (signal)
api.vrAnnotationService.annotationsAdded.connect(() => console.log('Annotations added'));

// Add an annotation with name and text to the scene
function addAnnotation(name, text) {
    api.vrAnnotationService.createAnnotation(name)
    .then(() => {
        api.vrAnnotationService.findAnnotation(name)
        .then(a => a.setText(text));
    })
    .catch(() => console.error('Add annotation failed'));
}

// Get a list of all annotations
function getAnnotations() {
    api.vrAnnotationService.getAnnotations()
    .then(annotations => annotations.forEach(a => console.log(a)))
    .catch(() => console.error('Get annotations failed')); 
}

// Add both functions to the global scope
// So that we can call them from the html document
window.addAnnotation = addAnnotation;
window.getAnnotations = getAnnotations;

custom_web_interface_endpoints.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <script>
            // Send a generic python command without return value
            function sendPythonCommand(command) {
                var http = new XMLHttpRequest();
                var url = 'http://localhost:8888/python?value=' + encodeURI(command);
                http.open('GET', url, true);
                http.send();
            }

            // Send a python command with expecting a return value
            function sendPythonCommandWithReturnValue(command) {
                var http = new XMLHttpRequest();
                var url = 'http://localhost:8888/pythoneval2?value=' + encodeURI(command);
                http.open('GET', url, true);
                http.onreadystatechange = function() {
                    if (this.readyState == 4 && this.status == 200) {
                        var response = eval(this.responseText);
                        console.log(response);
                    }
                };
                http.send();
            }

            // Write a message to the terminal
            function logInfo(message) {
                sendPythonCommand("logInfo('"+message+"')");
            }

            // Select a variant set 
            function selectVariantSet(variantSet) {
                sendPythonCommand("selectVariantSet('"+variantSet+"')");
            }

            // get all viewpoints
            function getViewpoints() {
                sendPythonCommandWithReturnValue('getViewpoints()');
            }
        </script>
    </head>
    <body>
                <button onclick="logInfo('This is a message')">Print Info Message</button>

        <button onclick="selectVariantSet('Black Metallic')">Variant: Select Black Metallic</button>

        <button onclick="getViewpoints()">Request Viewpoints</button>
    </body>
</html>

custom_web_interface_web_api.html

<!DOCTYPE html>
<html>
    <head>
        <script type="module" src="custom-api.js"></script>
    </head>
    <body>
                <button onclick="addAnnotation('New Annotation', 'This is a new annotation')">Add annotation</button>
        <button onclick="getAnnotations()">Get all annotations</button>
    </body>
</html>