@viamrobotics/motion-tools 1.3.4 → 1.4.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/dist/attribute.d.ts +2 -2
- package/dist/attribute.js +2 -2
- package/dist/components/Details.svelte +40 -11
- package/dist/components/FileDrop/FileDrop.svelte +4 -2
- package/dist/components/Geometry2.svelte +1 -1
- package/dist/components/StaticGeometries.svelte +2 -1
- package/dist/components/Tree/TreeContainer.svelte +4 -31
- package/dist/components/widgets/ArmPositions.svelte +0 -11
- package/dist/ecs/traits.d.ts +4 -0
- package/dist/ecs/traits.js +4 -0
- package/dist/hooks/useDrawAPI.svelte.js +40 -23
- package/dist/hooks/useEnvironment.svelte.d.ts +1 -1
- package/dist/hooks/useFrames.svelte.js +18 -14
- package/dist/hooks/usePointcloudObjects.svelte.js +3 -1
- package/dist/hooks/usePointclouds.svelte.js +3 -1
- package/dist/hooks/usePose.svelte.js +1 -1
- package/dist/hooks/useSelection.svelte.js +12 -13
- package/dist/hooks/useWorldState.svelte.js +6 -2
- package/dist/loaders/pcd/worker.d.ts +1 -1
- package/dist/loaders/pcd/worker.js +7 -1
- package/dist/snapshot.js +6 -7
- package/dist/three/InstancedArrows/InstancedArrows.js +2 -2
- package/package.json +8 -7
package/dist/attribute.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { BufferGeometry } from 'three';
|
|
2
|
-
export declare const createBufferGeometry: (positions: Float32Array, colors?:
|
|
3
|
-
export declare const updateBufferGeometry: (geometry: BufferGeometry, positions: Float32Array, colors?:
|
|
2
|
+
export declare const createBufferGeometry: (positions: Float32Array, colors?: Uint8Array | null) => BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
3
|
+
export declare const updateBufferGeometry: (geometry: BufferGeometry, positions: Float32Array, colors?: Uint8Array | null) => void;
|
package/dist/attribute.js
CHANGED
|
@@ -3,7 +3,7 @@ export const createBufferGeometry = (positions, colors) => {
|
|
|
3
3
|
const geometry = new BufferGeometry();
|
|
4
4
|
geometry.setAttribute('position', new BufferAttribute(positions, 3));
|
|
5
5
|
if (colors) {
|
|
6
|
-
geometry.setAttribute('color', new BufferAttribute(colors, 3));
|
|
6
|
+
geometry.setAttribute('color', new BufferAttribute(colors, 3, true));
|
|
7
7
|
}
|
|
8
8
|
return geometry;
|
|
9
9
|
};
|
|
@@ -24,7 +24,7 @@ export const updateBufferGeometry = (geometry, positions, colors) => {
|
|
|
24
24
|
colorAttr.needsUpdate = true;
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
|
-
geometry.setAttribute('color', new BufferAttribute(colors, 3));
|
|
27
|
+
geometry.setAttribute('color', new BufferAttribute(colors, 3, true));
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
lang="ts"
|
|
4
4
|
>
|
|
5
5
|
import { OrientationVector } from '../three/OrientationVector'
|
|
6
|
-
import { Quaternion, Vector3, MathUtils,
|
|
6
|
+
import { Quaternion, Vector3, MathUtils, BufferAttribute } from 'three'
|
|
7
7
|
|
|
8
8
|
const vec3 = new Vector3()
|
|
9
9
|
const quaternion = new Quaternion()
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script lang="ts">
|
|
14
14
|
import { draggable } from '@neodrag/svelte'
|
|
15
15
|
import { Check, Copy } from 'lucide-svelte'
|
|
16
|
-
import { useTask } from '@threlte/core'
|
|
16
|
+
import { useTask, isInstanceOf } from '@threlte/core'
|
|
17
17
|
import { Button, Icon, Select, Input, Tooltip } from '@viamrobotics/prime-core'
|
|
18
18
|
import {
|
|
19
19
|
useSelectedEntity,
|
|
@@ -25,15 +25,13 @@
|
|
|
25
25
|
import { usePartConfig } from '../hooks/usePartConfig.svelte'
|
|
26
26
|
import { FrameConfigUpdater } from '../FrameConfigUpdater.svelte'
|
|
27
27
|
import { useEnvironment } from '../hooks/useEnvironment.svelte'
|
|
28
|
-
import { traits, useTrait } from '../ecs'
|
|
28
|
+
import { traits, useTrait, useWorld } from '../ecs'
|
|
29
29
|
import { useResourceByName } from '../hooks/useResourceByName.svelte'
|
|
30
|
-
import { PersistedState } from 'runed'
|
|
31
30
|
import { useCameraControls } from '../hooks/useControls.svelte'
|
|
32
31
|
|
|
33
32
|
const { ...rest } = $props()
|
|
34
33
|
|
|
35
|
-
const
|
|
36
|
-
|
|
34
|
+
const world = useWorld()
|
|
37
35
|
const controls = useCameraControls()
|
|
38
36
|
const resourceByName = useResourceByName()
|
|
39
37
|
const frames = useFrames()
|
|
@@ -54,6 +52,7 @@
|
|
|
54
52
|
const box = useTrait(() => entity, traits.Box)
|
|
55
53
|
const sphere = useTrait(() => entity, traits.Sphere)
|
|
56
54
|
const capsule = useTrait(() => entity, traits.Capsule)
|
|
55
|
+
const removable = useTrait(() => entity, traits.Removable)
|
|
57
56
|
|
|
58
57
|
const framesAPI = useTrait(() => entity, traits.FramesAPI)
|
|
59
58
|
const isFrameNode = $derived(!!framesAPI.current)
|
|
@@ -232,21 +231,18 @@
|
|
|
232
231
|
{@const ScalarAttribute = showEditFrameOptions ? MutableField : ImmutableField}
|
|
233
232
|
|
|
234
233
|
<div
|
|
234
|
+
id="details-panel"
|
|
235
235
|
class="border-medium bg-extralight absolute top-0 right-0 z-10 m-2 {showEditFrameOptions
|
|
236
236
|
? 'w-80'
|
|
237
237
|
: 'w-60'} border p-2 text-xs"
|
|
238
238
|
use:draggable={{
|
|
239
239
|
bounds: 'body',
|
|
240
240
|
handle: dragElement,
|
|
241
|
-
defaultPosition: dragPosition.current,
|
|
242
|
-
onDragEnd(data) {
|
|
243
|
-
dragPosition.current = { x: data.offsetX, y: data.offsetY }
|
|
244
|
-
},
|
|
245
241
|
}}
|
|
246
242
|
{...rest}
|
|
247
243
|
>
|
|
248
244
|
<div class="flex items-center justify-between gap-2 pb-2">
|
|
249
|
-
<div class="flex w-[
|
|
245
|
+
<div class="flex w-[80%] items-center gap-1">
|
|
250
246
|
<button bind:this={dragElement}>
|
|
251
247
|
<Icon name="drag" />
|
|
252
248
|
</button>
|
|
@@ -286,6 +282,26 @@
|
|
|
286
282
|
<p slot="description">Zoom to object</p>
|
|
287
283
|
</Tooltip>
|
|
288
284
|
{/if}
|
|
285
|
+
|
|
286
|
+
{#if removable.current}
|
|
287
|
+
<Tooltip
|
|
288
|
+
let:tooltipID
|
|
289
|
+
location="bottom"
|
|
290
|
+
>
|
|
291
|
+
<button
|
|
292
|
+
class="text-subtle-2"
|
|
293
|
+
aria-describedby={tooltipID}
|
|
294
|
+
onclick={() => {
|
|
295
|
+
if (world.has(entity)) {
|
|
296
|
+
entity.destroy()
|
|
297
|
+
}
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
<Icon name="trash-can-outline" />
|
|
301
|
+
</button>
|
|
302
|
+
<p slot="description">Remove from scene</p>
|
|
303
|
+
</Tooltip>
|
|
304
|
+
{/if}
|
|
289
305
|
</div>
|
|
290
306
|
|
|
291
307
|
<div class="border-medium -mx-2 w-[100%+0.5rem] border-b"></div>
|
|
@@ -580,6 +596,19 @@
|
|
|
580
596
|
</div>
|
|
581
597
|
</div>
|
|
582
598
|
{/if}
|
|
599
|
+
|
|
600
|
+
{#if isInstanceOf(object3d, 'Points')}
|
|
601
|
+
<div>
|
|
602
|
+
<strong class="font-semibold">points</strong>
|
|
603
|
+
{@render ImmutableField({
|
|
604
|
+
label: 'count',
|
|
605
|
+
ariaLabel: 'points count',
|
|
606
|
+
value: new Intl.NumberFormat().format(
|
|
607
|
+
(object3d.geometry.getAttribute('position') as BufferAttribute).array.length / 3
|
|
608
|
+
),
|
|
609
|
+
})}
|
|
610
|
+
</div>
|
|
611
|
+
{/if}
|
|
583
612
|
</div>
|
|
584
613
|
|
|
585
614
|
<h3 class="text-subtle-2 pt-3 pb-2">Actions</h3>
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
traits.Name(result.name),
|
|
43
43
|
traits.BufferGeometry(geometry),
|
|
44
44
|
traits.Points,
|
|
45
|
-
traits.DroppedFile
|
|
45
|
+
traits.DroppedFile,
|
|
46
|
+
traits.Removable
|
|
46
47
|
)
|
|
47
48
|
break
|
|
48
49
|
}
|
|
@@ -50,7 +51,8 @@
|
|
|
50
51
|
world.spawn(
|
|
51
52
|
traits.Name(result.name),
|
|
52
53
|
traits.BufferGeometry(result.ply),
|
|
53
|
-
traits.DroppedFile
|
|
54
|
+
traits.DroppedFile,
|
|
55
|
+
traits.Removable
|
|
54
56
|
)
|
|
55
57
|
break
|
|
56
58
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { Vector2Like } from 'three'
|
|
3
2
|
import { draggable } from '@neodrag/svelte'
|
|
4
3
|
import Tree from './Tree.svelte'
|
|
5
4
|
import { useSelectedEntity } from '../../hooks/useSelection.svelte'
|
|
@@ -11,16 +10,13 @@
|
|
|
11
10
|
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
12
11
|
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
13
12
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
14
|
-
import { traits, useWorld } from '../../ecs'
|
|
13
|
+
import { traits, useQuery, useWorld } from '../../ecs'
|
|
15
14
|
import { IsExcluded, type Entity } from 'koota'
|
|
16
15
|
import { buildTreeNodes, type TreeNode } from './buildTree'
|
|
17
16
|
import { MIN_DIMENSIONS, useResizable } from '../../hooks/useResizable.svelte'
|
|
18
|
-
import { PersistedState } from 'runed'
|
|
19
17
|
|
|
20
18
|
const { ...rest } = $props()
|
|
21
19
|
|
|
22
|
-
const dragPosition = new PersistedState<Vector2Like | undefined>('tree-drag-position', undefined)
|
|
23
|
-
|
|
24
20
|
provideTreeExpandedContext()
|
|
25
21
|
|
|
26
22
|
let container = $state.raw<HTMLDivElement>()
|
|
@@ -38,32 +34,13 @@
|
|
|
38
34
|
|
|
39
35
|
const worldEntity = world.spawn(IsExcluded, traits.Name('World'))
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
let nodeMap = $state.raw<Record<string, TreeNode | undefined>>({})
|
|
43
|
-
|
|
44
|
-
let pending = false
|
|
45
|
-
const flush = () => {
|
|
46
|
-
if (pending) return
|
|
47
|
-
pending = true
|
|
48
|
-
|
|
49
|
-
window.setTimeout(() => {
|
|
50
|
-
const results = buildTreeNodes(world.query(traits.Name))
|
|
51
|
-
children = results.rootNodes
|
|
52
|
-
nodeMap = results.nodeMap
|
|
53
|
-
pending = false
|
|
54
|
-
})
|
|
55
|
-
}
|
|
37
|
+
const allEntities = useQuery(traits.Name)
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
world.onAdd(traits.Parent, flush)
|
|
59
|
-
world.onRemove(traits.Name, flush)
|
|
60
|
-
world.onRemove(traits.Parent, flush)
|
|
61
|
-
world.onChange(traits.Name, flush)
|
|
62
|
-
world.onChange(traits.Parent, flush)
|
|
39
|
+
const { rootNodes, nodeMap } = $derived(buildTreeNodes(allEntities.current))
|
|
63
40
|
|
|
64
41
|
const rootNode = $derived<TreeNode>({
|
|
65
42
|
entity: worldEntity,
|
|
66
|
-
children,
|
|
43
|
+
children: rootNodes,
|
|
67
44
|
})
|
|
68
45
|
|
|
69
46
|
$effect(() => {
|
|
@@ -83,10 +60,6 @@
|
|
|
83
60
|
use:draggable={{
|
|
84
61
|
bounds: 'body',
|
|
85
62
|
handle: dragElement,
|
|
86
|
-
defaultPosition: dragPosition.current,
|
|
87
|
-
onDragEnd(data) {
|
|
88
|
-
dragPosition.current = { x: Math.max(data.offsetX, 0), y: Math.max(data.offsetY, 0) }
|
|
89
|
-
},
|
|
90
63
|
}}
|
|
91
64
|
{...rest}
|
|
92
65
|
>
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { Vector2Like } from 'three'
|
|
3
2
|
import { draggable } from '@neodrag/svelte'
|
|
4
3
|
import { formatNumeric } from '../../format'
|
|
5
4
|
import Table from '../shared/Table.svelte'
|
|
6
5
|
import { useArmClient } from '../../hooks/useArmClient.svelte'
|
|
7
6
|
import { Icon, Label, Select } from '@viamrobotics/prime-core'
|
|
8
|
-
import { PersistedState } from 'runed'
|
|
9
7
|
|
|
10
8
|
const { ...rest } = $props()
|
|
11
9
|
|
|
12
|
-
const dragPosition = new PersistedState<Vector2Like | undefined>(
|
|
13
|
-
'details-drag-position',
|
|
14
|
-
undefined
|
|
15
|
-
)
|
|
16
|
-
|
|
17
10
|
let dragElement = $state.raw<HTMLElement>()
|
|
18
11
|
|
|
19
12
|
const armClient = useArmClient()
|
|
@@ -28,10 +21,6 @@
|
|
|
28
21
|
use:draggable={{
|
|
29
22
|
bounds: 'body',
|
|
30
23
|
handle: dragElement,
|
|
31
|
-
defaultPosition: dragPosition.current,
|
|
32
|
-
onDragEnd(data) {
|
|
33
|
-
dragPosition.current = { x: data.offsetX, y: data.offsetY }
|
|
34
|
-
},
|
|
35
24
|
}}
|
|
36
25
|
{...rest}
|
|
37
26
|
>
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -126,6 +126,10 @@ export declare const PointSize: import("koota").Trait<() => number>;
|
|
|
126
126
|
*/
|
|
127
127
|
export declare const LineWidth: import("koota").Trait<() => number>;
|
|
128
128
|
export declare const ReferenceFrame: import("koota").TagTrait;
|
|
129
|
+
/**
|
|
130
|
+
* This entity can be safetly removed from the scene by the user
|
|
131
|
+
*/
|
|
132
|
+
export declare const Removable: import("koota").TagTrait;
|
|
129
133
|
export declare const Geometry: (geometry: ViamGeometry) => import("koota").TagTrait | [import("koota").Trait<{
|
|
130
134
|
x: number;
|
|
131
135
|
y: number;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -78,6 +78,10 @@ export const PointSize = trait(() => 10);
|
|
|
78
78
|
*/
|
|
79
79
|
export const LineWidth = trait(() => 5);
|
|
80
80
|
export const ReferenceFrame = trait();
|
|
81
|
+
/**
|
|
82
|
+
* This entity can be safetly removed from the scene by the user
|
|
83
|
+
*/
|
|
84
|
+
export const Removable = trait();
|
|
81
85
|
export const Geometry = (geometry) => {
|
|
82
86
|
if (geometry.geometryType.case === 'box') {
|
|
83
87
|
return Box(createBox(geometry.geometryType.value));
|
|
@@ -71,6 +71,11 @@ class BinaryReader {
|
|
|
71
71
|
this.offsetBytes += 4;
|
|
72
72
|
return v;
|
|
73
73
|
}
|
|
74
|
+
readU32() {
|
|
75
|
+
const v = this.view.getUint32(this.offsetBytes, this.littleEndian);
|
|
76
|
+
this.offsetBytes += 4;
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
74
79
|
/**
|
|
75
80
|
* Get a Float32Array VIEW into the underlying buffer (no copy) and advance.
|
|
76
81
|
* Requires current offset to be 4-byte aligned (it will be, if you only readF32 so far).
|
|
@@ -144,7 +149,7 @@ export const provideDrawAPI = () => {
|
|
|
144
149
|
if (frame.geometry) {
|
|
145
150
|
entityTraits.push(geometryTrait());
|
|
146
151
|
}
|
|
147
|
-
entityTraits.push(traits.Name(name), traits.Pose(pose), traits.DrawAPI, traits.ReferenceFrame);
|
|
152
|
+
entityTraits.push(traits.Name(name), traits.Pose(pose), traits.DrawAPI, traits.ReferenceFrame, traits.Removable);
|
|
148
153
|
const entity = world.spawn(...entityTraits);
|
|
149
154
|
entities.set(name, entity);
|
|
150
155
|
}
|
|
@@ -178,7 +183,7 @@ export const provideDrawAPI = () => {
|
|
|
178
183
|
if (parent && parent !== 'world') {
|
|
179
184
|
entityTraits.push(traits.Parent(parent));
|
|
180
185
|
}
|
|
181
|
-
entityTraits.push(traits.Name(data.label ?? ++geometryIndex), traits.Pose(pose), traits.Color(colorUtil.set(color)), geometryTrait(), traits.DrawAPI);
|
|
186
|
+
entityTraits.push(traits.Name(data.label ?? ++geometryIndex), traits.Pose(pose), traits.Color(colorUtil.set(color)), geometryTrait(), traits.DrawAPI, traits.Removable);
|
|
182
187
|
const entity = world.spawn(...entityTraits);
|
|
183
188
|
entities.set(name, entity);
|
|
184
189
|
};
|
|
@@ -201,7 +206,7 @@ export const provideDrawAPI = () => {
|
|
|
201
206
|
existing.set(traits.LinePositions, points);
|
|
202
207
|
return;
|
|
203
208
|
}
|
|
204
|
-
const entity = world.spawn(traits.Name(name), traits.Color(colorUtil.set(color)), traits.LinePositions(points), traits.DrawAPI);
|
|
209
|
+
const entity = world.spawn(traits.Name(name), traits.Color(colorUtil.set(color)), traits.LinePositions(points), traits.DrawAPI, traits.Removable);
|
|
205
210
|
entities.set(name, entity);
|
|
206
211
|
};
|
|
207
212
|
const vec3 = new Vector3();
|
|
@@ -211,7 +216,7 @@ export const provideDrawAPI = () => {
|
|
|
211
216
|
const nColors = reader.read();
|
|
212
217
|
const arrowHeadAtPose = reader.read();
|
|
213
218
|
const entities = [];
|
|
214
|
-
const entity = world.spawn(traits.Name(`Arrow group ${++poseIndex}`), traits.Positions(reader.readF32Array(nPoints * STRIDE.ARROWS)), traits.Colors(reader.readU8Array(nColors * STRIDE.COLORS_RGB)), traits.Arrows({ headAtPose: arrowHeadAtPose === 1 }), traits.DrawAPI);
|
|
219
|
+
const entity = world.spawn(traits.Name(`Arrow group ${++poseIndex}`), traits.Positions(reader.readF32Array(nPoints * STRIDE.ARROWS)), traits.Colors(reader.readU8Array(nColors * STRIDE.COLORS_RGB)), traits.Arrows({ headAtPose: arrowHeadAtPose === 1 }), traits.DrawAPI, traits.Removable);
|
|
215
220
|
entities.push(entity);
|
|
216
221
|
};
|
|
217
222
|
const drawPoints = async (reader) => {
|
|
@@ -222,24 +227,32 @@ export const provideDrawAPI = () => {
|
|
|
222
227
|
label += String.fromCharCode(reader.read());
|
|
223
228
|
}
|
|
224
229
|
// Read counts
|
|
225
|
-
const nPoints = reader.
|
|
226
|
-
const nColors = reader.
|
|
230
|
+
const nPoints = reader.readU32();
|
|
231
|
+
const nColors = reader.readU32();
|
|
227
232
|
// Read default color
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
233
|
+
let r = reader.read();
|
|
234
|
+
let g = reader.read();
|
|
235
|
+
let b = reader.read();
|
|
231
236
|
const nPointsElements = nPoints * 3;
|
|
232
237
|
const positions = reader.readF32Array(nPointsElements);
|
|
233
238
|
const nColorsElements = nColors * 3;
|
|
234
|
-
const rawColors = reader.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
const rawColors = reader.readU8Array(nColorsElements);
|
|
240
|
+
let colors = null;
|
|
241
|
+
if (nColors > 1) {
|
|
242
|
+
colors = new Uint8Array(nPointsElements);
|
|
243
|
+
colors.set(rawColors);
|
|
244
|
+
// Cover the gap for any points not colored
|
|
245
|
+
for (let i = nColors; i < nPoints; i++) {
|
|
246
|
+
const offset = i * 3;
|
|
247
|
+
colors[offset] = Math.round(r * 255);
|
|
248
|
+
colors[offset + 1] = Math.round(g * 255);
|
|
249
|
+
colors[offset + 2] = Math.round(b * 255);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (nColors === 1) {
|
|
253
|
+
r = rawColors[0] / 255;
|
|
254
|
+
g = rawColors[1] / 255;
|
|
255
|
+
b = rawColors[2] / 255;
|
|
243
256
|
}
|
|
244
257
|
const entities = world.query(traits.DrawAPI);
|
|
245
258
|
const entity = entities.find((entity) => entity.get(traits.Name) === label);
|
|
@@ -251,7 +264,7 @@ export const provideDrawAPI = () => {
|
|
|
251
264
|
}
|
|
252
265
|
}
|
|
253
266
|
const geometry = createBufferGeometry(positions, colors);
|
|
254
|
-
world.spawn(traits.Name(label), traits.Color(colorUtil.set(r, g, b)), traits.BufferGeometry(geometry), traits.Points, traits.DrawAPI);
|
|
267
|
+
world.spawn(traits.Name(label), traits.Color(colorUtil.set(r, g, b)), traits.BufferGeometry(geometry), traits.Points, traits.DrawAPI, traits.Removable);
|
|
255
268
|
};
|
|
256
269
|
const drawLine = async (reader) => {
|
|
257
270
|
// Read label length
|
|
@@ -279,7 +292,7 @@ export const provideDrawAPI = () => {
|
|
|
279
292
|
points[i + 1] = reader.read();
|
|
280
293
|
points[i + 2] = reader.read();
|
|
281
294
|
}
|
|
282
|
-
world.spawn(traits.Name(label), traits.Color({ r, g, b }), traits.LinePositions(points), traits.PointColor({ r: dotR, g: dotG, b: dotB }), traits.DrawAPI);
|
|
295
|
+
world.spawn(traits.Name(label), traits.Color({ r, g, b }), traits.LinePositions(points), traits.PointColor({ r: dotR, g: dotG, b: dotB }), traits.DrawAPI, traits.Removable);
|
|
283
296
|
};
|
|
284
297
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
298
|
const drawGeometries = (geometries, colors, parent) => {
|
|
@@ -293,14 +306,16 @@ export const provideDrawAPI = () => {
|
|
|
293
306
|
const blob = new Blob([buffer], { type: 'model/gltf-binary' });
|
|
294
307
|
const url = URL.createObjectURL(blob);
|
|
295
308
|
const gltf = await loader.loadAsync(url);
|
|
296
|
-
world.spawn(traits.Name(gltf.scene.name), traits.GLTF({ source: { gltf }, animationName: '' }), traits.DrawAPI);
|
|
309
|
+
world.spawn(traits.Name(gltf.scene.name), traits.GLTF({ source: { gltf }, animationName: '' }), traits.DrawAPI, traits.Removable);
|
|
297
310
|
URL.revokeObjectURL(url);
|
|
298
311
|
};
|
|
299
312
|
const remove = (names) => {
|
|
300
313
|
for (const name of names) {
|
|
301
314
|
for (const entity of world.query(traits.DrawAPI)) {
|
|
302
315
|
if (entity.get(traits.Name) === name) {
|
|
303
|
-
|
|
316
|
+
if (world.has(entity)) {
|
|
317
|
+
entity.destroy();
|
|
318
|
+
}
|
|
304
319
|
entities.delete(name);
|
|
305
320
|
}
|
|
306
321
|
}
|
|
@@ -308,7 +323,9 @@ export const provideDrawAPI = () => {
|
|
|
308
323
|
};
|
|
309
324
|
const removeAll = () => {
|
|
310
325
|
for (const entity of world.query(traits.DrawAPI)) {
|
|
311
|
-
|
|
326
|
+
if (world.has(entity)) {
|
|
327
|
+
entity.destroy();
|
|
328
|
+
}
|
|
312
329
|
}
|
|
313
330
|
entities.clear();
|
|
314
331
|
geometryIndex = 0;
|
|
@@ -22,11 +22,6 @@ export const provideFrames = (partID) => {
|
|
|
22
22
|
}));
|
|
23
23
|
const revision = $derived(machineStatus.current?.config?.revision);
|
|
24
24
|
const partConfig = usePartConfig();
|
|
25
|
-
$effect(() => {
|
|
26
|
-
if (revision) {
|
|
27
|
-
untrack(() => query.refetch());
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
25
|
$effect(() => {
|
|
31
26
|
if (query.isFetching) {
|
|
32
27
|
logs.add('Fetching frames...');
|
|
@@ -118,15 +113,24 @@ export const provideFrames = (partID) => {
|
|
|
118
113
|
const current = $derived(Object.values(frames));
|
|
119
114
|
const entities = new Map();
|
|
120
115
|
$effect.pre(() => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
116
|
+
if (revision) {
|
|
117
|
+
untrack(async () => {
|
|
118
|
+
await query.refetch();
|
|
119
|
+
for (const [name, machineFrame] of Object.entries(machineFrames)) {
|
|
120
|
+
if (machineFrame === undefined) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const existing = entities.get(name);
|
|
124
|
+
if (existing) {
|
|
125
|
+
const pose = createPose(machineFrame.transform.poseInObserverFrame?.pose);
|
|
126
|
+
existing.set(traits.Pose, pose);
|
|
127
|
+
if (environment.current.viewerMode === 'monitor') {
|
|
128
|
+
// if we are in monitor mode, we want the network pose to overwrite any leftover edited poses
|
|
129
|
+
existing.set(traits.EditedPose, pose);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
130
134
|
}
|
|
131
135
|
});
|
|
132
136
|
$effect.pre(() => {
|
|
@@ -161,7 +161,9 @@ export const providePointcloudObjects = (partID) => {
|
|
|
161
161
|
// Clean up old entities
|
|
162
162
|
for (const [label, entity] of entities) {
|
|
163
163
|
if (!active[label]) {
|
|
164
|
-
|
|
164
|
+
if (world.has(entity)) {
|
|
165
|
+
entity.destroy();
|
|
166
|
+
}
|
|
165
167
|
entities.delete(label);
|
|
166
168
|
}
|
|
167
169
|
}
|
|
@@ -117,7 +117,9 @@ export const providePointclouds = (partID) => {
|
|
|
117
117
|
// Clean up old entities
|
|
118
118
|
for (const [name, entity] of entities) {
|
|
119
119
|
if (!queryMap[name]?.data) {
|
|
120
|
-
|
|
120
|
+
if (world.has(entity)) {
|
|
121
|
+
entity.destroy();
|
|
122
|
+
}
|
|
121
123
|
entities.delete(name);
|
|
122
124
|
}
|
|
123
125
|
}
|
|
@@ -10,7 +10,7 @@ import { RefetchRates } from '../components/RefreshRate.svelte';
|
|
|
10
10
|
import { useLogs } from './useLogs.svelte';
|
|
11
11
|
import { useResourceByName } from './useResourceByName.svelte';
|
|
12
12
|
import { useRefetchPoses } from './useRefetchPoses';
|
|
13
|
-
const origingFrameComponentTypes = ['arm', 'gantry', 'gripper'];
|
|
13
|
+
const origingFrameComponentTypes = ['arm', 'gantry', 'gripper', 'base'];
|
|
14
14
|
export const usePose = (name, parent) => {
|
|
15
15
|
const environment = useEnvironment();
|
|
16
16
|
const logs = useLogs();
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getContext, setContext } from 'svelte';
|
|
1
|
+
import { useThrelte } from '@threlte/core';
|
|
2
|
+
import { getContext, setContext, untrack } from 'svelte';
|
|
3
3
|
import { Object3D } from 'three';
|
|
4
4
|
import { traits, useWorld } from '../ecs';
|
|
5
|
+
import { useEnvironment } from './useEnvironment.svelte';
|
|
5
6
|
const selectedKey = Symbol('selected-frame-context');
|
|
6
7
|
const focusedKey = Symbol('focused-frame-context');
|
|
7
8
|
const focusedObject3dKey = Symbol('focused-object-3d-context');
|
|
8
9
|
export const provideSelection = () => {
|
|
9
10
|
const world = useWorld();
|
|
10
11
|
const { scene } = useThrelte();
|
|
12
|
+
const environment = useEnvironment();
|
|
11
13
|
let selected = $state.raw();
|
|
12
14
|
let selectedInstance = $state();
|
|
13
15
|
let focused = $state.raw();
|
|
@@ -46,18 +48,15 @@ export const provideSelection = () => {
|
|
|
46
48
|
},
|
|
47
49
|
};
|
|
48
50
|
setContext(focusedKey, focusedEntityContext);
|
|
49
|
-
const focusedObject3d = $derived.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const focusedObject3d = $derived(focused ? scene.getObjectByName(focused)?.clone() : undefined);
|
|
52
|
+
$effect(() => {
|
|
53
|
+
const previousMode = untrack(() => environment.current.viewerMode);
|
|
54
|
+
if (focusedObject3d) {
|
|
55
|
+
environment.current.viewerMode = 'focus';
|
|
56
|
+
return () => {
|
|
57
|
+
environment.current.viewerMode = previousMode;
|
|
58
|
+
};
|
|
53
59
|
}
|
|
54
|
-
const object = scene.getObjectByName(name)?.clone();
|
|
55
|
-
object?.traverse((child) => {
|
|
56
|
-
if (isInstanceOf(child, 'LineSegments')) {
|
|
57
|
-
child.raycast = () => null;
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
return object;
|
|
61
60
|
});
|
|
62
61
|
setContext(focusedObject3dKey, {
|
|
63
62
|
get current() {
|
|
@@ -71,7 +71,9 @@ const createWorldState = (client) => {
|
|
|
71
71
|
const entity = entities.get(uuid);
|
|
72
72
|
if (!entity)
|
|
73
73
|
return;
|
|
74
|
-
|
|
74
|
+
if (world.has(entity)) {
|
|
75
|
+
entity.destroy();
|
|
76
|
+
}
|
|
75
77
|
entities.delete(uuid);
|
|
76
78
|
};
|
|
77
79
|
const updateEntity = (transform, changes) => {
|
|
@@ -202,7 +204,9 @@ const createWorldState = (client) => {
|
|
|
202
204
|
});
|
|
203
205
|
return () => {
|
|
204
206
|
for (const [, entity] of entities) {
|
|
205
|
-
|
|
207
|
+
if (world.has(entity)) {
|
|
208
|
+
entity.destroy();
|
|
209
|
+
}
|
|
206
210
|
}
|
|
207
211
|
};
|
|
208
212
|
};
|
|
@@ -15,7 +15,13 @@ self.onmessage = async (event) => {
|
|
|
15
15
|
*/
|
|
16
16
|
const positions = pcd.geometry.attributes.position?.array ??
|
|
17
17
|
new Float32Array(0);
|
|
18
|
-
const
|
|
18
|
+
const colorsFloat = pcd.geometry.attributes.color?.array ?? null;
|
|
19
|
+
const colors = colorsFloat ? new Uint8Array(colorsFloat.length) : null;
|
|
20
|
+
if (colors) {
|
|
21
|
+
for (let i = 0, l = colorsFloat.length; i < l; i++) {
|
|
22
|
+
colors[i] = Math.round(colorsFloat[i] * 255);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
19
25
|
postMessage({ positions, colors, id }, colors ? [positions.buffer, colors.buffer] : [positions.buffer]);
|
|
20
26
|
}
|
|
21
27
|
else {
|
package/dist/snapshot.js
CHANGED
|
@@ -83,6 +83,7 @@ const spawnTransformEntity = (world, transform) => {
|
|
|
83
83
|
traits.Geometry(transform.physicalObject ?? Geometry.fromJson({})),
|
|
84
84
|
traits.Center(transform.physicalObject?.center),
|
|
85
85
|
traits.SnapshotAPI,
|
|
86
|
+
traits.Removable,
|
|
86
87
|
];
|
|
87
88
|
const poseInFrame = transform.poseInObserverFrame;
|
|
88
89
|
entityTraits.push(traits.Pose(poseInFrame?.pose));
|
|
@@ -117,7 +118,7 @@ const spawnEntitiesFromDrawing = (world, drawing) => {
|
|
|
117
118
|
if (colors) {
|
|
118
119
|
entityTraits.push(traits.Colors(colors));
|
|
119
120
|
}
|
|
120
|
-
const entity = world.spawn(...entityTraits, traits.
|
|
121
|
+
const entity = world.spawn(...entityTraits, traits.Arrows({ headAtPose: true }), traits.Instances({ count: poses.length / STRIDE.ARROWS }), traits.SnapshotAPI, traits.Removable);
|
|
121
122
|
entities.push(entity);
|
|
122
123
|
}
|
|
123
124
|
else if (geometryType?.case === 'model') {
|
|
@@ -129,7 +130,7 @@ const spawnEntitiesFromDrawing = (world, drawing) => {
|
|
|
129
130
|
if (parent) {
|
|
130
131
|
rootEntityTraits.push(traits.Parent(parent));
|
|
131
132
|
}
|
|
132
|
-
const rootEntity = world.spawn(...rootEntityTraits, traits.SnapshotAPI);
|
|
133
|
+
const rootEntity = world.spawn(...rootEntityTraits, traits.SnapshotAPI, traits.Removable);
|
|
133
134
|
entities.push(rootEntity);
|
|
134
135
|
let i = 1;
|
|
135
136
|
for (const asset of geometryType.value.assets) {
|
|
@@ -152,7 +153,7 @@ const spawnEntitiesFromDrawing = (world, drawing) => {
|
|
|
152
153
|
animationName: geometryType.value.animationName ?? '',
|
|
153
154
|
}));
|
|
154
155
|
}
|
|
155
|
-
const entity = world.spawn(...entityTraits, traits.SnapshotAPI);
|
|
156
|
+
const entity = world.spawn(...entityTraits, traits.SnapshotAPI, traits.Removable);
|
|
156
157
|
entities.push(entity);
|
|
157
158
|
}
|
|
158
159
|
}
|
|
@@ -191,9 +192,7 @@ const spawnEntitiesFromDrawing = (world, drawing) => {
|
|
|
191
192
|
for (let i = 0, l = positions.length; i < l; i += 1) {
|
|
192
193
|
positions[i] *= 0.001;
|
|
193
194
|
}
|
|
194
|
-
const colors = drawing.metadata?.colors
|
|
195
|
-
? rgbaBytesToFloat32(drawing.metadata.colors)
|
|
196
|
-
: undefined;
|
|
195
|
+
const colors = drawing.metadata?.colors;
|
|
197
196
|
const geometry = createBufferGeometry(positions, colors);
|
|
198
197
|
entityTraits.push(traits.BufferGeometry(geometry));
|
|
199
198
|
if (geometryType.value.pointSize) {
|
|
@@ -227,7 +226,7 @@ const spawnEntitiesFromDrawing = (world, drawing) => {
|
|
|
227
226
|
}
|
|
228
227
|
entityTraits.push(traits.LinePositions(points));
|
|
229
228
|
}
|
|
230
|
-
const entity = world.spawn(...entityTraits, traits.SnapshotAPI);
|
|
229
|
+
const entity = world.spawn(...entityTraits, traits.SnapshotAPI, traits.Removable);
|
|
231
230
|
entities.push(entity);
|
|
232
231
|
}
|
|
233
232
|
return entities;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RawShaderMaterial, FrontSide, Group, InstancedBufferAttribute, DynamicDrawUsage, Mesh, BufferGeometry, InstancedInterleavedBuffer, InterleavedBufferAttribute, Material, Color, Vector3, Box3, } from 'three';
|
|
2
|
-
import vertexShader from './vertex.glsl
|
|
3
|
-
import fragmentShader from './fragment.glsl
|
|
2
|
+
import vertexShader from './vertex.glsl';
|
|
3
|
+
import fragmentShader from './fragment.glsl';
|
|
4
4
|
import { createHeadGeometry, createShaftGeometry, toInstanced } from './geometry';
|
|
5
5
|
import { computeBoundingBox } from './box';
|
|
6
6
|
const defaults = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@skeletonlabs/skeleton": "3.2.0",
|
|
18
18
|
"@skeletonlabs/skeleton-svelte": "1.5.1",
|
|
19
19
|
"@sveltejs/adapter-static": "3.0.9",
|
|
20
|
-
"@sveltejs/kit": "2.
|
|
20
|
+
"@sveltejs/kit": "2.49.5",
|
|
21
21
|
"@sveltejs/package": "2.5.0",
|
|
22
22
|
"@sveltejs/vite-plugin-svelte": "6.1.4",
|
|
23
23
|
"@tailwindcss/forms": "0.5.10",
|
|
@@ -68,10 +68,14 @@
|
|
|
68
68
|
"typescript-eslint": "8.42.0",
|
|
69
69
|
"vite": "7.1.11",
|
|
70
70
|
"vite-plugin-devtools-json": "1.0.0",
|
|
71
|
+
"vite-plugin-glsl": "^1.5.5",
|
|
71
72
|
"vite-plugin-mkcert": "1.17.8",
|
|
72
73
|
"vitest": "3.2.4"
|
|
73
74
|
},
|
|
74
75
|
"peerDependencies": {
|
|
76
|
+
"@ag-grid-community/client-side-row-model": ">=32.3.0",
|
|
77
|
+
"@ag-grid-community/core": ">=32.3.0",
|
|
78
|
+
"@ag-grid-community/styles": ">=32.3.0",
|
|
75
79
|
"@dimforge/rapier3d-compat": ">=0.17",
|
|
76
80
|
"@threlte/core": ">=8",
|
|
77
81
|
"@threlte/extras": ">=9",
|
|
@@ -81,6 +85,7 @@
|
|
|
81
85
|
"@viamrobotics/sdk": ">=0.38",
|
|
82
86
|
"@viamrobotics/svelte-sdk": ">=0.1",
|
|
83
87
|
"@zag-js/collapsible": ">=1",
|
|
88
|
+
"@zag-js/dialog": ">=1.31",
|
|
84
89
|
"@zag-js/floating-panel": ">=1",
|
|
85
90
|
"@zag-js/svelte": ">=1",
|
|
86
91
|
"@zag-js/tree-view": ">=1",
|
|
@@ -89,11 +94,7 @@
|
|
|
89
94
|
"lucide-svelte": ">=0.511",
|
|
90
95
|
"runed": ">=0.28",
|
|
91
96
|
"svelte": ">=5",
|
|
92
|
-
"svelte-virtuallists": ">=1"
|
|
93
|
-
"@ag-grid-community/client-side-row-model": ">=32.3.0",
|
|
94
|
-
"@ag-grid-community/core": ">=32.3.0",
|
|
95
|
-
"@ag-grid-community/styles": ">=32.3.0",
|
|
96
|
-
"@zag-js/dialog": ">=1.31"
|
|
97
|
+
"svelte-virtuallists": ">=1"
|
|
97
98
|
},
|
|
98
99
|
"engines": {
|
|
99
100
|
"node": ">=22.12.0"
|