@viamrobotics/motion-tools 1.13.1 → 1.14.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.js +10 -2
- package/dist/buffer.d.ts +56 -7
- package/dist/buffer.js +70 -12
- package/dist/components/Entities/Entities.svelte +3 -3
- package/dist/components/Entities/Frame.svelte +42 -45
- package/dist/components/Entities/Frame.svelte.d.ts +1 -0
- package/dist/components/Entities/Geometry.svelte +43 -174
- package/dist/components/Entities/Geometry.svelte.d.ts +5 -14
- package/dist/components/Entities/Line.svelte +64 -17
- package/dist/components/Entities/Mesh.svelte +130 -0
- package/dist/components/Entities/Mesh.svelte.d.ts +4 -0
- package/dist/components/Selected.svelte +2 -0
- package/dist/components/overlay/left-pane/buildTree.js +15 -0
- package/dist/ecs/traits.d.ts +2 -19
- package/dist/ecs/traits.js +33 -6
- package/dist/hooks/use3DModels.svelte.js +1 -3
- package/dist/hooks/useFrames.svelte.js +12 -13
- package/dist/hooks/useGeometries.svelte.js +59 -36
- package/dist/hooks/usePointcloudObjects.svelte.js +103 -58
- package/dist/hooks/usePointclouds.svelte.js +48 -28
- package/dist/hooks/useWorldState.svelte.js +25 -25
- package/dist/metadata.d.ts +22 -0
- package/dist/metadata.js +66 -0
- package/dist/snapshot.d.ts +20 -0
- package/dist/snapshot.js +65 -23
- package/dist/three/OBBHelper.d.ts +3 -2
- package/dist/three/OBBHelper.js +17 -5
- package/package.json +1 -1
- package/dist/WorldObject.svelte.d.ts +0 -27
- package/dist/WorldObject.svelte.js +0 -127
|
@@ -5,12 +5,17 @@
|
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import Frame from './Frame.svelte'
|
|
9
8
|
import type { Snippet } from 'svelte'
|
|
9
|
+
import { T, useThrelte } from '@threlte/core'
|
|
10
|
+
import { meshBounds, Portal, PortalTarget } from '@threlte/extras'
|
|
10
11
|
import type { Entity } from 'koota'
|
|
11
12
|
import { traits, useTrait } from '../../ecs'
|
|
12
13
|
import LineDots from './LineDots.svelte'
|
|
13
14
|
import { darkenColor } from '../../color'
|
|
15
|
+
import { useEntityEvents } from './hooks/useEntityEvents.svelte'
|
|
16
|
+
import { Line2, LineMaterial } from 'three/examples/jsm/Addons.js'
|
|
17
|
+
import LineGeometry from './LineGeometry.svelte'
|
|
18
|
+
import { poseToObject3d } from '../../transform'
|
|
14
19
|
|
|
15
20
|
interface Props {
|
|
16
21
|
entity: Entity
|
|
@@ -19,25 +24,67 @@
|
|
|
19
24
|
|
|
20
25
|
let { entity, children }: Props = $props()
|
|
21
26
|
|
|
22
|
-
const
|
|
27
|
+
const { invalidate } = useThrelte()
|
|
28
|
+
const name = useTrait(() => entity, traits.Name)
|
|
29
|
+
const parent = useTrait(() => entity, traits.Parent)
|
|
30
|
+
const pose = useTrait(() => entity, traits.Pose)
|
|
23
31
|
const color = useTrait(() => entity, traits.Color)
|
|
24
32
|
const pointSize = useTrait(() => entity, traits.PointSize)
|
|
33
|
+
const linePositions = useTrait(() => entity, traits.LinePositions)
|
|
34
|
+
const lineWidth = useTrait(() => entity, traits.LineWidth)
|
|
35
|
+
const opacity = useTrait(() => entity, traits.Opacity)
|
|
36
|
+
const materialProps = useTrait(() => entity, traits.Material)
|
|
37
|
+
const renderOrder = useTrait(() => entity, traits.RenderOrder)
|
|
38
|
+
|
|
39
|
+
const events = useEntityEvents(() => entity)
|
|
40
|
+
|
|
41
|
+
const currentOpacity = $derived(opacity.current ?? 0.7)
|
|
25
42
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
const mesh = new Line2()
|
|
44
|
+
|
|
45
|
+
$effect.pre(() => {
|
|
46
|
+
if (pose.current) {
|
|
47
|
+
poseToObject3d(pose.current, mesh)
|
|
48
|
+
invalidate()
|
|
49
|
+
}
|
|
50
|
+
})
|
|
31
51
|
</script>
|
|
32
52
|
|
|
33
|
-
<
|
|
53
|
+
<Portal id={parent.current}>
|
|
54
|
+
<T
|
|
55
|
+
is={mesh}
|
|
56
|
+
name={entity}
|
|
57
|
+
userData.name={name}
|
|
58
|
+
raycast={meshBounds}
|
|
59
|
+
renderOrder={renderOrder.current}
|
|
60
|
+
{...events}
|
|
61
|
+
>
|
|
62
|
+
<LineGeometry positions={linePositions.current} />
|
|
63
|
+
<T
|
|
64
|
+
is={LineMaterial}
|
|
65
|
+
color={[color.current?.r ?? 1, color.current?.g ?? 0, color.current?.b ?? 0]}
|
|
66
|
+
transparent={currentOpacity < 1}
|
|
67
|
+
depthWrite={currentOpacity === 1}
|
|
68
|
+
opacity={currentOpacity}
|
|
69
|
+
width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
|
|
70
|
+
depthTest={materialProps.current?.depthTest ?? true}
|
|
71
|
+
/>
|
|
72
|
+
</T>
|
|
73
|
+
|
|
74
|
+
{#if linePositions.current && pointSize.current}
|
|
75
|
+
<LineDots
|
|
76
|
+
color={darkenColor(
|
|
77
|
+
colorUtil.setRGB(color.current?.r ?? 1, color.current?.g ?? 0, color.current?.b ?? 0),
|
|
78
|
+
10
|
|
79
|
+
)}
|
|
80
|
+
positions={linePositions.current}
|
|
81
|
+
scale={pointSize.current * 0.001}
|
|
82
|
+
/>
|
|
83
|
+
{/if}
|
|
84
|
+
|
|
85
|
+
{#if name.current}
|
|
86
|
+
<PortalTarget id={name.current} />
|
|
87
|
+
{/if}
|
|
88
|
+
|
|
34
89
|
{@render children?.()}
|
|
35
|
-
</
|
|
36
|
-
|
|
37
|
-
{#if linePositions.current && pointSize.current}
|
|
38
|
-
<LineDots
|
|
39
|
-
color={darkenColor(resolvedColor, 10)}
|
|
40
|
-
positions={linePositions.current}
|
|
41
|
-
scale={pointSize.current * 0.001}
|
|
42
|
-
/>
|
|
43
|
-
{/if}
|
|
90
|
+
</Portal>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { T, useThrelte, type Props as ThrelteProps } from '@threlte/core'
|
|
3
|
+
import { type Snippet } from 'svelte'
|
|
4
|
+
import { BufferGeometry, Color, DoubleSide, FrontSide, Mesh } from 'three'
|
|
5
|
+
import { CapsuleGeometry } from '../../three/CapsuleGeometry'
|
|
6
|
+
import { colors, darkenColor } from '../../color'
|
|
7
|
+
import AxesHelper from '../AxesHelper.svelte'
|
|
8
|
+
import type { Entity } from 'koota'
|
|
9
|
+
import { traits, useTrait } from '../../ecs'
|
|
10
|
+
import { poseToObject3d } from '../../transform'
|
|
11
|
+
import type { Pose } from '@viamrobotics/sdk'
|
|
12
|
+
|
|
13
|
+
interface Props extends ThrelteProps<Mesh> {
|
|
14
|
+
entity: Entity
|
|
15
|
+
color?: string
|
|
16
|
+
center?: Pose
|
|
17
|
+
children?: Snippet
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { entity, color: overrideColor, center, children, ...rest }: Props = $props()
|
|
21
|
+
|
|
22
|
+
const colorUtil = new Color()
|
|
23
|
+
|
|
24
|
+
const { invalidate } = useThrelte()
|
|
25
|
+
const name = useTrait(() => entity, traits.Name)
|
|
26
|
+
const entityColor = useTrait(() => entity, traits.Color)
|
|
27
|
+
const opacity = useTrait(() => entity, traits.Opacity)
|
|
28
|
+
const box = useTrait(() => entity, traits.Box)
|
|
29
|
+
const capsule = useTrait(() => entity, traits.Capsule)
|
|
30
|
+
const sphere = useTrait(() => entity, traits.Sphere)
|
|
31
|
+
const bufferGeometry = useTrait(() => entity, traits.BufferGeometry)
|
|
32
|
+
const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
|
|
33
|
+
const materialProps = useTrait(() => entity, traits.Material)
|
|
34
|
+
const renderOrder = useTrait(() => entity, traits.RenderOrder)
|
|
35
|
+
|
|
36
|
+
const color = $derived.by(() => {
|
|
37
|
+
if (overrideColor) {
|
|
38
|
+
return overrideColor
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (entityColor.current) {
|
|
42
|
+
return colorUtil.setRGB(entityColor.current.r, entityColor.current.g, entityColor.current.b)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return colors.default
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const mesh = new Mesh()
|
|
49
|
+
|
|
50
|
+
$effect.pre(() => {
|
|
51
|
+
if (center) {
|
|
52
|
+
poseToObject3d(center, mesh)
|
|
53
|
+
invalidate()
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
let geo = $state.raw<BufferGeometry>()
|
|
58
|
+
|
|
59
|
+
const oncreate = (bufferGeometry: BufferGeometry) => {
|
|
60
|
+
geo = bufferGeometry
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<T
|
|
65
|
+
is={mesh}
|
|
66
|
+
name={entity}
|
|
67
|
+
userData.name={name}
|
|
68
|
+
renderOrder={renderOrder.current}
|
|
69
|
+
{...rest}
|
|
70
|
+
>
|
|
71
|
+
{#if box.current}
|
|
72
|
+
{@const { x, y, z } = box.current ?? { x: 0, y: 0, z: 0 }}
|
|
73
|
+
<T.BoxGeometry
|
|
74
|
+
args={[x * 0.001, y * 0.001, z * 0.001]}
|
|
75
|
+
{oncreate}
|
|
76
|
+
/>
|
|
77
|
+
{:else if sphere.current}
|
|
78
|
+
{@const { r } = sphere.current ?? { r: 0 }}
|
|
79
|
+
<T.SphereGeometry
|
|
80
|
+
args={[r * 0.001]}
|
|
81
|
+
{oncreate}
|
|
82
|
+
/>
|
|
83
|
+
{:else if capsule.current}
|
|
84
|
+
{@const { r, l } = capsule.current ?? { r: 0, l: 0 }}
|
|
85
|
+
<T
|
|
86
|
+
is={CapsuleGeometry}
|
|
87
|
+
args={[r * 0.001, l * 0.001]}
|
|
88
|
+
{oncreate}
|
|
89
|
+
/>
|
|
90
|
+
{:else if bufferGeometry.current}
|
|
91
|
+
<T
|
|
92
|
+
is={bufferGeometry.current}
|
|
93
|
+
{oncreate}
|
|
94
|
+
/>
|
|
95
|
+
{/if}
|
|
96
|
+
|
|
97
|
+
{@const currentOpacity = opacity.current ?? 0.7}
|
|
98
|
+
<T.MeshToonMaterial
|
|
99
|
+
{color}
|
|
100
|
+
side={bufferGeometry.current ? DoubleSide : FrontSide}
|
|
101
|
+
transparent={currentOpacity < 1}
|
|
102
|
+
depthWrite={currentOpacity === 1}
|
|
103
|
+
opacity={currentOpacity}
|
|
104
|
+
depthTest={materialProps.current?.depthTest ?? true}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<!--
|
|
108
|
+
TODO(mp) currently some bufferGeometries are coming in empty,
|
|
109
|
+
this is a quick fix but this should be handled upstream
|
|
110
|
+
-->
|
|
111
|
+
{#if geo && geo.getAttribute('position').array.length > 0}
|
|
112
|
+
<T.LineSegments
|
|
113
|
+
raycast={() => null}
|
|
114
|
+
bvh={{ enabled: false }}
|
|
115
|
+
>
|
|
116
|
+
<T.EdgesGeometry args={[geo, 0]} />
|
|
117
|
+
<T.LineBasicMaterial color={darkenColor(color, 10)} />
|
|
118
|
+
</T.LineSegments>
|
|
119
|
+
{/if}
|
|
120
|
+
|
|
121
|
+
{@render children?.()}
|
|
122
|
+
</T>
|
|
123
|
+
|
|
124
|
+
{#if showAxesHelper.current}
|
|
125
|
+
<AxesHelper
|
|
126
|
+
name={entity}
|
|
127
|
+
width={3}
|
|
128
|
+
length={0.1}
|
|
129
|
+
/>
|
|
130
|
+
{/if}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { traits } from '../../../ecs';
|
|
2
|
+
function sortNodes(nodes) {
|
|
3
|
+
nodes.sort((a, b) => a.entity.get(traits.Name)?.localeCompare(b.entity.get(traits.Name) ?? '') ?? 0);
|
|
4
|
+
}
|
|
2
5
|
/**
|
|
3
6
|
* Creates a tree representing parent child / relationships from a set of frames.
|
|
4
7
|
*/
|
|
@@ -29,5 +32,17 @@ export const buildTreeNodes = (entities) => {
|
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
}
|
|
35
|
+
for (const node of rootNodes) {
|
|
36
|
+
if (!node.children)
|
|
37
|
+
continue;
|
|
38
|
+
sortNodes(node.children);
|
|
39
|
+
}
|
|
40
|
+
for (const node of childNodes) {
|
|
41
|
+
if (!node.children)
|
|
42
|
+
continue;
|
|
43
|
+
sortNodes(node.children);
|
|
44
|
+
}
|
|
45
|
+
sortNodes(rootNodes);
|
|
46
|
+
sortNodes(childNodes);
|
|
32
47
|
return { rootNodes, nodeMap };
|
|
33
48
|
};
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GLTF as ThreeGltf } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
|
+
import { type Entity } from 'koota';
|
|
2
3
|
import { BufferGeometry as ThreeBufferGeometry } from 'three';
|
|
3
4
|
import { Geometry as ViamGeometry } from '@viamrobotics/sdk';
|
|
4
5
|
export declare const Name: import("koota").Trait<() => string>;
|
|
@@ -178,22 +179,4 @@ export declare const Geometry: (geometry: ViamGeometry) => import("koota").Trait
|
|
|
178
179
|
}>, Partial<{
|
|
179
180
|
r: number;
|
|
180
181
|
}>] | [import("koota").Trait<() => ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>>, ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>];
|
|
181
|
-
export declare const
|
|
182
|
-
x: number;
|
|
183
|
-
y: number;
|
|
184
|
-
z: number;
|
|
185
|
-
} | import("koota").Trait<{
|
|
186
|
-
x: number;
|
|
187
|
-
y: number;
|
|
188
|
-
z: number;
|
|
189
|
-
}>)[] | ({
|
|
190
|
-
r: number;
|
|
191
|
-
l: number;
|
|
192
|
-
} | import("koota").Trait<{
|
|
193
|
-
l: number;
|
|
194
|
-
r: number;
|
|
195
|
-
}>)[] | ({
|
|
196
|
-
r: number;
|
|
197
|
-
} | import("koota").Trait<{
|
|
198
|
-
r: number;
|
|
199
|
-
}>)[];
|
|
182
|
+
export declare const updateGeometryTrait: (entity: Entity, geometry?: ViamGeometry) => void;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -126,18 +126,45 @@ export const Geometry = (geometry) => {
|
|
|
126
126
|
}
|
|
127
127
|
return ReferenceFrame;
|
|
128
128
|
};
|
|
129
|
-
export const
|
|
129
|
+
export const updateGeometryTrait = (entity, geometry) => {
|
|
130
|
+
if (!geometry) {
|
|
131
|
+
entity.remove(Box, Capsule, Sphere, BufferGeometry);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
130
134
|
if (geometry.geometryType.case === 'box') {
|
|
131
|
-
|
|
135
|
+
if (entity.has(Box)) {
|
|
136
|
+
entity.set(Box, createBox(geometry.geometryType.value));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
entity.remove(Capsule, Sphere, BufferGeometry);
|
|
140
|
+
entity.add(Box(createBox(geometry.geometryType.value)));
|
|
141
|
+
}
|
|
132
142
|
}
|
|
133
143
|
else if (geometry.geometryType.case === 'capsule') {
|
|
134
|
-
|
|
144
|
+
if (entity.has(Capsule)) {
|
|
145
|
+
entity.set(Capsule, createCapsule(geometry.geometryType.value));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
entity.remove(Box, Sphere, BufferGeometry);
|
|
149
|
+
entity.add(Capsule(createCapsule(geometry.geometryType.value)));
|
|
150
|
+
}
|
|
135
151
|
}
|
|
136
152
|
else if (geometry.geometryType.case === 'sphere') {
|
|
137
|
-
|
|
153
|
+
if (entity.has(Sphere)) {
|
|
154
|
+
entity.set(Sphere, createSphere(geometry.geometryType.value));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
entity.remove(Box, Capsule, BufferGeometry);
|
|
158
|
+
entity.add(Sphere(createSphere(geometry.geometryType.value)));
|
|
159
|
+
}
|
|
138
160
|
}
|
|
139
161
|
else if (geometry.geometryType.case === 'mesh') {
|
|
140
|
-
|
|
162
|
+
if (entity.has(BufferGeometry)) {
|
|
163
|
+
entity.set(BufferGeometry, parsePlyInput(geometry.geometryType.value.mesh));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
entity.remove(Box, Sphere, Capsule);
|
|
167
|
+
entity.add(BufferGeometry(parsePlyInput(geometry.geometryType.value.mesh)));
|
|
168
|
+
}
|
|
141
169
|
}
|
|
142
|
-
return [];
|
|
143
170
|
};
|
|
@@ -57,9 +57,7 @@ export const provide3DModels = (partID) => {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
$effect(() => {
|
|
60
|
-
const shouldFetchModels = settings.current.isLoaded &&
|
|
61
|
-
(settings.current.renderArmModels === 'model' ||
|
|
62
|
-
settings.current.renderArmModels === 'colliders+model');
|
|
60
|
+
const shouldFetchModels = settings.current.isLoaded && settings.current.renderArmModels.includes('model');
|
|
63
61
|
if (shouldFetchModels) {
|
|
64
62
|
fetch3DModels();
|
|
65
63
|
}
|
|
@@ -9,6 +9,7 @@ import { createPose } from '../transform';
|
|
|
9
9
|
import { useResourceByName } from './useResourceByName.svelte';
|
|
10
10
|
import { traits, useWorld } from '../ecs';
|
|
11
11
|
import { useConfigFrames } from './useConfigFrames.svelte';
|
|
12
|
+
import { updateGeometryTrait } from '../ecs/traits';
|
|
12
13
|
const key = Symbol('frames-context');
|
|
13
14
|
export const provideFrames = (partID) => {
|
|
14
15
|
const configFrames = useConfigFrames();
|
|
@@ -69,15 +70,17 @@ export const provideFrames = (partID) => {
|
|
|
69
70
|
}
|
|
70
71
|
});
|
|
71
72
|
$effect.pre(() => {
|
|
72
|
-
if (current.length === 0)
|
|
73
|
-
return;
|
|
74
73
|
const currentResourcesByName = resourceByName.current;
|
|
74
|
+
const currentPartID = partID();
|
|
75
75
|
// We only want to update whenever "current" or "resourceByName.current" changes
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
77
|
+
current.length;
|
|
76
78
|
untrack(() => {
|
|
77
79
|
const active = {};
|
|
78
80
|
for (const frame of current) {
|
|
79
81
|
const name = frame.referenceFrame;
|
|
80
|
-
|
|
82
|
+
const entityKey = `${currentPartID}:${name}`;
|
|
83
|
+
active[entityKey] = true;
|
|
81
84
|
const parent = frame.poseInObserverFrame?.referenceFrame;
|
|
82
85
|
const pose = createPose(frame.poseInObserverFrame?.pose);
|
|
83
86
|
const center = frame.physicalObject?.center
|
|
@@ -85,7 +88,7 @@ export const provideFrames = (partID) => {
|
|
|
85
88
|
: undefined;
|
|
86
89
|
const resourceName = currentResourcesByName[frame.referenceFrame];
|
|
87
90
|
const color = resourceNameToColor(resourceName);
|
|
88
|
-
const existing = entities.get(
|
|
91
|
+
const existing = entities.get(entityKey);
|
|
89
92
|
if (existing) {
|
|
90
93
|
if (!parent || parent === 'world') {
|
|
91
94
|
existing.remove(traits.Parent);
|
|
@@ -102,11 +105,7 @@ export const provideFrames = (partID) => {
|
|
|
102
105
|
if (center) {
|
|
103
106
|
existing.set(traits.Center, center);
|
|
104
107
|
}
|
|
105
|
-
existing
|
|
106
|
-
if (frame.physicalObject) {
|
|
107
|
-
const geometry = traits.Geometry(frame.physicalObject);
|
|
108
|
-
existing.add(geometry);
|
|
109
|
-
}
|
|
108
|
+
updateGeometryTrait(existing, frame.physicalObject);
|
|
110
109
|
existing.set(traits.EditedPose, pose);
|
|
111
110
|
continue;
|
|
112
111
|
}
|
|
@@ -130,13 +129,13 @@ export const provideFrames = (partID) => {
|
|
|
130
129
|
entityTraits.push(traits.Geometry(frame.physicalObject));
|
|
131
130
|
}
|
|
132
131
|
const entity = world.spawn(...entityTraits);
|
|
133
|
-
entities.set(
|
|
132
|
+
entities.set(entityKey, entity);
|
|
134
133
|
}
|
|
135
134
|
// Clean up non-active entities
|
|
136
|
-
for (const [
|
|
137
|
-
if (!active[
|
|
135
|
+
for (const [entityKey, entity] of entities) {
|
|
136
|
+
if (!active[entityKey]) {
|
|
138
137
|
entity?.destroy();
|
|
139
|
-
entities.delete(
|
|
138
|
+
entities.delete(entityKey);
|
|
140
139
|
continue;
|
|
141
140
|
}
|
|
142
141
|
}
|
|
@@ -11,6 +11,7 @@ import {} from 'koota';
|
|
|
11
11
|
import { createPose } from '../transform';
|
|
12
12
|
import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
|
|
13
13
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
14
|
+
import { updateGeometryTrait } from '../ecs/traits';
|
|
14
15
|
const key = Symbol('geometries-context');
|
|
15
16
|
const colorUtil = new Color();
|
|
16
17
|
export const provideGeometries = (partID) => {
|
|
@@ -39,6 +40,7 @@ export const provideGeometries = (partID) => {
|
|
|
39
40
|
const gripperQueries = $derived(gripperClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
40
41
|
const cameraQueries = $derived(cameraClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
41
42
|
const gantryQueries = $derived(gantryClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
43
|
+
const queries = $derived([...armQueries, ...gripperQueries, ...cameraQueries, ...gantryQueries]);
|
|
42
44
|
$effect(() => {
|
|
43
45
|
if (interval === RefetchRates.FPS_30 || interval === RefetchRates.FPS_60) {
|
|
44
46
|
return logs.add(`Fetching geometries every ${interval}ms...`);
|
|
@@ -56,49 +58,70 @@ export const provideGeometries = (partID) => {
|
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
60
|
});
|
|
59
|
-
const queries = $derived([...armQueries, ...gripperQueries, ...cameraQueries, ...gantryQueries]);
|
|
60
61
|
const entities = new Map();
|
|
62
|
+
const queryEntityKeys = new Map();
|
|
61
63
|
$effect(() => {
|
|
62
|
-
const
|
|
64
|
+
const activeQueryKeys = new Set();
|
|
65
|
+
const currentPartID = partID();
|
|
63
66
|
for (const [name, query] of queries) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
67
|
+
if (!name) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const queryKey = `${currentPartID}:${name}`;
|
|
71
|
+
activeQueryKeys.add(queryKey);
|
|
72
|
+
$effect(() => {
|
|
73
|
+
const nextKeys = new Set();
|
|
74
|
+
const resourceName = resources.current[name];
|
|
75
|
+
const subtype = resourceName?.subtype;
|
|
76
|
+
if (query.data) {
|
|
77
|
+
let index = 0;
|
|
78
|
+
for (const geometry of query.data) {
|
|
79
|
+
index += 1;
|
|
80
|
+
const label = geometry.label || `${name} geometry ${index}`;
|
|
81
|
+
const entityKey = `${currentPartID}:${name}:${label}`;
|
|
82
|
+
nextKeys.add(entityKey);
|
|
83
|
+
const center = createPose(geometry.center);
|
|
84
|
+
const existing = entities.get(entityKey);
|
|
85
|
+
if (existing) {
|
|
86
|
+
existing.set(traits.Center, center);
|
|
87
|
+
updateGeometryTrait(existing, geometry);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const entityTraits = [
|
|
91
|
+
traits.Parent(name),
|
|
92
|
+
traits.Name(label),
|
|
93
|
+
traits.Center(center),
|
|
94
|
+
traits.GeometriesAPI,
|
|
95
|
+
traits.Geometry(geometry),
|
|
96
|
+
];
|
|
97
|
+
if (subtype) {
|
|
98
|
+
entityTraits.push(traits.Color(subtype ? colorUtil.set(resourceColors[subtype]) : undefined));
|
|
92
99
|
}
|
|
100
|
+
const entity = world.spawn(...entityTraits);
|
|
101
|
+
entities.set(entityKey, entity);
|
|
93
102
|
}
|
|
94
|
-
}
|
|
103
|
+
}
|
|
104
|
+
const prevKeys = queryEntityKeys.get(queryKey) ?? new Set();
|
|
105
|
+
// Remove entities no longer present for this specific query
|
|
106
|
+
for (const key of prevKeys) {
|
|
107
|
+
if (!nextKeys.has(key)) {
|
|
108
|
+
entities.get(key)?.destroy();
|
|
109
|
+
entities.delete(key);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
queryEntityKeys.set(queryKey, nextKeys);
|
|
95
113
|
});
|
|
96
114
|
}
|
|
97
|
-
// Clean up
|
|
98
|
-
for (const [
|
|
99
|
-
if (!
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
// Clean up owners whose queries disappeared entirely
|
|
116
|
+
for (const [queryKey, keys] of queryEntityKeys) {
|
|
117
|
+
if (!activeQueryKeys.has(queryKey)) {
|
|
118
|
+
for (const key of keys) {
|
|
119
|
+
const entity = entities.get(key);
|
|
120
|
+
if (entity && world.has(entity))
|
|
121
|
+
entity.destroy();
|
|
122
|
+
entities.delete(key);
|
|
123
|
+
}
|
|
124
|
+
queryEntityKeys.delete(queryKey);
|
|
102
125
|
}
|
|
103
126
|
}
|
|
104
127
|
});
|