了解如何使用 VRED Core Web API。
视频字幕:大家好,欢迎学习最后一个面向 VRED Core 的教程视频。我是主持人 Christopher,今天我们将扩展上一个视频中的流式传输应用程序示例,并使用 VRED 的 Web API 实现更多功能。
借助 Python API v2,Autodesk 还引入了 Python Web API,该 API 是 Python 端点的直接换代产品,可用于将 Python 命令发送到 VRED。我们在上一个视频中使用了 Python 端点,在那种情况下您可以构建自己的面向 VRED Core 的 HTML 界面,以便将 Python 命令发送到 VRED,并实现我们的视频流。
例如,我们可以将任意 Python 命令发送到 Python 端点,并接收此操作的结果作为响应。这些端点的缺点是,您必须发送 Python 命令的字符串表示形式,而不是使用对象或渲染的界面,并且必须正确传递返回值,才能在 JavaScript 应用程序中使用它们。
借助新的 Web API,VRED 提供了完全集成的 JavaScript 模块,其作用相当于直接连接到 VRED 的 Python 界面。Web API 提供了一个面向对象的访问点,可以访问 Python API v2 中提供的所有功能。这意味着,您可以访问适当的 JavaScript 类型,并可以使用典型的 JavaScript 功能,例如 Promise 或错误处理。
举例来说,可以使用这样的 JavaScript 调用创建一个新的摄影机,并等待您刚刚创建的摄影机。然后,可以使用该摄影机对象来设置其名称、设置其活动状态以便以其他方式进行处理。当然,摄影机对象包含 Python 类 vrdCamera
中可用的所有函数。
我们来了解一下如何在我们自己的应用程序中使用新的 Web API。在本教程中,我们将在上一个视频的自定义 HTML 应用程序基础上集成一个标注管理器。如果您没有看过上一个视频,建议您先看一下,因为它会直接扩展本教程的这段代码。
好了。我们开始吧。
在上一个教程中,我们将所有 CSS 和 JavaScript 代码都放在 HTML 文件中。为了保持整洁,我将样式表和脚本移到单独的文件中。这样,我们的 HTML 就更简洁了。
首先,我想添加一种向场景中添加标注的方法。对于本教程,我们不想设置这些标注的位置,只是添加它们并为其指定名称和描述即可。为此,我们可以向 HTML 代码添加一个表单,其中包含名称和描述的 2 个输入,以及一个用于创建标注的提交按钮。我们可以将其封装在一个 div 容器中,使用 CSS 为其指定一些样式,然后将其放在我们的视频流上。这次,在左上角。
接下来,我们创建一个名为 webapiscript
的新 JavaScript 文件,该文件将包含用于标注管理器的 JavaScript 代码。可以使用此 import 语句将 VRED Core 的 JavaScript API 添加到 JavaScript 模块。如您所见,在我的示例中,我使用的是 localhost
以及端口 8888
。如果 VRED Core 的设置不同,则必须调整这些值。
首先,我将添加一个新函数 addAnnotation
,其参数为 name
和 description
。在该函数中,我直接调用我们导入的 VRED JavaScript API。如您所见,我们使用此 API 的方式与 Python API 非常相似。
在这里,我使用的是 vrAnnotation
服务器,并调用带有 name
参数的方法 createAnnotation
。这将返回我们使用 then
函数异步处理的 Promise 对象。如果您不熟悉 JavaScript 模式,我来介绍一下,它被称为 async/await 模式,是一种处理编程并发性的最新方法。createAnnotation
一般需要一些时间才能返回,因此我们等待它完成,然后执行下一个操作。这样,脚本就可以继续工作,在等待 VRED 时不会锁定。
在 and then
函数内,我们现在可以安全地调用 findAnnotation
函数以获取对新标注节点的引用。我们遵循相同的 async/await 模式,将新标注的文本设置为 description
参数。如果创建标注时出错,我们可以捕获此错误并通知用户。在此示例中,我只是将错误消息输出到 Python 终端。
添加标注现在应该可以正常工作,但我们仍需将其连接到我们创建的 HTML 表单。要执行此操作,我们将挂钩表单的 onsubmit 回调。在这里,我们可以提取名称和描述输入,并调用 addAnnotation
函数。当测试新功能时,可以看到一切正常,我们可以向场景中添加新标注。还不错!
我还想在场景中添加标注列表,并且能够删除标注。现在,在 HTML 中,我们只需添加一个带有 ID 的无序列表元素。这足以动态地在此列表中添加和移除标注。
在 JavaScript 脚本中,我们从一个将加载所有标注的函数开始。使用 vrAnnotationService
获取所有标注的列表。使用 async/await 模式时,等待 VRED 中所有标注的列表,然后逐个进行迭代。
对于每个标注,我们查询了一个名称和所包含的文本。这也可以通过 async/await 模式完成。这就是我们必须将 loop
函数标记为 async
的原因。使用名称和标注文本,我们可以调用稍后将实现的名为 addAnnotationItem
的第二个函数。
但首先,我们要在每次调用 loadAnnotation
函数时清除列表。为此,我们添加一个 deleteAnnotationItems
函数,用于清除所有列表中的所有项,并在 load
函数的开头调用它。然后,我们创建一个名为 addAnnotationItem
的新函数,参数为 name
和 description
。
在这里,我们创建一个新的 HTML 列表元素,用一个文本节点填充该元素,该节点将保存标注的名称和描述。如果名称未定义,则从该函数返回。
我们还想添加一个按钮,用于删除 VRED 中的标注。为此,我们可以添加一个指向标注的链接。我们需要将某些内容设置为链接属性,才能看到链接。但是,这不起作用。相反,我们挂钩到 onClick
函数,并使用 AnnotationService
删除此标注(如果已单击)。
我们在此处使用的也是 async/await 模式。最后,我们将标注元素附加到列表。现在,当我们加载流式传输应用程序时,标注列表将加载一次,但永远不会更新。为了使我们的应用程序真正具有动态性,可以使用来自 vrAnnotationService
的信号,这些信号会告诉我们何时添加或删除了标注。这与在 Python 界面中使用信号非常相似。
就是这样。当测试该应用程序时,我们可以添加新标注,并立即在标注列表中看到它们。还有一个“删除”按钮,用于从场景中移除标注。
我承认,所有标注都添加到同一位置,但这可能是另一个教程的任务。现在,您应该全面了解了有关使用 VRED 的 Web API 的信息,以及如何使用它构建动态交互式 HTML 应用程序来控制场景。本视频也结束了 VRED Core 教程系列。我真的希望我能帮助您快速入门 VRED Core,并让您了解扩展和使用本产品的各种可能性。
感谢您参加本课程,下次见。
下面是教程 6:如何使用 VRED Core Web API 视频随附的示例 Python 脚本。
要下载这些压缩文件,请单击此处。
<!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 --