Skip to content

Commit 8d9db16

Browse files
authored
Use threejs for tesseract_viewer_python instead of babylonjs (#21)
* Use three.js for tesseract_viewer_python * Remove cv2 import in viewer
1 parent 43e77ec commit 8d9db16

File tree

9 files changed

+962
-1262
lines changed

9 files changed

+962
-1262
lines changed

tesseract_viewer_python/cmake/setup.py.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ setup(name='tesseract_robotics_viewer',
99
author_email='[email protected]',
1010
url='http://robotraconteur.com/',
1111
packages=['tesseract_robotics_viewer','tesseract_robotics_viewer.resources'],
12-
package_data={'tesseract_robotics_viewer.resources':['static/index.html','static/tesseract_viewer.js','geometries.json']},
12+
package_data={'tesseract_robotics_viewer.resources':['static/index.html','static/app.js','geometries.json']},
1313
license='Apache-2.0',
1414
install_requires=['numpy','tesseract_robotics'],
1515
long_description='Tesseract viewer package for Python'

tesseract_viewer_python/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0"?>
22
<package format="3">
33
<name>tesseract_viewer_python</name>
4-
<version>0.1.0</version>
4+
<version>0.2.0</version>
55
<description>The tesseract_viewer_python package</description>
66
<maintainer email="[email protected]">John Wason</maintainer>
77
<license>Apache 2.0</license>
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
import * as THREE from 'https://unpkg.com/[email protected]/build/three.module.js';
2+
3+
import { OrbitControls } from 'https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js';
4+
import { GLTFLoader } from 'https://unpkg.com/[email protected]/examples/jsm/loaders/GLTFLoader.js'
5+
import { VRButton } from 'https://unpkg.com/[email protected]/examples/jsm/webxr/VRButton.js'
6+
7+
class TesseractViewer {
8+
9+
constructor()
10+
{
11+
this._scene = null;
12+
this._clock = null;
13+
this._camera = null;
14+
this._renderer = null;
15+
this._light = null;
16+
this._scene_etag = null;
17+
this._trajectory_etag = null;
18+
this._disable_update_trajectory = false;
19+
this._animation_mixer = null;
20+
this._animation = null;
21+
this._animation_action = null;
22+
this._root_z_up = null;
23+
this._root_env = null;
24+
25+
}
26+
27+
async createScene() {
28+
this._scene = new THREE.Scene();
29+
this._clock = new THREE.Clock();
30+
31+
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 0.1, 1000 );
32+
camera.position.x = 3;
33+
camera.position.y = 3;
34+
camera.position.z = -1.5;
35+
this._camera = camera;
36+
37+
const renderer = new THREE.WebGLRenderer( { antialias: true } );
38+
renderer.setPixelRatio( window.devicePixelRatio );
39+
renderer.setSize( window.innerWidth, window.innerHeight );
40+
renderer.outputEncoding = THREE.sRGBEncoding;
41+
renderer.xr.enabled = true;
42+
43+
renderer.setClearColor("#000000");
44+
45+
this._renderer = renderer;
46+
47+
48+
window.addEventListener( 'resize', onWindowResize, false );
49+
50+
function onWindowResize(){
51+
52+
camera.aspect = window.innerWidth / window.innerHeight;
53+
camera.updateProjectionMatrix();
54+
55+
renderer.setSize( window.innerWidth, window.innerHeight );
56+
}
57+
58+
const light = new THREE.HemisphereLight( 0xffffbb, 0x202018, 1 );
59+
this._scene.add( light );
60+
this._light = light;
61+
62+
document.body.appendChild( renderer.domElement );
63+
64+
const controls = new OrbitControls( camera, renderer.domElement );
65+
66+
document.body.appendChild( VRButton.createButton( renderer ) );
67+
68+
renderer.setAnimationLoop( this.render.bind(this) );
69+
70+
const gridHelper = new THREE.GridHelper( 10, 10 );
71+
this._scene.add( gridHelper );
72+
73+
const root_z_up = new THREE.Object3D();
74+
root_z_up.rotateX(-Math.PI / 2.0);
75+
this._scene.add(root_z_up);
76+
77+
const root_env = new THREE.Object3D();
78+
root_z_up.add(root_env);
79+
80+
this._root_z_up = root_z_up;
81+
this._root_env = root_env;
82+
83+
this._animation_mixer = new THREE.AnimationMixer( this._root_env );
84+
85+
await this.updateScene();
86+
87+
let _this = this;
88+
const queryString = window.location.search;
89+
const urlParams = new URLSearchParams(queryString);
90+
let do_update = true;
91+
if (urlParams.has("noupdate")) {
92+
if (urlParams.get("noupdate") === "true") {
93+
do_update = false;
94+
}
95+
}
96+
if (do_update) {
97+
setTimeout(() => _this.updateTrajectory(), 2000);
98+
}
99+
100+
}
101+
102+
render() {
103+
// Render the scene
104+
this._renderer.render(this._scene, this._camera);
105+
106+
var delta = this._clock.getDelta();
107+
if ( this._animation_mixer ) this._animation_mixer.update( delta );
108+
};
109+
110+
async updateScene() {
111+
let fetch_res;
112+
try {
113+
fetch_res = await fetch("tesseract_scene.gltf", { method: "HEAD" });
114+
}
115+
catch (_a) {
116+
let _this = this;
117+
setTimeout(() => _this.updateScene(), 1000);
118+
return;
119+
}
120+
let etag = fetch_res.headers.get('etag');
121+
if (etag !== null) {
122+
if (this._scene_etag !== null) {
123+
if (this._scene_etag != etag) {
124+
this._scene_etag = null;
125+
let _this = this;
126+
setTimeout(() => _this.updateScene(), 0);
127+
return;
128+
}
129+
else {
130+
let _this = this;
131+
setTimeout(() => _this.updateScene(), 1000);
132+
return;
133+
}
134+
}
135+
}
136+
const loader = new GLTFLoader();
137+
138+
let gltf = await new Promise((resolve, reject) => {
139+
loader.load('tesseract_scene.gltf', data=> resolve(data), null, reject);
140+
});
141+
142+
if (this._root_env)
143+
{
144+
for( var i = this._root_env.children.length - 1; i >= 0; i--) {
145+
let obj = this._root_env.children[i];
146+
this._root_env.remove(obj);
147+
}
148+
}
149+
150+
this._root_env.add(gltf.scene);
151+
152+
if (gltf.animations.length > 0)
153+
{
154+
155+
this._animation_mixer.stopAllAction();
156+
this._animation_mixer.uncacheRoot(this._root_env);
157+
158+
let animation_action = this._animation_mixer.clipAction(gltf.animations[0]);
159+
animation_action.play();
160+
161+
this._animation = gltf.animations[0];
162+
this._animation_action = animation_action;
163+
}
164+
165+
if (etag !== null) {
166+
this._scene_etag = etag;
167+
let _this = this;
168+
setTimeout(() => _this.updateScene(), 1000);
169+
}
170+
}
171+
172+
async updateTrajectory() {
173+
174+
if (this._disable_update_trajectory) {
175+
return;
176+
}
177+
let fetch_res;
178+
let _this = this;
179+
try {
180+
fetch_res = await fetch("tesseract_trajectory.json", { method: "HEAD" });
181+
}
182+
catch (_a) {
183+
setTimeout(() => _this.updateTrajectory(), 1000);
184+
return;
185+
}
186+
if (!fetch_res.ok) {
187+
setTimeout(() => _this.updateTrajectory(), 1000);
188+
return;
189+
}
190+
let etag = fetch_res.headers.get('etag');
191+
if (etag == null || this._trajectory_etag == etag) {
192+
console.log("No updated trajectory");
193+
setTimeout(() => _this.updateTrajectory(), 1000);
194+
return;
195+
}
196+
try {
197+
let trajectory_response = await fetch("./tesseract_trajectory.json");
198+
let trajectory_json = await trajectory_response.json();
199+
console.log(trajectory_json)
200+
this.setTrajectory(trajectory_json.joint_names, trajectory_json.trajectory);
201+
}
202+
catch (e) {
203+
console.log("Trajectory not available");
204+
console.log(e);
205+
}
206+
if (etag !== null) {
207+
this._trajectory_etag = etag;
208+
setTimeout(() => _this.updateTrajectory(), 1000);
209+
}
210+
211+
}
212+
disableUpdateTrajectory() {
213+
this._disable_update_trajectory = true;
214+
}
215+
enableUpdateTrajectory() {
216+
this._disable_update_trajectory = false;
217+
}
218+
setJointPositions(joint_names, joint_positions) {
219+
let trajectory = [[...joint_positions, 0], [...joint_positions, 100000]];
220+
this.setTrajectory(joint_names, trajectory);
221+
}
222+
223+
setTrajectory(joint_names, trajectory) {
224+
225+
this._animation_mixer.stopAllAction();
226+
this._animation_mixer.uncacheRoot(this._root_env);
227+
228+
let anim = this.trajectoryToAnimation(joint_names, trajectory);
229+
let animation_action = this._animation_mixer.clipAction(anim);
230+
animation_action.play();
231+
232+
this._animation = anim;
233+
this._animation_action = animation_action;
234+
}
235+
236+
trajectoryToAnimation(joint_names, trajectory) {
237+
let joints = this.findJoints(joint_names);
238+
let tracks = []
239+
joint_names.forEach((joint_name, joint_index) => {
240+
let joint = joints[joint_name];
241+
switch (joint.type) {
242+
case 1:
243+
{
244+
let values = [];
245+
let times = []
246+
trajectory.forEach(ee => {
247+
let axis_vec = new THREE.Vector3().fromArray(joint.axes);
248+
let quat = new THREE.Quaternion().setFromAxisAngle(axis_vec, ee[joint_index]);
249+
let quat_array = quat.toArray();
250+
values.push(...quat_array);
251+
times.push(ee[ee.length - 1])
252+
});
253+
let track = new THREE.QuaternionKeyframeTrack(joint.joint.name + ".quaternion", times, values);
254+
tracks.push(track);
255+
}
256+
break;
257+
case 2:
258+
{
259+
let values = [];
260+
let times = []
261+
trajectory.forEach(ee => {
262+
let axis_vec = new THREE.Vector3().fromArray(joint.axes);
263+
let vec = axis_vec.multiplyScalar(ee[joint_index]);
264+
let vec_array = vec.toArray();
265+
values.push(...vec_array);
266+
times.push(ee[ee.length - 1])
267+
});
268+
let track = new THREE.VectorKeyframeTrack(joint.joint.name + ".position", times, values);
269+
tracks.push(track);
270+
}
271+
break;
272+
default:
273+
throw new Error("Unknown joint type");
274+
}
275+
});
276+
277+
let animation_clip = new THREE.AnimationClip("tesseract_trajectory", -1, tracks);
278+
279+
return animation_clip;
280+
}
281+
282+
findJoints(joint_names)
283+
{
284+
let ret = {}
285+
this._root_env.traverse(tf => {
286+
if (tf.userData && tf.userData["tesseract_joint"])
287+
{
288+
let t_j = tf.userData["tesseract_joint"];
289+
290+
if (joint_names && joint_names.indexOf(t_j["name"]) == -1) {
291+
return;
292+
}
293+
let t = {};
294+
t.joint_name = t_j["name"];
295+
t.node_name = tf.name;
296+
t.joint = tf;
297+
t.axes = t_j.axis;
298+
t.type = t_j.type;
299+
ret[t.joint_name] = t;
300+
}
301+
});
302+
return ret;
303+
}
304+
}
305+
306+
window.addEventListener("DOMContentLoaded", async function () {
307+
let viewer = new TesseractViewer();
308+
window.tesseract_viewer = viewer;
309+
await viewer.createScene();
310+
window.addEventListener("message", function (event) {
311+
let data = event.data;
312+
if (data.command === "joint_positions") {
313+
viewer.disableUpdateTrajectory();
314+
viewer.setJointPositions(data.joint_names, data.joint_positions);
315+
}
316+
if (data.command === "joint_trajectory") {
317+
viewer.disableUpdateTrajectory();
318+
viewer.setTrajectory(data.joint_names, data.joint_trajectory);
319+
}
320+
});
321+
viewer.render();
322+
})
Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,19 @@
11
<!DOCTYPE html>
2-
<html xmlns="http://www.w3.org/1999/xhtml">
3-
4-
<head>
5-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6-
<title>Tesseract Viewer</title>
7-
8-
<style>
9-
html, body {
10-
overflow: hidden;
11-
width: 100%;
12-
height: 100%;
13-
margin: 0;
14-
padding: 0;
15-
}
16-
17-
#renderCanvas {
18-
width: 100%;
19-
height: 100%;
20-
touch-action: none;
21-
}
22-
</style>
23-
24-
<script src="https://preview.babylonjs.com/babylon.js"></script>
25-
<script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.js"></script>
26-
<script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
27-
<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>
28-
<script src="tesseract_viewer.js"></script>
29-
</head>
30-
31-
<body>
32-
33-
<canvas id="renderCanvas" touch-action="none"></canvas> //touch-action="none" for best results from PEP
34-
35-
36-
37-
</body>
38-
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Tesseract Viewer</title>
6+
<!-- Simple reset to delete the margins -->
7+
<style>
8+
body { margin: 0; }
9+
canvas { width: 100%; height: 100% }
10+
</style>
11+
12+
<script src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
13+
14+
<script src="app.js" type="module"></script>
15+
</head>
16+
<body>
17+
18+
</body>
3919
</html>

0 commit comments

Comments
 (0)