@viamrobotics/motion-tools 0.16.4 → 0.17.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.
@@ -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'}
@@ -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)
@@ -7,6 +7,8 @@
7
7
  import { useResourceNames } from '@viamrobotics/svelte-sdk'
8
8
  import { usePartID } from '../../hooks/usePartID.svelte'
9
9
  import { RefreshRates, useMachineSettings } from '../../hooks/useMachineSettings.svelte'
10
+ import WeblabActive from '../weblab/WeblabActive.svelte'
11
+ import { WEBLABS_EXPERIMENTS } from '../../hooks/useWeblabs.svelte'
10
12
 
11
13
  const partID = usePartID()
12
14
  const cameras = useResourceNames(() => partID.current, 'camera')
@@ -167,6 +169,26 @@
167
169
  <label class="flex items-center justify-between gap-2">
168
170
  Render stats <Switch bind:on={settings.current.renderStats} />
169
171
  </label>
172
+ <WeblabActive experiment={WEBLABS_EXPERIMENTS.MOTION_TOOLS_RENDER_ARM_MODELS}>
173
+ <label class="flex items-center justify-between gap-2">
174
+ Render Arm Models
175
+ <Select
176
+ value={settings.current.renderArmModels}
177
+ onchange={(event: InputEvent) => {
178
+ if (event.target instanceof HTMLSelectElement) {
179
+ settings.current.renderArmModels = event.target.value as
180
+ | 'colliders'
181
+ | 'colliders+model'
182
+ | 'model'
183
+ }
184
+ }}
185
+ >
186
+ <option value="colliders">Colliders</option>
187
+ <option value="colliders+model">Colliders + Model</option>
188
+ <option value="model">Model</option>
189
+ </Select>
190
+ </label>
191
+ </WeblabActive>
170
192
  </div>
171
193
  </div>
172
194
  </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
+ };
@@ -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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.16.4",
3
+ "version": "0.17.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>;