@viamrobotics/motion-tools 1.25.0 → 1.25.1
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/FrameConfigUpdater.svelte.js +5 -5
- package/dist/components/BatchedArrows.svelte +4 -4
- package/dist/components/Entities/Arrows/Arrows.svelte +2 -2
- package/dist/components/Entities/Frame.svelte +2 -2
- package/dist/components/Entities/GLTF.svelte +2 -2
- package/dist/components/Entities/Geometry.svelte +2 -2
- package/dist/components/Entities/Line.svelte +2 -2
- package/dist/components/Entities/Points.svelte +2 -2
- package/dist/components/Entities/Pose.svelte +2 -2
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Snapshot.svelte +20 -10
- package/dist/components/overlay/Details.svelte +3 -3
- package/dist/components/overlay/__tests__/__fixtures__/entity.js +2 -2
- package/dist/components/overlay/left-pane/buildTree.js +3 -3
- package/dist/draw.js +7 -8
- package/dist/ecs/hierarchy.d.ts +36 -0
- package/dist/ecs/hierarchy.js +80 -0
- package/dist/ecs/index.d.ts +4 -0
- package/dist/ecs/index.js +4 -0
- package/dist/ecs/provideHierarchy.svelte.d.ts +17 -0
- package/dist/ecs/provideHierarchy.svelte.js +31 -0
- package/dist/ecs/relations.d.ts +7 -0
- package/dist/ecs/relations.js +8 -1
- package/dist/ecs/traits.d.ts +9 -4
- package/dist/ecs/traits.js +8 -14
- package/dist/ecs/useParentName.svelte.d.ts +11 -0
- package/dist/ecs/useParentName.svelte.js +21 -0
- package/dist/ecs/useTarget.svelte.d.ts +10 -0
- package/dist/ecs/useTarget.svelte.js +42 -0
- package/dist/editing/FrameEditSession.js +6 -6
- package/dist/hooks/useDrawAPI.svelte.js +4 -4
- package/dist/hooks/useFrames.svelte.js +3 -3
- package/dist/hooks/useGeometries.svelte.js +3 -2
- package/dist/hooks/usePointcloudObjects.svelte.js +3 -2
- package/dist/hooks/usePointclouds.svelte.js +3 -2
- package/dist/snapshot.d.ts +7 -0
- package/dist/snapshot.js +74 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { traits } from './ecs';
|
|
1
|
+
import { hierarchy, traits } from './ecs';
|
|
2
2
|
export class FrameConfigUpdater {
|
|
3
3
|
updateFrame;
|
|
4
4
|
removeFrame;
|
|
@@ -19,7 +19,7 @@ export class FrameConfigUpdater {
|
|
|
19
19
|
change.z = z;
|
|
20
20
|
entity.set(traits.EditedPose, change);
|
|
21
21
|
const name = entity.get(traits.Name);
|
|
22
|
-
const parent =
|
|
22
|
+
const parent = hierarchy.getParentName(entity) ?? 'world';
|
|
23
23
|
const updatedPose = entity.get(traits.EditedPose);
|
|
24
24
|
if (name && updatedPose) {
|
|
25
25
|
this.updateFrame(name, parent, updatedPose);
|
|
@@ -41,7 +41,7 @@ export class FrameConfigUpdater {
|
|
|
41
41
|
change.theta = theta;
|
|
42
42
|
entity.set(traits.EditedPose, change);
|
|
43
43
|
const name = entity.get(traits.Name);
|
|
44
|
-
const parent =
|
|
44
|
+
const parent = hierarchy.getParentName(entity) ?? 'world';
|
|
45
45
|
const updatedPose = entity.get(traits.EditedPose);
|
|
46
46
|
if (name && updatedPose) {
|
|
47
47
|
this.updateFrame(name, parent, updatedPose);
|
|
@@ -49,7 +49,7 @@ export class FrameConfigUpdater {
|
|
|
49
49
|
};
|
|
50
50
|
updateGeometry = (entity, geometry) => {
|
|
51
51
|
const name = entity.get(traits.Name);
|
|
52
|
-
const parent =
|
|
52
|
+
const parent = hierarchy.getParentName(entity) ?? 'world';
|
|
53
53
|
const pose = entity.get(traits.EditedPose);
|
|
54
54
|
if (geometry?.type === 'box') {
|
|
55
55
|
const { x, y, z } = geometry;
|
|
@@ -109,7 +109,7 @@ export class FrameConfigUpdater {
|
|
|
109
109
|
};
|
|
110
110
|
setGeometryType = (entity, type) => {
|
|
111
111
|
const name = entity.get(traits.Name);
|
|
112
|
-
const parent =
|
|
112
|
+
const parent = hierarchy.getParentName(entity) ?? 'world';
|
|
113
113
|
const pose = entity.get(traits.EditedPose);
|
|
114
114
|
if (!name || !pose)
|
|
115
115
|
return;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Portal } from '@threlte/extras'
|
|
6
6
|
import { Color, Vector3 } from 'three'
|
|
7
7
|
|
|
8
|
-
import { traits, useWorld } from '../ecs'
|
|
8
|
+
import { hierarchy, traits, useWorld } from '../ecs'
|
|
9
9
|
import { BatchedArrow } from '../three/BatchedArrow'
|
|
10
10
|
|
|
11
11
|
const arrowBatchMap = $state<Record<string, BatchedArrow>>({
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
const color = new Color()
|
|
21
21
|
|
|
22
22
|
const onAdd = (entity: Entity) => {
|
|
23
|
-
const parent =
|
|
23
|
+
const parent = hierarchy.getParentName(entity) ?? 'world'
|
|
24
24
|
|
|
25
25
|
arrowBatchMap[parent] ??= new BatchedArrow()
|
|
26
26
|
const batched = arrowBatchMap[parent]
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
const onPoseChange = (entity: Entity) => {
|
|
41
41
|
if (!entity.has(traits.Arrow)) return
|
|
42
42
|
|
|
43
|
-
const parent =
|
|
43
|
+
const parent = hierarchy.getParentName(entity) ?? 'world'
|
|
44
44
|
const batch = arrowBatchMap[parent]
|
|
45
45
|
const instanceID = entity.get(traits.Instance)?.instanceID
|
|
46
46
|
const pose = entity.get(traits.Pose)
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
const onColorChange = (entity: Entity) => {
|
|
58
58
|
if (!entity.has(traits.Arrow)) return
|
|
59
59
|
|
|
60
|
-
const parent =
|
|
60
|
+
const parent = hierarchy.getParentName(entity) ?? 'world'
|
|
61
61
|
const batch = arrowBatchMap[parent]
|
|
62
62
|
const instanceID = entity.get(traits.Instance)?.instanceID
|
|
63
63
|
const colorRGB = entity.get(traits.Color)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import AxesHelper from '../../AxesHelper.svelte'
|
|
10
10
|
import { useEntityEvents } from '../hooks/useEntityEvents.svelte'
|
|
11
|
-
import { traits, useTrait } from '../../../ecs'
|
|
11
|
+
import { traits, useParentName, useTrait } from '../../../ecs'
|
|
12
12
|
import { useFocusedEntity, useSelectedEntity } from '../../../hooks/useSelection.svelte'
|
|
13
13
|
import { meshBoundsRaycast, raycast } from '../../../three/InstancedArrows/raycast'
|
|
14
14
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
let { entity, arrows }: Props = $props()
|
|
21
21
|
|
|
22
|
-
const parent =
|
|
22
|
+
const parent = useParentName(() => entity)
|
|
23
23
|
const invisible = useTrait(() => entity, traits.Invisible)
|
|
24
24
|
const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
|
|
25
25
|
|
|
@@ -20,7 +20,7 @@ Renders a Viam Frame object
|
|
|
20
20
|
|
|
21
21
|
import { asColor } from '../../buffer'
|
|
22
22
|
import { colors, resourceColors } from '../../color'
|
|
23
|
-
import { traits, useTrait } from '../../ecs'
|
|
23
|
+
import { traits, useParentName, useTrait } from '../../ecs'
|
|
24
24
|
import { useResourceByName } from '../../hooks/useResourceByName.svelte'
|
|
25
25
|
import { poseToObject3d } from '../../transform'
|
|
26
26
|
|
|
@@ -39,7 +39,7 @@ Renders a Viam Frame object
|
|
|
39
39
|
const resourceByName = useResourceByName()
|
|
40
40
|
|
|
41
41
|
const name = useTrait(() => entity, traits.Name)
|
|
42
|
-
const parent =
|
|
42
|
+
const parent = useParentName(() => entity)
|
|
43
43
|
const entityColors = useTrait(() => entity, traits.Colors)
|
|
44
44
|
const entityColor = useTrait(() => entity, traits.Color)
|
|
45
45
|
const entityPose = useTrait(() => entity, traits.Pose)
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { Portal, PortalTarget, type ThrelteGltf, useGltfAnimations } from '@threlte/extras'
|
|
20
20
|
import { Group, type Object3D } from 'three'
|
|
21
21
|
|
|
22
|
-
import { traits, useTrait } from '../../ecs'
|
|
22
|
+
import { traits, useParentName, useTrait } from '../../ecs'
|
|
23
23
|
import { poseToObject3d } from '../../transform'
|
|
24
24
|
|
|
25
25
|
import AxesHelper from '../AxesHelper.svelte'
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
const { gltf, actions } = useGltfAnimations()
|
|
36
36
|
|
|
37
37
|
const name = useTrait(() => entity, traits.Name)
|
|
38
|
-
const parent =
|
|
38
|
+
const parent = useParentName(() => entity)
|
|
39
39
|
const pose = useTrait(() => entity, traits.Pose)
|
|
40
40
|
const gltfTrait = useTrait(() => entity, traits.GLTF)
|
|
41
41
|
const scale = useTrait(() => entity, traits.Scale)
|
|
@@ -10,7 +10,7 @@ Renders a Viam Geometry object
|
|
|
10
10
|
import { T, useThrelte } from '@threlte/core'
|
|
11
11
|
import { Portal } from '@threlte/extras'
|
|
12
12
|
|
|
13
|
-
import { traits, useTrait } from '../../ecs'
|
|
13
|
+
import { traits, useParentName, useTrait } from '../../ecs'
|
|
14
14
|
import { use3DModels } from '../../hooks/use3DModels.svelte'
|
|
15
15
|
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
16
16
|
import { poseToObject3d } from '../../transform'
|
|
@@ -31,7 +31,7 @@ Renders a Viam Geometry object
|
|
|
31
31
|
const models = use3DModels()
|
|
32
32
|
|
|
33
33
|
const name = useTrait(() => entity, traits.Name)
|
|
34
|
-
const parent =
|
|
34
|
+
const parent = useParentName(() => entity)
|
|
35
35
|
const center = useTrait(() => entity, traits.Center)
|
|
36
36
|
const invisible = useTrait(() => entity, traits.Invisible)
|
|
37
37
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { Line2, LineMaterial } from 'three/examples/jsm/Addons.js'
|
|
8
8
|
|
|
9
9
|
import { isVertexColors, STRIDE } from '../../buffer'
|
|
10
|
-
import { traits, useTrait } from '../../ecs'
|
|
10
|
+
import { traits, useParentName, useTrait } from '../../ecs'
|
|
11
11
|
import { poseToObject3d } from '../../transform'
|
|
12
12
|
|
|
13
13
|
import AxesHelper from '../AxesHelper.svelte'
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
const { invalidate } = useThrelte()
|
|
26
26
|
const name = useTrait(() => entity, traits.Name)
|
|
27
|
-
const parent =
|
|
27
|
+
const parent = useParentName(() => entity)
|
|
28
28
|
const pose = useTrait(() => entity, traits.Pose)
|
|
29
29
|
const color = useTrait(() => entity, traits.Color)
|
|
30
30
|
const colors = useTrait(() => entity, traits.Colors)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { OrthographicCamera, Points, PointsMaterial } from 'three'
|
|
8
8
|
|
|
9
9
|
import { asColor, isSingleColor } from '../../buffer'
|
|
10
|
-
import { traits, useTrait } from '../../ecs'
|
|
10
|
+
import { traits, useParentName, useTrait } from '../../ecs'
|
|
11
11
|
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
12
12
|
import { poseToObject3d } from '../../transform'
|
|
13
13
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
const { camera } = useThrelte()
|
|
25
25
|
const settings = useSettings()
|
|
26
26
|
|
|
27
|
-
const parent =
|
|
27
|
+
const parent = useParentName(() => entity)
|
|
28
28
|
const pose = useTrait(() => entity, traits.Pose)
|
|
29
29
|
const geometry = useTrait(() => entity, traits.BufferGeometry)
|
|
30
30
|
const entityColor = useTrait(() => entity, traits.Color)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { Entity } from 'koota'
|
|
4
4
|
import type { Snippet } from 'svelte'
|
|
5
5
|
|
|
6
|
-
import { traits, useTrait } from '../../ecs'
|
|
6
|
+
import { traits, useParentName, useTrait } from '../../ecs'
|
|
7
7
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
8
8
|
import { usePose } from '../../hooks/usePose.svelte'
|
|
9
9
|
import { composeRenderedPose } from '../../transform'
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const partConfig = usePartConfig()
|
|
18
18
|
const name = useTrait(() => entity, traits.Name)
|
|
19
|
-
const parent =
|
|
19
|
+
const parent = useParentName(() => entity)
|
|
20
20
|
const editedPose = useTrait(() => entity, traits.EditedPose)
|
|
21
21
|
const entityPose = useTrait(() => entity, traits.Pose)
|
|
22
22
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
3
|
|
|
4
|
+
import { provideHierarchy } from '../ecs'
|
|
4
5
|
import { provide3DModels } from '../hooks/use3DModels.svelte'
|
|
5
6
|
import { provideArmClient } from '../hooks/useArmClient.svelte'
|
|
6
7
|
import { provideArmKinematics } from '../hooks/useArmKinematics.svelte'
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
provideTransformControls()
|
|
42
43
|
provideLogs()
|
|
43
44
|
|
|
45
|
+
provideHierarchy()
|
|
44
46
|
provideOrigin()
|
|
45
47
|
provideDrawAPI()
|
|
46
48
|
provideRelationships()
|
|
@@ -23,7 +23,7 @@ Renders a Snapshot protobuf by spawning its transforms and drawings as entities
|
|
|
23
23
|
import { useCameraControls } from '../hooks/useControls.svelte'
|
|
24
24
|
import { useRelationships } from '../hooks/useRelationships.svelte'
|
|
25
25
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
26
|
-
import { applySceneMetadata, type SnapshotEntity
|
|
26
|
+
import { applySceneMetadata, reconcileSnapshotEntities, type SnapshotEntity } from '../snapshot'
|
|
27
27
|
|
|
28
28
|
interface Props {
|
|
29
29
|
snapshot: SnapshotProto
|
|
@@ -36,17 +36,24 @@ Renders a Snapshot protobuf by spawning its transforms and drawings as entities
|
|
|
36
36
|
const cameraControls = useCameraControls()
|
|
37
37
|
const relationships = useRelationships()
|
|
38
38
|
|
|
39
|
-
let
|
|
39
|
+
let entitiesByUuid = new Map<string, SnapshotEntity>()
|
|
40
|
+
let unkeyedEntities: SnapshotEntity[] = []
|
|
40
41
|
|
|
41
42
|
$effect(() => {
|
|
42
|
-
|
|
43
|
-
snapshot.uuid.toString()
|
|
43
|
+
void snapshot
|
|
44
44
|
|
|
45
45
|
untrack(() => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
for (const entry of unkeyedEntities) {
|
|
47
|
+
if (world.has(entry.entity)) entry.entity.destroy()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = reconcileSnapshotEntities(world, snapshot, entitiesByUuid)
|
|
51
|
+
entitiesByUuid = result.current
|
|
52
|
+
unkeyedEntities = result.unkeyed
|
|
53
|
+
|
|
54
|
+
for (const entry of [...result.spawned, ...result.updated]) {
|
|
55
|
+
relationships.apply(entry.entity, entry.relationships)
|
|
56
|
+
const uuid = entry.entity.get(traits.UUID)
|
|
50
57
|
if (uuid) relationships.flush(uuid)
|
|
51
58
|
}
|
|
52
59
|
})
|
|
@@ -83,8 +90,11 @@ Renders a Snapshot protobuf by spawning its transforms and drawings as entities
|
|
|
83
90
|
})
|
|
84
91
|
|
|
85
92
|
onDestroy(() => {
|
|
86
|
-
for (const
|
|
87
|
-
if (world.has(
|
|
93
|
+
for (const entry of entitiesByUuid.values()) {
|
|
94
|
+
if (world.has(entry.entity)) entry.entity.destroy()
|
|
95
|
+
}
|
|
96
|
+
for (const entry of unkeyedEntities) {
|
|
97
|
+
if (world.has(entry.entity)) entry.entity.destroy()
|
|
88
98
|
}
|
|
89
99
|
})
|
|
90
100
|
</script>
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
} from 'svelte-tweakpane-ui'
|
|
39
39
|
|
|
40
40
|
import AddRelationship from './AddRelationship.svelte'
|
|
41
|
-
import { relations, traits, useTrait, useWorld } from '../../ecs'
|
|
41
|
+
import { hierarchy, relations, traits, useParentName, useTrait, useWorld } from '../../ecs'
|
|
42
42
|
import { FrameConfigUpdater } from '../../FrameConfigUpdater.svelte'
|
|
43
43
|
import { useConfigFrames } from '../../hooks/useConfigFrames.svelte'
|
|
44
44
|
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
const worldOrientation = $state({ x: 0, y: 0, z: 1, th: 0 })
|
|
80
80
|
const linkedEntities = useLinkedEntities()
|
|
81
81
|
const name = useTrait(() => entity, traits.Name)
|
|
82
|
-
const parent =
|
|
82
|
+
const parent = useParentName(() => entity)
|
|
83
83
|
const localPose = useTrait(() => entity, traits.EditedPose)
|
|
84
84
|
const box = useTrait(() => entity, traits.Box)
|
|
85
85
|
const sphere = useTrait(() => entity, traits.Sphere)
|
|
@@ -220,7 +220,7 @@
|
|
|
220
220
|
if (event.detail.origin !== 'internal' || !entity) return
|
|
221
221
|
const value = event.detail.value as string
|
|
222
222
|
if (value === parent.current) return
|
|
223
|
-
|
|
223
|
+
hierarchy.setParent(entity, value)
|
|
224
224
|
detailConfigUpdater.setFrameParent(entity, value)
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { traits } from '../../../../ecs';
|
|
1
|
+
import { hierarchy, traits } from '../../../../ecs';
|
|
2
2
|
export const createEntityFixture = (world) => {
|
|
3
|
-
return world.spawn(
|
|
3
|
+
return world.spawn(...hierarchy.parentTraits('parent_frame'), traits.Name('Test Object'), traits.Pose({
|
|
4
4
|
x: 10,
|
|
5
5
|
y: 20,
|
|
6
6
|
z: 30,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { traits } from '../../../ecs';
|
|
1
|
+
import { hierarchy, traits } from '../../../ecs';
|
|
2
2
|
function sortNodes(nodes) {
|
|
3
3
|
nodes.sort((a, b) => a.entity.get(traits.Name)?.localeCompare(b.entity.get(traits.Name) ?? '') ?? 0);
|
|
4
4
|
}
|
|
@@ -10,7 +10,7 @@ export const buildTreeNodes = (entities) => {
|
|
|
10
10
|
const rootNodes = [];
|
|
11
11
|
const childNodes = [];
|
|
12
12
|
for (const entity of entities) {
|
|
13
|
-
const parent =
|
|
13
|
+
const parent = hierarchy.getParentName(entity);
|
|
14
14
|
const name = entity.get(traits.Name) ?? '';
|
|
15
15
|
const node = { entity };
|
|
16
16
|
nodeMap[name] = node;
|
|
@@ -22,7 +22,7 @@ export const buildTreeNodes = (entities) => {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
for (const node of childNodes) {
|
|
25
|
-
const parent = node.entity
|
|
25
|
+
const parent = hierarchy.getParentName(node.entity);
|
|
26
26
|
if (parent) {
|
|
27
27
|
const parentNode = nodeMap[parent];
|
|
28
28
|
node.parent = parentNode;
|
package/dist/draw.js
CHANGED
|
@@ -3,7 +3,7 @@ import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
|
|
|
3
3
|
import { UuidTool } from 'uuid-tool';
|
|
4
4
|
import { createBufferGeometry, preAllocateBufferGeometry, updateBufferGeometry, writeBufferGeometryRange, } from './attribute';
|
|
5
5
|
import { asFloat32Array, asOpacity, asRGB, inMeters, isSingleColor, isVertexColors, STRIDE, } from './buffer';
|
|
6
|
-
import { relations, traits } from './ecs';
|
|
6
|
+
import { hierarchy, relations, traits } from './ecs';
|
|
7
7
|
import { parsePcdInWorker } from './loaders/pcd';
|
|
8
8
|
import { metadataFromStruct } from './metadata';
|
|
9
9
|
import { createPose } from './transform';
|
|
@@ -55,7 +55,7 @@ export const drawTransform = (world, { referenceFrame, poseInObserverFrame, phys
|
|
|
55
55
|
}
|
|
56
56
|
if (removable)
|
|
57
57
|
entityTraits.push(traits.Removable);
|
|
58
|
-
entityTraits.push(...
|
|
58
|
+
entityTraits.push(...hierarchy.parentTraits(poseInObserverFrame?.referenceFrame));
|
|
59
59
|
const parsedMetadata = metadataFromStruct(metadata?.fields);
|
|
60
60
|
if (parsedMetadata.showAxesHelper)
|
|
61
61
|
entityTraits.push(traits.ShowAxesHelper);
|
|
@@ -88,7 +88,7 @@ export const drawDrawing = (world, drawing, api, { removable = true } = {}) => {
|
|
|
88
88
|
const uuidStr = uuidBytesToString(uuid);
|
|
89
89
|
if (uuidStr)
|
|
90
90
|
uuidTraits.push(traits.UUID(uuidStr));
|
|
91
|
-
const entity = world.spawn(traits.Name(referenceFrame), traits.Pose(createPose(poseInObserverFrame?.pose)), api, ...
|
|
91
|
+
const entity = world.spawn(traits.Name(referenceFrame), traits.Pose(createPose(poseInObserverFrame?.pose)), api, ...hierarchy.parentTraits(poseInObserverFrame?.referenceFrame), ...uuidTraits);
|
|
92
92
|
if (removable)
|
|
93
93
|
entity.add(traits.Removable);
|
|
94
94
|
if (metadata?.showAxesHelper)
|
|
@@ -100,7 +100,7 @@ export const drawDrawing = (world, drawing, api, { removable = true } = {}) => {
|
|
|
100
100
|
};
|
|
101
101
|
export const updateTransform = (entity, { poseInObserverFrame, physicalObject, metadata }, { removable = true } = {}) => {
|
|
102
102
|
entity.set(traits.Pose, createPose(poseInObserverFrame?.pose));
|
|
103
|
-
|
|
103
|
+
hierarchy.setParent(entity, poseInObserverFrame?.referenceFrame);
|
|
104
104
|
if (physicalObject) {
|
|
105
105
|
traits.updateGeometryTrait(entity, physicalObject);
|
|
106
106
|
const center = physicalObject.center;
|
|
@@ -145,7 +145,7 @@ export const updateDrawing = (world, entity, drawing, { removable = true } = {})
|
|
|
145
145
|
if (!world.has(entity))
|
|
146
146
|
return { entity, relationships: metadata?.relationships };
|
|
147
147
|
entity.set(traits.Pose, createPose(poseInObserverFrame?.pose));
|
|
148
|
-
|
|
148
|
+
hierarchy.setParent(entity, poseInObserverFrame?.referenceFrame);
|
|
149
149
|
if (metadata?.showAxesHelper)
|
|
150
150
|
entity.add(traits.ShowAxesHelper);
|
|
151
151
|
if (!metadata?.showAxesHelper)
|
|
@@ -163,7 +163,7 @@ export const updateDrawing = (world, entity, drawing, { removable = true } = {})
|
|
|
163
163
|
};
|
|
164
164
|
export const updateModel = (world, entity, drawing, api, { removable = true } = {}) => {
|
|
165
165
|
if (world.has(entity))
|
|
166
|
-
|
|
166
|
+
hierarchy.destroyEntityTree(world, entity);
|
|
167
167
|
return drawDrawing(world, drawing, api, { removable });
|
|
168
168
|
};
|
|
169
169
|
const applyShape = (entity, { physicalObject, metadata }) => {
|
|
@@ -272,7 +272,7 @@ const drawModel = (world, model, api, { removable = true }) => {
|
|
|
272
272
|
traits.Name(referenceFrame),
|
|
273
273
|
traits.Pose(createPose(poseInObserverFrame?.pose)),
|
|
274
274
|
api,
|
|
275
|
-
...
|
|
275
|
+
...hierarchy.parentTraits(poseInObserverFrame?.referenceFrame),
|
|
276
276
|
];
|
|
277
277
|
const uuidStr = uuidBytesToString(uuid);
|
|
278
278
|
if (uuidStr)
|
|
@@ -288,7 +288,6 @@ const drawModel = (world, model, api, { removable = true }) => {
|
|
|
288
288
|
for (const asset of assets) {
|
|
289
289
|
const subEntityTraits = [
|
|
290
290
|
traits.Name(`${referenceFrame} model ${i++}`),
|
|
291
|
-
traits.Parent(referenceFrame),
|
|
292
291
|
relations.ChildOf(root),
|
|
293
292
|
api,
|
|
294
293
|
];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ConfigurableTrait, type Entity, type QueryResult, type Trait, type World } from 'koota';
|
|
2
|
+
/**
|
|
3
|
+
* Trait list for `world.spawn(...)`. Always emits `Orphan(name)` for non-root
|
|
4
|
+
* parents; the hierarchy resolver (`provideHierarchy`) swaps it to
|
|
5
|
+
* `ChildOf(parentEntity)` once a frame with that name exists. Returns `[]`
|
|
6
|
+
* for the world root (`undefined`, `''`, or `'world'`).
|
|
7
|
+
*/
|
|
8
|
+
export declare const parentTraits: (name: string | undefined) => ConfigurableTrait[];
|
|
9
|
+
/**
|
|
10
|
+
* Set or clear an entity's parent. Strips any existing `ChildOf` or `Orphan`,
|
|
11
|
+
* then writes `Orphan(name)` (the resolver converts it to `ChildOf` on the
|
|
12
|
+
* next reactive flush). Pass `undefined` or `'world'` to detach to root.
|
|
13
|
+
*/
|
|
14
|
+
export declare const setParent: (entity: Entity, name: string | undefined) => void;
|
|
15
|
+
/** The parent entity, or `undefined` at the world root or while orphaned. */
|
|
16
|
+
export declare const getParentEntity: (entity: Entity) => Entity | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The parent's name string. Reads through `ChildOf` first, falls back to
|
|
19
|
+
* `Orphan(name)` while the parent isn't present. Returns `undefined` for
|
|
20
|
+
* world-root entities.
|
|
21
|
+
*/
|
|
22
|
+
export declare const getParentName: (entity: Entity) => string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Destroy an entity and every `ChildOf` descendant, depth-first. Use for
|
|
25
|
+
* sub-trees whose lifetimes are tied together (e.g. a model root and its
|
|
26
|
+
* GLTF assets). General frame removal should use `entity.destroy()` so
|
|
27
|
+
* children survive as orphans.
|
|
28
|
+
*/
|
|
29
|
+
export declare const destroyEntityTree: (world: World, entity: Entity) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Synchronously resolve every `Orphan` whose desired parent now exists in
|
|
32
|
+
* the world. Called by `provideHierarchy` when the orphan/named query sets
|
|
33
|
+
* change or when a `Name` is renamed; also exposed for tests so they can
|
|
34
|
+
* drive resolution without mounting a component.
|
|
35
|
+
*/
|
|
36
|
+
export declare const resolveOrphans: (named: QueryResult<[Trait<() => string>]>, orphans: QueryResult<[Trait<() => string>]>) => void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {} from 'koota';
|
|
2
|
+
import { ChildOf } from './relations';
|
|
3
|
+
import { Name, Orphan } from './traits';
|
|
4
|
+
/**
|
|
5
|
+
* Trait list for `world.spawn(...)`. Always emits `Orphan(name)` for non-root
|
|
6
|
+
* parents; the hierarchy resolver (`provideHierarchy`) swaps it to
|
|
7
|
+
* `ChildOf(parentEntity)` once a frame with that name exists. Returns `[]`
|
|
8
|
+
* for the world root (`undefined`, `''`, or `'world'`).
|
|
9
|
+
*/
|
|
10
|
+
export const parentTraits = (name) => {
|
|
11
|
+
if (!name || name === 'world')
|
|
12
|
+
return [];
|
|
13
|
+
return [Orphan(name)];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Set or clear an entity's parent. Strips any existing `ChildOf` or `Orphan`,
|
|
17
|
+
* then writes `Orphan(name)` (the resolver converts it to `ChildOf` on the
|
|
18
|
+
* next reactive flush). Pass `undefined` or `'world'` to detach to root.
|
|
19
|
+
*/
|
|
20
|
+
export const setParent = (entity, name) => {
|
|
21
|
+
const target = entity.targetFor(ChildOf);
|
|
22
|
+
if (target)
|
|
23
|
+
entity.remove(ChildOf(target));
|
|
24
|
+
entity.remove(Orphan);
|
|
25
|
+
if (!name || name === 'world')
|
|
26
|
+
return;
|
|
27
|
+
entity.add(Orphan(name));
|
|
28
|
+
};
|
|
29
|
+
/** The parent entity, or `undefined` at the world root or while orphaned. */
|
|
30
|
+
export const getParentEntity = (entity) => entity.targetFor(ChildOf);
|
|
31
|
+
/**
|
|
32
|
+
* The parent's name string. Reads through `ChildOf` first, falls back to
|
|
33
|
+
* `Orphan(name)` while the parent isn't present. Returns `undefined` for
|
|
34
|
+
* world-root entities.
|
|
35
|
+
*/
|
|
36
|
+
export const getParentName = (entity) => {
|
|
37
|
+
const parent = entity.targetFor(ChildOf);
|
|
38
|
+
if (parent && parent.isAlive())
|
|
39
|
+
return parent.get(Name);
|
|
40
|
+
const orphanFor = entity.get(Orphan);
|
|
41
|
+
return orphanFor || undefined;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Destroy an entity and every `ChildOf` descendant, depth-first. Use for
|
|
45
|
+
* sub-trees whose lifetimes are tied together (e.g. a model root and its
|
|
46
|
+
* GLTF assets). General frame removal should use `entity.destroy()` so
|
|
47
|
+
* children survive as orphans.
|
|
48
|
+
*/
|
|
49
|
+
export const destroyEntityTree = (world, entity) => {
|
|
50
|
+
if (!entity.isAlive())
|
|
51
|
+
return;
|
|
52
|
+
for (const child of world.query(ChildOf(entity))) {
|
|
53
|
+
destroyEntityTree(world, child);
|
|
54
|
+
}
|
|
55
|
+
entity.destroy();
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Synchronously resolve every `Orphan` whose desired parent now exists in
|
|
59
|
+
* the world. Called by `provideHierarchy` when the orphan/named query sets
|
|
60
|
+
* change or when a `Name` is renamed; also exposed for tests so they can
|
|
61
|
+
* drive resolution without mounting a component.
|
|
62
|
+
*/
|
|
63
|
+
export const resolveOrphans = (named, orphans) => {
|
|
64
|
+
const index = new Map();
|
|
65
|
+
for (const entity of named) {
|
|
66
|
+
const name = entity.get(Name);
|
|
67
|
+
if (name)
|
|
68
|
+
index.set(name, entity);
|
|
69
|
+
}
|
|
70
|
+
for (const orphan of orphans) {
|
|
71
|
+
const wantedName = orphan.get(Orphan);
|
|
72
|
+
if (!wantedName)
|
|
73
|
+
continue;
|
|
74
|
+
const parent = index.get(wantedName);
|
|
75
|
+
if (!parent)
|
|
76
|
+
continue;
|
|
77
|
+
orphan.remove(Orphan);
|
|
78
|
+
orphan.add(ChildOf(parent));
|
|
79
|
+
}
|
|
80
|
+
};
|
package/dist/ecs/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { provideWorld, useWorld } from './useWorld';
|
|
2
2
|
export { useQuery } from './useQuery.svelte';
|
|
3
3
|
export { useTrait } from './useTrait.svelte';
|
|
4
|
+
export { useTarget } from './useTarget.svelte';
|
|
5
|
+
export { useParentName } from './useParentName.svelte';
|
|
6
|
+
export { provideHierarchy } from './provideHierarchy.svelte';
|
|
4
7
|
export * as traits from './traits';
|
|
5
8
|
export * as relations from './relations';
|
|
9
|
+
export * as hierarchy from './hierarchy';
|
package/dist/ecs/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { provideWorld, useWorld } from './useWorld';
|
|
2
2
|
export { useQuery } from './useQuery.svelte';
|
|
3
3
|
export { useTrait } from './useTrait.svelte';
|
|
4
|
+
export { useTarget } from './useTarget.svelte';
|
|
5
|
+
export { useParentName } from './useParentName.svelte';
|
|
6
|
+
export { provideHierarchy } from './provideHierarchy.svelte';
|
|
4
7
|
export * as traits from './traits';
|
|
5
8
|
export * as relations from './relations';
|
|
9
|
+
export * as hierarchy from './hierarchy';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mounts the hierarchy resolver: each tick, any entity with `Orphan(name)`
|
|
3
|
+
* whose desired parent is now in the world is converted to
|
|
4
|
+
* `ChildOf(parentEntity)`. Call once at the top of the app, alongside the
|
|
5
|
+
* other `provide*` hooks.
|
|
6
|
+
*
|
|
7
|
+
* Reactive on:
|
|
8
|
+
* - `useQuery(Orphan)` membership — new orphans appear or resolve away
|
|
9
|
+
* - `useQuery(Name)` membership — new candidate parents appear/disappear
|
|
10
|
+
* - `world.onChange(Name)` — an existing entity is renamed into a name
|
|
11
|
+
* that some orphan is waiting for
|
|
12
|
+
*
|
|
13
|
+
* Children whose `ChildOf` parent is destroyed are *not* automatically
|
|
14
|
+
* re-orphaned. Call `hierarchy.setParent` on the affected children
|
|
15
|
+
* explicitly if you need them to reattach to a same-named replacement.
|
|
16
|
+
*/
|
|
17
|
+
export declare const provideHierarchy: () => void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolveOrphans } from './hierarchy';
|
|
2
|
+
import { Name, Orphan } from './traits';
|
|
3
|
+
import { useQuery } from './useQuery.svelte';
|
|
4
|
+
import { useWorld } from './useWorld';
|
|
5
|
+
/**
|
|
6
|
+
* Mounts the hierarchy resolver: each tick, any entity with `Orphan(name)`
|
|
7
|
+
* whose desired parent is now in the world is converted to
|
|
8
|
+
* `ChildOf(parentEntity)`. Call once at the top of the app, alongside the
|
|
9
|
+
* other `provide*` hooks.
|
|
10
|
+
*
|
|
11
|
+
* Reactive on:
|
|
12
|
+
* - `useQuery(Orphan)` membership — new orphans appear or resolve away
|
|
13
|
+
* - `useQuery(Name)` membership — new candidate parents appear/disappear
|
|
14
|
+
* - `world.onChange(Name)` — an existing entity is renamed into a name
|
|
15
|
+
* that some orphan is waiting for
|
|
16
|
+
*
|
|
17
|
+
* Children whose `ChildOf` parent is destroyed are *not* automatically
|
|
18
|
+
* re-orphaned. Call `hierarchy.setParent` on the affected children
|
|
19
|
+
* explicitly if you need them to reattach to a same-named replacement.
|
|
20
|
+
*/
|
|
21
|
+
export const provideHierarchy = () => {
|
|
22
|
+
const world = useWorld();
|
|
23
|
+
const orphans = useQuery(Orphan);
|
|
24
|
+
const named = useQuery(Name);
|
|
25
|
+
$effect(() => {
|
|
26
|
+
resolveOrphans(named.current, orphans.current);
|
|
27
|
+
});
|
|
28
|
+
$effect(() => {
|
|
29
|
+
return world.onChange(Name, () => resolveOrphans(named.current, orphans.current));
|
|
30
|
+
});
|
|
31
|
+
};
|
package/dist/ecs/relations.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent → child hierarchy relation. `exclusive: true` because each entity has
|
|
3
|
+
* at most one parent. Cascade-on-orphan is intentionally OFF: when a parent is
|
|
4
|
+
* destroyed, children survive and gain an `Orphan(parentName)` trait so they
|
|
5
|
+
* reattach if a frame with that name reappears. Sub-trees that should be torn
|
|
6
|
+
* down together (e.g. model roots + assets) call `destroyEntityTree` instead.
|
|
7
|
+
*/
|
|
1
8
|
export declare const ChildOf: import("koota").Relation<import("koota").Trait<Record<string, never>>>;
|
|
2
9
|
export declare const SubEntityLinkType: {
|
|
3
10
|
readonly HoverLink: "HoverLink";
|
package/dist/ecs/relations.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { relation } from 'koota';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Parent → child hierarchy relation. `exclusive: true` because each entity has
|
|
4
|
+
* at most one parent. Cascade-on-orphan is intentionally OFF: when a parent is
|
|
5
|
+
* destroyed, children survive and gain an `Orphan(parentName)` trait so they
|
|
6
|
+
* reattach if a frame with that name reappears. Sub-trees that should be torn
|
|
7
|
+
* down together (e.g. model roots + assets) call `destroyEntityTree` instead.
|
|
8
|
+
*/
|
|
9
|
+
export const ChildOf = relation({ exclusive: true });
|
|
3
10
|
export const SubEntityLinkType = {
|
|
4
11
|
HoverLink: 'HoverLink',
|
|
5
12
|
};
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { GLTF as ThreeGltf } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
2
|
import { Geometry as ViamGeometry } from '@viamrobotics/sdk';
|
|
3
|
-
import { type
|
|
3
|
+
import { type Entity } from 'koota';
|
|
4
4
|
import { BufferGeometry as ThreeBufferGeometry } from 'three';
|
|
5
5
|
export declare const Name: import("koota").Trait<() => string>;
|
|
6
|
-
export declare const Parent: import("koota").Trait<() => string>;
|
|
7
6
|
export declare const UUID: import("koota").Trait<() => string>;
|
|
7
|
+
/**
|
|
8
|
+
* Set on an entity whose desired parent (by name) doesn't yet exist in the
|
|
9
|
+
* world. Replaced with `relations.ChildOf(parentEntity)` once a frame with
|
|
10
|
+
* the matching `Name` is added. Managed by the hierarchy module — call sites
|
|
11
|
+
* should use `hierarchy.setParent` / `hierarchy.parentTraits` rather than
|
|
12
|
+
* adding this trait directly.
|
|
13
|
+
*/
|
|
14
|
+
export declare const Orphan: import("koota").Trait<() => string>;
|
|
8
15
|
export declare const Pose: import("koota").Trait<{
|
|
9
16
|
x: number;
|
|
10
17
|
y: number;
|
|
@@ -223,6 +230,4 @@ export declare const Geometry: (geometry: ViamGeometry) => import("koota").Trait
|
|
|
223
230
|
}>, Partial<{
|
|
224
231
|
r: number;
|
|
225
232
|
}>] | [import("koota").Trait<() => ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>>, ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>];
|
|
226
|
-
export declare const getParentTrait: (parent: string | undefined) => ConfigurableTrait[];
|
|
227
|
-
export declare const setParentTrait: (entity: Entity, parent: string | undefined) => void;
|
|
228
233
|
export declare const updateGeometryTrait: (entity: Entity, geometry?: ViamGeometry) => void;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -7,8 +7,15 @@ import { createBox, createCapsule, createSphere } from '../geometry';
|
|
|
7
7
|
import { parsePcdInWorker } from '../loaders/pcd';
|
|
8
8
|
import { parsePlyInput } from '../ply';
|
|
9
9
|
export const Name = trait(() => '');
|
|
10
|
-
export const Parent = trait(() => 'world');
|
|
11
10
|
export const UUID = trait(() => '');
|
|
11
|
+
/**
|
|
12
|
+
* Set on an entity whose desired parent (by name) doesn't yet exist in the
|
|
13
|
+
* world. Replaced with `relations.ChildOf(parentEntity)` once a frame with
|
|
14
|
+
* the matching `Name` is added. Managed by the hierarchy module — call sites
|
|
15
|
+
* should use `hierarchy.setParent` / `hierarchy.parentTraits` rather than
|
|
16
|
+
* adding this trait directly.
|
|
17
|
+
*/
|
|
18
|
+
export const Orphan = trait(() => '');
|
|
12
19
|
export const Pose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
13
20
|
export const EditedPose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
14
21
|
export const LivePose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
@@ -161,19 +168,6 @@ export const Geometry = (geometry) => {
|
|
|
161
168
|
}
|
|
162
169
|
return ReferenceFrame;
|
|
163
170
|
};
|
|
164
|
-
export const getParentTrait = (parent) => !parent || parent === 'world' ? [] : [Parent(parent)];
|
|
165
|
-
export const setParentTrait = (entity, parent) => {
|
|
166
|
-
if (!parent || parent === 'world') {
|
|
167
|
-
entity.remove(Parent);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (entity.has(Parent)) {
|
|
171
|
-
entity.set(Parent, parent);
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
entity.add(Parent(parent));
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
171
|
export const updateGeometryTrait = (entity, geometry) => {
|
|
178
172
|
if (!geometry) {
|
|
179
173
|
entity.remove(Box, Capsule, Sphere, BufferGeometry);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Entity } from 'koota';
|
|
2
|
+
/**
|
|
3
|
+
* Reactive view of an entity's parent name — the string consumed by Threlte
|
|
4
|
+
* `<Portal id={...}>` and other lookups that key off the parent's `Name`.
|
|
5
|
+
*
|
|
6
|
+
* Reads through `ChildOf` to the parent's `Name` when the parent is alive,
|
|
7
|
+
* else falls back to `Orphan(parentName)`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const useParentName: (target: () => Entity | undefined) => {
|
|
10
|
+
readonly current: string | undefined;
|
|
11
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ChildOf } from './relations';
|
|
2
|
+
import { Name, Orphan } from './traits';
|
|
3
|
+
import { useTarget } from './useTarget.svelte';
|
|
4
|
+
import { useTrait } from './useTrait.svelte';
|
|
5
|
+
/**
|
|
6
|
+
* Reactive view of an entity's parent name — the string consumed by Threlte
|
|
7
|
+
* `<Portal id={...}>` and other lookups that key off the parent's `Name`.
|
|
8
|
+
*
|
|
9
|
+
* Reads through `ChildOf` to the parent's `Name` when the parent is alive,
|
|
10
|
+
* else falls back to `Orphan(parentName)`.
|
|
11
|
+
*/
|
|
12
|
+
export const useParentName = (target) => {
|
|
13
|
+
const parent = useTarget(target, ChildOf);
|
|
14
|
+
const parentName = useTrait(() => parent.current, Name);
|
|
15
|
+
const orphan = useTrait(target, Orphan);
|
|
16
|
+
return {
|
|
17
|
+
get current() {
|
|
18
|
+
return parentName.current || orphan.current || undefined;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Entity, type Relation, type Trait, type World } from 'koota';
|
|
2
|
+
/**
|
|
3
|
+
* Reactive view of an entity's target for an exclusive relation. Mirrors the
|
|
4
|
+
* forthcoming `useTarget` from the upstream `@koota/svelte` package — kept
|
|
5
|
+
* here until that package is published. See:
|
|
6
|
+
* https://github.com/michealparks/koota/tree/svelte/packages/svelte
|
|
7
|
+
*/
|
|
8
|
+
export declare const useTarget: <T extends Trait>(target: () => Entity | World | undefined | null, relation: Relation<T>) => {
|
|
9
|
+
readonly current: Entity | undefined;
|
|
10
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { $internal as internal } from 'koota';
|
|
2
|
+
import { isWorld } from './useTrait.svelte';
|
|
3
|
+
import { useWorld } from './useWorld';
|
|
4
|
+
/**
|
|
5
|
+
* Reactive view of an entity's target for an exclusive relation. Mirrors the
|
|
6
|
+
* forthcoming `useTarget` from the upstream `@koota/svelte` package — kept
|
|
7
|
+
* here until that package is published. See:
|
|
8
|
+
* https://github.com/michealparks/koota/tree/svelte/packages/svelte
|
|
9
|
+
*/
|
|
10
|
+
export const useTarget = (target, relation) => {
|
|
11
|
+
const contextWorld = useWorld();
|
|
12
|
+
const targetEntity = $derived(target());
|
|
13
|
+
const world = $derived(isWorld(targetEntity) ? targetEntity : contextWorld);
|
|
14
|
+
const entity = $derived(isWorld(targetEntity) ? targetEntity[internal].worldEntity : targetEntity);
|
|
15
|
+
let value = $derived(entity?.targetFor(relation));
|
|
16
|
+
$effect(() => {
|
|
17
|
+
if (!entity)
|
|
18
|
+
return;
|
|
19
|
+
const onAddUnsub = world.onAdd(relation, (e) => {
|
|
20
|
+
if (e === entity)
|
|
21
|
+
value = entity.targetFor(relation);
|
|
22
|
+
});
|
|
23
|
+
const onRemoveUnsub = world.onRemove(relation, (e) => {
|
|
24
|
+
if (e === entity)
|
|
25
|
+
value = undefined;
|
|
26
|
+
});
|
|
27
|
+
const onChangeUnsub = world.onChange(relation, (e) => {
|
|
28
|
+
if (e === entity)
|
|
29
|
+
value = entity.targetFor(relation);
|
|
30
|
+
});
|
|
31
|
+
return () => {
|
|
32
|
+
onAddUnsub();
|
|
33
|
+
onRemoveUnsub();
|
|
34
|
+
onChangeUnsub();
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
get current() {
|
|
39
|
+
return value;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { traits } from '../ecs';
|
|
1
|
+
import { hierarchy, traits } from '../ecs';
|
|
2
2
|
import { isFinitePose } from '../transform';
|
|
3
3
|
const captureGeometry = (entity) => {
|
|
4
4
|
const box = entity.get(traits.Box);
|
|
@@ -76,7 +76,7 @@ export class FrameEditSession {
|
|
|
76
76
|
continue;
|
|
77
77
|
this.snapshots.set(entity, {
|
|
78
78
|
name,
|
|
79
|
-
parent:
|
|
79
|
+
parent: hierarchy.getParentName(entity) ?? 'world',
|
|
80
80
|
editedPose: { ...editedPose },
|
|
81
81
|
geometry: captureGeometry(entity),
|
|
82
82
|
});
|
|
@@ -97,7 +97,7 @@ export class FrameEditSession {
|
|
|
97
97
|
return;
|
|
98
98
|
const next = { ...current, ...pose };
|
|
99
99
|
entity.set(traits.EditedPose, next);
|
|
100
|
-
this.updateFrame(snap.name,
|
|
100
|
+
this.updateFrame(snap.name, hierarchy.getParentName(entity) ?? 'world', next, liveGeometry(entity));
|
|
101
101
|
};
|
|
102
102
|
stageGeometry = (entity, geometry) => {
|
|
103
103
|
const snap = this.snapshots.get(entity);
|
|
@@ -118,14 +118,14 @@ export class FrameEditSession {
|
|
|
118
118
|
}
|
|
119
119
|
const editedPose = entity.get(traits.EditedPose);
|
|
120
120
|
if (editedPose) {
|
|
121
|
-
this.updateFrame(snap.name,
|
|
121
|
+
this.updateFrame(snap.name, hierarchy.getParentName(entity) ?? 'world', editedPose, geometry);
|
|
122
122
|
}
|
|
123
123
|
};
|
|
124
124
|
stageParent = (entity, parent) => {
|
|
125
125
|
const snap = this.snapshots.get(entity);
|
|
126
126
|
if (!snap || this.#closed)
|
|
127
127
|
return;
|
|
128
|
-
|
|
128
|
+
hierarchy.setParent(entity, parent === 'world' ? undefined : parent);
|
|
129
129
|
const editedPose = entity.get(traits.EditedPose);
|
|
130
130
|
if (editedPose) {
|
|
131
131
|
this.updateFrame(snap.name, parent, editedPose, liveGeometry(entity));
|
|
@@ -164,7 +164,7 @@ export class FrameEditSession {
|
|
|
164
164
|
for (const [entity, snap] of this.snapshots) {
|
|
165
165
|
if (entity.isAlive()) {
|
|
166
166
|
entity.set(traits.EditedPose, snap.editedPose);
|
|
167
|
-
|
|
167
|
+
hierarchy.setParent(entity, snap.parent === 'world' ? undefined : snap.parent);
|
|
168
168
|
restoreGeometryTrait(entity, snap.geometry);
|
|
169
169
|
}
|
|
170
170
|
this.updateFrame(snap.name, snap.parent, snap.editedPose, snapshotToFrameGeometry(snap.geometry));
|
|
@@ -8,7 +8,7 @@ import { UuidTool } from 'uuid-tool';
|
|
|
8
8
|
import { createBufferGeometry, updateBufferGeometry } from '../attribute';
|
|
9
9
|
import { ColorFormat } from '../buf/draw/v1/metadata_pb';
|
|
10
10
|
import { asRGB, STRIDE } from '../buffer';
|
|
11
|
-
import { traits, useWorld } from '../ecs';
|
|
11
|
+
import { hierarchy, traits, useWorld } from '../ecs';
|
|
12
12
|
import { createBox, createCapsule, createSphere } from '../geometry';
|
|
13
13
|
import { parsePlyInput } from '../ply';
|
|
14
14
|
import { createPose, createPoseFromFrame } from '../transform';
|
|
@@ -127,7 +127,7 @@ export const provideDrawAPI = () => {
|
|
|
127
127
|
const existing = entities.get(name);
|
|
128
128
|
if (existing) {
|
|
129
129
|
existing.set(traits.Pose, pose);
|
|
130
|
-
|
|
130
|
+
hierarchy.setParent(existing, parent);
|
|
131
131
|
continue;
|
|
132
132
|
}
|
|
133
133
|
const geometryTrait = () => {
|
|
@@ -142,7 +142,7 @@ export const provideDrawAPI = () => {
|
|
|
142
142
|
}
|
|
143
143
|
return traits.ReferenceFrame;
|
|
144
144
|
};
|
|
145
|
-
const entityTraits = [...
|
|
145
|
+
const entityTraits = [...hierarchy.parentTraits(parent)];
|
|
146
146
|
if (frame.geometry) {
|
|
147
147
|
entityTraits.push(geometryTrait());
|
|
148
148
|
}
|
|
@@ -178,7 +178,7 @@ export const provideDrawAPI = () => {
|
|
|
178
178
|
};
|
|
179
179
|
const entityTraits = [
|
|
180
180
|
traits.Name(data.label ?? ++geometryIndex),
|
|
181
|
-
...
|
|
181
|
+
...hierarchy.parentTraits(parent),
|
|
182
182
|
traits.Pose(pose),
|
|
183
183
|
traits.Color(colorUtil.set(color)),
|
|
184
184
|
geometryTrait(),
|
|
@@ -3,7 +3,7 @@ import { createRobotQuery, useConnectionStatus, useMachineStatus, useRobotClient
|
|
|
3
3
|
import {} from 'koota';
|
|
4
4
|
import { getContext, setContext, untrack } from 'svelte';
|
|
5
5
|
import { resourceNameToColor, subtypeToColor } from '../color';
|
|
6
|
-
import { traits, useWorld } from '../ecs';
|
|
6
|
+
import { hierarchy, traits, useWorld } from '../ecs';
|
|
7
7
|
import { createPose } from '../transform';
|
|
8
8
|
import { useConfigFrames } from './useConfigFrames.svelte';
|
|
9
9
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
@@ -169,7 +169,7 @@ export const provideFrames = (partID) => {
|
|
|
169
169
|
if (editSession.current?.owns(existing)) {
|
|
170
170
|
continue;
|
|
171
171
|
}
|
|
172
|
-
|
|
172
|
+
hierarchy.setParent(existing, parent);
|
|
173
173
|
if (color) {
|
|
174
174
|
existing.set(traits.Color, color);
|
|
175
175
|
}
|
|
@@ -202,7 +202,7 @@ export const provideFrames = (partID) => {
|
|
|
202
202
|
traits.FramesAPI,
|
|
203
203
|
traits.Transformable,
|
|
204
204
|
traits.ShowAxesHelper,
|
|
205
|
-
...
|
|
205
|
+
...hierarchy.parentTraits(parent),
|
|
206
206
|
];
|
|
207
207
|
if (color) {
|
|
208
208
|
entityTraits.push(traits.Color(color));
|
|
@@ -5,7 +5,7 @@ import { getContext, setContext, untrack } from 'svelte';
|
|
|
5
5
|
import { Color } from 'three';
|
|
6
6
|
import { resourceColors } from '../color';
|
|
7
7
|
import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
|
|
8
|
-
import { traits, useWorld } from '../ecs';
|
|
8
|
+
import { hierarchy, traits, useWorld } from '../ecs';
|
|
9
9
|
import { updateGeometryTrait } from '../ecs/traits';
|
|
10
10
|
import { createPose } from '../transform';
|
|
11
11
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
@@ -90,12 +90,13 @@ export const provideGeometries = (partID) => {
|
|
|
90
90
|
const center = createPose(geometry.center);
|
|
91
91
|
const existing = entities.get(entityKey);
|
|
92
92
|
if (existing) {
|
|
93
|
+
hierarchy.setParent(existing, name);
|
|
93
94
|
existing.set(traits.Center, center);
|
|
94
95
|
updateGeometryTrait(existing, geometry);
|
|
95
96
|
continue;
|
|
96
97
|
}
|
|
97
98
|
const entityTraits = [
|
|
98
|
-
|
|
99
|
+
...hierarchy.parentTraits(name),
|
|
99
100
|
traits.Name(label),
|
|
100
101
|
traits.Center(center),
|
|
101
102
|
traits.GeometriesAPI,
|
|
@@ -4,7 +4,7 @@ import { getContext, setContext, untrack } from 'svelte';
|
|
|
4
4
|
import { createBufferGeometry, updateBufferGeometry } from '../attribute';
|
|
5
5
|
import { ColorFormat } from '../buf/draw/v1/metadata_pb';
|
|
6
6
|
import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
|
|
7
|
-
import { traits, useWorld } from '../ecs';
|
|
7
|
+
import { hierarchy, traits, useWorld } from '../ecs';
|
|
8
8
|
import { parsePcdInWorker } from '../lib';
|
|
9
9
|
import { createPose } from '../transform';
|
|
10
10
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
@@ -160,13 +160,14 @@ export const providePointcloudObjects = (partID) => {
|
|
|
160
160
|
const center = createPose(geometry.center);
|
|
161
161
|
const existing = entities.get(geometryLabel);
|
|
162
162
|
if (existing) {
|
|
163
|
+
hierarchy.setParent(existing, geometriesInFrame.referenceFrame);
|
|
163
164
|
existing.set(traits.Center, center);
|
|
164
165
|
traits.updateGeometryTrait(existing, geometry);
|
|
165
166
|
}
|
|
166
167
|
else {
|
|
167
168
|
const entityTraits = [
|
|
168
169
|
traits.Name(geometryLabel),
|
|
169
|
-
...
|
|
170
|
+
...hierarchy.parentTraits(geometriesInFrame.referenceFrame),
|
|
170
171
|
traits.Center(center),
|
|
171
172
|
traits.GeometriesAPI,
|
|
172
173
|
traits.Geometry(geometry),
|
|
@@ -4,7 +4,7 @@ import { getContext, setContext, untrack } from 'svelte';
|
|
|
4
4
|
import { createBufferGeometry, updateBufferGeometry } from '../attribute';
|
|
5
5
|
import { ColorFormat } from '../buf/draw/v1/metadata_pb';
|
|
6
6
|
import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
|
|
7
|
-
import { traits, useWorld } from '../ecs';
|
|
7
|
+
import { hierarchy, traits, useWorld } from '../ecs';
|
|
8
8
|
import { parsePcdInWorker } from '../loaders/pcd';
|
|
9
9
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
10
10
|
import { useLogs } from './useLogs.svelte';
|
|
@@ -108,6 +108,7 @@ export const providePointclouds = (partID) => {
|
|
|
108
108
|
colorFormat: ColorFormat.RGB,
|
|
109
109
|
};
|
|
110
110
|
if (existing) {
|
|
111
|
+
hierarchy.setParent(existing, name);
|
|
111
112
|
const geometry = existing.get(traits.BufferGeometry);
|
|
112
113
|
if (geometry) {
|
|
113
114
|
updateBufferGeometry(geometry, positions, metadata);
|
|
@@ -115,7 +116,7 @@ export const providePointclouds = (partID) => {
|
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
const geometry = createBufferGeometry(positions, metadata);
|
|
118
|
-
const entity = world.spawn(
|
|
119
|
+
const entity = world.spawn(...hierarchy.parentTraits(name), traits.Name(`${name} pointcloud`), traits.BufferGeometry(geometry), traits.Points);
|
|
119
120
|
entities.set(queryKey, entity);
|
|
120
121
|
})
|
|
121
122
|
.catch((error) => {
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -24,3 +24,10 @@ export declare const applySceneMetadata: (settings: Settings, metadata: SceneMet
|
|
|
24
24
|
* @returns The spawned entities
|
|
25
25
|
*/
|
|
26
26
|
export declare const spawnSnapshotEntities: (world: World, snapshot: Snapshot) => SnapshotEntity[];
|
|
27
|
+
export interface ReconcileResult {
|
|
28
|
+
current: Map<string, SnapshotEntity>;
|
|
29
|
+
unkeyed: SnapshotEntity[];
|
|
30
|
+
spawned: SnapshotEntity[];
|
|
31
|
+
updated: SnapshotEntity[];
|
|
32
|
+
}
|
|
33
|
+
export declare const reconcileSnapshotEntities: (world: World, snapshot: Snapshot, prev: Map<string, SnapshotEntity>) => ReconcileResult;
|
package/dist/snapshot.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RenderArmModels } from './buf/draw/v1/scene_pb';
|
|
2
2
|
import { traits } from './ecs';
|
|
3
3
|
import { rgbToHex } from './color';
|
|
4
|
-
import { drawDrawing, drawTransform } from './draw';
|
|
4
|
+
import { drawDrawing, drawTransform, updateDrawing, updateModel, updateTransform, uuidBytesToString, } from './draw';
|
|
5
5
|
/**
|
|
6
6
|
* Merges scene-level metadata (grid, camera, point/line settings) into the
|
|
7
7
|
* current viewer settings. Millimeter values from the proto are converted
|
|
@@ -73,6 +73,79 @@ export const spawnSnapshotEntities = (world, snapshot) => {
|
|
|
73
73
|
}
|
|
74
74
|
return entities;
|
|
75
75
|
};
|
|
76
|
+
export const reconcileSnapshotEntities = (world, snapshot, prev) => {
|
|
77
|
+
const options = { removable: true, showAxesHelper: false };
|
|
78
|
+
const next = new Map(prev);
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
const unkeyed = [];
|
|
81
|
+
const spawned = [];
|
|
82
|
+
const updated = [];
|
|
83
|
+
for (const transform of snapshot.transforms) {
|
|
84
|
+
const uuidStr = uuidBytesToString(transform.uuid);
|
|
85
|
+
if (!uuidStr) {
|
|
86
|
+
const result = drawTransform(world, transform, traits.SnapshotAPI, options);
|
|
87
|
+
const entry = { entity: result.entity, relationships: result.relationships };
|
|
88
|
+
unkeyed.push(entry);
|
|
89
|
+
spawned.push(entry);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const existing = next.get(uuidStr);
|
|
93
|
+
if (existing && world.has(existing.entity)) {
|
|
94
|
+
const result = updateTransform(existing.entity, transform, options);
|
|
95
|
+
const entry = { entity: result.entity, relationships: result.relationships };
|
|
96
|
+
next.set(uuidStr, entry);
|
|
97
|
+
updated.push(entry);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const result = drawTransform(world, transform, traits.SnapshotAPI, options);
|
|
101
|
+
const entry = { entity: result.entity, relationships: result.relationships };
|
|
102
|
+
next.set(uuidStr, entry);
|
|
103
|
+
spawned.push(entry);
|
|
104
|
+
}
|
|
105
|
+
seen.add(uuidStr);
|
|
106
|
+
}
|
|
107
|
+
for (const drawing of snapshot.drawings) {
|
|
108
|
+
const uuidStr = uuidBytesToString(drawing.uuid);
|
|
109
|
+
if (!uuidStr) {
|
|
110
|
+
const result = drawDrawing(world, drawing, traits.SnapshotAPI, options);
|
|
111
|
+
const entry = { entity: result.entity, relationships: result.relationships };
|
|
112
|
+
unkeyed.push(entry);
|
|
113
|
+
spawned.push(entry);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const existing = next.get(uuidStr);
|
|
117
|
+
const isModel = drawing.physicalObject?.geometryType.case === 'model';
|
|
118
|
+
if (existing && world.has(existing.entity)) {
|
|
119
|
+
if (isModel) {
|
|
120
|
+
const result = updateModel(world, existing.entity, drawing, traits.SnapshotAPI, options);
|
|
121
|
+
const entry = { entity: result.entity, relationships: result.relationships };
|
|
122
|
+
next.set(uuidStr, entry);
|
|
123
|
+
spawned.push(entry);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const result = updateDrawing(world, existing.entity, drawing, options);
|
|
127
|
+
const entry = { entity: result.entity, relationships: result.relationships };
|
|
128
|
+
next.set(uuidStr, entry);
|
|
129
|
+
updated.push(entry);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const result = drawDrawing(world, drawing, traits.SnapshotAPI, options);
|
|
134
|
+
const entry = { entity: result.entity, relationships: result.relationships };
|
|
135
|
+
next.set(uuidStr, entry);
|
|
136
|
+
spawned.push(entry);
|
|
137
|
+
}
|
|
138
|
+
seen.add(uuidStr);
|
|
139
|
+
}
|
|
140
|
+
for (const [uuid, entry] of prev) {
|
|
141
|
+
if (seen.has(uuid))
|
|
142
|
+
continue;
|
|
143
|
+
if (world.has(entry.entity))
|
|
144
|
+
entry.entity.destroy();
|
|
145
|
+
next.delete(uuid);
|
|
146
|
+
}
|
|
147
|
+
return { current: next, unkeyed, spawned, updated };
|
|
148
|
+
};
|
|
76
149
|
const getRenderArmModels = (renderArmModels) => {
|
|
77
150
|
switch (renderArmModels) {
|
|
78
151
|
case RenderArmModels.COLLIDERS: {
|