VRED Core Web API を使用する方法について説明します。
ビデオ キャプション: VRED Core の最後のチュートリアル ビデオへようこそ。Christopher といいます。本日は、前回のビデオで作成したストリーミング アプリのサンプルを拡張し、VRED の Web API を使用してさらに多くの機能を実装します。
Python API v2 で、オートデスクは Python Web API も導入しました。これは、Python コマンドを VRED に送信するために使用できる Python エンドポイントの直接の後継機能です。前回のビデオでは Python エンドポイントを使用しました。VRED Core 向けの独自の HTML インタフェースを構築して Python コマンドを VRED に送信し、ビデオ ストリームを実装しました。
たとえば、任意の Python コマンドを Python エンドポイントに送信して、応答としてこの操作の結果を受け取ることができます。これらのエンドポイントの短所は、オブジェクトやレンダリングしたインタフェースを使用する代わりに、Python コマンドの文字列表現を送信する必要があること、および javascript アプリで使用するために戻り値を適切に渡す必要があることです。
新しい Web API を使用すると、VRED は完全に統合された javascript モジュールを提供します。これは VRED の Python インタフェースへの直接接続のように動作します。Web API は、Python API バージョン 2 で使用できるすべての機能に対して、オブジェクト指向のアクセス ポイントを提供します。つまり、適切な javascript タイプにアクセスでき、Promise やエラー処理など、一般的な javascript 機能を使用できます。
たとえば、このような javascript 呼び出しを使用して新しいカメラを作成し、作成したカメラを呼び出すことができます。その後、そのカメラ オブジェクトを使用して名前を設定したり、アクティブにしたり、別の方法で処理したりすることができます。もちろん、カメラ オブジェクトには、Python クラス vrdCamera でも使用できるすべての関数が含まれています。
独自のアプリで新しい Web API を使用する方法を見てみましょう。このチュートリアルでは、前回のビデオのカスタム HTML アプリの上に注釈マネージャを統合します。前回のビデオを見ていない場合は、このチュートリアルのコードを直接拡張するので、最初にビデオを見ることをお勧めします。
ここまで、よろしいですか?では、始めましょう。
前回のチュートリアルでは、HTML ファイルにすべての CSS と javascript コードを配置しました。整理するために、スタイルシートとスクリプトを別々のファイルに移動しました。これで、HTML は大幅に整理されました。
最初に、シーンに注釈を追加する方法を追加します。このチュートリアルでは、これらの注釈の位置を設定せず、注釈を単に追加して名前と説明を付けます。これを行うには、名前と説明の 2 つの入力を含むフォームを HTML コードに追加し、[送信] ボタンをクリックして注釈を作成します。これを div コンテナにラップし、CSS を使用していくつかのスタイルを与え、ビデオ ストリームに配置します。今回は、左上隅に配置します。
次に、webapiscript という名前の新しい javascript ファイルを作成します。このファイルには、注釈マネージャ用の javascript コードが含まれています。この読み込みステートメントを使用して、VRED Core の javascript API を javascript モジュールに追加します。ご覧のように、サンプルではポート 8888 の localhost を使用しています。VRED Core の設定方法が異なる場合は、これらの値を調整する必要があります。
最初に、パラメータ name と description を持つ新しい関数 addAnnotation を追加します。関数内で、読み込んだ VRED の javascript API を直接呼び出します。ご覧のように、Python API と非常によく似た API を使用できます。
ここでは、vrAnnotation サーバを使用し、name パラメータを使用して createAnnotation メソッドを呼び出します。これは、then 関数と非同期に処理する Promise オブジェクトを返します。javascript のパターンに慣れていないかもしれませんが、これは async/await パターンと呼ばれ、プログラミングで同時に処理するための最新のアプローチです。基本的に、createAnnotation が戻るまでには時間がかかります。それを待ってから次のアクションを実行してください。このように、スクリプトは作業を続行するだけが可能であり、VRED を待つときにルックアップすることはありません。
and then 関数内で、findAnnotation 関数を安全に呼び出して、新しい注釈ノードへの参照を取得できるようになりました。同じ async/await パターンに従って、新しい注釈のテキストを description パラメータに設定します。注釈の作成中にエラーが発生した場合は、このエラーを検出してユーザに通知することができます。この場合は、Python ターミナルにエラー メッセージを出力します。
注釈の追加が可能になりましたが、作成した HTML フォームに接続する必要があります。これを行うには、フォームの送信解除コールバックにフックします。ここでは、名前と説明の入力を抽出し、addAnnotation 関数を呼び出すことができます。新しい機能をテストすると、すべてが問題なく動作し、シーンに新しい注釈を追加できることが分かります。よくできました。
また、注釈を削除する機能とともに、シーンに注釈のリストを追加します。HTML では、ID を持つ順序付けされていないリスト要素を追加するだけです。このリストに注釈を動的に追加および削除するには、この操作を行うだけで十分です。
javascript スクリプトでは、すべての注釈をロードする関数から開始します。vrAnnotationService を使用してすべての注釈のリストを取得します。async/await パターンでは、VRED からのすべての注釈のリストを待ってから、1 つずつ反復処理します。
注釈ごとに、名前と名前を含むテキストを照会します。この操作には、async/await パターンを使用します。そのために、loop 関数を async としてマークする必要があります。名前と注釈テキストを使用して、後で実装する addAnnotationItem という 2 番目の関数を呼び出すことができます。
まず最初に、loadAnnotation 関数が呼び出されるたびにリストがクリアされるようにします。このため、すべてのリストからすべてのアイテムをクリアする deleteAnnotationItems 関数を追加し、load 関数の最初でそれを呼び出します。そして、name と description のパラメータを持つ addAnnotationItem という新しい関数を作成します。
ここでは、注釈の名前と説明を保持するテキスト ノードを挿入する新しい HTML リスト要素を作成します。名前が定義されていない場合は、この関数から戻ります。
また、VRED で注釈を削除するボタンを追加する必要があります。これを行うには、注釈にリンクを追加します。リンクを表示するには、何かをリンク アトリビュートとして設定する必要があります。しかし、これには機能がありません。代わりに、onClick 関数にフックし、クリックした場合は、AnnotationService を使用してこの注釈を削除します。
ここでは「async/await」パターンも使用しました。最後に、リストに注釈要素を追加します。現在、ストリーミング アプリをロードすると、注釈リストが 1 回ロードされますが、更新はされません。アプリを動的にするために、vrAnnotationService からのシグナルを使用して、注釈が追加または削除された日時を通知することができます。これは、Python インタフェースで信号を使用する場合と非常に似ています。
以上です。アプリをテストする際に、新しい注釈を追加して、すぐに注釈リストに表示することができます。また、シーンから注釈を削除する[削除]ボタンもあります。
すべての注釈が同じ位置に追加されますが、これは別のチュートリアルで行うタスクになる場合もあります。これで、VRED の Web API を使用する方法と、Web API を使用してシーンをコントロールする動的でインタラクティブな HTML アプリケーションを構築する方法について、かなり良いアイデアが得られたと思います。このビデオで、VRED Core のチュートリアル シリーズも終了します。VRED Core の使用を開始する方法、およびこの製品を拡張して使用するときに実行できる機能についてご覧いただきました。
ご参加いただき、ありがとうございました。またお会いしましょう。
これは、「チュートリアル 6: VRED Core Web API を使用する方法」ビデオに付属する Python サンプル スクリプトです。
以下のファイルが含まれた ZIP ファイルをダウンロードするには、ここをクリックしてください。
<!DOCTYPE html>
<html>
<head>
<title>Web API Example: Create Camera</title>
Web API Module
<script type="module">
import { api } from 'http://localhost:8888/api.js';
window.onload = function() {
// Use vrCameraService to create a camera in the scene
// Wait for the result and "then"...
api.vrCameraService.createCamera("New Custom Camera").then(async (vrdCamera) => {
// ... set a new name for the camera...
await vrdCamera.setName("New Custom Camera - Changed Name");
// ... and set the camera active
await vrdCamera.activate();
// Test
api.vrCameraService.getCameraNames().then((names) => {
window.alert(names.join('\n'));
})
});
}
</script>
</head>
</html>
<body>
</body>
<!DOCTYPE html>
<html>
<head>
<title>My Custom App</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
<script type="module" src="web-api-script.js"></script>
</head>
<body>
<div id="page">
<div id="add-annotations-container">
<h2>Annotations</h2>
<form id="annotationForm">
<input name="name" type="text" placeholder="Name" required />
<input name="description" type="text" placeholder="Description" required />
<button type="submit">Add</button>
</form>
<ul id="annotations"></ul>
</div>
Variant Set Container
<div id="variant-set-container">
<h2>Variant Sets</h2>
</div>
Video Stream Container
<iframe id="stream-container" frameborder="0" overflow="hidden" scroll="no"></iframe>
Python Terminal Container
<div class="python-interface">
<h2>Python</h2>
<div class="python-send-wrapper">
<input id="python-value" type="text" placeholder="Enter your python code..."></input>
<button onclick="executeTerminalCode(document.getElementById('python-value').value)">Send Python</button>
</div>
<textarea id="python-response"></textarea>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>My Custom App</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<div id="page">
Variant Set Container
<div id="variant-set-container"></div>
Video Stream Container
<iframe id="stream-container" frameborder="0" overflow="hidden" scroll="no"></iframe>
Python Terminal Container
<div class="python-interface">
<div class="python-send-wrapper">
<input id="python-value" type="text" placeholder="Enter your python code..."></input>
<button onclick="executeTerminalCode(document.getElementById('python-value').value)">Send Python</button>
</div>
<textarea id="python-response"></textarea>
</div>
</div>
</body>
</html>
var host = "localhost";
var port = "8888";
function initializeStream() {
var w = window.innerWidth;
var h = window.innerHeight;
document.getElementById("stream-container").src = 'http://' + host + ':' + port + '/apps/VREDStream/index.html?width='+w+'&height='+h+'';
}
function updateVariantSets() {
sendAndReceivePython("getVariantSets()",
(response) => {
var variantSets = eval(response);
if(variantSets) {
// get a reference to our variant set container
var variantSetContainer = document.getElementById('variant-set-container');
// add element for each variant set
variantSets.forEach(variantSet => {
var variantSetNode = document.createElement('div');
variantSetNode.innerHTML = variantSet;
variantSetNode.onclick = function() {
sendAndReceivePython("selectVariantSet('"+variantSet+"')");
}
variantSetContainer.appendChild(variantSetNode);
});
}
}
);
}
function executeTerminalCode(python) {
console.log(python);
sendAndReceivePython(python,
(response) => {
var text = document.getElementById('python-response').value;
text += '\n' + response;
document.getElementById('python-response').value = text;
},
(error) => console.error("Error sending pyhton command.", python)
);
}
function sendAndReceivePython(command, successCallback, errorCallback) {
var http = new XMLHttpRequest();
var url = 'http://' + host + ':' + port + '/pythoneval2?value=' + encodeURI(command);
http.open('GET', url, true);
http.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
if(this.responseText && successCallback) {
successCallback(this.responseText)
}
}
};
http.onerror = function() {
if(errorCallback) {
errorCallback();
}
}
http.send();
}
window.onload = function() {
initializeStream();
updateVariantSets();
}
window.onresize = function() {
initializeStream();
}
html, body {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
}
h2 {
color: white;
}
#page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
#variant-set-container {
padding: 1em 2em;
position: absolute;
left: 1em;
bottom: 1em;
background-color: #2d2d2d;
display: flex;
flex-direction: column;
height: 20em;
overflow-y: scroll;
color: white;
}
#variant-set-container > div {
padding: 0.1em 0.2em;
background-color: #4b4b4b;
margin-bottom: 0.1em;
cursor: pointer;
}
#stream-container {
width: 100%;
height: 100%;
overflow: hidden;
}
.python-interface {
position: absolute;
padding: 1em 2em;
right: 1em;
bottom: 1em;
background-color: #2d2d2d;
display: flex;
flex-direction: column;
}
.python-send-wrapper {
margin-bottom: 0.5em;
}
.python-send-wrapper > input {
width: 25em;
}
textarea#python-response {
height: 10em;
resize: none;
}
#add-annotations-container {
position: absolute;
padding: 1em 2em;
left: 1em;
top: 1em;
background-color: #2d2d2d;
display: flex;
flex-direction: column;
}
#annotations {
color: white;
}
#annotations li {
list-style: none;
display: flex;
}
#annotations li strong {
margin-right: 0.5em;
}
#annotations li a {
color: white;
margin-left: 0.5em;
float: right;
}
import { api } from "http://localhost:8888/api.js";
// -- PART 1 --
// Add an annotation with a name and a description
function addAnnotation(name, description) {
api.vrAnnotationService
.createAnnotation(name)
.then(() => {
api.vrAnnotationService.findAnnotation(name).then((a) => {
a.setText(description);
addEventMessage("Annotations added successfully");
});
})
.catch(() =>
document.getElementById("python-response")("Add annotation failed")
);
}
// Annotation form
annotationForm.onsubmit = (ev) => {
ev.preventDefault();
const name = ev.target.elements.name.value;
const description = ev.target.elements.description.value;
addAnnotation(name, description);
};
// -- END: PART 1 --
function addEventMessage(message) {
var textarea = document.getElementById("python-response");
var text = textarea.value;
textarea.value = message + "\n" + text;
}
// Adds a new list item to the unordered list #annotations.
// function addAnnotationItem(name, description, deleteCallback) {
function addAnnotationItem(name, description) {
let item = undefined;
if (name) {
item = document.createElement("li");
const nameItem = document.createElement("strong");
nameItem.appendChild(document.createTextNode(name));
item.appendChild(nameItem);
} else {
return;
}
if (description) {
const descriptionItem = document.createElement("span");
descriptionItem.appendChild(document.createTextNode(description));
item.appendChild(descriptionItem);
}
const link = document.createElement("a");
link.href = `?delete=${name}`;
link.appendChild(document.createTextNode("( X )"));
link.onclick = (e) => {
deleteAnnotation(name);
e.preventDefault();
};
item.appendChild(link);
document.getElementById("annotations").appendChild(item);
}
function deleteAnnotation(name) {
api.vrAnnotationService
.findAnnotation(name)
.then((a) => api.vrAnnotationService.deleteAnnotation(a));
}
// Delete all annotations in the list
function deleteAnnotationItems() {
const list = document.getElementById("annotations");
list.textContent = "";
}
// -- PART 2.2 --
// Loads annotations and adds annotation names to the unordered list #annotations.
function loadAnnotations() {
deleteAnnotationItems();
api.vrAnnotationService
.getAnnotations()
.then((annotations) => {
if (annotations.length > 0) {
annotations.forEach(async (a) => {
const name = await a.getName();
const description = await a.getText();
addAnnotationItem(name, description);
});
} else {
addAnnotationItem("No annotations found.");
}
})
.then(() => addEventMessage("Annotations loaded successfully"))
.catch(() => addEventMessage("Loading annotations failed"));
}
// -- END: PART 2.2 --
// -- PART 2.1 --
// Add event listeners
api.vrAnnotationService.annotationsAdded.connect(() => {
loadAnnotations();
});
api.vrAnnotationService.annotationsDeleted.connect(() => {
loadAnnotations();
});
// Load annotations
loadAnnotations();
// -- END: PART 2.1 --