@viamrobotics/motion-tools 1.10.0 → 1.11.0

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 (106) hide show
  1. package/dist/HoverUpdater.svelte.d.ts +0 -3
  2. package/dist/HoverUpdater.svelte.js +8 -50
  3. package/dist/WorldObject.svelte.d.ts +27 -0
  4. package/dist/WorldObject.svelte.js +8 -55
  5. package/dist/{draw → buf/draw}/v1/drawing_pb.d.ts +6 -0
  6. package/dist/{draw → buf/draw}/v1/drawing_pb.js +7 -0
  7. package/dist/buf/draw/v1/service_connect.d.ts +122 -0
  8. package/dist/buf/draw/v1/service_connect.js +126 -0
  9. package/dist/buf/draw/v1/service_pb.d.ts +382 -0
  10. package/dist/buf/draw/v1/service_pb.js +612 -0
  11. package/dist/components/App.svelte +0 -1
  12. package/dist/components/Arrows/Arrows.svelte +16 -3
  13. package/dist/components/FileDrop/file-dropper.d.ts +1 -1
  14. package/dist/components/FileDrop/snapshot-dropper.js +1 -1
  15. package/dist/components/FileDrop/useFileDrop.svelte.d.ts +2 -1
  16. package/dist/components/Frame.svelte +1 -1
  17. package/dist/components/Geometry.svelte +113 -71
  18. package/dist/components/Geometry.svelte.d.ts +6 -7
  19. package/dist/components/SceneProviders.svelte +2 -0
  20. package/dist/components/Snapshot.svelte +1 -1
  21. package/dist/components/Snapshot.svelte.d.ts +1 -1
  22. package/dist/components/overlay/Details.svelte +20 -0
  23. package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -2
  24. package/dist/components/overlay/settings/Settings.svelte +51 -0
  25. package/dist/components/overlay/widgets/Camera.svelte +20 -12
  26. package/dist/components/xr/ArmTeleop.svelte +469 -0
  27. package/dist/components/xr/ArmTeleop.svelte.d.ts +10 -0
  28. package/dist/components/xr/CameraFeed.svelte +191 -47
  29. package/dist/components/xr/CameraFeed.svelte.d.ts +7 -0
  30. package/dist/components/xr/Controllers.svelte +45 -38
  31. package/dist/components/xr/Controllers.svelte.d.ts +2 -17
  32. package/dist/components/xr/Hands.svelte +2 -4
  33. package/dist/components/xr/JointLimitsWidget.svelte +209 -0
  34. package/dist/components/xr/JointLimitsWidget.svelte.d.ts +13 -0
  35. package/dist/components/xr/OriginMarker.svelte +1 -15
  36. package/dist/components/xr/XR.svelte +78 -5
  37. package/dist/components/xr/XRConfigPanel.svelte +449 -0
  38. package/dist/components/xr/XRConfigPanel.svelte.d.ts +11 -0
  39. package/dist/components/xr/XRControllerSettings.svelte +240 -0
  40. package/dist/components/xr/XRControllerSettings.svelte.d.ts +3 -0
  41. package/dist/components/xr/XRToast.svelte +215 -0
  42. package/dist/components/xr/XRToast.svelte.d.ts +3 -0
  43. package/dist/components/xr/math.d.ts +14 -0
  44. package/dist/components/xr/math.js +26 -0
  45. package/dist/components/xr/toasts.svelte.d.ts +20 -0
  46. package/dist/components/xr/toasts.svelte.js +32 -0
  47. package/dist/components/xr/useOrigin.svelte.d.ts +2 -2
  48. package/dist/components/xr/useOrigin.svelte.js +4 -4
  49. package/dist/ecs/traits.d.ts +9 -0
  50. package/dist/ecs/traits.js +9 -0
  51. package/dist/ecs/useTrait.svelte.d.ts +3 -3
  52. package/dist/frame.d.ts +0 -3
  53. package/dist/hooks/useArmKinematics.svelte.d.ts +12 -0
  54. package/dist/hooks/useArmKinematics.svelte.js +31 -0
  55. package/dist/hooks/useGeometries.svelte.js +46 -35
  56. package/dist/hooks/useObjectEvents.svelte.js +24 -7
  57. package/dist/hooks/usePartConfig.svelte.d.ts +0 -35
  58. package/dist/hooks/usePartConfig.svelte.js +2 -2
  59. package/dist/hooks/usePointcloudObjects.svelte.js +44 -63
  60. package/dist/hooks/usePointclouds.svelte.js +10 -6
  61. package/dist/hooks/usePose.svelte.js +4 -1
  62. package/dist/hooks/useResourceByName.svelte.d.ts +7 -0
  63. package/dist/hooks/useResourceByName.svelte.js +2 -2
  64. package/dist/hooks/useSettings.svelte.d.ts +14 -0
  65. package/dist/hooks/useSettings.svelte.js +10 -0
  66. package/dist/hooks/useWorldState.svelte.d.ts +0 -8
  67. package/dist/lib.d.ts +1 -3
  68. package/dist/lib.js +1 -3
  69. package/dist/snapshot.d.ts +2 -2
  70. package/dist/snapshot.js +2 -2
  71. package/dist/three/InstancedArrows/raycast.d.ts +2 -4
  72. package/dist/three/InstancedArrows/raycast.js +5 -5
  73. package/dist/transform.js +1 -0
  74. package/package.json +4 -5
  75. package/dist/assert.d.ts +0 -14
  76. package/dist/assert.js +0 -21
  77. package/dist/components/BatchedGeometry.svelte +0 -0
  78. package/dist/components/BatchedGeometry.svelte.d.ts +0 -26
  79. package/dist/components/Detections.svelte +0 -41
  80. package/dist/components/Detections.svelte.d.ts +0 -3
  81. package/dist/components/DetectionsPlane.svelte +0 -23
  82. package/dist/components/DetectionsPlane.svelte.d.ts +0 -21
  83. package/dist/components/Geometry2.svelte +0 -211
  84. package/dist/components/Geometry2.svelte.d.ts +0 -19
  85. package/dist/components/overlay/left-pane/Widgets.svelte +0 -65
  86. package/dist/components/overlay/left-pane/Widgets.svelte.d.ts +0 -3
  87. package/dist/entries.d.ts +0 -1
  88. package/dist/entries.js +0 -3
  89. package/dist/hooks/index.d.ts +0 -0
  90. package/dist/hooks/index.js +0 -1
  91. package/dist/test.d.ts +0 -1
  92. package/dist/test.js +0 -1
  93. package/dist/three/BoxHelper.d.ts +0 -50
  94. package/dist/three/BoxHelper.js +0 -134
  95. /package/dist/{common → buf/common}/v1/common_pb.d.ts +0 -0
  96. /package/dist/{common → buf/common}/v1/common_pb.js +0 -0
  97. /package/dist/{draw → buf/draw}/v1/metadata_pb.d.ts +0 -0
  98. /package/dist/{draw → buf/draw}/v1/metadata_pb.js +0 -0
  99. /package/dist/{draw → buf/draw}/v1/scene_pb.d.ts +0 -0
  100. /package/dist/{draw → buf/draw}/v1/scene_pb.js +0 -0
  101. /package/dist/{draw → buf/draw}/v1/snapshot_pb.d.ts +0 -0
  102. /package/dist/{draw → buf/draw}/v1/snapshot_pb.js +0 -0
  103. /package/dist/{draw → buf/draw}/v1/transforms_pb.d.ts +0 -0
  104. /package/dist/{draw → buf/draw}/v1/transforms_pb.js +0 -0
  105. /package/dist/components/{BentPlaneGeometry.svelte → xr/BentPlaneGeometry.svelte} +0 -0
  106. /package/dist/components/{BentPlaneGeometry.svelte.d.ts → xr/BentPlaneGeometry.svelte.d.ts} +0 -0
@@ -1,57 +1,84 @@
1
- <!--
2
-
3
- This component is consumed as a library export
4
- and should remain pure, i.e. no hooks should be used.
5
-
6
- -->
7
1
  <script lang="ts">
8
- import { T, type Props as ThrelteProps } from '@threlte/core'
2
+ import { T, useThrelte, type Props as ThrelteProps } from '@threlte/core'
9
3
  import { type Snippet } from 'svelte'
10
- import { meshBounds, MeshLineGeometry, MeshLineMaterial } from '@threlte/extras'
11
- import { BufferGeometry, DoubleSide, FrontSide, Group, Mesh } from 'three'
4
+ import { meshBounds } from '@threlte/extras'
5
+ import { BufferGeometry, Color, DoubleSide, FrontSide, Group, Mesh } from 'three'
6
+ import { Line2, LineGeometry, LineMaterial } from 'three/examples/jsm/Addons.js'
12
7
  import { CapsuleGeometry } from '../three/CapsuleGeometry'
13
- import { poseToObject3d } from '../transform'
14
8
  import { colors, darkenColor } from '../color'
15
9
  import AxesHelper from './AxesHelper.svelte'
16
- import type { WorldObject } from '../WorldObject.svelte'
17
- import { parsePlyInput } from '../ply'
10
+ import type { Entity } from 'koota'
11
+ import { traits, useTrait } from '../ecs'
12
+ import { poseToObject3d } from '../transform'
13
+ import type { Pose } from '@viamrobotics/sdk'
18
14
 
19
15
  interface Props extends ThrelteProps<Group> {
20
- uuid: string
21
- name: string
22
- geometry?: WorldObject['geometry']
23
- pose: WorldObject['pose']
24
- metadata: WorldObject['metadata']
16
+ entity: Entity
25
17
  color?: string
26
18
  model?: Group
19
+ pose?: Pose
27
20
  renderMode?: 'model' | 'colliders' | 'colliders+model'
21
+ ref?: Group
28
22
  children?: Snippet<[{ ref: Group }]>
29
23
  }
30
24
 
31
25
  let {
32
- uuid,
33
- name,
34
- geometry,
35
- metadata,
36
- pose,
26
+ entity,
37
27
  color: overrideColor,
38
28
  model,
39
29
  renderMode = 'colliders',
30
+ pose,
31
+ ref = $bindable(),
40
32
  children,
41
33
  ...rest
42
34
  }: Props = $props()
43
35
 
44
- const type = $derived(geometry?.geometryType?.case)
45
- const color = $derived(overrideColor ?? metadata.color ?? colors.default)
36
+ const colorUtil = new Color()
37
+
38
+ const { invalidate } = useThrelte()
39
+ const name = useTrait(() => entity, traits.Name)
40
+ const entityColor = useTrait(() => entity, traits.Color)
41
+ const opacity = useTrait(() => entity, traits.Opacity)
42
+ const box = useTrait(() => entity, traits.Box)
43
+ const capsule = useTrait(() => entity, traits.Capsule)
44
+ const sphere = useTrait(() => entity, traits.Sphere)
45
+ const bufferGeometry = useTrait(() => entity, traits.BufferGeometry)
46
+ const linePositions = useTrait(() => entity, traits.LinePositions)
47
+ const lineWidth = useTrait(() => entity, traits.LineWidth)
48
+ const center = useTrait(() => entity, traits.Center)
49
+ const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
50
+
51
+ const geometryType = $derived.by(() => {
52
+ if (box.current) return 'box'
53
+ if (capsule.current) return 'capsule'
54
+ if (sphere.current) return 'sphere'
55
+ if (bufferGeometry.current) return 'buffer'
56
+ if (linePositions.current) return 'line'
57
+ })
58
+
59
+ const color = $derived.by(() => {
60
+ if (overrideColor) {
61
+ return overrideColor
62
+ }
63
+ if (entityColor.current) {
64
+ return colorUtil
65
+ .setRGB(entityColor.current.r, entityColor.current.g, entityColor.current.b)
66
+ .getHexString()
67
+ }
68
+ return colors.default
69
+ })
46
70
 
47
71
  const group = new Group()
72
+ ref = group
73
+
48
74
  const mesh = $derived.by(() => {
49
- if (type === undefined) {
75
+ if (geometryType === undefined) {
50
76
  return
51
77
  }
52
- const result = new Mesh()
53
78
 
54
- if (type === 'mesh' || type === 'points' || type === 'line') {
79
+ const result = geometryType === 'line' ? new Line2() : new Mesh()
80
+
81
+ if (geometryType === 'line') {
55
82
  result.raycast = meshBounds
56
83
  }
57
84
 
@@ -59,90 +86,106 @@ and should remain pure, i.e. no hooks should be used.
59
86
  })
60
87
 
61
88
  $effect.pre(() => {
62
- if (geometry?.center && mesh) {
63
- poseToObject3d(geometry.center, mesh)
89
+ if (mesh && center.current) {
90
+ poseToObject3d(center.current, mesh)
91
+ invalidate()
64
92
  }
65
93
  })
66
94
 
95
+ const entityPose = useTrait(() => entity, traits.Pose)
96
+ const resolvedPose = $derived(pose ?? entityPose.current)
67
97
  $effect.pre(() => {
68
- poseToObject3d(pose, group)
98
+ if (resolvedPose) {
99
+ poseToObject3d(resolvedPose, group)
100
+ invalidate()
101
+ }
69
102
  })
70
103
 
71
104
  let geo = $state.raw<BufferGeometry>()
72
105
 
73
- const oncreate = (ref: BufferGeometry) => {
74
- geo = ref
106
+ const oncreate = (bufferGeometry: BufferGeometry) => {
107
+ geo = bufferGeometry
75
108
  }
109
+
110
+ $effect.pre(() => {
111
+ if (mesh && bufferGeometry.current) {
112
+ mesh.geometry = bufferGeometry.current
113
+ oncreate(bufferGeometry.current)
114
+
115
+ return () => {
116
+ geo = undefined
117
+ mesh?.geometry?.dispose()
118
+ }
119
+ }
120
+ })
76
121
  </script>
77
122
 
78
123
  <T
79
124
  is={group}
80
125
  {...rest}
81
126
  >
82
- {#if geometry?.geometryType}
83
- <AxesHelper
84
- width={3}
85
- length={0.1}
86
- />
127
+ {#if geometryType}
128
+ {#if showAxesHelper.current}
129
+ <AxesHelper
130
+ width={3}
131
+ length={0.1}
132
+ />
133
+ {/if}
87
134
 
88
135
  <T
89
136
  is={mesh}
90
- {name}
91
- {uuid}
92
- bvh={{ enabled: false }}
137
+ name={entity}
138
+ userData.name={name}
139
+ bvh={{ enabled: geometryType === 'buffer' }}
93
140
  >
94
141
  {#if model && renderMode.includes('model')}
95
142
  <T is={model} />
96
143
  {/if}
97
144
 
98
- {#if renderMode.includes('colliders')}
99
- {#if geometry.geometryType.case === 'bufferGeometry'}
100
- <T
101
- is={geometry.geometryType.value}
102
- {oncreate}
103
- />
104
- {:else if geometry.geometryType.case === 'mesh'}
105
- {@const mesh = geometry.geometryType.value.mesh}
106
- {@const meshGeometry = parsePlyInput(mesh)}
145
+ {#if !model || renderMode.includes('colliders')}
146
+ {#if linePositions.current}
107
147
  <T
108
- is={meshGeometry}
109
- {oncreate}
148
+ is={LineGeometry}
149
+ oncreate={(ref) => {
150
+ if (linePositions.current) {
151
+ ref.setPositions(linePositions.current)
152
+ }
153
+ }}
110
154
  />
111
- {:else if geometry.geometryType.case === 'line' && metadata.points}
112
- <MeshLineGeometry points={metadata.points} />
113
- {:else if geometry.geometryType.case === 'box'}
114
- {@const dimsMm = geometry.geometryType.value.dimsMm ?? { x: 0, y: 0, z: 0 }}
155
+ {:else if box.current}
156
+ {@const { x, y, z } = box.current ?? { x: 0, y: 0, z: 0 }}
115
157
  <T.BoxGeometry
116
- args={[dimsMm.x * 0.001, dimsMm.y * 0.001, dimsMm.z * 0.001]}
158
+ args={[x * 0.001, y * 0.001, z * 0.001]}
117
159
  {oncreate}
118
160
  />
119
- {:else if geometry.geometryType.case === 'sphere'}
120
- {@const radiusMm = geometry.geometryType.value.radiusMm ?? 0}
161
+ {:else if sphere.current}
162
+ {@const { r } = sphere.current ?? { r: 0 }}
121
163
  <T.SphereGeometry
122
- args={[radiusMm * 0.001]}
164
+ args={[r * 0.001]}
123
165
  {oncreate}
124
166
  />
125
- {:else if geometry.geometryType.case === 'capsule'}
126
- {@const { lengthMm, radiusMm } = geometry.geometryType.value}
167
+ {:else if capsule.current}
168
+ {@const { r, l } = capsule.current ?? { r: 0, l: 0 }}
127
169
  <T
128
170
  is={CapsuleGeometry}
129
- args={[radiusMm * 0.001, lengthMm * 0.001]}
171
+ args={[r * 0.001, l * 0.001]}
130
172
  {oncreate}
131
173
  />
132
174
  {/if}
133
175
  {/if}
134
176
 
135
- {#if geometry.geometryType.case === 'line'}
136
- <MeshLineMaterial
177
+ {#if linePositions.current}
178
+ <T
179
+ is={LineMaterial}
137
180
  {color}
138
- width={metadata.lineWidth ?? 0.005}
181
+ width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
139
182
  />
140
183
  {:else}
141
184
  <T.MeshToonMaterial
142
185
  {color}
143
- side={geometry.geometryType.case === 'mesh' ? DoubleSide : FrontSide}
144
- transparent
145
- opacity={metadata.opacity ?? 0.7}
186
+ side={geometryType === 'buffer' ? DoubleSide : FrontSide}
187
+ transparent={(opacity.current ?? 0.7) < 1}
188
+ opacity={opacity.current ?? 0.7}
146
189
  />
147
190
 
148
191
  {#if geo && renderMode.includes('colliders')}
@@ -156,10 +199,9 @@ and should remain pure, i.e. no hooks should be used.
156
199
  {/if}
157
200
  {/if}
158
201
  </T>
159
- {:else}
202
+ {:else if showAxesHelper.current}
160
203
  <AxesHelper
161
- {name}
162
- {uuid}
204
+ name={name.current}
163
205
  width={3}
164
206
  length={0.1}
165
207
  />
@@ -1,20 +1,19 @@
1
1
  import { type Props as ThrelteProps } from '@threlte/core';
2
2
  import { type Snippet } from 'svelte';
3
3
  import { Group } from 'three';
4
- import type { WorldObject } from '../WorldObject.svelte';
4
+ import type { Entity } from 'koota';
5
+ import type { Pose } from '@viamrobotics/sdk';
5
6
  interface Props extends ThrelteProps<Group> {
6
- uuid: string;
7
- name: string;
8
- geometry?: WorldObject['geometry'];
9
- pose: WorldObject['pose'];
10
- metadata: WorldObject['metadata'];
7
+ entity: Entity;
11
8
  color?: string;
12
9
  model?: Group;
10
+ pose?: Pose;
13
11
  renderMode?: 'model' | 'colliders' | 'colliders+model';
12
+ ref?: Group;
14
13
  children?: Snippet<[{
15
14
  ref: Group;
16
15
  }]>;
17
16
  }
18
- declare const Geometry: import("svelte").Component<Props, {}, "">;
17
+ declare const Geometry: import("svelte").Component<Props, {}, "ref">;
19
18
  type Geometry = ReturnType<typeof Geometry>;
20
19
  export default Geometry;
@@ -17,6 +17,7 @@
17
17
  import { provideOrigin } from './xr/useOrigin.svelte'
18
18
  import { provideWorldStates } from '../hooks/useWorldState.svelte'
19
19
  import { provideArmClient } from '../hooks/useArmClient.svelte'
20
+ import { provideArmKinematics } from '../hooks/useArmKinematics.svelte'
20
21
  import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
21
22
  import { provideResourceByName } from '../hooks/useResourceByName.svelte'
22
23
  import { provide3DModels } from '../hooks/use3DModels.svelte'
@@ -48,6 +49,7 @@
48
49
  providePointclouds(() => partID.current)
49
50
  providePointcloudObjects(() => partID.current)
50
51
  provideArmClient(() => partID.current)
52
+ provideArmKinematics(() => partID.current)
51
53
  provideWorldStates()
52
54
  provideFramelessComponents()
53
55
 
@@ -14,7 +14,7 @@ Renders a Snapshot protobuf by spawning its transforms and drawings as entities
14
14
  ```
15
15
  -->
16
16
  <script lang="ts">
17
- import type { Snapshot as SnapshotProto } from '../draw/v1/snapshot_pb'
17
+ import type { Snapshot as SnapshotProto } from '../buf/draw/v1/snapshot_pb'
18
18
  import { useWorld } from '../ecs'
19
19
  import { useSettings } from '../hooks/useSettings.svelte'
20
20
  import { spawnSnapshotEntities, destroyEntities, applySceneMetadata } from '../snapshot'
@@ -1,4 +1,4 @@
1
- import type { Snapshot as SnapshotProto } from '../draw/v1/snapshot_pb';
1
+ import type { Snapshot as SnapshotProto } from '../buf/draw/v1/snapshot_pb';
2
2
  interface Props {
3
3
  snapshot: SnapshotProto;
4
4
  }
@@ -30,6 +30,7 @@
30
30
  import { useCameraControls } from '../../hooks/useControls.svelte'
31
31
  import { useLinkedEntities } from '../../hooks/useLinked.svelte'
32
32
  import AddRelationship from './AddRelationship.svelte'
33
+ import { createPose } from '../../transform'
33
34
 
34
35
  const { ...rest } = $props()
35
36
 
@@ -124,6 +125,25 @@
124
125
  }
125
126
  })
126
127
 
128
+ $effect(() => {
129
+ if (entity) {
130
+ const worldPose = createPose({
131
+ x: worldPosition.x,
132
+ y: worldPosition.y,
133
+ z: worldPosition.z,
134
+ oX: worldOrientation.x,
135
+ oY: worldOrientation.y,
136
+ oZ: worldOrientation.z,
137
+ theta: MathUtils.radToDeg(worldOrientation.th),
138
+ })
139
+ if (entity.has(traits.WorldPose)) {
140
+ entity.set(traits.WorldPose, worldPose)
141
+ } else {
142
+ entity.add(traits.WorldPose(worldPose))
143
+ }
144
+ }
145
+ })
146
+
127
147
  const getCopyClipboardText = () => {
128
148
  return JSON.stringify(
129
149
  {
@@ -4,7 +4,6 @@
4
4
  import { useSelectedEntity } from '../../../hooks/useSelection.svelte'
5
5
  import { provideTreeExpandedContext } from './useExpanded.svelte'
6
6
  import Logs from './Logs.svelte'
7
- import Widgets from './Widgets.svelte'
8
7
  import AddFrames from './AddFrames.svelte'
9
8
  import { useEnvironment } from '../../../hooks/useEnvironment.svelte'
10
9
  import { usePartID } from '../../../hooks/usePartID.svelte'
@@ -85,5 +84,4 @@
85
84
  {/if}
86
85
 
87
86
  <Logs />
88
- <Widgets />
89
87
  </div>
@@ -15,6 +15,7 @@
15
15
  import Tabs from './Tabs.svelte'
16
16
  import { PersistedState } from 'runed'
17
17
  import ToggleGroup from '../ToggleGroup.svelte'
18
+ import XRControllerSettings from '../../xr/XRControllerSettings.svelte'
18
19
 
19
20
  const { invalidate } = useThrelte()
20
21
  const partID = usePartID()
@@ -36,6 +37,10 @@
36
37
  invalidate()
37
38
  })
38
39
 
40
+ const currentRobotCameraWidgets = $derived(
41
+ settings.current.openCameraWidgets[partID.current] || []
42
+ )
43
+
39
44
  const isOpen = new PersistedState('settings-is-open', false)
40
45
  const activeTab = new PersistedState('settings-active-tab', 'Connection')
41
46
  </script>
@@ -258,6 +263,50 @@
258
263
  </div>
259
264
  {/snippet}
260
265
 
266
+ {#snippet XR()}
267
+ <div class="flex flex-col gap-2.5 text-xs">
268
+ <XRControllerSettings />
269
+ </div>
270
+ {/snippet}
271
+
272
+ {#snippet Widgets()}
273
+ <div class="text-gray-9 flex flex-col gap-1 text-xs">
274
+ <label class="flex items-center justify-between gap-2 py-1">
275
+ Arm positions
276
+ <Switch bind:on={settings.current.enableArmPositionsWidget} />
277
+ </label>
278
+
279
+ {@render SectionTitle('Camera widgets')}
280
+
281
+ {#each cameras.current as camera (camera)}
282
+ {@const isWidgetOpen = currentRobotCameraWidgets.includes(camera.name)}
283
+ <div class="flex items-center justify-between gap-2 py-0.5">
284
+ <span class="min-w-0 truncate">{camera.name}</span>
285
+ <Switch
286
+ on={isWidgetOpen}
287
+ on:change={(event) => {
288
+ if (event.detail) {
289
+ settings.current.openCameraWidgets = {
290
+ ...settings.current.openCameraWidgets,
291
+ [partID.current]: [...currentRobotCameraWidgets, camera.name],
292
+ }
293
+ } else {
294
+ settings.current.openCameraWidgets = {
295
+ ...settings.current.openCameraWidgets,
296
+ [partID.current]: currentRobotCameraWidgets.filter(
297
+ (widget) => widget !== camera.name
298
+ ),
299
+ }
300
+ }
301
+ }}
302
+ />
303
+ </div>
304
+ {:else}
305
+ No cameras detected
306
+ {/each}
307
+ </div>
308
+ {/snippet}
309
+
261
310
  <FloatingPanel
262
311
  title="Settings"
263
312
  bind:isOpen={isOpen.current}
@@ -270,7 +319,9 @@
270
319
  { label: 'Scene', content: Scene },
271
320
  { label: 'Pointclouds', content: Pointclouds },
272
321
  { label: 'Vision', content: Vision },
322
+ { label: 'Widgets', content: Widgets },
273
323
  { label: 'Stats', content: Stats },
324
+ ...('xr' in navigator ? [{ label: 'VR / AR', content: XR }] : []),
274
325
  ]}
275
326
  onValueChange={(value) => {
276
327
  activeTab.current = value
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { draggable } from '@neodrag/svelte'
3
3
  import { Icon, Select } from '@viamrobotics/prime-core'
4
- import { CameraStream, useRobotClient } from '@viamrobotics/svelte-sdk'
5
- import { StreamClient } from '@viamrobotics/sdk'
4
+ import { CameraStream, useRobotClient, useConnectionStatus } from '@viamrobotics/svelte-sdk'
5
+ import { StreamClient, MachineConnectionEvent } from '@viamrobotics/sdk'
6
6
  import { useSettings } from '../../../hooks/useSettings.svelte'
7
7
  import { usePartID } from '../../../hooks/usePartID.svelte'
8
8
  import { useEnvironment } from '../../../hooks/useEnvironment.svelte'
@@ -17,6 +17,7 @@
17
17
  const settings = useSettings()
18
18
  const partID = usePartID()
19
19
  const client = useRobotClient(() => partID.current)
20
+ const connectionStatus = useConnectionStatus(() => partID.current)
20
21
  const environment = useEnvironment()
21
22
 
22
23
  let dragElement = $state.raw<HTMLElement>()
@@ -70,13 +71,18 @@
70
71
  }
71
72
  }
72
73
 
73
- // Create a single StreamClient instance per robot client
74
- let streamClient = $derived(client.current ? new StreamClient(client.current) : undefined)
74
+ // Only create StreamClient when connection is fully established
75
+ let streamClient = $derived(
76
+ client.current && connectionStatus.current === MachineConnectionEvent.CONNECTED
77
+ ? new StreamClient(client.current)
78
+ : undefined
79
+ )
75
80
 
76
81
  $effect(() => {
77
82
  if (streamClient) {
78
83
  isLoading = true
79
84
  error = undefined
85
+
80
86
  streamClient
81
87
  .getOptions(name)
82
88
  .then((options) => {
@@ -164,14 +170,16 @@
164
170
  class="relative min-h-0 w-full flex-1 overflow-hidden bg-black [&_img]:h-full [&_img]:w-full [&_img]:object-fill [&_video]:h-full [&_video]:w-full [&_video]:object-fill"
165
171
  style:aspect-ratio={aspectRatio}
166
172
  >
167
- {#key environment.current.viewerMode === 'monitor'}
168
- <CameraStream
169
- {name}
170
- partID={partID.current}
171
- onloadedmetadata={onMediaLoad}
172
- onload={onMediaLoad}
173
- />
174
- {/key}
173
+ {#if connectionStatus.current === MachineConnectionEvent.CONNECTED}
174
+ {#key environment.current.viewerMode === 'monitor'}
175
+ <CameraStream
176
+ {name}
177
+ partID={partID.current}
178
+ onloadedmetadata={onMediaLoad}
179
+ onload={onMediaLoad}
180
+ />
181
+ {/key}
182
+ {/if}
175
183
 
176
184
  <!-- FPS Pill -->
177
185
  {#if fps > 0}