@viamrobotics/motion-tools 1.4.0 → 1.7.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 +16 -9
- package/dist/common/v1/common_pb.d.ts +8 -0
- package/dist/common/v1/common_pb.js +7 -0
- package/dist/components/App.svelte +12 -0
- package/dist/components/Frame.svelte +0 -7
- package/dist/components/Tree/Drawer.svelte +0 -1
- package/dist/components/Tree/Settings.svelte +18 -22
- package/dist/components/Tree/TreeContainer.svelte +8 -1
- package/dist/components/Tree/Widgets.svelte +44 -0
- package/dist/components/Tree/Widgets.svelte.d.ts +2 -17
- package/dist/components/widgets/Camera.svelte +195 -0
- package/dist/components/widgets/Camera.svelte.d.ts +6 -0
- package/dist/hooks/use3DModels.svelte.js +1 -3
- package/dist/hooks/useDrawAPI.svelte.js +2 -1
- package/dist/hooks/useSettings.svelte.d.ts +1 -0
- package/dist/hooks/useSettings.svelte.js +1 -0
- package/dist/hooks/useWeblabs.svelte.d.ts +1 -3
- package/dist/hooks/useWeblabs.svelte.js +1 -3
- package/dist/hooks/useWorldState.svelte.js +18 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,21 +17,28 @@ make setup
|
|
|
17
17
|
|
|
18
18
|
This single command will:
|
|
19
19
|
|
|
20
|
-
1. Install
|
|
21
|
-
2. Install
|
|
22
|
-
3. Install **
|
|
23
|
-
4. Install **
|
|
20
|
+
1. Install **fnm** (Fast Node Manager) and **Node.js 22**
|
|
21
|
+
2. Install **pnpm** package manager
|
|
22
|
+
3. Install **bun** runtime
|
|
23
|
+
4. Install **Go** and **buf** (for protobuf generation)
|
|
24
24
|
5. Install all project dependencies
|
|
25
|
+
6. Generate protobuf code
|
|
26
|
+
|
|
27
|
+
After setup completes, add the shell configuration it prints to your shell config file (`~/.zshrc` or `~/.bashrc`), then restart your terminal.
|
|
25
28
|
|
|
26
29
|
#### Manual setup
|
|
27
30
|
|
|
28
31
|
If the above does not work for you, or if you prefer to install dependencies manually:
|
|
29
32
|
|
|
30
|
-
1. [Install
|
|
31
|
-
2. Install Node.js
|
|
32
|
-
3. [Install pnpm](https://pnpm.io/installation)
|
|
33
|
-
4. [Install bun](https://bun.sh/docs/installation)
|
|
34
|
-
5. Install
|
|
33
|
+
1. [Install fnm](https://github.com/Schniz/fnm#installation): `curl -fsSL https://fnm.vercel.app/install | bash`
|
|
34
|
+
2. Install Node.js: `fnm install 22 && fnm use 22`
|
|
35
|
+
3. [Install pnpm](https://pnpm.io/installation): `curl -fsSL https://get.pnpm.io/install.sh | sh -`
|
|
36
|
+
4. [Install bun](https://bun.sh/docs/installation): `curl -fsSL https://bun.sh/install | bash`
|
|
37
|
+
5. [Install Go](https://go.dev/doc/install)
|
|
38
|
+
6. [Install buf](https://buf.build/docs/installation): download from GitHub releases
|
|
39
|
+
7. Install Go tools: `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest`
|
|
40
|
+
8. Install dependencies: `pnpm install`
|
|
41
|
+
9. Generate protobufs: `make proto`
|
|
35
42
|
|
|
36
43
|
### Env files for machine configs
|
|
37
44
|
|
|
@@ -665,6 +665,14 @@ export declare class GetKinematicsResponse extends Message<GetKinematicsResponse
|
|
|
665
665
|
* @generated from field: bytes kinematics_data = 2;
|
|
666
666
|
*/
|
|
667
667
|
kinematicsData: Uint8Array<ArrayBuffer>;
|
|
668
|
+
/**
|
|
669
|
+
* Map of URDF mesh file paths to mesh data
|
|
670
|
+
*
|
|
671
|
+
* @generated from field: map<string, viam.common.v1.Mesh> meshes_by_urdf_filepath = 3;
|
|
672
|
+
*/
|
|
673
|
+
meshesByUrdfFilepath: {
|
|
674
|
+
[key: string]: Mesh;
|
|
675
|
+
};
|
|
668
676
|
constructor(data?: PartialMessage<GetKinematicsResponse>);
|
|
669
677
|
static readonly runtime: typeof proto3;
|
|
670
678
|
static readonly typeName = "viam.common.v1.GetKinematicsResponse";
|
|
@@ -959,6 +959,12 @@ export class GetKinematicsResponse extends Message {
|
|
|
959
959
|
* @generated from field: bytes kinematics_data = 2;
|
|
960
960
|
*/
|
|
961
961
|
kinematicsData = new Uint8Array(0);
|
|
962
|
+
/**
|
|
963
|
+
* Map of URDF mesh file paths to mesh data
|
|
964
|
+
*
|
|
965
|
+
* @generated from field: map<string, viam.common.v1.Mesh> meshes_by_urdf_filepath = 3;
|
|
966
|
+
*/
|
|
967
|
+
meshesByUrdfFilepath = {};
|
|
962
968
|
constructor(data) {
|
|
963
969
|
super();
|
|
964
970
|
proto3.util.initPartial(data, this);
|
|
@@ -968,6 +974,7 @@ export class GetKinematicsResponse extends Message {
|
|
|
968
974
|
static fields = proto3.util.newFieldList(() => [
|
|
969
975
|
{ no: 1, name: "format", kind: "enum", T: proto3.getEnumType(KinematicsFileFormat) },
|
|
970
976
|
{ no: 2, name: "kinematics_data", kind: "scalar", T: 12 /* ScalarType.BYTES */ },
|
|
977
|
+
{ no: 3, name: "meshes_by_urdf_filepath", kind: "map", K: 9 /* ScalarType.STRING */, V: { kind: "message", T: Mesh } },
|
|
971
978
|
]);
|
|
972
979
|
static fromBinary(bytes, options) {
|
|
973
980
|
return new GetKinematicsResponse().fromBinary(bytes, options);
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
provideDrawConnectionConfig,
|
|
27
27
|
type DrawConnectionConfig,
|
|
28
28
|
} from '../hooks/useDrawConnectionConfig.svelte'
|
|
29
|
+
import Camera from './widgets/Camera.svelte'
|
|
29
30
|
|
|
30
31
|
interface LocalConfigProps {
|
|
31
32
|
getLocalPartConfig: () => Struct
|
|
@@ -64,6 +65,8 @@
|
|
|
64
65
|
const settings = provideSettings()
|
|
65
66
|
const environment = provideEnvironment()
|
|
66
67
|
|
|
68
|
+
const currentRobotCameraWidgets = $derived(settings.current.openCameraWidgets[partID] || [])
|
|
69
|
+
|
|
67
70
|
$effect(() => {
|
|
68
71
|
settings.current.enableKeybindings = enableKeybindings
|
|
69
72
|
})
|
|
@@ -138,6 +141,15 @@
|
|
|
138
141
|
<ArmPositions {@attach domPortal(root)} />
|
|
139
142
|
{/if}
|
|
140
143
|
|
|
144
|
+
{#if !focus}
|
|
145
|
+
{#each currentRobotCameraWidgets as cameraName (cameraName)}
|
|
146
|
+
<Camera
|
|
147
|
+
name={cameraName}
|
|
148
|
+
{@attach domPortal(root)}
|
|
149
|
+
/>
|
|
150
|
+
{/each}
|
|
151
|
+
{/if}
|
|
152
|
+
|
|
141
153
|
<FileDrop {@attach domPortal(root)} />
|
|
142
154
|
{/snippet}
|
|
143
155
|
</SceneProviders>
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
|
|
4
4
|
import { Color, Group, type Object3D } from 'three'
|
|
5
5
|
import Geometry from './Geometry2.svelte'
|
|
6
|
-
import { useWeblabs } from '../hooks/useWeblabs.svelte'
|
|
7
6
|
import { useSelectedEntity } from '../hooks/useSelection.svelte'
|
|
8
7
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
9
8
|
import { use3DModels } from '../hooks/use3DModels.svelte'
|
|
10
9
|
import { colors, darkenColor, resourceColors } from '../color'
|
|
11
|
-
import { WEBLABS_EXPERIMENTS } from '../hooks/useWeblabs.svelte'
|
|
12
10
|
import type { Entity } from 'koota'
|
|
13
11
|
import { traits, useTrait } from '../ecs'
|
|
14
12
|
import type { Pose } from '@viamrobotics/sdk'
|
|
@@ -31,7 +29,6 @@
|
|
|
31
29
|
const componentModels = use3DModels()
|
|
32
30
|
const selectedEntity = useSelectedEntity()
|
|
33
31
|
const resourceByName = useResourceByName()
|
|
34
|
-
const weblabs = useWeblabs()
|
|
35
32
|
|
|
36
33
|
const name = useTrait(() => entity, traits.Name)
|
|
37
34
|
const parent = useTrait(() => entity, traits.Parent)
|
|
@@ -57,10 +54,6 @@
|
|
|
57
54
|
})
|
|
58
55
|
|
|
59
56
|
const model = $derived.by(() => {
|
|
60
|
-
if (!weblabs.isActive(WEBLABS_EXPERIMENTS.MOTION_TOOLS_RENDER_ARM_MODELS)) {
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
57
|
if (!name.current) {
|
|
65
58
|
return
|
|
66
59
|
}
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
import { useResourceNames } from '@viamrobotics/svelte-sdk'
|
|
7
7
|
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
8
8
|
import { RefreshRates, useMachineSettings } from '../../hooks/useMachineSettings.svelte'
|
|
9
|
-
import WeblabActive from '../weblab/WeblabActive.svelte'
|
|
10
|
-
import { WEBLABS_EXPERIMENTS } from '../../hooks/useWeblabs.svelte'
|
|
11
9
|
import { useGeometries } from '../../hooks/useGeometries.svelte'
|
|
12
10
|
import { usePointClouds } from '../../hooks/usePointclouds.svelte'
|
|
13
11
|
import { useThrelte } from '@threlte/core'
|
|
@@ -195,26 +193,24 @@
|
|
|
195
193
|
<label class="flex items-center justify-between gap-2">
|
|
196
194
|
Render stats <Switch bind:on={settings.current.renderStats} />
|
|
197
195
|
</label>
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
</label>
|
|
217
|
-
</WeblabActive>
|
|
196
|
+
<label class="flex items-center justify-between gap-2">
|
|
197
|
+
Render Arm Models
|
|
198
|
+
<Select
|
|
199
|
+
value={settings.current.renderArmModels}
|
|
200
|
+
onchange={(event: InputEvent) => {
|
|
201
|
+
if (event.target instanceof HTMLSelectElement) {
|
|
202
|
+
settings.current.renderArmModels = event.target.value as
|
|
203
|
+
| 'colliders'
|
|
204
|
+
| 'colliders+model'
|
|
205
|
+
| 'model'
|
|
206
|
+
}
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
<option value="colliders">Colliders</option>
|
|
210
|
+
<option value="colliders+model">Colliders + Model</option>
|
|
211
|
+
<option value="model">Model</option>
|
|
212
|
+
</Select>
|
|
213
|
+
</label>
|
|
218
214
|
</div>
|
|
219
215
|
</div>
|
|
220
216
|
</Drawer>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
11
11
|
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
12
12
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
13
|
+
import { useFrames } from '../../hooks/useFrames.svelte'
|
|
13
14
|
import { traits, useQuery, useWorld } from '../../ecs'
|
|
14
15
|
import { IsExcluded, type Entity } from 'koota'
|
|
15
16
|
import { buildTreeNodes, type TreeNode } from './buildTree'
|
|
@@ -30,13 +31,19 @@
|
|
|
30
31
|
)
|
|
31
32
|
const environment = useEnvironment()
|
|
32
33
|
const partConfig = usePartConfig()
|
|
34
|
+
const frames = useFrames()
|
|
33
35
|
const world = useWorld()
|
|
34
36
|
|
|
35
37
|
const worldEntity = world.spawn(IsExcluded, traits.Name('World'))
|
|
36
38
|
|
|
37
39
|
const allEntities = useQuery(traits.Name)
|
|
38
40
|
|
|
39
|
-
const { rootNodes, nodeMap } = $derived(
|
|
41
|
+
const { rootNodes, nodeMap } = $derived.by(() => {
|
|
42
|
+
// This ensures the tree rebuilds when frame parent relationships change
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
44
|
+
frames.current
|
|
45
|
+
return buildTreeNodes(allEntities.current)
|
|
46
|
+
})
|
|
40
47
|
|
|
41
48
|
const rootNode = $derived<TreeNode>({
|
|
42
49
|
entity: worldEntity,
|
|
@@ -2,8 +2,20 @@
|
|
|
2
2
|
import { Switch } from '@viamrobotics/prime-core'
|
|
3
3
|
import Drawer from './Drawer.svelte'
|
|
4
4
|
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
5
|
+
import { useResourceByName } from '../../hooks/useResourceByName.svelte'
|
|
6
|
+
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
5
7
|
|
|
6
8
|
const settings = useSettings()
|
|
9
|
+
const resourceByName = useResourceByName()
|
|
10
|
+
const partID = usePartID()
|
|
11
|
+
|
|
12
|
+
const cameras = $derived(
|
|
13
|
+
Object.values(resourceByName.current).filter((resource) => resource?.subtype === 'camera')
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const currentRobotCameraWidgets = $derived(
|
|
17
|
+
settings.current.openCameraWidgets[partID.current] || []
|
|
18
|
+
)
|
|
7
19
|
</script>
|
|
8
20
|
|
|
9
21
|
<Drawer name="Widgets">
|
|
@@ -17,5 +29,37 @@
|
|
|
17
29
|
}}
|
|
18
30
|
/>
|
|
19
31
|
</div>
|
|
32
|
+
|
|
33
|
+
<div class="mt-4">
|
|
34
|
+
<h3 class="text-sm"><strong>Camera Widgets</strong></h3>
|
|
35
|
+
{#each cameras as camera (camera?.name)}
|
|
36
|
+
{#if camera}
|
|
37
|
+
{@const isOpen = currentRobotCameraWidgets.includes(camera.name)}
|
|
38
|
+
<div class="flex items-center justify-between gap-4 py-2">
|
|
39
|
+
{camera.name}
|
|
40
|
+
<Switch
|
|
41
|
+
on={isOpen}
|
|
42
|
+
on:change={(event) => {
|
|
43
|
+
if (event.detail) {
|
|
44
|
+
settings.current.openCameraWidgets = {
|
|
45
|
+
...settings.current.openCameraWidgets,
|
|
46
|
+
[partID.current]: [...currentRobotCameraWidgets, camera.name],
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
settings.current.openCameraWidgets = {
|
|
50
|
+
...settings.current.openCameraWidgets,
|
|
51
|
+
[partID.current]: currentRobotCameraWidgets.filter(
|
|
52
|
+
(widget) => widget !== camera.name
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
{/if}
|
|
60
|
+
{:else}
|
|
61
|
+
<div class="py-2">No cameras detected</div>
|
|
62
|
+
{/each}
|
|
63
|
+
</div>
|
|
20
64
|
</div>
|
|
21
65
|
</Drawer>
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
$$bindings?: Bindings;
|
|
4
|
-
} & Exports;
|
|
5
|
-
(internal: unknown, props: {
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const Widgets: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type Widgets = InstanceType<typeof Widgets>;
|
|
1
|
+
declare const Widgets: import("svelte").Component<Record<string, never>, {}, "">;
|
|
2
|
+
type Widgets = ReturnType<typeof Widgets>;
|
|
18
3
|
export default Widgets;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { draggable } from '@neodrag/svelte'
|
|
3
|
+
import { Icon, Select } from '@viamrobotics/prime-core'
|
|
4
|
+
import { CameraStream, useRobotClient } from '@viamrobotics/svelte-sdk'
|
|
5
|
+
import { StreamClient } from '@viamrobotics/sdk'
|
|
6
|
+
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
7
|
+
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
8
|
+
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
9
|
+
|
|
10
|
+
interface Resolution {
|
|
11
|
+
width: number
|
|
12
|
+
height: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { name, ...rest } = $props<{ name: string }>()
|
|
16
|
+
|
|
17
|
+
const settings = useSettings()
|
|
18
|
+
const partID = usePartID()
|
|
19
|
+
const client = useRobotClient(() => partID.current)
|
|
20
|
+
const environment = useEnvironment()
|
|
21
|
+
|
|
22
|
+
let dragElement = $state.raw<HTMLElement>()
|
|
23
|
+
let aspectRatio = $state.raw<number | undefined>(undefined)
|
|
24
|
+
let fps = $state(0)
|
|
25
|
+
let resolutions = $state<Resolution[]>([])
|
|
26
|
+
let currentResolution = $state<string>('')
|
|
27
|
+
let isLoading = $state(true)
|
|
28
|
+
let error = $state<string | undefined>(undefined)
|
|
29
|
+
|
|
30
|
+
let fpsInterval: ReturnType<typeof setInterval> | undefined
|
|
31
|
+
let fpsCounterActive = false
|
|
32
|
+
|
|
33
|
+
// Cleanup on destroy
|
|
34
|
+
$effect(() => {
|
|
35
|
+
return () => {
|
|
36
|
+
if (fpsInterval) clearInterval(fpsInterval)
|
|
37
|
+
fpsCounterActive = false
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const onMediaLoad = (e: Event) => {
|
|
42
|
+
const target = e.target as HTMLVideoElement
|
|
43
|
+
|
|
44
|
+
// Update aspect ratio
|
|
45
|
+
if (target.videoWidth && target.videoHeight) {
|
|
46
|
+
aspectRatio = target.videoWidth / target.videoHeight
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Start FPS counter
|
|
50
|
+
if ('requestVideoFrameCallback' in target) {
|
|
51
|
+
if (fpsInterval) clearInterval(fpsInterval)
|
|
52
|
+
fpsCounterActive = false
|
|
53
|
+
|
|
54
|
+
let frameCount = 0
|
|
55
|
+
fpsCounterActive = true
|
|
56
|
+
|
|
57
|
+
const onFrame = () => {
|
|
58
|
+
if (!fpsCounterActive) return
|
|
59
|
+
frameCount++
|
|
60
|
+
target.requestVideoFrameCallback(onFrame)
|
|
61
|
+
}
|
|
62
|
+
target.requestVideoFrameCallback(onFrame)
|
|
63
|
+
|
|
64
|
+
// Update FPS state every 500ms
|
|
65
|
+
fpsInterval = setInterval(() => {
|
|
66
|
+
// FPS = frames / 0.5s = frames * 2
|
|
67
|
+
fps = frameCount * 2
|
|
68
|
+
frameCount = 0
|
|
69
|
+
}, 500)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create a single StreamClient instance per robot client
|
|
74
|
+
let streamClient = $derived(client.current ? new StreamClient(client.current) : undefined)
|
|
75
|
+
|
|
76
|
+
$effect(() => {
|
|
77
|
+
if (streamClient) {
|
|
78
|
+
isLoading = true
|
|
79
|
+
error = undefined
|
|
80
|
+
streamClient
|
|
81
|
+
.getOptions(name)
|
|
82
|
+
.then((options) => {
|
|
83
|
+
resolutions = options.map((opt) => ({ width: opt.width, height: opt.height }))
|
|
84
|
+
isLoading = false
|
|
85
|
+
})
|
|
86
|
+
.catch((e) => {
|
|
87
|
+
error = e instanceof Error ? e.message : 'Failed to get stream options'
|
|
88
|
+
isLoading = false
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const handleResolutionChange = async (e: Event) => {
|
|
94
|
+
const target = e.target as HTMLSelectElement
|
|
95
|
+
if (!target.value || !streamClient) return
|
|
96
|
+
|
|
97
|
+
const [w, h] = target.value.split('x').map(Number)
|
|
98
|
+
if (isNaN(w) || isNaN(h)) return
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await streamClient.setOptions(name, w, h)
|
|
102
|
+
error = undefined
|
|
103
|
+
} catch (err) {
|
|
104
|
+
error = err instanceof Error ? err.message : 'Failed to set resolution'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<div
|
|
110
|
+
class="bg-extralight border-medium absolute top-0 left-0 z-1000 m-2 flex resize-x flex-col overflow-hidden border text-xs"
|
|
111
|
+
style:width="320px"
|
|
112
|
+
style:height="auto !important"
|
|
113
|
+
use:draggable={{
|
|
114
|
+
bounds: 'body',
|
|
115
|
+
handle: dragElement,
|
|
116
|
+
}}
|
|
117
|
+
{...rest}
|
|
118
|
+
>
|
|
119
|
+
<div class="flex h-full min-w-0 flex-col">
|
|
120
|
+
<div class="flex w-full shrink-0 items-center justify-between">
|
|
121
|
+
<div class="border-medium flex w-full items-center gap-1 border-b p-2">
|
|
122
|
+
<button bind:this={dragElement}>
|
|
123
|
+
<Icon name="drag" />
|
|
124
|
+
</button>
|
|
125
|
+
<h3 class="min-w-0 truncate">{name}</h3>
|
|
126
|
+
<div class="flex-1"></div>
|
|
127
|
+
|
|
128
|
+
{#if isLoading}
|
|
129
|
+
<span class="text-subtle mr-2">Loading...</span>
|
|
130
|
+
{:else if resolutions.length > 0}
|
|
131
|
+
<div class="mr-2 w-32">
|
|
132
|
+
<Select
|
|
133
|
+
bind:value={currentResolution}
|
|
134
|
+
onchange={handleResolutionChange}
|
|
135
|
+
>
|
|
136
|
+
<option value="">Default</option>
|
|
137
|
+
{#each resolutions as res (`${res.width}x${res.height}`)}
|
|
138
|
+
<option value={`${res.width}x${res.height}`}>{res.width}x{res.height}</option>
|
|
139
|
+
{/each}
|
|
140
|
+
</Select>
|
|
141
|
+
</div>
|
|
142
|
+
{/if}
|
|
143
|
+
|
|
144
|
+
<button
|
|
145
|
+
aria-label="close"
|
|
146
|
+
class="hover:text-default"
|
|
147
|
+
onclick={() => {
|
|
148
|
+
const widgets = settings.current.openCameraWidgets[partID.current] || []
|
|
149
|
+
settings.current.openCameraWidgets = {
|
|
150
|
+
...settings.current.openCameraWidgets,
|
|
151
|
+
[partID.current]: widgets.filter((widget) => widget !== name),
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<Icon
|
|
156
|
+
name="close"
|
|
157
|
+
size="xs"
|
|
158
|
+
/>
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div
|
|
164
|
+
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
|
+
style:aspect-ratio={aspectRatio}
|
|
166
|
+
>
|
|
167
|
+
{#key environment.current.viewerMode === 'monitor'}
|
|
168
|
+
<CameraStream
|
|
169
|
+
{name}
|
|
170
|
+
partID={partID.current}
|
|
171
|
+
onloadedmetadata={onMediaLoad}
|
|
172
|
+
onload={onMediaLoad}
|
|
173
|
+
/>
|
|
174
|
+
{/key}
|
|
175
|
+
|
|
176
|
+
<!-- FPS Pill -->
|
|
177
|
+
{#if fps > 0}
|
|
178
|
+
<div
|
|
179
|
+
class="absolute bottom-2 left-2 z-10 rounded-[3px] bg-black/30 px-1 py-0.5 text-right font-mono text-xs text-white"
|
|
180
|
+
>
|
|
181
|
+
{fps.toFixed(1)}fps
|
|
182
|
+
</div>
|
|
183
|
+
{/if}
|
|
184
|
+
|
|
185
|
+
<!-- Error display -->
|
|
186
|
+
{#if error}
|
|
187
|
+
<div
|
|
188
|
+
class="absolute inset-0 flex items-center justify-center bg-black/50 p-2 text-center text-white"
|
|
189
|
+
>
|
|
190
|
+
{error}
|
|
191
|
+
</div>
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ArmClient } from '@viamrobotics/sdk';
|
|
2
2
|
import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk';
|
|
3
3
|
import { getContext, setContext } from 'svelte';
|
|
4
|
-
import { useWeblabs, WEBLABS_EXPERIMENTS } from './useWeblabs.svelte';
|
|
5
4
|
import { useSettings } from './useSettings.svelte';
|
|
6
5
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
7
6
|
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
@@ -11,7 +10,6 @@ dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5
|
|
|
11
10
|
gltfLoader.setDRACOLoader(dracoLoader);
|
|
12
11
|
const key = Symbol('3d-models-context');
|
|
13
12
|
export const provide3DModels = (partID) => {
|
|
14
|
-
const weblabs = useWeblabs();
|
|
15
13
|
const settings = useSettings();
|
|
16
14
|
let current = $state.raw({});
|
|
17
15
|
const arms = useResourceNames(partID, 'arm');
|
|
@@ -52,7 +50,7 @@ export const provide3DModels = (partID) => {
|
|
|
52
50
|
const shouldFetchModels = settings.current.isLoaded &&
|
|
53
51
|
(settings.current.renderArmModels === 'model' ||
|
|
54
52
|
settings.current.renderArmModels === 'colliders+model');
|
|
55
|
-
if (
|
|
53
|
+
if (shouldFetchModels) {
|
|
56
54
|
fetch3DModels();
|
|
57
55
|
}
|
|
58
56
|
});
|
|
@@ -383,7 +383,8 @@ export const provideDrawAPI = () => {
|
|
|
383
383
|
}
|
|
384
384
|
else if (type === bufferTypes.DRAW_GLTF) {
|
|
385
385
|
operation = 'DrawGLTF';
|
|
386
|
-
|
|
386
|
+
// GLTF payload starts after the 20-byte header (16 bytes UUID + 4 bytes type)
|
|
387
|
+
drawGLTF(reader.buffer.slice(20));
|
|
387
388
|
}
|
|
388
389
|
else {
|
|
389
390
|
throw new Error('Invalid buffer');
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export declare const WEBLABS_EXPERIMENTS: {
|
|
2
|
-
readonly MOTION_TOOLS_RENDER_ARM_MODELS: "MOTION_TOOLS_RENDER_ARM_MODELS";
|
|
3
|
-
};
|
|
1
|
+
export declare const WEBLABS_EXPERIMENTS: {};
|
|
4
2
|
export declare const WEBLABS_CONTEXT_KEY: unique symbol;
|
|
5
3
|
interface Context {
|
|
6
4
|
load: (experiments: string[]) => void;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
2
|
import { SvelteSet } from 'svelte/reactivity';
|
|
3
|
-
export const WEBLABS_EXPERIMENTS = {
|
|
4
|
-
MOTION_TOOLS_RENDER_ARM_MODELS: 'MOTION_TOOLS_RENDER_ARM_MODELS',
|
|
5
|
-
};
|
|
3
|
+
export const WEBLABS_EXPERIMENTS = {};
|
|
6
4
|
export const WEBLABS_CONTEXT_KEY = Symbol('weblabs-context');
|
|
7
5
|
const getCookie = (name) => {
|
|
8
6
|
const value = `; ${document.cookie}`;
|
|
@@ -7,6 +7,8 @@ import { createPose } from '../transform';
|
|
|
7
7
|
import { useThrelte } from '@threlte/core';
|
|
8
8
|
import { createBox, createCapsule, createSphere } from '../geometry';
|
|
9
9
|
import { parsePlyInput } from '../ply';
|
|
10
|
+
import { parsePcdInWorker } from '../loaders/pcd';
|
|
11
|
+
import { createBufferGeometry } from '../attribute';
|
|
10
12
|
export const provideWorldStates = () => {
|
|
11
13
|
const partID = usePartID();
|
|
12
14
|
const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
|
|
@@ -45,7 +47,22 @@ const createWorldState = (client) => {
|
|
|
45
47
|
entityTraits.push(traits.VertexColors(metadata.colors));
|
|
46
48
|
}
|
|
47
49
|
if (transform.physicalObject) {
|
|
48
|
-
|
|
50
|
+
if (transform.physicalObject.geometryType.case === 'pointcloud') {
|
|
51
|
+
parsePcdInWorker(new Uint8Array(transform.physicalObject.geometryType.value.pointCloud)).then((pointcloud) => {
|
|
52
|
+
// pcds are a special case since they have to be loaded in a worker and the trait will be added to the existing entity
|
|
53
|
+
const entity = entities.get(transform.uuidString);
|
|
54
|
+
if (!entity) {
|
|
55
|
+
console.error('Entity not found to add pointcloud trait to', transform.uuidString);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const geometry = createBufferGeometry(pointcloud.positions, pointcloud.colors);
|
|
59
|
+
entity.add(traits.BufferGeometry(geometry));
|
|
60
|
+
entity.add(traits.Points);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
entityTraits.push(traits.Geometry(transform.physicalObject));
|
|
65
|
+
}
|
|
49
66
|
}
|
|
50
67
|
if (metadata.shape === 'line' && metadata.points) {
|
|
51
68
|
const { points } = metadata;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"@neodrag/svelte": "^2.3.3",
|
|
130
130
|
"@tanstack/svelte-query-devtools": "^6.0.2",
|
|
131
131
|
"koota": "^0.5.3",
|
|
132
|
-
"lodash-es": "4.17.
|
|
132
|
+
"lodash-es": "4.17.23",
|
|
133
133
|
"uuid-tool": "^2.0.3"
|
|
134
134
|
},
|
|
135
135
|
"scripts": {
|