@viamrobotics/motion-tools 1.24.0 → 1.25.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.
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
|
|
4
|
+
A compound capsule via a shared open-ended unit cylinder and two
|
|
5
|
+
hemispheres, each scaled per `r` and `l`, so dimension changes update
|
|
6
|
+
transforms only and cause no geometry rebuild.
|
|
7
|
+
|
|
8
|
+
Viam's capsule `l` is the *total* length, including the rounded caps, so the
|
|
9
|
+
midsection has length `l - 2r`.
|
|
10
|
+
-->
|
|
11
|
+
<script
|
|
12
|
+
module
|
|
13
|
+
lang="ts"
|
|
14
|
+
>
|
|
15
|
+
import { CylinderGeometry, EdgesGeometry, SphereGeometry } from 'three'
|
|
16
|
+
|
|
17
|
+
const unitCylinder = new CylinderGeometry(1, 1, 1, 16, 1, true)
|
|
18
|
+
unitCylinder.rotateX(Math.PI / 2)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Hemisphere with rounded part toward +Z and an open boundary on the XY plane.
|
|
22
|
+
* 6 height segments matches the existing sphere's density (`Mesh.svelte` uses `SphereGeometry(1, 16, 12)`).
|
|
23
|
+
*/
|
|
24
|
+
const unitHemisphere = new SphereGeometry(1, 16, 6, 0, Math.PI * 2, 0, Math.PI / 2)
|
|
25
|
+
unitHemisphere.rotateX(Math.PI / 2)
|
|
26
|
+
|
|
27
|
+
const unitCylinderEdges = new EdgesGeometry(unitCylinder, 0)
|
|
28
|
+
const unitHemisphereEdges = new EdgesGeometry(unitHemisphere, 0)
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import type { ColorRepresentation } from 'three'
|
|
33
|
+
|
|
34
|
+
import { T, useThrelte } from '@threlte/core'
|
|
35
|
+
import { LineBasicMaterial, MeshToonMaterial } from 'three'
|
|
36
|
+
|
|
37
|
+
import { darkenColor } from '../../color'
|
|
38
|
+
|
|
39
|
+
interface Props {
|
|
40
|
+
r: number
|
|
41
|
+
l: number
|
|
42
|
+
color: ColorRepresentation
|
|
43
|
+
opacity?: number
|
|
44
|
+
depthTest?: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let { r, l, color, opacity = 1, depthTest = true }: Props = $props()
|
|
48
|
+
|
|
49
|
+
const { invalidate } = useThrelte()
|
|
50
|
+
const material = new MeshToonMaterial()
|
|
51
|
+
const lineMaterial = new LineBasicMaterial()
|
|
52
|
+
|
|
53
|
+
$effect(() => {
|
|
54
|
+
material.color.set(color)
|
|
55
|
+
lineMaterial.color.set(darkenColor(color, 10))
|
|
56
|
+
invalidate()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
$effect(() => {
|
|
60
|
+
const isTransparent = opacity < 1
|
|
61
|
+
material.opacity = opacity
|
|
62
|
+
material.depthWrite = opacity === 1
|
|
63
|
+
material.depthTest = depthTest
|
|
64
|
+
lineMaterial.depthTest = depthTest
|
|
65
|
+
if (material.transparent !== isTransparent) {
|
|
66
|
+
material.transparent = isTransparent
|
|
67
|
+
material.needsUpdate = true
|
|
68
|
+
}
|
|
69
|
+
invalidate()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const midsection = $derived(Math.max(0, l - 2 * r))
|
|
73
|
+
const halfMid = $derived(midsection / 2)
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if midsection > 0}
|
|
77
|
+
<T.Mesh scale={[r, r, midsection]}>
|
|
78
|
+
<T
|
|
79
|
+
is={unitCylinder}
|
|
80
|
+
dispose={false}
|
|
81
|
+
/>
|
|
82
|
+
<T is={material} />
|
|
83
|
+
<T.LineSegments
|
|
84
|
+
raycast={() => null}
|
|
85
|
+
bvh={{ enabled: false }}
|
|
86
|
+
>
|
|
87
|
+
<T
|
|
88
|
+
is={unitCylinderEdges}
|
|
89
|
+
dispose={false}
|
|
90
|
+
/>
|
|
91
|
+
<T is={lineMaterial} />
|
|
92
|
+
</T.LineSegments>
|
|
93
|
+
</T.Mesh>
|
|
94
|
+
{/if}
|
|
95
|
+
|
|
96
|
+
<T.Mesh
|
|
97
|
+
position={[0, 0, halfMid]}
|
|
98
|
+
scale={r}
|
|
99
|
+
>
|
|
100
|
+
<T
|
|
101
|
+
is={unitHemisphere}
|
|
102
|
+
dispose={false}
|
|
103
|
+
/>
|
|
104
|
+
<T is={material} />
|
|
105
|
+
<T.LineSegments
|
|
106
|
+
raycast={() => null}
|
|
107
|
+
bvh={{ enabled: false }}
|
|
108
|
+
>
|
|
109
|
+
<T
|
|
110
|
+
is={unitHemisphereEdges}
|
|
111
|
+
dispose={false}
|
|
112
|
+
/>
|
|
113
|
+
<T is={lineMaterial} />
|
|
114
|
+
</T.LineSegments>
|
|
115
|
+
</T.Mesh>
|
|
116
|
+
|
|
117
|
+
<T.Mesh
|
|
118
|
+
position={[0, 0, -halfMid]}
|
|
119
|
+
rotation={[Math.PI, 0, 0]}
|
|
120
|
+
scale={[r, r, r]}
|
|
121
|
+
>
|
|
122
|
+
<T
|
|
123
|
+
is={unitHemisphere}
|
|
124
|
+
dispose={false}
|
|
125
|
+
/>
|
|
126
|
+
<T is={material} />
|
|
127
|
+
<T.LineSegments
|
|
128
|
+
raycast={() => null}
|
|
129
|
+
bvh={{ enabled: false }}
|
|
130
|
+
>
|
|
131
|
+
<T
|
|
132
|
+
is={unitHemisphereEdges}
|
|
133
|
+
dispose={false}
|
|
134
|
+
/>
|
|
135
|
+
<T is={lineMaterial} />
|
|
136
|
+
</T.LineSegments>
|
|
137
|
+
</T.Mesh>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ColorRepresentation } from 'three';
|
|
2
|
+
interface Props {
|
|
3
|
+
r: number;
|
|
4
|
+
l: number;
|
|
5
|
+
color: ColorRepresentation;
|
|
6
|
+
opacity?: number;
|
|
7
|
+
depthTest?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A compound capsule via a shared open-ended unit cylinder and two
|
|
11
|
+
* hemispheres, each scaled per `r` and `l`, so dimension changes update
|
|
12
|
+
* transforms only and cause no geometry rebuild.
|
|
13
|
+
*
|
|
14
|
+
* Viam's capsule `l` is the *total* length, including the rounded caps, so the
|
|
15
|
+
* midsection has length `l - 2r`.
|
|
16
|
+
*/
|
|
17
|
+
declare const Capsule: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type Capsule = ReturnType<typeof Capsule>;
|
|
19
|
+
export default Capsule;
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
+
<script
|
|
2
|
+
module
|
|
3
|
+
lang="ts"
|
|
4
|
+
>
|
|
5
|
+
import { BoxGeometry, EdgesGeometry, SphereGeometry } from 'three'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared unit geometries — every mesh references these and sets
|
|
9
|
+
* dimensions through `mesh.scale`, so resizing never rebuilds GPU buffers.
|
|
10
|
+
*/
|
|
11
|
+
const unitBox = new BoxGeometry(1, 1, 1)
|
|
12
|
+
const unitSphere = new SphereGeometry(1, 16, 12)
|
|
13
|
+
const unitBoxEdges = new EdgesGeometry(unitBox, 0)
|
|
14
|
+
const unitSphereEdges = new EdgesGeometry(unitSphere, 0)
|
|
15
|
+
</script>
|
|
16
|
+
|
|
1
17
|
<script lang="ts">
|
|
2
18
|
import type { Pose } from '@viamrobotics/sdk'
|
|
3
19
|
import type { Entity } from 'koota'
|
|
4
20
|
|
|
5
21
|
import { T, type Props as ThrelteProps, useThrelte } from '@threlte/core'
|
|
6
22
|
import { type Snippet } from 'svelte'
|
|
7
|
-
import {
|
|
23
|
+
import { Color, DoubleSide, FrontSide, Group, Material, Mesh } from 'three'
|
|
8
24
|
|
|
9
25
|
import { asColor } from '../../buffer'
|
|
10
26
|
import { colors, darkenColor } from '../../color'
|
|
11
27
|
import { traits, useTrait } from '../../ecs'
|
|
12
|
-
import { CapsuleGeometry } from '../../three/CapsuleGeometry'
|
|
13
28
|
import { poseToObject3d } from '../../transform'
|
|
14
29
|
|
|
15
30
|
import AxesHelper from '../AxesHelper.svelte'
|
|
31
|
+
import Capsule from './Capsule.svelte'
|
|
16
32
|
|
|
17
|
-
interface Props extends ThrelteProps<Mesh> {
|
|
33
|
+
interface Props extends Omit<ThrelteProps<Mesh>, 'ref'> {
|
|
18
34
|
entity: Entity
|
|
19
35
|
color?: string
|
|
20
36
|
center?: Pose
|
|
@@ -56,6 +72,8 @@
|
|
|
56
72
|
|
|
57
73
|
const currentOpacity = $derived(opacity.current ?? 0.7)
|
|
58
74
|
|
|
75
|
+
const isCapsule = $derived(capsule.current !== undefined)
|
|
76
|
+
|
|
59
77
|
let material = $state.raw<Material>(new Material())
|
|
60
78
|
$effect(() => {
|
|
61
79
|
const isTransparent = currentOpacity < 1
|
|
@@ -69,83 +87,118 @@
|
|
|
69
87
|
})
|
|
70
88
|
|
|
71
89
|
const mesh = new Mesh()
|
|
72
|
-
|
|
90
|
+
const group = new Group()
|
|
91
|
+
|
|
92
|
+
$effect(() => {
|
|
93
|
+
const target = isCapsule ? group : mesh
|
|
73
94
|
if (center) {
|
|
74
|
-
poseToObject3d(center,
|
|
95
|
+
poseToObject3d(center, target)
|
|
75
96
|
invalidate()
|
|
76
97
|
}
|
|
77
98
|
})
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
$effect(() => {
|
|
101
|
+
if (box.current) {
|
|
102
|
+
const { x, y, z } = box.current
|
|
103
|
+
mesh.scale.set(x * 0.001, y * 0.001, z * 0.001)
|
|
104
|
+
} else if (sphere.current) {
|
|
105
|
+
mesh.scale.setScalar((sphere.current.r ?? 0) * 0.001)
|
|
106
|
+
} else {
|
|
107
|
+
mesh.scale.set(1, 1, 1)
|
|
83
108
|
}
|
|
109
|
+
invalidate()
|
|
84
110
|
})
|
|
85
|
-
|
|
86
|
-
const oncreate = (bufferGeometry: BufferGeometry) => {
|
|
87
|
-
geo = bufferGeometry
|
|
88
|
-
}
|
|
89
111
|
</script>
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<T.SphereGeometry
|
|
107
|
-
args={[r * 0.001]}
|
|
108
|
-
{oncreate}
|
|
109
|
-
/>
|
|
110
|
-
{:else if capsule.current}
|
|
111
|
-
{@const { r, l } = capsule.current ?? { r: 0, l: 0 }}
|
|
112
|
-
<T
|
|
113
|
-
is={CapsuleGeometry}
|
|
114
|
-
args={[r * 0.001, l * 0.001]}
|
|
115
|
-
{oncreate}
|
|
113
|
+
{#if isCapsule}
|
|
114
|
+
{@const { r, l } = capsule.current ?? { r: 0, l: 0 }}
|
|
115
|
+
<T
|
|
116
|
+
is={group}
|
|
117
|
+
name={entity}
|
|
118
|
+
userData.name={name}
|
|
119
|
+
renderOrder={renderOrder.current}
|
|
120
|
+
{...rest}
|
|
121
|
+
>
|
|
122
|
+
<Capsule
|
|
123
|
+
r={r * 0.001}
|
|
124
|
+
l={l * 0.001}
|
|
125
|
+
{color}
|
|
126
|
+
opacity={currentOpacity}
|
|
127
|
+
depthTest={materialProps.current?.depthTest ?? true}
|
|
116
128
|
/>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
|
|
130
|
+
{@render children?.()}
|
|
131
|
+
</T>
|
|
132
|
+
{:else}
|
|
133
|
+
<T
|
|
134
|
+
is={mesh}
|
|
135
|
+
name={entity}
|
|
136
|
+
userData.name={name}
|
|
137
|
+
renderOrder={renderOrder.current}
|
|
138
|
+
{...rest}
|
|
139
|
+
>
|
|
140
|
+
{#if box.current}
|
|
141
|
+
<T
|
|
142
|
+
is={unitBox}
|
|
143
|
+
dispose={false}
|
|
144
|
+
/>
|
|
145
|
+
<T.LineSegments
|
|
146
|
+
raycast={() => null}
|
|
147
|
+
bvh={{ enabled: false }}
|
|
148
|
+
>
|
|
149
|
+
<T
|
|
150
|
+
is={unitBoxEdges}
|
|
151
|
+
dispose={false}
|
|
152
|
+
/>
|
|
153
|
+
<T.LineBasicMaterial color={darkenColor(color, 10)} />
|
|
154
|
+
</T.LineSegments>
|
|
155
|
+
{:else if sphere.current}
|
|
156
|
+
<T
|
|
157
|
+
is={unitSphere}
|
|
158
|
+
dispose={false}
|
|
159
|
+
/>
|
|
160
|
+
<T.LineSegments
|
|
161
|
+
raycast={() => null}
|
|
162
|
+
bvh={{ enabled: false }}
|
|
163
|
+
>
|
|
164
|
+
<T
|
|
165
|
+
is={unitSphereEdges}
|
|
166
|
+
dispose={false}
|
|
167
|
+
/>
|
|
168
|
+
<T.LineBasicMaterial color={darkenColor(color, 10)} />
|
|
169
|
+
</T.LineSegments>
|
|
170
|
+
{:else if bufferGeometry.current}
|
|
171
|
+
<T is={bufferGeometry.current}>
|
|
172
|
+
{#snippet children({ ref: geo })}
|
|
173
|
+
<!--
|
|
174
|
+
TODO(mp) currently some bufferGeometries are coming in empty,
|
|
175
|
+
this is a quick fix but this should be handled upstream
|
|
176
|
+
-->
|
|
177
|
+
{#if geo.getAttribute('position').array.length > 0}
|
|
178
|
+
<T.LineSegments
|
|
179
|
+
raycast={() => null}
|
|
180
|
+
bvh={{ enabled: false }}
|
|
181
|
+
>
|
|
182
|
+
<T.EdgesGeometry args={[geo, 0]} />
|
|
183
|
+
<T.LineBasicMaterial color={darkenColor(color, 10)} />
|
|
184
|
+
</T.LineSegments>
|
|
185
|
+
{/if}
|
|
186
|
+
{/snippet}
|
|
187
|
+
</T>
|
|
188
|
+
{/if}
|
|
189
|
+
|
|
190
|
+
<T.MeshToonMaterial
|
|
191
|
+
{color}
|
|
192
|
+
side={bufferGeometry.current ? DoubleSide : FrontSide}
|
|
193
|
+
depthTest={materialProps.current?.depthTest ?? true}
|
|
194
|
+
oncreate={(m) => {
|
|
195
|
+
material = m
|
|
196
|
+
}}
|
|
121
197
|
/>
|
|
122
|
-
{/if}
|
|
123
|
-
|
|
124
|
-
<T.MeshToonMaterial
|
|
125
|
-
{color}
|
|
126
|
-
side={bufferGeometry.current ? DoubleSide : FrontSide}
|
|
127
|
-
depthTest={materialProps.current?.depthTest ?? true}
|
|
128
|
-
oncreate={(m) => {
|
|
129
|
-
material = m
|
|
130
|
-
}}
|
|
131
|
-
/>
|
|
132
198
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
-->
|
|
137
|
-
{#if geo && geo.getAttribute('position').array.length > 0}
|
|
138
|
-
<T.LineSegments
|
|
139
|
-
raycast={() => null}
|
|
140
|
-
bvh={{ enabled: false }}
|
|
141
|
-
>
|
|
142
|
-
<T.EdgesGeometry args={[geo, 0]} />
|
|
143
|
-
<T.LineBasicMaterial color={darkenColor(color, 10)} />
|
|
144
|
-
</T.LineSegments>
|
|
145
|
-
{/if}
|
|
146
|
-
|
|
147
|
-
{@render children?.()}
|
|
148
|
-
</T>
|
|
199
|
+
{@render children?.()}
|
|
200
|
+
</T>
|
|
201
|
+
{/if}
|
|
149
202
|
|
|
150
203
|
{#if showAxesHelper.current}
|
|
151
204
|
<AxesHelper
|
|
@@ -9,14 +9,18 @@ export const provideArmKinematics = (partID) => {
|
|
|
9
9
|
const names = $derived(arms.current.map((arm) => arm.name));
|
|
10
10
|
const clients = $derived(arms.current.map((arm) => createResourceClient(ArmClient, partID, () => arm.name)));
|
|
11
11
|
const kinematicsQueries = $derived(clients.map((client) => [client.current?.name, createResourceQuery(client, 'getKinematics', () => options)]));
|
|
12
|
-
const kinematics = $derived(Object.fromEntries(kinematicsQueries.map(([name, query]) =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
const kinematics = $derived(Object.fromEntries(kinematicsQueries.map(([name, query]) => {
|
|
13
|
+
const data = query.data;
|
|
14
|
+
const joints = data && ('joints' in data ? data.joints : data.kinematicsData.joints);
|
|
15
|
+
return [
|
|
16
|
+
name,
|
|
17
|
+
joints?.map((j) => ({
|
|
18
|
+
id: j.id,
|
|
19
|
+
min: j.min,
|
|
20
|
+
max: j.max,
|
|
21
|
+
})),
|
|
22
|
+
];
|
|
23
|
+
})));
|
|
20
24
|
setContext(key, {
|
|
21
25
|
get names() {
|
|
22
26
|
return names;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { createRobotQuery, useRobotClient } from '@viamrobotics/svelte-sdk';
|
|
1
|
+
import { commonApi, MachineConnectionEvent, Pose } from '@viamrobotics/sdk';
|
|
2
|
+
import { createRobotQuery, useConnectionStatus, useRobotClient } from '@viamrobotics/svelte-sdk';
|
|
4
3
|
import { untrack } from 'svelte';
|
|
5
4
|
import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
|
|
6
5
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
@@ -12,10 +11,11 @@ import { useResourceByName } from './useResourceByName.svelte';
|
|
|
12
11
|
import { RefreshRates, useSettings } from './useSettings.svelte';
|
|
13
12
|
const originFrameComponentTypes = new Set(['arm', 'gantry', 'gripper', 'base']);
|
|
14
13
|
export const usePose = (name, parent) => {
|
|
14
|
+
const partID = usePartID();
|
|
15
|
+
const connectionStatus = useConnectionStatus(() => partID.current);
|
|
15
16
|
const environment = useEnvironment();
|
|
16
17
|
const logs = useLogs();
|
|
17
18
|
const settings = useSettings();
|
|
18
|
-
const partID = usePartID();
|
|
19
19
|
const robotClient = useRobotClient(() => partID.current);
|
|
20
20
|
const currentName = $derived(name());
|
|
21
21
|
const currentParent = $derived(parent());
|
|
@@ -49,8 +49,10 @@ export const usePose = (name, parent) => {
|
|
|
49
49
|
logs.add(`Error fetching pose for ${currentName}: ${query.error.message}`, 'error');
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
|
-
|
|
53
|
-
if (environment.current.viewerMode === 'monitor'
|
|
52
|
+
$effect(() => {
|
|
53
|
+
if (environment.current.viewerMode === 'monitor' &&
|
|
54
|
+
frames.current &&
|
|
55
|
+
connectionStatus.current === MachineConnectionEvent.CONNECTED) {
|
|
54
56
|
untrack(() => query.refetch());
|
|
55
57
|
}
|
|
56
58
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "8.56.1",
|
|
38
38
|
"@typescript-eslint/parser": "8.56.1",
|
|
39
39
|
"@viamrobotics/prime-core": "0.1.5",
|
|
40
|
-
"@viamrobotics/sdk": "0.
|
|
41
|
-
"@viamrobotics/svelte-sdk": "1.
|
|
40
|
+
"@viamrobotics/sdk": "0.69.0",
|
|
41
|
+
"@viamrobotics/svelte-sdk": "1.2.2",
|
|
42
42
|
"@vitest/browser": "3.2.4",
|
|
43
43
|
"@vitest/coverage-v8": "^3.2.4",
|
|
44
44
|
"@zag-js/collapsible": "1.22.1",
|