urdf-loader 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/package.json +61 -62
- package/src/URDFLoader.d.ts +1 -1
- package/src/urdf-viewer-element.js +551 -551
- package/umd/URDFLoader.js +38 -16
- package/umd/URDFLoader.js.map +1 -1
- package/umd/urdf-manipulator-element.js +23 -2
- package/umd/urdf-manipulator-element.js.map +1 -1
- package/umd/urdf-viewer-element.js +563 -542
- package/umd/urdf-viewer-element.js.map +1 -1
- package/CHANGELOG.md +0 -268
- package/example/styles.css +0 -281
|
@@ -1,558 +1,579 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
2
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('three'), require('three/examples/jsm/controls/OrbitControls.js'), require('./URDFLoader.js')) :
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['three', 'three/examples/jsm/controls/OrbitControls.js', './URDFLoader
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['three', 'three/examples/jsm/controls/OrbitControls.js', './URDFLoader'], factory) :
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.URDFViewer = factory(global.THREE, global.THREE, global.URDFLoader));
|
|
5
5
|
}(this, (function (THREE, OrbitControls_js, URDFLoader) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// ignore-limits-change: Fires when the 'ignore-limits' attribute changes
|
|
21
|
-
// angle-change: Fires when an angle changes
|
|
22
|
-
class URDFViewer extends HTMLElement {
|
|
23
|
-
|
|
24
|
-
static get observedAttributes() {
|
|
25
|
-
|
|
26
|
-
return ['package', 'urdf', 'up', 'display-shadow', 'ambient-color', 'ignore-limits'];
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get package() { return this.getAttribute('package') || ''; }
|
|
31
|
-
set package(val) { this.setAttribute('package', val); }
|
|
32
|
-
|
|
33
|
-
get urdf() { return this.getAttribute('urdf') || ''; }
|
|
34
|
-
set urdf(val) { this.setAttribute('urdf', val); }
|
|
35
|
-
|
|
36
|
-
get ignoreLimits() { return this.hasAttribute('ignore-limits') || false; }
|
|
37
|
-
set ignoreLimits(val) { val ? this.setAttribute('ignore-limits', val) : this.removeAttribute('ignore-limits'); }
|
|
38
|
-
|
|
39
|
-
get up() { return this.getAttribute('up') || '+Z'; }
|
|
40
|
-
set up(val) { this.setAttribute('up', val); }
|
|
41
|
-
|
|
42
|
-
get displayShadow() { return this.hasAttribute('display-shadow') || false; }
|
|
43
|
-
set displayShadow(val) { val ? this.setAttribute('display-shadow', '') : this.removeAttribute('display-shadow'); }
|
|
44
|
-
|
|
45
|
-
get ambientColor() { return this.getAttribute('ambient-color') || '#455A64'; }
|
|
46
|
-
set ambientColor(val) { val ? this.setAttribute('ambient-color', val) : this.removeAttribute('ambient-color'); }
|
|
47
|
-
|
|
48
|
-
get autoRedraw() { return this.hasAttribute('auto-redraw') || false; }
|
|
49
|
-
set autoRedraw(val) { val ? this.setAttribute('auto-redraw', true) : this.removeAttribute('auto-redraw'); }
|
|
50
|
-
|
|
51
|
-
get noAutoRecenter() { return this.hasAttribute('no-auto-recenter') || false; }
|
|
52
|
-
set noAutoRecenter(val) { val ? this.setAttribute('no-auto-recenter', true) : this.removeAttribute('no-auto-recenter'); }
|
|
53
|
-
|
|
54
|
-
get jointValues() {
|
|
55
|
-
|
|
56
|
-
const values = {};
|
|
57
|
-
if (this.robot) {
|
|
58
|
-
|
|
59
|
-
for (const name in this.robot.joints) {
|
|
60
|
-
|
|
61
|
-
const joint = this.robot.joints[name];
|
|
62
|
-
values[name] = joint.jointValue.length === 1 ? joint.angle : [...joint.jointValue];
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return values;
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
set jointValues(val) { this.setJointValues(val); }
|
|
72
|
-
|
|
73
|
-
get angles() {
|
|
74
|
-
|
|
75
|
-
return this.jointValues;
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
set angles(v) {
|
|
79
|
-
|
|
80
|
-
this.jointValues = v;
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* Lifecycle Functions */
|
|
85
|
-
constructor() {
|
|
86
|
-
|
|
87
|
-
super();
|
|
88
|
-
|
|
89
|
-
this._requestId = 0;
|
|
90
|
-
this._dirty = false;
|
|
91
|
-
this._loadScheduled = false;
|
|
92
|
-
this.robot = null;
|
|
93
|
-
this.loadMeshFunc = null;
|
|
94
|
-
this.urlModifierFunc = null;
|
|
95
|
-
|
|
96
|
-
// Scene setup
|
|
97
|
-
const scene = new THREE.Scene();
|
|
98
|
-
|
|
99
|
-
const ambientLight = new THREE.HemisphereLight(this.ambientColor, '#000');
|
|
100
|
-
ambientLight.groundColor.lerp(ambientLight.color, 0.5);
|
|
101
|
-
ambientLight.intensity = 0.5;
|
|
102
|
-
ambientLight.position.set(0, 1, 0);
|
|
103
|
-
scene.add(ambientLight);
|
|
104
|
-
|
|
105
|
-
// Light setup
|
|
106
|
-
const dirLight = new THREE.DirectionalLight(0xffffff);
|
|
107
|
-
dirLight.position.set(4, 10, 1);
|
|
108
|
-
dirLight.shadow.mapSize.width = 2048;
|
|
109
|
-
dirLight.shadow.mapSize.height = 2048;
|
|
110
|
-
dirLight.shadow.normalBias = 0.001;
|
|
111
|
-
dirLight.castShadow = true;
|
|
112
|
-
scene.add(dirLight);
|
|
113
|
-
scene.add(dirLight.target);
|
|
114
|
-
|
|
115
|
-
// Renderer setup
|
|
116
|
-
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
117
|
-
renderer.setClearColor(0xffffff);
|
|
118
|
-
renderer.setClearAlpha(0);
|
|
119
|
-
renderer.shadowMap.enabled = true;
|
|
120
|
-
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
121
|
-
renderer.outputEncoding = THREE.sRGBEncoding;
|
|
122
|
-
|
|
123
|
-
// Camera setup
|
|
124
|
-
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
|
|
125
|
-
camera.position.z = -10;
|
|
126
|
-
|
|
127
|
-
// World setup
|
|
128
|
-
const world = new THREE.Object3D();
|
|
129
|
-
scene.add(world);
|
|
130
|
-
|
|
131
|
-
const plane = new THREE.Mesh(
|
|
132
|
-
new THREE.PlaneBufferGeometry(40, 40),
|
|
133
|
-
new THREE.ShadowMaterial({ side: THREE.DoubleSide, transparent: true, opacity: 0.5 }),
|
|
134
|
-
);
|
|
135
|
-
plane.rotation.x = -Math.PI / 2;
|
|
136
|
-
plane.position.y = -0.5;
|
|
137
|
-
plane.receiveShadow = true;
|
|
138
|
-
plane.scale.set(10, 10, 10);
|
|
139
|
-
scene.add(plane);
|
|
140
|
-
|
|
141
|
-
// Controls setup
|
|
142
|
-
const controls = new OrbitControls_js.OrbitControls(camera, renderer.domElement);
|
|
143
|
-
controls.rotateSpeed = 2.0;
|
|
144
|
-
controls.zoomSpeed = 5;
|
|
145
|
-
controls.panSpeed = 2;
|
|
146
|
-
controls.enableZoom = true;
|
|
147
|
-
controls.enableDamping = false;
|
|
148
|
-
controls.maxDistance = 50;
|
|
149
|
-
controls.minDistance = 0.25;
|
|
150
|
-
controls.addEventListener('change', () => this.recenter());
|
|
151
|
-
|
|
152
|
-
this.scene = scene;
|
|
153
|
-
this.world = world;
|
|
154
|
-
this.renderer = renderer;
|
|
155
|
-
this.camera = camera;
|
|
156
|
-
this.controls = controls;
|
|
157
|
-
this.plane = plane;
|
|
158
|
-
this.directionalLight = dirLight;
|
|
159
|
-
this.ambientLight = ambientLight;
|
|
160
|
-
|
|
161
|
-
this._setUp(this.up);
|
|
162
|
-
|
|
163
|
-
const _renderLoop = () => {
|
|
164
|
-
|
|
165
|
-
if (this.parentNode) {
|
|
166
|
-
|
|
167
|
-
this.updateSize();
|
|
168
|
-
|
|
169
|
-
if (this._dirty || this.autoRedraw) {
|
|
170
|
-
|
|
171
|
-
if (!this.noAutoRecenter) {
|
|
172
|
-
|
|
173
|
-
this._updateEnvironment();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
this.renderer.render(scene, camera);
|
|
177
|
-
this._dirty = false;
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// update controls after the environment in
|
|
182
|
-
// case the controls are retargeted
|
|
183
|
-
this.controls.update();
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
this._renderLoopId = requestAnimationFrame(_renderLoop);
|
|
187
|
-
|
|
188
|
-
};
|
|
189
|
-
_renderLoop();
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
connectedCallback() {
|
|
194
|
-
|
|
195
|
-
// Add our initialize styles for the element if they haven't
|
|
196
|
-
// been added yet
|
|
197
|
-
if (!this.constructor._styletag) {
|
|
198
|
-
|
|
199
|
-
const styletag = document.createElement('style');
|
|
200
|
-
styletag.innerHTML =
|
|
201
|
-
`
|
|
202
|
-
${ this.tagName } { display: block; }
|
|
203
|
-
${ this.tagName } canvas {
|
|
204
|
-
width: 100%;
|
|
205
|
-
height: 100%;
|
|
206
|
-
}
|
|
207
|
-
`;
|
|
208
|
-
document.head.appendChild(styletag);
|
|
209
|
-
this.constructor._styletag = styletag;
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// add the renderer
|
|
214
|
-
if (this.childElementCount === 0) {
|
|
215
|
-
|
|
216
|
-
this.appendChild(this.renderer.domElement);
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
this.updateSize();
|
|
221
|
-
requestAnimationFrame(() => this.updateSize());
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
disconnectedCallback() {
|
|
226
|
-
|
|
227
|
-
cancelAnimationFrame(this._renderLoopId);
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
attributeChangedCallback(attr, oldval, newval) {
|
|
232
|
-
|
|
233
|
-
this.recenter();
|
|
234
|
-
|
|
235
|
-
switch (attr) {
|
|
236
|
-
|
|
237
|
-
case 'package':
|
|
238
|
-
case 'urdf': {
|
|
239
|
-
|
|
240
|
-
this._scheduleLoad();
|
|
241
|
-
break;
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
case 'up': {
|
|
246
|
-
|
|
247
|
-
this._setUp(this.up);
|
|
248
|
-
break;
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
case 'ambient-color': {
|
|
253
|
-
|
|
254
|
-
this.ambientLight.color.set(this.ambientColor);
|
|
255
|
-
this.ambientLight.groundColor.set('#000').lerp(this.ambientLight.color, 0.5);
|
|
256
|
-
break;
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
case 'ignore-limits': {
|
|
261
|
-
|
|
262
|
-
this._setIgnoreLimits(this.ignoreLimits, true);
|
|
263
|
-
break;
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/* Public API */
|
|
272
|
-
updateSize() {
|
|
273
|
-
|
|
274
|
-
const r = this.renderer;
|
|
275
|
-
const w = this.clientWidth;
|
|
276
|
-
const h = this.clientHeight;
|
|
277
|
-
const currsize = r.getSize(tempVec2);
|
|
278
|
-
|
|
279
|
-
if (currsize.width !== w || currsize.height !== h) {
|
|
280
|
-
|
|
281
|
-
this.recenter();
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
r.setPixelRatio(window.devicePixelRatio);
|
|
286
|
-
r.setSize(w, h, false);
|
|
287
|
-
|
|
288
|
-
this.camera.aspect = w / h;
|
|
289
|
-
this.camera.updateProjectionMatrix();
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
redraw() {
|
|
294
|
-
|
|
295
|
-
this._dirty = true;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
recenter() {
|
|
299
|
-
|
|
300
|
-
this._updateEnvironment();
|
|
301
|
-
this.redraw();
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Set the joint with jointName to
|
|
306
|
-
// angle in degrees
|
|
307
|
-
setJointValue(jointName, ...values) {
|
|
308
|
-
|
|
309
|
-
if (!this.robot) return;
|
|
310
|
-
if (!this.robot.joints[jointName]) return;
|
|
311
|
-
|
|
312
|
-
if (this.robot.joints[jointName].setJointValue(...values)) {
|
|
313
|
-
|
|
314
|
-
this.redraw();
|
|
315
|
-
this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }));
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
setJointValues(values) {
|
|
322
|
-
|
|
323
|
-
for (const name in values) this.setJointValue(name, values[name]);
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/* Private Functions */
|
|
328
|
-
// Updates the position of the plane to be at the
|
|
329
|
-
// lowest point below the robot and focuses the
|
|
330
|
-
// camera on the center of the scene
|
|
331
|
-
_updateEnvironment() {
|
|
332
|
-
|
|
333
|
-
if (!this.robot) return;
|
|
334
|
-
|
|
335
|
-
this.world.updateMatrixWorld();
|
|
336
|
-
|
|
337
|
-
const bbox = new THREE.Box3();
|
|
338
|
-
bbox.setFromObject(this.robot);
|
|
339
|
-
|
|
340
|
-
const center = bbox.getCenter(new THREE.Vector3());
|
|
341
|
-
this.controls.target.y = center.y;
|
|
342
|
-
this.plane.position.y = bbox.min.y - 1e-3;
|
|
343
|
-
|
|
344
|
-
const dirLight = this.directionalLight;
|
|
345
|
-
dirLight.castShadow = this.displayShadow;
|
|
346
|
-
|
|
347
|
-
if (this.displayShadow) {
|
|
348
|
-
|
|
349
|
-
// Update the shadow camera rendering bounds to encapsulate the
|
|
350
|
-
// model. We use the bounding sphere of the bounding box for
|
|
351
|
-
// simplicity -- this could be a tighter fit.
|
|
352
|
-
const sphere = bbox.getBoundingSphere(new THREE.Sphere());
|
|
353
|
-
const minmax = sphere.radius;
|
|
354
|
-
const cam = dirLight.shadow.camera;
|
|
355
|
-
cam.left = cam.bottom = -minmax;
|
|
356
|
-
cam.right = cam.top = minmax;
|
|
357
|
-
|
|
358
|
-
// Update the camera to focus on the center of the model so the
|
|
359
|
-
// shadow can encapsulate it
|
|
360
|
-
const offset = dirLight.position.clone().sub(dirLight.target.position);
|
|
361
|
-
dirLight.target.position.copy(center);
|
|
362
|
-
dirLight.position.copy(center).add(offset);
|
|
363
|
-
|
|
364
|
-
cam.updateProjectionMatrix();
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
_scheduleLoad() {
|
|
371
|
-
|
|
372
|
-
// if our current model is already what's being requested
|
|
373
|
-
// or has been loaded then early out
|
|
374
|
-
if (this._prevload === `${ this.package }|${ this.urdf }`) return;
|
|
375
|
-
this._prevload = `${ this.package }|${ this.urdf }`;
|
|
376
|
-
|
|
377
|
-
// if we're already waiting on a load then early out
|
|
378
|
-
if (this._loadScheduled) return;
|
|
379
|
-
this._loadScheduled = true;
|
|
380
|
-
|
|
381
|
-
if (this.robot) {
|
|
382
|
-
|
|
383
|
-
this.robot.traverse(c => c.dispose && c.dispose());
|
|
384
|
-
this.robot.parent.remove(this.robot);
|
|
385
|
-
this.robot = null;
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
requestAnimationFrame(() => {
|
|
390
|
-
|
|
391
|
-
this._loadUrdf(this.package, this.urdf);
|
|
392
|
-
this._loadScheduled = false;
|
|
393
|
-
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Watch the package and urdf field and load the robot model.
|
|
399
|
-
// This should _only_ be called from _scheduleLoad because that
|
|
400
|
-
// ensures the that current robot has been removed
|
|
401
|
-
_loadUrdf(pkg, urdf) {
|
|
402
|
-
|
|
403
|
-
this.dispatchEvent(new CustomEvent('urdf-change', { bubbles: true, cancelable: true, composed: true }));
|
|
404
|
-
|
|
405
|
-
if (urdf) {
|
|
406
|
-
|
|
407
|
-
// Keep track of this request and make
|
|
408
|
-
// sure it doesn't get overwritten by
|
|
409
|
-
// a subsequent one
|
|
410
|
-
this._requestId++;
|
|
411
|
-
const requestId = this._requestId;
|
|
412
|
-
|
|
413
|
-
const updateMaterials = mesh => {
|
|
414
|
-
|
|
415
|
-
mesh.traverse(c => {
|
|
416
|
-
|
|
417
|
-
if (c.isMesh) {
|
|
418
|
-
|
|
419
|
-
c.castShadow = true;
|
|
420
|
-
c.receiveShadow = true;
|
|
421
|
-
|
|
422
|
-
if (c.material) {
|
|
423
|
-
|
|
424
|
-
const mats =
|
|
425
|
-
(Array.isArray(c.material) ? c.material : [c.material])
|
|
426
|
-
.map(m => {
|
|
427
|
-
|
|
428
|
-
if (m instanceof THREE.MeshBasicMaterial) {
|
|
429
|
-
|
|
430
|
-
m = new THREE.MeshPhongMaterial();
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (m.map) {
|
|
435
|
-
|
|
436
|
-
m.map.encoding = THREE.GammaEncoding;
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return m;
|
|
441
|
-
|
|
442
|
-
});
|
|
443
|
-
c.material = mats.length === 1 ? mats[0] : mats;
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () {
|
|
19
|
+
return e[k];
|
|
447
20
|
}
|
|
448
|
-
|
|
449
21
|
});
|
|
450
|
-
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
if (pkg.includes(':') && (pkg.split(':')[1].substring(0, 2)) !== '//') {
|
|
454
|
-
// E.g. pkg = "pkg_name: path/to/pkg_name, pk2: path2/to/pk2"}
|
|
455
|
-
|
|
456
|
-
// Convert pkg(s) into a map. E.g.
|
|
457
|
-
// { "pkg_name": "path/to/pkg_name",
|
|
458
|
-
// "pk2": "path2/to/pk2" }
|
|
459
|
-
|
|
460
|
-
pkg = pkg.split(',').reduce((map, value) => {
|
|
461
|
-
|
|
462
|
-
const split = value.split(/:/).filter(x => !!x);
|
|
463
|
-
const pkgName = split.shift().trim();
|
|
464
|
-
const pkgPath = split.join(':').trim();
|
|
465
|
-
map[pkgName] = pkgPath;
|
|
466
|
-
|
|
467
|
-
return map;
|
|
468
|
-
|
|
469
|
-
}, {});
|
|
470
22
|
}
|
|
471
|
-
|
|
472
|
-
let robot = null;
|
|
473
|
-
const manager = new THREE.LoadingManager();
|
|
474
|
-
manager.onLoad = () => {
|
|
475
|
-
|
|
476
|
-
// If another request has come in to load a new
|
|
477
|
-
// robot, then ignore this one
|
|
478
|
-
if (this._requestId !== requestId) {
|
|
479
|
-
|
|
480
|
-
robot.traverse(c => c.dispose && c.dispose());
|
|
481
|
-
return;
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
this.robot = robot;
|
|
486
|
-
this.world.add(robot);
|
|
487
|
-
updateMaterials(robot);
|
|
488
|
-
|
|
489
|
-
this._setIgnoreLimits(this.ignoreLimits);
|
|
490
|
-
|
|
491
|
-
this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
|
|
492
|
-
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
|
|
493
|
-
|
|
494
|
-
this.recenter();
|
|
495
|
-
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
if (this.urlModifierFunc) {
|
|
499
|
-
|
|
500
|
-
manager.setURLModifier(this.urlModifierFunc);
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const loader = new URDFLoader__default['default'](manager);
|
|
505
|
-
loader.packages = pkg;
|
|
506
|
-
loader.loadMeshCb = this.loadMeshFunc;
|
|
507
|
-
loader.fetchOptions = { mode: 'cors', credentials: 'same-origin' };
|
|
508
|
-
loader.load(urdf, model => robot = model);
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Watch the coordinate frame and update the
|
|
515
|
-
// rotation of the scene to match
|
|
516
|
-
_setUp(up) {
|
|
517
|
-
|
|
518
|
-
if (!up) up = '+Z';
|
|
519
|
-
up = up.toUpperCase();
|
|
520
|
-
const sign = up.replace(/[^-+]/g, '')[0] || '+';
|
|
521
|
-
const axis = up.replace(/[^XYZ]/gi, '')[0] || 'Z';
|
|
522
|
-
|
|
523
|
-
const PI = Math.PI;
|
|
524
|
-
const HALFPI = PI / 2;
|
|
525
|
-
if (axis === 'X') this.world.rotation.set(0, 0, sign === '+' ? HALFPI : -HALFPI);
|
|
526
|
-
if (axis === 'Z') this.world.rotation.set(sign === '+' ? -HALFPI : HALFPI, 0, 0);
|
|
527
|
-
if (axis === 'Y') this.world.rotation.set(sign === '+' ? 0 : PI, 0, 0);
|
|
528
|
-
|
|
23
|
+
});
|
|
529
24
|
}
|
|
25
|
+
n['default'] = e;
|
|
26
|
+
return Object.freeze(n);
|
|
27
|
+
}
|
|
530
28
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
_setIgnoreLimits(ignore, dispatch = false) {
|
|
534
|
-
|
|
535
|
-
if (this.robot) {
|
|
536
|
-
|
|
537
|
-
Object
|
|
538
|
-
.values(this.robot.joints)
|
|
539
|
-
.forEach(joint => {
|
|
540
|
-
|
|
541
|
-
joint.ignoreLimits = ignore;
|
|
542
|
-
joint.setJointValue(...joint.jointValue);
|
|
543
|
-
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (dispatch) {
|
|
549
|
-
|
|
550
|
-
this.dispatchEvent(new CustomEvent('ignore-limits-change', { bubbles: true, cancelable: true, composed: true }));
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
}
|
|
29
|
+
var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
|
|
30
|
+
var URDFLoader__default = /*#__PURE__*/_interopDefaultLegacy(URDFLoader);
|
|
555
31
|
|
|
32
|
+
const tempVec2 = new THREE__namespace.Vector2();
|
|
33
|
+
|
|
34
|
+
// urdf-viewer element
|
|
35
|
+
// Loads and displays a 3D view of a URDF-formatted robot
|
|
36
|
+
|
|
37
|
+
// Events
|
|
38
|
+
// urdf-change: Fires when the URDF has finished loading and getting processed
|
|
39
|
+
// urdf-processed: Fires when the URDF has finished loading and getting processed
|
|
40
|
+
// geometry-loaded: Fires when all the geometry has been fully loaded
|
|
41
|
+
// ignore-limits-change: Fires when the 'ignore-limits' attribute changes
|
|
42
|
+
// angle-change: Fires when an angle changes
|
|
43
|
+
class URDFViewer extends HTMLElement {
|
|
44
|
+
|
|
45
|
+
static get observedAttributes() {
|
|
46
|
+
|
|
47
|
+
return ['package', 'urdf', 'up', 'display-shadow', 'ambient-color', 'ignore-limits'];
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get package() { return this.getAttribute('package') || ''; }
|
|
52
|
+
set package(val) { this.setAttribute('package', val); }
|
|
53
|
+
|
|
54
|
+
get urdf() { return this.getAttribute('urdf') || ''; }
|
|
55
|
+
set urdf(val) { this.setAttribute('urdf', val); }
|
|
56
|
+
|
|
57
|
+
get ignoreLimits() { return this.hasAttribute('ignore-limits') || false; }
|
|
58
|
+
set ignoreLimits(val) { val ? this.setAttribute('ignore-limits', val) : this.removeAttribute('ignore-limits'); }
|
|
59
|
+
|
|
60
|
+
get up() { return this.getAttribute('up') || '+Z'; }
|
|
61
|
+
set up(val) { this.setAttribute('up', val); }
|
|
62
|
+
|
|
63
|
+
get displayShadow() { return this.hasAttribute('display-shadow') || false; }
|
|
64
|
+
set displayShadow(val) { val ? this.setAttribute('display-shadow', '') : this.removeAttribute('display-shadow'); }
|
|
65
|
+
|
|
66
|
+
get ambientColor() { return this.getAttribute('ambient-color') || '#455A64'; }
|
|
67
|
+
set ambientColor(val) { val ? this.setAttribute('ambient-color', val) : this.removeAttribute('ambient-color'); }
|
|
68
|
+
|
|
69
|
+
get autoRedraw() { return this.hasAttribute('auto-redraw') || false; }
|
|
70
|
+
set autoRedraw(val) { val ? this.setAttribute('auto-redraw', true) : this.removeAttribute('auto-redraw'); }
|
|
71
|
+
|
|
72
|
+
get noAutoRecenter() { return this.hasAttribute('no-auto-recenter') || false; }
|
|
73
|
+
set noAutoRecenter(val) { val ? this.setAttribute('no-auto-recenter', true) : this.removeAttribute('no-auto-recenter'); }
|
|
74
|
+
|
|
75
|
+
get jointValues() {
|
|
76
|
+
|
|
77
|
+
const values = {};
|
|
78
|
+
if (this.robot) {
|
|
79
|
+
|
|
80
|
+
for (const name in this.robot.joints) {
|
|
81
|
+
|
|
82
|
+
const joint = this.robot.joints[name];
|
|
83
|
+
values[name] = joint.jointValue.length === 1 ? joint.angle : [...joint.jointValue];
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return values;
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
set jointValues(val) { this.setJointValues(val); }
|
|
93
|
+
|
|
94
|
+
get angles() {
|
|
95
|
+
|
|
96
|
+
return this.jointValues;
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
set angles(v) {
|
|
100
|
+
|
|
101
|
+
this.jointValues = v;
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Lifecycle Functions */
|
|
106
|
+
constructor() {
|
|
107
|
+
|
|
108
|
+
super();
|
|
109
|
+
|
|
110
|
+
this._requestId = 0;
|
|
111
|
+
this._dirty = false;
|
|
112
|
+
this._loadScheduled = false;
|
|
113
|
+
this.robot = null;
|
|
114
|
+
this.loadMeshFunc = null;
|
|
115
|
+
this.urlModifierFunc = null;
|
|
116
|
+
|
|
117
|
+
// Scene setup
|
|
118
|
+
const scene = new THREE__namespace.Scene();
|
|
119
|
+
|
|
120
|
+
const ambientLight = new THREE__namespace.HemisphereLight(this.ambientColor, '#000');
|
|
121
|
+
ambientLight.groundColor.lerp(ambientLight.color, 0.5);
|
|
122
|
+
ambientLight.intensity = 0.5;
|
|
123
|
+
ambientLight.position.set(0, 1, 0);
|
|
124
|
+
scene.add(ambientLight);
|
|
125
|
+
|
|
126
|
+
// Light setup
|
|
127
|
+
const dirLight = new THREE__namespace.DirectionalLight(0xffffff);
|
|
128
|
+
dirLight.position.set(4, 10, 1);
|
|
129
|
+
dirLight.shadow.mapSize.width = 2048;
|
|
130
|
+
dirLight.shadow.mapSize.height = 2048;
|
|
131
|
+
dirLight.shadow.normalBias = 0.001;
|
|
132
|
+
dirLight.castShadow = true;
|
|
133
|
+
scene.add(dirLight);
|
|
134
|
+
scene.add(dirLight.target);
|
|
135
|
+
|
|
136
|
+
// Renderer setup
|
|
137
|
+
const renderer = new THREE__namespace.WebGLRenderer({ antialias: true, alpha: true });
|
|
138
|
+
renderer.setClearColor(0xffffff);
|
|
139
|
+
renderer.setClearAlpha(0);
|
|
140
|
+
renderer.shadowMap.enabled = true;
|
|
141
|
+
renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
|
|
142
|
+
renderer.outputEncoding = THREE__namespace.sRGBEncoding;
|
|
143
|
+
|
|
144
|
+
// Camera setup
|
|
145
|
+
const camera = new THREE__namespace.PerspectiveCamera(75, 1, 0.1, 1000);
|
|
146
|
+
camera.position.z = -10;
|
|
147
|
+
|
|
148
|
+
// World setup
|
|
149
|
+
const world = new THREE__namespace.Object3D();
|
|
150
|
+
scene.add(world);
|
|
151
|
+
|
|
152
|
+
const plane = new THREE__namespace.Mesh(
|
|
153
|
+
new THREE__namespace.PlaneBufferGeometry(40, 40),
|
|
154
|
+
new THREE__namespace.ShadowMaterial({ side: THREE__namespace.DoubleSide, transparent: true, opacity: 0.5 }),
|
|
155
|
+
);
|
|
156
|
+
plane.rotation.x = -Math.PI / 2;
|
|
157
|
+
plane.position.y = -0.5;
|
|
158
|
+
plane.receiveShadow = true;
|
|
159
|
+
plane.scale.set(10, 10, 10);
|
|
160
|
+
scene.add(plane);
|
|
161
|
+
|
|
162
|
+
// Controls setup
|
|
163
|
+
const controls = new OrbitControls_js.OrbitControls(camera, renderer.domElement);
|
|
164
|
+
controls.rotateSpeed = 2.0;
|
|
165
|
+
controls.zoomSpeed = 5;
|
|
166
|
+
controls.panSpeed = 2;
|
|
167
|
+
controls.enableZoom = true;
|
|
168
|
+
controls.enableDamping = false;
|
|
169
|
+
controls.maxDistance = 50;
|
|
170
|
+
controls.minDistance = 0.25;
|
|
171
|
+
controls.addEventListener('change', () => this.recenter());
|
|
172
|
+
|
|
173
|
+
this.scene = scene;
|
|
174
|
+
this.world = world;
|
|
175
|
+
this.renderer = renderer;
|
|
176
|
+
this.camera = camera;
|
|
177
|
+
this.controls = controls;
|
|
178
|
+
this.plane = plane;
|
|
179
|
+
this.directionalLight = dirLight;
|
|
180
|
+
this.ambientLight = ambientLight;
|
|
181
|
+
|
|
182
|
+
this._setUp(this.up);
|
|
183
|
+
|
|
184
|
+
const _renderLoop = () => {
|
|
185
|
+
|
|
186
|
+
if (this.parentNode) {
|
|
187
|
+
|
|
188
|
+
this.updateSize();
|
|
189
|
+
|
|
190
|
+
if (this._dirty || this.autoRedraw) {
|
|
191
|
+
|
|
192
|
+
if (!this.noAutoRecenter) {
|
|
193
|
+
|
|
194
|
+
this._updateEnvironment();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.renderer.render(scene, camera);
|
|
198
|
+
this._dirty = false;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// update controls after the environment in
|
|
203
|
+
// case the controls are retargeted
|
|
204
|
+
this.controls.update();
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
this._renderLoopId = requestAnimationFrame(_renderLoop);
|
|
208
|
+
|
|
209
|
+
};
|
|
210
|
+
_renderLoop();
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
connectedCallback() {
|
|
215
|
+
|
|
216
|
+
// Add our initialize styles for the element if they haven't
|
|
217
|
+
// been added yet
|
|
218
|
+
if (!this.constructor._styletag) {
|
|
219
|
+
|
|
220
|
+
const styletag = document.createElement('style');
|
|
221
|
+
styletag.innerHTML =
|
|
222
|
+
`
|
|
223
|
+
${ this.tagName } { display: block; }
|
|
224
|
+
${ this.tagName } canvas {
|
|
225
|
+
width: 100%;
|
|
226
|
+
height: 100%;
|
|
227
|
+
}
|
|
228
|
+
`;
|
|
229
|
+
document.head.appendChild(styletag);
|
|
230
|
+
this.constructor._styletag = styletag;
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// add the renderer
|
|
235
|
+
if (this.childElementCount === 0) {
|
|
236
|
+
|
|
237
|
+
this.appendChild(this.renderer.domElement);
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.updateSize();
|
|
242
|
+
requestAnimationFrame(() => this.updateSize());
|
|
243
|
+
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
disconnectedCallback() {
|
|
247
|
+
|
|
248
|
+
cancelAnimationFrame(this._renderLoopId);
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
attributeChangedCallback(attr, oldval, newval) {
|
|
253
|
+
|
|
254
|
+
this.recenter();
|
|
255
|
+
|
|
256
|
+
switch (attr) {
|
|
257
|
+
|
|
258
|
+
case 'package':
|
|
259
|
+
case 'urdf': {
|
|
260
|
+
|
|
261
|
+
this._scheduleLoad();
|
|
262
|
+
break;
|
|
263
|
+
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
case 'up': {
|
|
267
|
+
|
|
268
|
+
this._setUp(this.up);
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
case 'ambient-color': {
|
|
274
|
+
|
|
275
|
+
this.ambientLight.color.set(this.ambientColor);
|
|
276
|
+
this.ambientLight.groundColor.set('#000').lerp(this.ambientLight.color, 0.5);
|
|
277
|
+
break;
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
case 'ignore-limits': {
|
|
282
|
+
|
|
283
|
+
this._setIgnoreLimits(this.ignoreLimits, true);
|
|
284
|
+
break;
|
|
285
|
+
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Public API */
|
|
293
|
+
updateSize() {
|
|
294
|
+
|
|
295
|
+
const r = this.renderer;
|
|
296
|
+
const w = this.clientWidth;
|
|
297
|
+
const h = this.clientHeight;
|
|
298
|
+
const currsize = r.getSize(tempVec2);
|
|
299
|
+
|
|
300
|
+
if (currsize.width !== w || currsize.height !== h) {
|
|
301
|
+
|
|
302
|
+
this.recenter();
|
|
303
|
+
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
r.setPixelRatio(window.devicePixelRatio);
|
|
307
|
+
r.setSize(w, h, false);
|
|
308
|
+
|
|
309
|
+
this.camera.aspect = w / h;
|
|
310
|
+
this.camera.updateProjectionMatrix();
|
|
311
|
+
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
redraw() {
|
|
315
|
+
|
|
316
|
+
this._dirty = true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
recenter() {
|
|
320
|
+
|
|
321
|
+
this._updateEnvironment();
|
|
322
|
+
this.redraw();
|
|
323
|
+
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Set the joint with jointName to
|
|
327
|
+
// angle in degrees
|
|
328
|
+
setJointValue(jointName, ...values) {
|
|
329
|
+
|
|
330
|
+
if (!this.robot) return;
|
|
331
|
+
if (!this.robot.joints[jointName]) return;
|
|
332
|
+
|
|
333
|
+
if (this.robot.joints[jointName].setJointValue(...values)) {
|
|
334
|
+
|
|
335
|
+
this.redraw();
|
|
336
|
+
this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }));
|
|
337
|
+
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
setJointValues(values) {
|
|
343
|
+
|
|
344
|
+
for (const name in values) this.setJointValue(name, values[name]);
|
|
345
|
+
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* Private Functions */
|
|
349
|
+
// Updates the position of the plane to be at the
|
|
350
|
+
// lowest point below the robot and focuses the
|
|
351
|
+
// camera on the center of the scene
|
|
352
|
+
_updateEnvironment() {
|
|
353
|
+
|
|
354
|
+
if (!this.robot) return;
|
|
355
|
+
|
|
356
|
+
this.world.updateMatrixWorld();
|
|
357
|
+
|
|
358
|
+
const bbox = new THREE__namespace.Box3();
|
|
359
|
+
bbox.setFromObject(this.robot);
|
|
360
|
+
|
|
361
|
+
const center = bbox.getCenter(new THREE__namespace.Vector3());
|
|
362
|
+
this.controls.target.y = center.y;
|
|
363
|
+
this.plane.position.y = bbox.min.y - 1e-3;
|
|
364
|
+
|
|
365
|
+
const dirLight = this.directionalLight;
|
|
366
|
+
dirLight.castShadow = this.displayShadow;
|
|
367
|
+
|
|
368
|
+
if (this.displayShadow) {
|
|
369
|
+
|
|
370
|
+
// Update the shadow camera rendering bounds to encapsulate the
|
|
371
|
+
// model. We use the bounding sphere of the bounding box for
|
|
372
|
+
// simplicity -- this could be a tighter fit.
|
|
373
|
+
const sphere = bbox.getBoundingSphere(new THREE__namespace.Sphere());
|
|
374
|
+
const minmax = sphere.radius;
|
|
375
|
+
const cam = dirLight.shadow.camera;
|
|
376
|
+
cam.left = cam.bottom = -minmax;
|
|
377
|
+
cam.right = cam.top = minmax;
|
|
378
|
+
|
|
379
|
+
// Update the camera to focus on the center of the model so the
|
|
380
|
+
// shadow can encapsulate it
|
|
381
|
+
const offset = dirLight.position.clone().sub(dirLight.target.position);
|
|
382
|
+
dirLight.target.position.copy(center);
|
|
383
|
+
dirLight.position.copy(center).add(offset);
|
|
384
|
+
|
|
385
|
+
cam.updateProjectionMatrix();
|
|
386
|
+
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
_scheduleLoad() {
|
|
392
|
+
|
|
393
|
+
// if our current model is already what's being requested
|
|
394
|
+
// or has been loaded then early out
|
|
395
|
+
if (this._prevload === `${ this.package }|${ this.urdf }`) return;
|
|
396
|
+
this._prevload = `${ this.package }|${ this.urdf }`;
|
|
397
|
+
|
|
398
|
+
// if we're already waiting on a load then early out
|
|
399
|
+
if (this._loadScheduled) return;
|
|
400
|
+
this._loadScheduled = true;
|
|
401
|
+
|
|
402
|
+
if (this.robot) {
|
|
403
|
+
|
|
404
|
+
this.robot.traverse(c => c.dispose && c.dispose());
|
|
405
|
+
this.robot.parent.remove(this.robot);
|
|
406
|
+
this.robot = null;
|
|
407
|
+
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
requestAnimationFrame(() => {
|
|
411
|
+
|
|
412
|
+
this._loadUrdf(this.package, this.urdf);
|
|
413
|
+
this._loadScheduled = false;
|
|
414
|
+
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Watch the package and urdf field and load the robot model.
|
|
420
|
+
// This should _only_ be called from _scheduleLoad because that
|
|
421
|
+
// ensures the that current robot has been removed
|
|
422
|
+
_loadUrdf(pkg, urdf) {
|
|
423
|
+
|
|
424
|
+
this.dispatchEvent(new CustomEvent('urdf-change', { bubbles: true, cancelable: true, composed: true }));
|
|
425
|
+
|
|
426
|
+
if (urdf) {
|
|
427
|
+
|
|
428
|
+
// Keep track of this request and make
|
|
429
|
+
// sure it doesn't get overwritten by
|
|
430
|
+
// a subsequent one
|
|
431
|
+
this._requestId++;
|
|
432
|
+
const requestId = this._requestId;
|
|
433
|
+
|
|
434
|
+
const updateMaterials = mesh => {
|
|
435
|
+
|
|
436
|
+
mesh.traverse(c => {
|
|
437
|
+
|
|
438
|
+
if (c.isMesh) {
|
|
439
|
+
|
|
440
|
+
c.castShadow = true;
|
|
441
|
+
c.receiveShadow = true;
|
|
442
|
+
|
|
443
|
+
if (c.material) {
|
|
444
|
+
|
|
445
|
+
const mats =
|
|
446
|
+
(Array.isArray(c.material) ? c.material : [c.material])
|
|
447
|
+
.map(m => {
|
|
448
|
+
|
|
449
|
+
if (m instanceof THREE__namespace.MeshBasicMaterial) {
|
|
450
|
+
|
|
451
|
+
m = new THREE__namespace.MeshPhongMaterial();
|
|
452
|
+
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (m.map) {
|
|
456
|
+
|
|
457
|
+
m.map.encoding = THREE__namespace.GammaEncoding;
|
|
458
|
+
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return m;
|
|
462
|
+
|
|
463
|
+
});
|
|
464
|
+
c.material = mats.length === 1 ? mats[0] : mats;
|
|
465
|
+
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
if (pkg.includes(':') && (pkg.split(':')[1].substring(0, 2)) !== '//') {
|
|
475
|
+
// E.g. pkg = "pkg_name: path/to/pkg_name, pk2: path2/to/pk2"}
|
|
476
|
+
|
|
477
|
+
// Convert pkg(s) into a map. E.g.
|
|
478
|
+
// { "pkg_name": "path/to/pkg_name",
|
|
479
|
+
// "pk2": "path2/to/pk2" }
|
|
480
|
+
|
|
481
|
+
pkg = pkg.split(',').reduce((map, value) => {
|
|
482
|
+
|
|
483
|
+
const split = value.split(/:/).filter(x => !!x);
|
|
484
|
+
const pkgName = split.shift().trim();
|
|
485
|
+
const pkgPath = split.join(':').trim();
|
|
486
|
+
map[pkgName] = pkgPath;
|
|
487
|
+
|
|
488
|
+
return map;
|
|
489
|
+
|
|
490
|
+
}, {});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
let robot = null;
|
|
494
|
+
const manager = new THREE__namespace.LoadingManager();
|
|
495
|
+
manager.onLoad = () => {
|
|
496
|
+
|
|
497
|
+
// If another request has come in to load a new
|
|
498
|
+
// robot, then ignore this one
|
|
499
|
+
if (this._requestId !== requestId) {
|
|
500
|
+
|
|
501
|
+
robot.traverse(c => c.dispose && c.dispose());
|
|
502
|
+
return;
|
|
503
|
+
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
this.robot = robot;
|
|
507
|
+
this.world.add(robot);
|
|
508
|
+
updateMaterials(robot);
|
|
509
|
+
|
|
510
|
+
this._setIgnoreLimits(this.ignoreLimits);
|
|
511
|
+
|
|
512
|
+
this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
|
|
513
|
+
this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
|
|
514
|
+
|
|
515
|
+
this.recenter();
|
|
516
|
+
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
if (this.urlModifierFunc) {
|
|
520
|
+
|
|
521
|
+
manager.setURLModifier(this.urlModifierFunc);
|
|
522
|
+
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const loader = new URDFLoader__default['default'](manager);
|
|
526
|
+
loader.packages = pkg;
|
|
527
|
+
loader.loadMeshCb = this.loadMeshFunc;
|
|
528
|
+
loader.fetchOptions = { mode: 'cors', credentials: 'same-origin' };
|
|
529
|
+
loader.load(urdf, model => robot = model);
|
|
530
|
+
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Watch the coordinate frame and update the
|
|
536
|
+
// rotation of the scene to match
|
|
537
|
+
_setUp(up) {
|
|
538
|
+
|
|
539
|
+
if (!up) up = '+Z';
|
|
540
|
+
up = up.toUpperCase();
|
|
541
|
+
const sign = up.replace(/[^-+]/g, '')[0] || '+';
|
|
542
|
+
const axis = up.replace(/[^XYZ]/gi, '')[0] || 'Z';
|
|
543
|
+
|
|
544
|
+
const PI = Math.PI;
|
|
545
|
+
const HALFPI = PI / 2;
|
|
546
|
+
if (axis === 'X') this.world.rotation.set(0, 0, sign === '+' ? HALFPI : -HALFPI);
|
|
547
|
+
if (axis === 'Z') this.world.rotation.set(sign === '+' ? -HALFPI : HALFPI, 0, 0);
|
|
548
|
+
if (axis === 'Y') this.world.rotation.set(sign === '+' ? 0 : PI, 0, 0);
|
|
549
|
+
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Updates the current robot's angles to ignore
|
|
553
|
+
// joint limits or not
|
|
554
|
+
_setIgnoreLimits(ignore, dispatch = false) {
|
|
555
|
+
|
|
556
|
+
if (this.robot) {
|
|
557
|
+
|
|
558
|
+
Object
|
|
559
|
+
.values(this.robot.joints)
|
|
560
|
+
.forEach(joint => {
|
|
561
|
+
|
|
562
|
+
joint.ignoreLimits = ignore;
|
|
563
|
+
joint.setJointValue(...joint.jointValue);
|
|
564
|
+
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (dispatch) {
|
|
570
|
+
|
|
571
|
+
this.dispatchEvent(new CustomEvent('ignore-limits-change', { bubbles: true, cancelable: true, composed: true }));
|
|
572
|
+
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
|
|
556
577
|
};
|
|
557
578
|
|
|
558
579
|
return URDFViewer;
|