@operato/scene-urdf 1.2.2

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +41 -0
  3. package/assets/images/spinner.png +0 -0
  4. package/demo/index.html +109 -0
  5. package/dist/editors/index.d.ts +0 -0
  6. package/dist/editors/index.js +2 -0
  7. package/dist/editors/index.js.map +1 -0
  8. package/dist/elements/drag-n-drop.d.ts +2 -0
  9. package/dist/elements/drag-n-drop.js +126 -0
  10. package/dist/elements/drag-n-drop.js.map +1 -0
  11. package/dist/elements/urdf-controller-element.d.ts +12 -0
  12. package/dist/elements/urdf-controller-element.js +283 -0
  13. package/dist/elements/urdf-controller-element.js.map +1 -0
  14. package/dist/elements/urdf-drag-controls.d.ts +32 -0
  15. package/dist/elements/urdf-drag-controls.js +185 -0
  16. package/dist/elements/urdf-drag-controls.js.map +1 -0
  17. package/dist/elements/urdf-manipulator-element.d.ts +15 -0
  18. package/dist/elements/urdf-manipulator-element.js +110 -0
  19. package/dist/elements/urdf-manipulator-element.js.map +1 -0
  20. package/dist/elements/urdf-viewer-element.d.ts +53 -0
  21. package/dist/elements/urdf-viewer-element.js +401 -0
  22. package/dist/elements/urdf-viewer-element.js.map +1 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +3 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/templates/index.d.ts +14 -0
  27. package/dist/templates/index.js +4 -0
  28. package/dist/templates/index.js.map +1 -0
  29. package/dist/templates/urdf-controller.d.ts +14 -0
  30. package/dist/templates/urdf-controller.js +16 -0
  31. package/dist/templates/urdf-controller.js.map +1 -0
  32. package/dist/templates/urdf-viewer.d.ts +14 -0
  33. package/dist/templates/urdf-viewer.js +16 -0
  34. package/dist/templates/urdf-viewer.js.map +1 -0
  35. package/dist/urdf-controller.d.ts +27 -0
  36. package/dist/urdf-controller.js +65 -0
  37. package/dist/urdf-controller.js.map +1 -0
  38. package/dist/urdf-viewer.d.ts +50 -0
  39. package/dist/urdf-viewer.js +198 -0
  40. package/dist/urdf-viewer.js.map +1 -0
  41. package/icons/urdf-controller.png +0 -0
  42. package/icons/urdf-viewer.png +0 -0
  43. package/package.json +66 -0
  44. package/src/editors/index.ts +0 -0
  45. package/src/elements/drag-n-drop.ts +142 -0
  46. package/src/elements/urdf-controller-element.ts +297 -0
  47. package/src/elements/urdf-drag-controls.ts +248 -0
  48. package/src/elements/urdf-manipulator-element.ts +133 -0
  49. package/src/elements/urdf-viewer-element.ts +495 -0
  50. package/src/index.ts +2 -0
  51. package/src/templates/index.ts +4 -0
  52. package/src/templates/urdf-controller.ts +16 -0
  53. package/src/templates/urdf-viewer.ts +16 -0
  54. package/src/urdf-controller.ts +82 -0
  55. package/src/urdf-viewer.ts +249 -0
  56. package/things-scene.config.js +7 -0
  57. package/translations/en.json +16 -0
  58. package/translations/ko.json +16 -0
  59. package/translations/ms.json +16 -0
  60. package/translations/zh.json +16 -0
  61. package/tsconfig.json +22 -0
  62. package/tsconfig.tsbuildinfo +1 -0
  63. package/web-dev-server.config.mjs +27 -0
@@ -0,0 +1,495 @@
1
+ import * as THREE from 'three'
2
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
3
+ import URDFLoader, { URDFJoint, URDFRobot } from 'urdf-loader/src/URDFLoader'
4
+ import { XacroLoader } from 'xacro-parser'
5
+
6
+ // urdf-viewer element
7
+ // Loads and displays a 3D view of a URDF-formatted robot
8
+
9
+ // Events
10
+ // urdf-change: Fires when the URDF has finished loading and getting processed
11
+ // urdf-processed: Fires when the URDF has finished loading and getting processed
12
+ // geometry-loaded: Fires when all the geometry has been fully loaded
13
+ // ignore-limits-change: Fires when the 'ignore-limits' attribute changes
14
+ // angle-change: Fires when an angle changes
15
+ export default class URDFViewerElement extends HTMLElement {
16
+ static get observedAttributes() {
17
+ return ['package', 'urdf', 'up', 'display-shadow', 'ambient-color', 'ignore-limits', 'no-auto-recenter']
18
+ }
19
+
20
+ get package() {
21
+ return this.getAttribute('package') || ''
22
+ }
23
+ set package(val) {
24
+ this.setAttribute('package', val)
25
+ }
26
+
27
+ get urdf() {
28
+ return this.getAttribute('urdf') || ''
29
+ }
30
+ set urdf(val) {
31
+ this.setAttribute('urdf', val)
32
+ }
33
+
34
+ get ignoreLimits() {
35
+ return this.hasAttribute('ignore-limits') || false
36
+ }
37
+ set ignoreLimits(val) {
38
+ val ? this.setAttribute('ignore-limits', String(val)) : this.removeAttribute('ignore-limits')
39
+ }
40
+
41
+ get up() {
42
+ return this.getAttribute('up') || '+Z'
43
+ }
44
+ set up(val) {
45
+ this.setAttribute('up', val)
46
+ }
47
+
48
+ get displayShadow() {
49
+ return this.hasAttribute('display-shadow') || false
50
+ }
51
+ set displayShadow(val) {
52
+ val ? this.setAttribute('display-shadow', '') : this.removeAttribute('display-shadow')
53
+ }
54
+
55
+ get ambientColor() {
56
+ return this.getAttribute('ambient-color') || '#455A64'
57
+ }
58
+ set ambientColor(val) {
59
+ val ? this.setAttribute('ambient-color', val) : this.removeAttribute('ambient-color')
60
+ }
61
+
62
+ get noAutoRecenter() {
63
+ return this.hasAttribute('no-auto-recenter') || false
64
+ }
65
+ set noAutoRecenter(val) {
66
+ val ? this.setAttribute('no-auto-recenter', String(true)) : this.removeAttribute('no-auto-recenter')
67
+ }
68
+
69
+ get jointValues() {
70
+ const values = {} as any
71
+ if (this.robot) {
72
+ for (const name in this.robot.joints) {
73
+ const joint = this.robot.joints[name]
74
+ values[name] = joint.jointValue.length === 1 ? joint.angle : [...joint.jointValue]
75
+ }
76
+ }
77
+
78
+ return values
79
+ }
80
+ set jointValues(val) {
81
+ this.setJointValues(val)
82
+ }
83
+
84
+ get angles() {
85
+ return this.jointValues
86
+ }
87
+ set angles(v) {
88
+ this.jointValues = v
89
+ }
90
+
91
+ robot?: URDFRobot
92
+ controls: OrbitControls
93
+ scene: THREE.Scene
94
+ camera: THREE.PerspectiveCamera
95
+
96
+ world: THREE.Object3D
97
+ renderer: THREE.WebGLRenderer
98
+ plane: THREE.Mesh
99
+ directionalLight: THREE.DirectionalLight
100
+ ambientLight: THREE.HemisphereLight
101
+
102
+ _prevload?: string
103
+ _requestId = 0
104
+ _dirty = true
105
+ _loadScheduled = false
106
+ loadMeshFunc?: (
107
+ url: string,
108
+ manager: THREE.LoadingManager,
109
+ onLoad: (mesh: THREE.Object3D, err?: Error) => void
110
+ ) => void
111
+ urlModifierFunc?: (url: string) => string
112
+
113
+ /* Lifecycle Functions */
114
+ constructor() {
115
+ super()
116
+
117
+ const scene = new THREE.Scene()
118
+
119
+ const ambientLight = new THREE.HemisphereLight(this.ambientColor, '#000')
120
+ ambientLight.groundColor.lerp(ambientLight.color, 0.5)
121
+ ambientLight.intensity = 0.5
122
+ ambientLight.position.set(0, 1, 0)
123
+ scene.add(ambientLight)
124
+
125
+ // Light setup
126
+ const dirLight = new THREE.DirectionalLight(0xffffff)
127
+ dirLight.position.set(4, 10, 1)
128
+ dirLight.shadow.mapSize.width = 2048
129
+ dirLight.shadow.mapSize.height = 2048
130
+ dirLight.shadow.normalBias = 0.001
131
+ dirLight.castShadow = true
132
+ scene.add(dirLight)
133
+ scene.add(dirLight.target)
134
+
135
+ // Renderer setup
136
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
137
+ renderer.setClearColor(0xffffff)
138
+ renderer.setClearAlpha(0)
139
+ renderer.shadowMap.enabled = true
140
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap
141
+ renderer.outputEncoding = THREE.sRGBEncoding
142
+
143
+ // Camera setup
144
+ const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000)
145
+ camera.position.z = -10
146
+
147
+ // World setup
148
+ const world = new THREE.Object3D()
149
+ scene.add(world)
150
+
151
+ const plane = new THREE.Mesh(
152
+ new THREE.PlaneBufferGeometry(40, 40),
153
+ new THREE.ShadowMaterial({ side: THREE.DoubleSide, transparent: true, opacity: 0.5 })
154
+ )
155
+ plane.rotation.x = -Math.PI / 2
156
+ plane.position.y = -0.5
157
+ plane.receiveShadow = true
158
+ plane.scale.set(10, 10, 10)
159
+ scene.add(plane)
160
+
161
+ // Controls setup
162
+ const controls = new OrbitControls(camera, renderer.domElement)
163
+ controls.rotateSpeed = 2.0
164
+ controls.zoomSpeed = 5
165
+ controls.panSpeed = 2
166
+ controls.enableZoom = true
167
+ controls.enableDamping = false
168
+ controls.maxDistance = 50
169
+ controls.minDistance = 0.25
170
+ controls.addEventListener('change', () => this.recenter())
171
+
172
+ this.scene = scene
173
+ this.world = world
174
+ this.renderer = renderer
175
+ this.camera = camera
176
+ this.controls = controls
177
+ this.plane = plane
178
+ this.directionalLight = dirLight
179
+ this.ambientLight = ambientLight
180
+
181
+ this._setUp(this.up)
182
+ }
183
+
184
+ connectedCallback() {
185
+ if (!this.querySelector('canvas')) {
186
+ var canvas = this.renderer.domElement
187
+ canvas.style.display = 'block'
188
+ canvas.style.width = '100%'
189
+ canvas.style.height = '100%'
190
+
191
+ this.appendChild(canvas)
192
+ }
193
+
194
+ requestAnimationFrame(() => this.recenter())
195
+ }
196
+
197
+ disconnectedCallback() {}
198
+
199
+ attributeChangedCallback(attr: string, oldval: any, newval: any) {
200
+ this.recenter()
201
+
202
+ switch (attr) {
203
+ case 'package':
204
+ case 'urdf': {
205
+ this._scheduleLoad()
206
+ break
207
+ }
208
+
209
+ case 'up': {
210
+ this._setUp(this.up)
211
+ break
212
+ }
213
+
214
+ case 'ambient-color': {
215
+ this.ambientLight.color.set(this.ambientColor)
216
+ this.ambientLight.groundColor.set('#000').lerp(this.ambientLight.color, 0.5)
217
+ break
218
+ }
219
+
220
+ case 'ignore-limits': {
221
+ this._setIgnoreLimits(this.ignoreLimits, true)
222
+ break
223
+ }
224
+ }
225
+ }
226
+
227
+ /* Public API */
228
+ updateSize() {
229
+ const r = this.renderer
230
+ const w = this.clientWidth
231
+ const h = this.clientHeight
232
+
233
+ const currsize = r.getSize(new THREE.Vector2())
234
+
235
+ if (currsize.width !== w || currsize.height !== h) {
236
+ this.recenter()
237
+ }
238
+
239
+ r.setPixelRatio(window.devicePixelRatio)
240
+ r.setSize(w, h, false)
241
+
242
+ this.camera.aspect = w / h
243
+ this.camera.updateProjectionMatrix()
244
+ }
245
+
246
+ redraw() {
247
+ if (this.parentNode && this._dirty) {
248
+ this._dirty = false
249
+ try {
250
+ this.updateSize()
251
+
252
+ if (!this.noAutoRecenter) {
253
+ this._updateEnvironment()
254
+ }
255
+
256
+ this.renderer.render(this.scene, this.camera)
257
+
258
+ // update controls after the environment in
259
+ // case the controls are retargeted
260
+ this.controls.update()
261
+ } catch (err) {
262
+ console.error(err)
263
+ } finally {
264
+ this._dirty = true
265
+ }
266
+ }
267
+ }
268
+
269
+ recenter() {
270
+ this._updateEnvironment()
271
+ this.redraw()
272
+ }
273
+
274
+ // Set the joint with jointName to
275
+ // angle in degrees
276
+ setJointValue(jointName: string, ...values: Number[]) {
277
+ if (!this.robot) return
278
+ if (!this.robot.joints[jointName]) return
279
+
280
+ if (this.robot.joints[jointName].setJointValue(values[0], values[1], values[2]) as any) {
281
+ this.redraw()
282
+ this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }))
283
+ }
284
+ }
285
+
286
+ setJointValues(values: Number[]) {
287
+ for (const name in values) this.setJointValue(name, values[name])
288
+ }
289
+
290
+ /* Private Functions */
291
+ // Updates the position of the plane to be at the
292
+ // lowest point below the robot and focuses the
293
+ // camera on the center of the scene
294
+ _updateEnvironment() {
295
+ if (!this.robot) return
296
+
297
+ this.world.updateMatrixWorld()
298
+
299
+ const bbox = new THREE.Box3()
300
+ bbox.setFromObject(this.robot)
301
+
302
+ const center = bbox.getCenter(new THREE.Vector3())
303
+ this.controls.target.y = center.y
304
+ this.plane.position.y = bbox.min.y - 1e-3
305
+
306
+ const dirLight = this.directionalLight
307
+ dirLight.castShadow = this.displayShadow
308
+
309
+ if (this.displayShadow) {
310
+ // Update the shadow camera rendering bounds to encapsulate the
311
+ // model. We use the bounding sphere of the bounding box for
312
+ // simplicity -- this could be a tighter fit.
313
+ const sphere = bbox.getBoundingSphere(new THREE.Sphere())
314
+ const minmax = sphere.radius
315
+ const cam = dirLight.shadow.camera
316
+ cam.left = cam.bottom = -minmax
317
+ cam.right = cam.top = minmax
318
+
319
+ // Update the camera to focus on the center of the model so the
320
+ // shadow can encapsulate it
321
+ const offset = dirLight.position.clone().sub(dirLight.target.position)
322
+ dirLight.target.position.copy(center)
323
+ dirLight.position.copy(center).add(offset)
324
+
325
+ cam.updateProjectionMatrix()
326
+ }
327
+ }
328
+
329
+ _scheduleLoad() {
330
+ // if our current model is already what's being requested
331
+ // or has been loaded then early out
332
+ if (this._prevload === `${this.package}|${this.urdf}`) return
333
+ this._prevload = `${this.package}|${this.urdf}`
334
+
335
+ // if we're already waiting on a load then early out
336
+ if (this._loadScheduled) return
337
+ this._loadScheduled = true
338
+
339
+ if (this.robot) {
340
+ this.robot.traverse((c: any) => c.dispose && c.dispose())
341
+ this.robot.parent!.remove(this.robot)
342
+ delete this.robot
343
+ }
344
+
345
+ requestAnimationFrame(() => {
346
+ this._loadUrdf(this.package, this.urdf)
347
+ this._loadScheduled = false
348
+ })
349
+ }
350
+
351
+ // Watch the package and urdf field and load the robot model.
352
+ // This should _only_ be called from _scheduleLoad because that
353
+ // ensures the that current robot has been removed
354
+ _loadUrdf(pkg: string, urdf: string) {
355
+ this.dispatchEvent(new CustomEvent('urdf-change', { bubbles: true, cancelable: true, composed: true }))
356
+
357
+ if (urdf) {
358
+ // Keep track of this request and make
359
+ // sure it doesn't get overwritten by
360
+ // a subsequent one
361
+ this._requestId++
362
+ const requestId = this._requestId
363
+
364
+ const updateMaterials = (mesh: URDFRobot) => {
365
+ mesh.traverse(c => {
366
+ if ((c as any).isMesh) {
367
+ c.castShadow = true
368
+ c.receiveShadow = true
369
+
370
+ if ((c as any).material) {
371
+ const mats = (Array.isArray((c as any).material) ? (c as any).material : [(c as any).material]).map(
372
+ (m: any) => {
373
+ if (m instanceof THREE.MeshBasicMaterial) {
374
+ m = new THREE.MeshPhongMaterial()
375
+ }
376
+
377
+ if (m.map) {
378
+ m.map.encoding = THREE.GammaEncoding
379
+ }
380
+
381
+ return m
382
+ }
383
+ )
384
+
385
+ ;(c as any).material = mats.length === 1 ? mats[0] : mats
386
+ }
387
+ }
388
+ })
389
+ }
390
+
391
+ if (pkg.includes(':') && pkg.split(':')[1].substring(0, 2) !== '//') {
392
+ // E.g. pkg = "pkg_name: path/to/pkg_name, pk2: path2/to/pk2"}
393
+
394
+ // Convert pkg(s) into a map. E.g.
395
+ // { "pkg_name": "path/to/pkg_name",
396
+ // "pk2": "path2/to/pk2" }
397
+
398
+ pkg = pkg.split(',').reduce((map: any, value: string) => {
399
+ const split = value.split(/:/).filter(x => !!x)
400
+ const pkgName = split.shift()!.trim()
401
+ const pkgPath = split.join(':').trim()
402
+ map[pkgName] = pkgPath
403
+
404
+ return map
405
+ }, {} as any)
406
+ }
407
+
408
+ let robot: URDFRobot
409
+ const manager = new THREE.LoadingManager()
410
+ manager.onLoad = () => {
411
+ // If another request has come in to load a new
412
+ // robot, then ignore this one
413
+ if (this._requestId !== requestId) {
414
+ robot.traverse(c => (c as any).dispose && (c as any).dispose())
415
+ return
416
+ }
417
+
418
+ this.robot = robot
419
+
420
+ this.world.add(robot)
421
+ updateMaterials(robot)
422
+
423
+ this._setIgnoreLimits(this.ignoreLimits)
424
+
425
+ this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }))
426
+ this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }))
427
+
428
+ this.recenter()
429
+ }
430
+
431
+ if (this.urlModifierFunc) {
432
+ manager.setURLModifier(this.urlModifierFunc)
433
+ }
434
+
435
+ const checkXacro = /[.]/.exec(urdf) ? /[^.]+$/.exec(urdf) : undefined
436
+
437
+ if (checkXacro && checkXacro[0] === 'xacro') {
438
+ const xacroLoader = new XacroLoader()
439
+ try {
440
+ xacroLoader.load(
441
+ urdf,
442
+ xml => {
443
+ const loader = new URDFLoader(manager)
444
+ loader.packages = pkg
445
+ loader.loadMeshCb = this.loadMeshFunc!
446
+ loader.fetchOptions = { mode: 'cors', credentials: 'same-origin' }
447
+ robot = loader.parse(xml)
448
+ },
449
+ err => {
450
+ console.error('xacroloader error: ', err)
451
+ }
452
+ )
453
+ } catch (error) {
454
+ console.error(error)
455
+ }
456
+ } else {
457
+ const loader = new URDFLoader(manager)
458
+ loader.packages = pkg
459
+ loader.loadMeshCb = this.loadMeshFunc!
460
+ loader.fetchOptions = { mode: 'cors', credentials: 'same-origin' }
461
+ loader.load(urdf, model => (robot = model))
462
+ }
463
+ }
464
+ }
465
+
466
+ // Watch the coordinate frame and update the
467
+ // rotation of the scene to match
468
+ _setUp(up: string) {
469
+ if (!up) up = '+Z'
470
+ up = up.toUpperCase()
471
+ const sign = up.replace(/[^-+]/g, '')[0] || '+'
472
+ const axis = up.replace(/[^XYZ]/gi, '')[0] || 'Z'
473
+
474
+ const PI = Math.PI
475
+ const HALFPI = PI / 2
476
+ if (axis === 'X') this.world.rotation.set(0, 0, sign === '+' ? HALFPI : -HALFPI)
477
+ if (axis === 'Z') this.world.rotation.set(sign === '+' ? -HALFPI : HALFPI, 0, 0)
478
+ if (axis === 'Y') this.world.rotation.set(sign === '+' ? 0 : PI, 0, 0)
479
+ }
480
+
481
+ // Updates the current robot's angles to ignore
482
+ // joint limits or not
483
+ _setIgnoreLimits(ignore: boolean, dispatch = false) {
484
+ if (this.robot) {
485
+ Object.values(this.robot.joints).forEach(joint => {
486
+ joint.ignoreLimits = ignore
487
+ joint.setJointValue(joint.jointValue[0], joint.jointValue[1], joint.jointValue[2])
488
+ })
489
+ }
490
+
491
+ if (dispatch) {
492
+ this.dispatchEvent(new CustomEvent('ignore-limits-change', { bubbles: true, cancelable: true, composed: true }))
493
+ }
494
+ }
495
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default as UrdfViewer } from './urdf-viewer'
2
+ export { default as UrdfController } from './urdf-controller'
@@ -0,0 +1,4 @@
1
+ import URDFViewer from './urdf-viewer'
2
+ import URDFController from './urdf-controller'
3
+
4
+ export default [URDFViewer, URDFController]
@@ -0,0 +1,16 @@
1
+ const icon = new URL('../../icons/urdf-controller.png', import.meta.url).href
2
+
3
+ export default {
4
+ type: 'urdf-controller',
5
+ description: 'urdf-controller',
6
+ group: '3D',
7
+ /* line|shape|textAndMedia|chartAndGauge|table|container|dataSource|IoT|3D|warehouse|form|etc */
8
+ icon,
9
+ model: {
10
+ type: 'urdf-controller',
11
+ left: 10,
12
+ top: 10,
13
+ width: 300,
14
+ height: 120
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ const icon = new URL('../../icons/urdf-viewer.png', import.meta.url).href
2
+
3
+ export default {
4
+ type: 'urdf-viewer',
5
+ description: 'urdf-viewer',
6
+ group: '3D',
7
+ /* line|shape|textAndMedia|chartAndGauge|table|container|dataSource|IoT|3D|warehouse|form|etc */
8
+ icon,
9
+ model: {
10
+ type: 'urdf-viewer',
11
+ left: 10,
12
+ top: 10,
13
+ width: 300,
14
+ height: 200
15
+ }
16
+ }
@@ -0,0 +1,82 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+
5
+ const NATURE = {
6
+ mutable: false,
7
+ resizable: true,
8
+ rotatable: true,
9
+ properties: [
10
+ {
11
+ type: 'id-input',
12
+ label: 'urdf-reference',
13
+ name: 'reference',
14
+ property: {
15
+ component: 'urdf-viewer'
16
+ }
17
+ }
18
+ ]
19
+ }
20
+
21
+ import { Component, HTMLOverlayContainer } from '@hatiolab/things-scene'
22
+ import './elements/urdf-controller-element'
23
+ import URDFControllerElement from './elements/urdf-controller-element'
24
+ import URDFManipulatorElement from './elements/urdf-manipulator-element'
25
+ import UrdfViewer from './urdf-viewer'
26
+
27
+ export default class UrdfController extends HTMLOverlayContainer {
28
+ _reference?: UrdfViewer
29
+
30
+ static get nature() {
31
+ return NATURE
32
+ }
33
+
34
+ oncreate_element(controller: URDFControllerElement) {}
35
+
36
+ dispose() {
37
+ super.dispose()
38
+ }
39
+
40
+ /*
41
+ * 컴포넌트의 생성 또는 속성 변화 시에 호출되며,
42
+ * 그에 따른 html element의 반영이 필요한 부분을 구현한다.
43
+ *
44
+ * ThingsComponent state => HTML element properties
45
+ */
46
+ setElementProperties(controller: URDFControllerElement) {
47
+ var reference = this.reference
48
+ controller.viewer = reference ? (reference.element as URDFManipulatorElement) : undefined
49
+ }
50
+
51
+ /*
52
+ * 컴포넌트가 ready 상태가 되거나, 컴포넌트의 속성이 변화될 시 setElementProperties 뒤에 호출된다.
53
+ * 변화에 따른 기본적인 html 속성이 super.reposition()에서 진행되고, 그 밖의 작업이 필요할 때, 오버라이드 한다.
54
+ */
55
+ reposition() {
56
+ super.reposition()
57
+ }
58
+
59
+ get tagName() {
60
+ return 'urdf-controller'
61
+ }
62
+
63
+ get reference() {
64
+ var { reference } = this.state
65
+ if (!reference) {
66
+ return
67
+ }
68
+
69
+ if (!this._reference) {
70
+ this._reference = this.root.findById(reference) as UrdfViewer
71
+ }
72
+
73
+ return this._reference
74
+ }
75
+
76
+ set reference(reference) {
77
+ delete this._reference
78
+ this.state.reference = reference
79
+ }
80
+ }
81
+
82
+ Component.register('urdf-controller', UrdfController)