@viamrobotics/motion-tools 0.16.4 → 0.18.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.
package/README.md CHANGED
@@ -72,3 +72,11 @@ VITE_CONFIGS='
72
72
  The visualizer includes a golang package that allows executing commands to the visualizer.
73
73
 
74
74
  The list of available commands [can be found here](https://pkg.go.dev/github.com/viam-labs/motion-tools@v0.9.0/client/client).
75
+
76
+ ### Programmatic camera control
77
+
78
+ It is possible to programmatically move the viewer camera and even modify the camera settings during runtime.
79
+
80
+ To do this, open the Javascript console while using the visualizer and call methods or set properties on the `cameraControls` object.
81
+
82
+ The following APIs are available: https://github.com/yomotsu/camera-controls?tab=readme-ov-file#properties
@@ -10,7 +10,14 @@
10
10
  import type { WorldObject } from '../WorldObject.svelte'
11
11
  import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'
12
12
 
13
+ import { WEBLABS_EXPERIMENTS } from '../hooks/useWeblabs.svelte'
14
+ import { useSettings } from '../hooks/useSettings.svelte'
15
+ import { useWeblabs } from '../hooks/useWeblabs.svelte'
16
+ import { use3DModels } from '../hooks/use3DModels.svelte'
17
+ const settings = useSettings()
13
18
  const plyLoader = new PLYLoader()
19
+ const weblabs = useWeblabs()
20
+ const componentModels = use3DModels()
14
21
 
15
22
  interface Props extends ThrelteProps<Group> {
16
23
  uuid: string
@@ -33,9 +40,28 @@
33
40
  ...rest
34
41
  }: Props = $props()
35
42
 
43
+ const gltfModel = $derived.by(() => {
44
+ const [componentName, id] = name.split(':')
45
+ if (!componentName || !id) {
46
+ return undefined
47
+ }
48
+ return componentModels.current?.[componentName]?.[id]
49
+ })
50
+
36
51
  const type = $derived(geometry?.geometryType?.case)
37
52
  const color = $derived(overrideColor ?? metadata.color ?? colors.default)
38
53
 
54
+ const renderModels = $derived(
55
+ (settings.current.renderArmModels === 'model' ||
56
+ settings.current.renderArmModels === 'colliders+model') &&
57
+ gltfModel
58
+ )
59
+ const renderPrimitives = $derived(
60
+ settings.current.renderArmModels === 'colliders' ||
61
+ settings.current.renderArmModels === 'colliders+model' ||
62
+ !gltfModel
63
+ )
64
+
39
65
  const group = new Group()
40
66
  const mesh = $derived.by(() => {
41
67
  if (type === undefined) {
@@ -50,6 +76,16 @@
50
76
  return result
51
77
  })
52
78
 
79
+ $effect.pre(() => {
80
+ if (
81
+ weblabs.isActive(WEBLABS_EXPERIMENTS.MOTION_TOOLS_RENDER_ARM_MODELS) &&
82
+ renderModels &&
83
+ !renderPrimitives
84
+ ) {
85
+ geo = undefined
86
+ }
87
+ })
88
+
53
89
  $effect.pre(() => {
54
90
  if (geometry?.center && mesh) {
55
91
  poseToObject3d(geometry.center, mesh)
@@ -103,39 +139,45 @@
103
139
  {uuid}
104
140
  bvh={{ enabled: false }}
105
141
  >
106
- {#if geometry.geometryType.case === 'bufferGeometry'}
107
- <T
108
- is={geometry.geometryType.value}
109
- {oncreate}
110
- />
111
- {:else if geometry.geometryType.case === 'mesh'}
112
- {@const mesh = geometry.geometryType.value.mesh}
113
- {@const meshGeometry = parsePlyInput(mesh)}
114
- <T
115
- is={meshGeometry}
116
- {oncreate}
117
- />
118
- {:else if geometry.geometryType.case === 'line' && metadata.points}
119
- <MeshLineGeometry points={metadata.points} />
120
- {:else if geometry.geometryType.case === 'box'}
121
- {@const dimsMm = geometry.geometryType.value.dimsMm ?? { x: 0, y: 0, z: 0 }}
122
- <T.BoxGeometry
123
- args={[dimsMm.x * 0.001, dimsMm.y * 0.001, dimsMm.z * 0.001]}
124
- {oncreate}
125
- />
126
- {:else if geometry.geometryType.case === 'sphere'}
127
- {@const radiusMm = geometry.geometryType.value.radiusMm ?? 0}
128
- <T.SphereGeometry
129
- args={[radiusMm * 0.001]}
130
- {oncreate}
131
- />
132
- {:else if geometry.geometryType.case === 'capsule'}
133
- {@const { lengthMm, radiusMm } = geometry.geometryType.value}
134
- <T
135
- is={CapsuleGeometry}
136
- args={[radiusMm * 0.001, lengthMm * 0.001]}
137
- {oncreate}
138
- />
142
+ {#if weblabs.isActive(WEBLABS_EXPERIMENTS.MOTION_TOOLS_RENDER_ARM_MODELS) && renderModels}
143
+ <T is={gltfModel} />
144
+ {/if}
145
+
146
+ {#if !weblabs.isActive(WEBLABS_EXPERIMENTS.MOTION_TOOLS_RENDER_ARM_MODELS) || renderPrimitives}
147
+ {#if geometry.geometryType.case === 'bufferGeometry'}
148
+ <T
149
+ is={geometry.geometryType.value}
150
+ {oncreate}
151
+ />
152
+ {:else if geometry.geometryType.case === 'mesh'}
153
+ {@const mesh = geometry.geometryType.value.mesh}
154
+ {@const meshGeometry = parsePlyInput(mesh)}
155
+ <T
156
+ is={meshGeometry}
157
+ {oncreate}
158
+ />
159
+ {:else if geometry.geometryType.case === 'line' && metadata.points}
160
+ <MeshLineGeometry points={metadata.points} />
161
+ {:else if geometry.geometryType.case === 'box'}
162
+ {@const dimsMm = geometry.geometryType.value.dimsMm ?? { x: 0, y: 0, z: 0 }}
163
+ <T.BoxGeometry
164
+ args={[dimsMm.x * 0.001, dimsMm.y * 0.001, dimsMm.z * 0.001]}
165
+ {oncreate}
166
+ />
167
+ {:else if geometry.geometryType.case === 'sphere'}
168
+ {@const radiusMm = geometry.geometryType.value.radiusMm ?? 0}
169
+ <T.SphereGeometry
170
+ args={[radiusMm * 0.001]}
171
+ {oncreate}
172
+ />
173
+ {:else if geometry.geometryType.case === 'capsule'}
174
+ {@const { lengthMm, radiusMm } = geometry.geometryType.value}
175
+ <T
176
+ is={CapsuleGeometry}
177
+ args={[radiusMm * 0.001, lengthMm * 0.001]}
178
+ {oncreate}
179
+ />
180
+ {/if}
139
181
  {/if}
140
182
 
141
183
  {#if geometry.geometryType.case === 'line'}
@@ -1,5 +1,22 @@
1
+ <script
2
+ module
3
+ lang="ts"
4
+ >
5
+ export const RefetchRates = {
6
+ OFF: -1,
7
+ MANUAL: 0,
8
+ FPS_60: 17,
9
+ FPS_30: 33,
10
+ MS_500: 500,
11
+ MS_1000: 1000,
12
+ MS_2000: 2000,
13
+ MS_5000: 5000,
14
+ MS_10000: 10_000,
15
+ } as const
16
+ </script>
17
+
1
18
  <script lang="ts">
2
- import { Select } from '@viamrobotics/prime-core'
19
+ import { Select, IconButton } from '@viamrobotics/prime-core'
3
20
  import { useMachineSettings } from '../hooks/useMachineSettings.svelte'
4
21
  import type { Snippet } from 'svelte'
5
22
 
@@ -7,46 +24,70 @@
7
24
  id: string
8
25
  label: string
9
26
  allowLive?: boolean
27
+ onManualRefetch: () => void
10
28
  children?: Snippet
11
29
  }
12
30
 
13
- let { id, label, allowLive = false, children }: Props = $props()
31
+ let { id, label, allowLive = false, onManualRefetch, children }: Props = $props()
14
32
 
15
33
  const { refreshRates } = useMachineSettings()
16
- const rate = $derived(refreshRates.get(id))
34
+ const rate = $derived(refreshRates.get(id) ?? RefetchRates.MANUAL)
17
35
  </script>
18
36
 
19
37
  <label class="flex flex-col gap-1">
20
38
  {label}
21
- <Select
22
- onchange={(event: InputEvent) => {
23
- if (event.target instanceof HTMLSelectElement) {
24
- const { value } = event.target
25
- refreshRates.set(id, Number.parseInt(value, 10))
26
- }
27
- }}
28
- value={String(rate ?? '')}
29
- >
30
- {#if children}
31
- {@render children()}
32
- {:else}
33
- <option value="-1">Do not fetch</option>
34
- <option value="0">Do not refresh</option>
35
- {#if allowLive}
36
- <option value="17">60fps</option>
37
- <option value="33">30fps</option>
39
+ <div class="flex items-center gap-1">
40
+ <Select
41
+ style="
42
+ -webkit-appearance: none;
43
+ -moz-appearance: none;
44
+ appearance: none;
45
+ "
46
+ onchange={(event: InputEvent) => {
47
+ if (event.target instanceof HTMLSelectElement) {
48
+ const { value } = event.target
49
+ refreshRates.set(id, Number.parseInt(value, 10))
50
+ }
51
+ }}
52
+ value={String(rate)}
53
+ >
54
+ {#if children}
55
+ {@render children()}
56
+ {:else}
57
+ <option value={String(RefetchRates.OFF)}>Do not fetch</option>
58
+ <option value={String(RefetchRates.MANUAL)}>Manual</option>
59
+ {#if allowLive}
60
+ <option value={String(RefetchRates.FPS_60)}>60fps</option>
61
+ <option value={String(RefetchRates.FPS_30)}>30fps</option>
62
+ {/if}
63
+ <option value={String(RefetchRates.MS_500)}>Refresh every 0.5 second</option>
64
+ <option value={String(RefetchRates.MS_1000)}>Refresh every second</option>
65
+ <option value={String(RefetchRates.MS_2000)}>Refresh every 2 seconds</option>
66
+ <option value={String(RefetchRates.MS_5000)}>Refresh every 5 seconds</option>
67
+ <option value={String(RefetchRates.MS_10000)}>Refresh every 10 seconds</option>
38
68
  {/if}
39
- <option value="500">Refresh every 0.5 second</option>
40
- <option value="1000">Refresh every second</option>
41
- <option value="2000">Refresh every 2 seconds</option>
42
- <option value="5000">Refresh every 5 seconds</option>
43
- <option value="10000">Refresh every 10 seconds</option>
69
+ </Select>
70
+
71
+ {#if rate === RefetchRates.MANUAL}
72
+ <IconButton
73
+ icon="refresh"
74
+ label="refetch"
75
+ variant="secondary"
76
+ cx="border-light border"
77
+ onclick={() => {
78
+ onManualRefetch()
79
+ }}
80
+ />
81
+ {:else}
82
+ <IconButton
83
+ icon={rate === RefetchRates.OFF ? 'play-circle-outline' : 'pause'}
84
+ label="pause"
85
+ variant="secondary"
86
+ cx="border-light border"
87
+ onclick={() => {
88
+ refreshRates.set(id, RefetchRates.MANUAL)
89
+ }}
90
+ />
44
91
  {/if}
45
- </Select>
92
+ </div>
46
93
  </label>
47
-
48
- <style>
49
- label :global svg {
50
- display: none;
51
- }
52
- </style>
@@ -1,8 +1,20 @@
1
+ export declare const RefetchRates: {
2
+ readonly OFF: -1;
3
+ readonly MANUAL: 0;
4
+ readonly FPS_60: 17;
5
+ readonly FPS_30: 33;
6
+ readonly MS_500: 500;
7
+ readonly MS_1000: 1000;
8
+ readonly MS_2000: 2000;
9
+ readonly MS_5000: 5000;
10
+ readonly MS_10000: 10000;
11
+ };
1
12
  import type { Snippet } from 'svelte';
2
13
  interface Props {
3
14
  id: string;
4
15
  label: string;
5
16
  allowLive?: boolean;
17
+ onManualRefetch: () => void;
6
18
  children?: Snippet;
7
19
  }
8
20
  declare const RefreshRate: import("svelte").Component<Props, {}, "">;
@@ -23,6 +23,7 @@
23
23
  import { provideArrows } from '../hooks/useArrows.svelte'
24
24
  import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
25
25
  import { provideResourceByName } from '../hooks/useResourceByName.svelte'
26
+ import { provide3DModels } from '../hooks/use3DModels.svelte'
26
27
 
27
28
  interface Props {
28
29
  cameraPose?: CameraPose
@@ -47,6 +48,7 @@
47
48
  provideResourceByName(() => partID.current)
48
49
  provideFrames(() => partID.current)
49
50
  provideGeometries(() => partID.current)
51
+ provide3DModels(() => partID.current)
50
52
  providePointclouds(() => partID.current)
51
53
  provideMotionClient(() => partID.current)
52
54
  provideArmClient(() => partID.current)
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { Select, Switch, Input } from '@viamrobotics/prime-core'
3
+ import { useQueryClient } from '@tanstack/svelte-query'
3
4
  import RefreshRate from '../RefreshRate.svelte'
4
5
  import { useMotionClient } from '../../hooks/useMotionClient.svelte'
5
6
  import Drawer from './Drawer.svelte'
@@ -7,7 +8,10 @@
7
8
  import { useResourceNames } from '@viamrobotics/svelte-sdk'
8
9
  import { usePartID } from '../../hooks/usePartID.svelte'
9
10
  import { RefreshRates, useMachineSettings } from '../../hooks/useMachineSettings.svelte'
11
+ import WeblabActive from '../weblab/WeblabActive.svelte'
12
+ import { WEBLABS_EXPERIMENTS } from '../../hooks/useWeblabs.svelte'
10
13
 
14
+ const queryClient = useQueryClient()
11
15
  const partID = usePartID()
12
16
  const cameras = useResourceNames(() => partID.current, 'camera')
13
17
  const settings = useSettings()
@@ -26,10 +30,16 @@
26
30
  id={RefreshRates.poses}
27
31
  label="Poses"
28
32
  allowLive
33
+ onManualRefetch={() => {
34
+ queryClient.refetchQueries({ queryKey: ['getPose', 'getGeometries'], exact: false })
35
+ }}
29
36
  />
30
37
  <RefreshRate
31
38
  id={RefreshRates.pointclouds}
32
39
  label="Pointclouds"
40
+ onManualRefetch={() => {
41
+ queryClient.refetchQueries({ queryKey: ['getPointCloud'], exact: false })
42
+ }}
33
43
  />
34
44
  <div>
35
45
  <div>Enabled pointcloud cameras</div>
@@ -167,6 +177,26 @@
167
177
  <label class="flex items-center justify-between gap-2">
168
178
  Render stats <Switch bind:on={settings.current.renderStats} />
169
179
  </label>
180
+ <WeblabActive experiment={WEBLABS_EXPERIMENTS.MOTION_TOOLS_RENDER_ARM_MODELS}>
181
+ <label class="flex items-center justify-between gap-2">
182
+ Render Arm Models
183
+ <Select
184
+ value={settings.current.renderArmModels}
185
+ onchange={(event: InputEvent) => {
186
+ if (event.target instanceof HTMLSelectElement) {
187
+ settings.current.renderArmModels = event.target.value as
188
+ | 'colliders'
189
+ | 'colliders+model'
190
+ | 'model'
191
+ }
192
+ }}
193
+ >
194
+ <option value="colliders">Colliders</option>
195
+ <option value="colliders+model">Colliders + Model</option>
196
+ <option value="model">Model</option>
197
+ </Select>
198
+ </label>
199
+ </WeblabActive>
170
200
  </div>
171
201
  </div>
172
202
  </Drawer>
@@ -1,3 +1,4 @@
1
- declare const Xr: import("svelte").Component<Record<string, any>, {}, "">;
2
- type Xr = ReturnType<typeof Xr>;
3
- export default Xr;
1
+ import { XR } from '@threlte/xr';
2
+ declare const XR: import("svelte").Component<Record<string, any>, {}, "">;
3
+ type XR = ReturnType<typeof XR>;
4
+ export default XR;
@@ -0,0 +1,7 @@
1
+ import type { Group } from 'three';
2
+ interface Context {
3
+ current: Record<string, Record<string, Group>>;
4
+ }
5
+ export declare const provide3DModels: (partID: () => string) => void;
6
+ export declare const use3DModels: () => Context;
7
+ export {};
@@ -0,0 +1,59 @@
1
+ import { ArmClient } from '@viamrobotics/sdk';
2
+ import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk';
3
+ import { getContext, setContext } from 'svelte';
4
+ import { useWeblabs, WEBLABS_EXPERIMENTS } from './useWeblabs.svelte';
5
+ import { useSettings } from './useSettings.svelte';
6
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
7
+ import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
8
+ const gltfLoader = new GLTFLoader();
9
+ const dracoLoader = new DRACOLoader();
10
+ dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
11
+ gltfLoader.setDRACOLoader(dracoLoader);
12
+ const key = Symbol('3d-models-context');
13
+ export const provide3DModels = (partID) => {
14
+ const weblabs = useWeblabs();
15
+ const settings = useSettings();
16
+ const current = $state.raw({});
17
+ const arms = useResourceNames(partID, 'arm');
18
+ const armClients = $derived(arms.current.map((arm) => createResourceClient(ArmClient, partID, () => arm.name)));
19
+ const clients = $derived(armClients.filter((client) => {
20
+ return arms.current.some((arm) => arm.name === client.current?.name);
21
+ }));
22
+ $effect(() => {
23
+ const fetch3DModels = async () => {
24
+ for (const client of clients) {
25
+ if (!client.current)
26
+ continue;
27
+ try {
28
+ const models = await client.current.get3DModels();
29
+ if (!(client.current.name in current)) {
30
+ current[client.current.name] = {};
31
+ }
32
+ for (const [id, model] of Object.entries(models)) {
33
+ const arrayBuffer = model.mesh.buffer.slice(model.mesh.byteOffset, model.mesh.byteOffset + model.mesh.byteLength);
34
+ const gltfModel = await gltfLoader.parseAsync(arrayBuffer, '');
35
+ current[client.current.name][id] = gltfModel.scene;
36
+ }
37
+ }
38
+ catch (error) {
39
+ // some arms may not implement this api yet
40
+ console.warn(`${client.current.name} returned an error: ${error} when getting 3D models`);
41
+ }
42
+ }
43
+ };
44
+ const shouldFetchModels = settings.current.isLoaded &&
45
+ (settings.current.renderArmModels === 'model' ||
46
+ settings.current.renderArmModels === 'colliders+model');
47
+ if (weblabs.isActive(WEBLABS_EXPERIMENTS.MOTION_TOOLS_RENDER_ARM_MODELS) && shouldFetchModels) {
48
+ fetch3DModels();
49
+ }
50
+ });
51
+ setContext(key, {
52
+ get current() {
53
+ return current;
54
+ },
55
+ });
56
+ };
57
+ export const use3DModels = () => {
58
+ return getContext(key);
59
+ };
@@ -10,6 +10,7 @@ import { useLogs } from './useLogs.svelte';
10
10
  import { resourceColors } from '../color';
11
11
  import { Color } from 'three';
12
12
  import { useFrames } from './useFrames.svelte';
13
+ import { RefetchRates } from '../components/RefreshRate.svelte';
13
14
  const key = Symbol('geometries-context');
14
15
  export const provideGeometries = (partID) => {
15
16
  const frames = useFrames();
@@ -32,9 +33,9 @@ export const provideGeometries = (partID) => {
32
33
  const results = [];
33
34
  for (const client of clients) {
34
35
  const options = queryOptions({
35
- enabled: interval !== -1 && client.current !== undefined,
36
- refetchInterval: interval === 0 ? false : interval,
37
- queryKey: ['partID', partID(), client.current?.name, 'getGeometries'],
36
+ enabled: interval !== RefetchRates.OFF && client.current !== undefined,
37
+ refetchInterval: interval === RefetchRates.MANUAL ? false : interval,
38
+ queryKey: ['getGeometries', 'partID', partID(), client.current?.name],
38
39
  queryFn: async () => {
39
40
  if (!client.current) {
40
41
  throw new Error('No client');
@@ -8,6 +8,7 @@ import { RefreshRates, useMachineSettings } from './useMachineSettings.svelte';
8
8
  import { WorldObject } from '../WorldObject.svelte';
9
9
  import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
10
10
  import { useLogs } from './useLogs.svelte';
11
+ import { RefetchRates } from '../components/RefreshRate.svelte';
11
12
  const key = Symbol('pointcloud-context');
12
13
  export const providePointclouds = (partID) => {
13
14
  const logs = useLogs();
@@ -20,11 +21,11 @@ export const providePointclouds = (partID) => {
20
21
  for (const cameraClient of clients) {
21
22
  const name = cameraClient.current?.name ?? '';
22
23
  const options = queryOptions({
23
- enabled: interval !== -1 &&
24
+ enabled: interval !== RefetchRates.OFF &&
24
25
  cameraClient.current !== undefined &&
25
26
  disabledCameras.get(name) !== true,
26
- refetchInterval: interval === 0 ? false : interval,
27
- queryKey: ['partID', partID(), name, 'getPointCloud'],
27
+ refetchInterval: interval === RefetchRates.MANUAL ? false : interval,
28
+ queryKey: ['getPointCloud', 'partID', partID(), name],
28
29
  queryFn: async () => {
29
30
  if (!cameraClient.current) {
30
31
  throw new Error('No camera client');
@@ -9,6 +9,7 @@ import { useEnvironment } from './useEnvironment.svelte';
9
9
  import { observe } from '@threlte/core';
10
10
  import { untrack } from 'svelte';
11
11
  import { useFrames } from './useFrames.svelte';
12
+ import { RefetchRates } from '../components/RefreshRate.svelte';
12
13
  export const usePose = (name, parent) => {
13
14
  const { refreshRates } = useMachineSettings();
14
15
  const partID = usePartID();
@@ -21,11 +22,11 @@ export const usePose = (name, parent) => {
21
22
  const client = createResourceClient(MotionClient, () => partID.current, () => motionClient.current ?? '');
22
23
  const interval = $derived(refreshRates.get(RefreshRates.poses));
23
24
  const options = $derived(queryOptions({
24
- enabled: interval !== -1 &&
25
+ enabled: interval !== RefetchRates.OFF &&
25
26
  client.current !== undefined &&
26
27
  environment.current.viewerMode === 'monitor',
27
- refetchInterval: interval === 0 ? false : interval,
28
- queryKey: ['partID', partID.current, client.current?.name, 'getPose', name(), parent()],
28
+ refetchInterval: interval === RefetchRates.MANUAL ? false : interval,
29
+ queryKey: ['getPose', 'partID', partID.current, client.current?.name, name(), parent()],
29
30
  queryFn: async () => {
30
31
  if (!client.current) {
31
32
  throw new Error('No client');
@@ -1,4 +1,5 @@
1
1
  interface Settings {
2
+ isLoaded: boolean;
2
3
  cameraMode: 'orthographic' | 'perspective';
3
4
  transforming: boolean;
4
5
  snapping: boolean;
@@ -18,6 +19,7 @@ interface Settings {
18
19
  enableXR: boolean;
19
20
  enableArmPositionsWidget: boolean;
20
21
  renderStats: boolean;
22
+ renderArmModels: 'colliders' | 'colliders+model' | 'model';
21
23
  }
22
24
  interface Context {
23
25
  current: Settings;
@@ -2,6 +2,7 @@ import { get, set } from 'idb-keyval';
2
2
  import { getContext, setContext } from 'svelte';
3
3
  const key = Symbol('dashboard-context');
4
4
  const defaults = () => ({
5
+ isLoaded: false,
5
6
  cameraMode: 'perspective',
6
7
  transforming: false,
7
8
  snapping: false,
@@ -21,6 +22,7 @@ const defaults = () => ({
21
22
  enableXR: false,
22
23
  enableArmPositionsWidget: false,
23
24
  renderStats: false,
25
+ renderArmModels: 'colliders+model',
24
26
  });
25
27
  export const provideSettings = () => {
26
28
  let settings = $state(defaults());
@@ -30,6 +32,7 @@ export const provideSettings = () => {
30
32
  settings = { ...settings, ...response };
31
33
  }
32
34
  settingsLoaded = true;
35
+ settings.isLoaded = true;
33
36
  });
34
37
  $effect(() => {
35
38
  if (settingsLoaded) {
@@ -1,5 +1,6 @@
1
1
  export declare const WEBLABS_EXPERIMENTS: {
2
2
  readonly MOTION_TOOLS_EDIT_FRAME: "MOTION_TOOLS_EDIT_FRAME";
3
+ readonly MOTION_TOOLS_RENDER_ARM_MODELS: "MOTION_TOOLS_RENDER_ARM_MODELS";
3
4
  };
4
5
  export declare const WEBLABS_CONTEXT_KEY: unique symbol;
5
6
  interface Context {
@@ -2,6 +2,7 @@ import { getContext, setContext } from 'svelte';
2
2
  import { SvelteSet } from 'svelte/reactivity';
3
3
  export const WEBLABS_EXPERIMENTS = {
4
4
  MOTION_TOOLS_EDIT_FRAME: 'MOTION_TOOLS_EDIT_FRAME',
5
+ MOTION_TOOLS_RENDER_ARM_MODELS: 'MOTION_TOOLS_RENDER_ARM_MODELS',
5
6
  };
6
7
  export const WEBLABS_CONTEXT_KEY = Symbol('weblabs-context');
7
8
  const getCookie = (name) => {
@@ -54,5 +55,12 @@ export const provideWeblabs = () => {
54
55
  setContext(WEBLABS_CONTEXT_KEY, createWeblabs());
55
56
  };
56
57
  export const useWeblabs = () => {
57
- return getContext(WEBLABS_CONTEXT_KEY);
58
+ const context = getContext(WEBLABS_CONTEXT_KEY);
59
+ if (!context) {
60
+ return {
61
+ load: () => { },
62
+ isActive: () => false,
63
+ };
64
+ }
65
+ return context;
58
66
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.16.4",
3
+ "version": "0.18.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "@typescript-eslint/eslint-plugin": "8.42.0",
38
38
  "@typescript-eslint/parser": "8.42.0",
39
39
  "@viamrobotics/prime-core": "0.1.5",
40
- "@viamrobotics/sdk": "0.52.0",
40
+ "@viamrobotics/sdk": "0.55.0",
41
41
  "@viamrobotics/svelte-sdk": "0.7.1",
42
42
  "@vitejs/plugin-basic-ssl": "2.1.0",
43
43
  "@zag-js/svelte": "1.22.1",
@@ -1,65 +0,0 @@
1
- import type { Geometry, PlainMessage, Pose, Struct, TransformWithUUID } from '@viamrobotics/sdk';
2
- import { BatchedMesh, Color, Object3D, Vector3, type BufferGeometry } from 'three';
3
- import type { ValueOf } from 'type-fest';
4
- import type { OBB } from 'three/addons/math/OBB.js';
5
- export type PointsGeometry = {
6
- center: undefined;
7
- geometryType: {
8
- case: 'points';
9
- value: Float32Array<ArrayBuffer>;
10
- };
11
- };
12
- export type LinesGeometry = {
13
- center: undefined;
14
- geometryType: {
15
- case: 'line';
16
- value: Float32Array;
17
- };
18
- };
19
- export type ThreeBufferGeometry = {
20
- center: undefined;
21
- geometryType: {
22
- case: 'bufferGeometry';
23
- value: BufferGeometry;
24
- };
25
- };
26
- export type Geometries = Geometry | PointsGeometry | LinesGeometry | ThreeBufferGeometry;
27
- export declare const SupportedShapes: {
28
- readonly points: "points";
29
- readonly line: "line";
30
- readonly arrow: "arrow";
31
- };
32
- export type Metadata = {
33
- colors?: Float32Array;
34
- color?: Color;
35
- opacity?: number;
36
- gltf?: {
37
- scene: Object3D;
38
- };
39
- points?: Vector3[];
40
- pointSize?: number;
41
- lineWidth?: number;
42
- lineDotColor?: Color;
43
- batched?: {
44
- id: number;
45
- object: BatchedMesh;
46
- };
47
- shape?: ValueOf<typeof SupportedShapes>;
48
- getBoundingBoxAt?: (box: OBB) => void;
49
- };
50
- export declare const isMetadataKey: (key: string) => key is keyof Metadata;
51
- export declare class WorldObject<T extends Geometries = Geometries> {
52
- uuid: string;
53
- name: string;
54
- referenceFrame: string | undefined;
55
- pose: PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>;
56
- geometry?: T;
57
- metadata: Metadata;
58
- localEditedPose: PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>;
59
- constructor(name?: string, pose?: Pose, parent?: string, geometry?: T, metadata?: Metadata);
60
- toJSON(): Omit<WorldObject, 'toJSON' | 'fromJSON' | 'metadata'>;
61
- fromJSON(json: WorldObject): this;
62
- }
63
- export declare const parseMetadata: (fields?: PlainMessage<Struct>["fields"]) => Metadata;
64
- export declare const fromTransform: (transform: TransformWithUUID) => WorldObject<PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Geometry>>;
65
- export declare const determinePose: (object: WorldObject, pose: Pose | undefined) => Pose;
@@ -1,4 +0,0 @@
1
- import type { Geometry } from '@viamrobotics/sdk';
2
- import type { Frame } from './frame';
3
- export declare const createGeometry: (geometryType?: Geometry["geometryType"], label?: string) => Geometry;
4
- export declare const createGeometryFromFrame: (frame: Frame) => import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Geometry> | undefined;
@@ -1,3 +0,0 @@
1
- export declare const usePose: (name: () => string, parent: () => string | undefined) => {
2
- readonly current: import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose | undefined;
3
- };
@@ -1,22 +0,0 @@
1
- import { type TransformWithUUID, ResourceName } from '@viamrobotics/sdk';
2
- interface Context {
3
- names: ResourceName[];
4
- current: Record<string, ReturnType<typeof createWorldState>>;
5
- }
6
- export declare const provideWorldStates: () => void;
7
- export declare const useWorldStates: () => Context;
8
- export declare const useWorldState: (resourceName: () => string) => {
9
- readonly name: string;
10
- readonly transforms: TransformWithUUID[];
11
- readonly worldObjects: import("../WorldObject.svelte").WorldObject<import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Geometry>>[];
12
- readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
13
- readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<TransformWithUUID>[] | undefined;
14
- };
15
- declare const createWorldState: (partID: () => string, resourceName: () => string) => {
16
- readonly name: string;
17
- readonly transforms: TransformWithUUID[];
18
- readonly worldObjects: import("../WorldObject.svelte").WorldObject<import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Geometry>>[];
19
- readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
20
- readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<TransformWithUUID>[] | undefined;
21
- };
22
- export {};
@@ -1,15 +0,0 @@
1
- import type { Geometry, Pose } from '@viamrobotics/sdk';
2
- import { type Object3D, Matrix4, Quaternion, Vector3 } from 'three';
3
- import type { Frame } from './frame';
4
- export declare const createPose: (pose?: Partial<Pose>) => Pose;
5
- export declare const createPoseFromFrame: (frame: Partial<Frame>) => Pose;
6
- export declare const quaternionToPose: (quaternion: Quaternion, pose: Partial<Pose>) => void;
7
- export declare const vector3ToPose: (vec3: Vector3, pose: Partial<Pose>) => void;
8
- export declare const object3dToPose: (object3d: Object3D, pose: Partial<Pose>) => Partial<import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>>;
9
- export declare const poseToQuaternion: (pose: Partial<Pose>, quaternion: Quaternion) => void;
10
- export declare const poseToVector3: (pose: Partial<Pose>, vec3: Vector3) => void;
11
- export declare const poseToObject3d: (pose: Partial<Pose>, object3d: Object3D) => void;
12
- export declare const poseToDirection: (pose: Pose) => Vector3;
13
- export declare const scaleToDimensions: (scale: Vector3, geometry: Geometry["geometryType"]) => void;
14
- export declare const poseToMatrix: (pose: Pose) => Matrix4;
15
- export declare const matrixToPose: (matrix: Matrix4) => import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>;