@viamrobotics/motion-tools 1.24.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.
Files changed (43) hide show
  1. package/dist/FrameConfigUpdater.svelte.js +5 -5
  2. package/dist/components/BatchedArrows.svelte +4 -4
  3. package/dist/components/Entities/Arrows/Arrows.svelte +2 -2
  4. package/dist/components/Entities/Capsule.svelte +137 -0
  5. package/dist/components/Entities/Capsule.svelte.d.ts +19 -0
  6. package/dist/components/Entities/Frame.svelte +2 -2
  7. package/dist/components/Entities/GLTF.svelte +2 -2
  8. package/dist/components/Entities/Geometry.svelte +2 -2
  9. package/dist/components/Entities/Line.svelte +2 -2
  10. package/dist/components/Entities/Mesh.svelte +121 -68
  11. package/dist/components/Entities/Points.svelte +2 -2
  12. package/dist/components/Entities/Pose.svelte +2 -2
  13. package/dist/components/SceneProviders.svelte +2 -0
  14. package/dist/components/Snapshot.svelte +20 -10
  15. package/dist/components/overlay/Details.svelte +3 -3
  16. package/dist/components/overlay/__tests__/__fixtures__/entity.js +2 -2
  17. package/dist/components/overlay/left-pane/buildTree.js +3 -3
  18. package/dist/draw.js +7 -8
  19. package/dist/ecs/hierarchy.d.ts +36 -0
  20. package/dist/ecs/hierarchy.js +80 -0
  21. package/dist/ecs/index.d.ts +4 -0
  22. package/dist/ecs/index.js +4 -0
  23. package/dist/ecs/provideHierarchy.svelte.d.ts +17 -0
  24. package/dist/ecs/provideHierarchy.svelte.js +31 -0
  25. package/dist/ecs/relations.d.ts +7 -0
  26. package/dist/ecs/relations.js +8 -1
  27. package/dist/ecs/traits.d.ts +9 -4
  28. package/dist/ecs/traits.js +8 -14
  29. package/dist/ecs/useParentName.svelte.d.ts +11 -0
  30. package/dist/ecs/useParentName.svelte.js +21 -0
  31. package/dist/ecs/useTarget.svelte.d.ts +10 -0
  32. package/dist/ecs/useTarget.svelte.js +42 -0
  33. package/dist/editing/FrameEditSession.js +6 -6
  34. package/dist/hooks/useArmKinematics.svelte.js +12 -8
  35. package/dist/hooks/useDrawAPI.svelte.js +4 -4
  36. package/dist/hooks/useFrames.svelte.js +3 -3
  37. package/dist/hooks/useGeometries.svelte.js +3 -2
  38. package/dist/hooks/usePointcloudObjects.svelte.js +3 -2
  39. package/dist/hooks/usePointclouds.svelte.js +3 -2
  40. package/dist/hooks/usePose.svelte.js +8 -6
  41. package/dist/snapshot.d.ts +7 -0
  42. package/dist/snapshot.js +74 -1
  43. package/package.json +3 -3
@@ -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 = entity.get(traits.Parent) ?? 'world';
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 = entity.get(traits.Parent) ?? 'world';
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 = entity.get(traits.Parent) ?? 'world';
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 = entity.get(traits.Parent) ?? 'world';
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 = entity.get(traits.Parent) ?? 'world'
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 = entity.get(traits.Parent) ?? 'world'
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 = entity.get(traits.Parent) ?? 'world'
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 = useTrait(() => entity, traits.Parent)
22
+ const parent = useParentName(() => entity)
23
23
  const invisible = useTrait(() => entity, traits.Invisible)
24
24
  const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
25
25
 
@@ -0,0 +1,137 @@
1
+ <!--
2
+ @component
3
+
4
+ A compound capsule via a shared open-ended unit cylinder and two
5
+ hemispheres, each scaled per `r` and `l`, so dimension changes update
6
+ transforms only and cause no geometry rebuild.
7
+
8
+ Viam's capsule `l` is the *total* length, including the rounded caps, so the
9
+ midsection has length `l - 2r`.
10
+ -->
11
+ <script
12
+ module
13
+ lang="ts"
14
+ >
15
+ import { CylinderGeometry, EdgesGeometry, SphereGeometry } from 'three'
16
+
17
+ const unitCylinder = new CylinderGeometry(1, 1, 1, 16, 1, true)
18
+ unitCylinder.rotateX(Math.PI / 2)
19
+
20
+ /**
21
+ * Hemisphere with rounded part toward +Z and an open boundary on the XY plane.
22
+ * 6 height segments matches the existing sphere's density (`Mesh.svelte` uses `SphereGeometry(1, 16, 12)`).
23
+ */
24
+ const unitHemisphere = new SphereGeometry(1, 16, 6, 0, Math.PI * 2, 0, Math.PI / 2)
25
+ unitHemisphere.rotateX(Math.PI / 2)
26
+
27
+ const unitCylinderEdges = new EdgesGeometry(unitCylinder, 0)
28
+ const unitHemisphereEdges = new EdgesGeometry(unitHemisphere, 0)
29
+ </script>
30
+
31
+ <script lang="ts">
32
+ import type { ColorRepresentation } from 'three'
33
+
34
+ import { T, useThrelte } from '@threlte/core'
35
+ import { LineBasicMaterial, MeshToonMaterial } from 'three'
36
+
37
+ import { darkenColor } from '../../color'
38
+
39
+ interface Props {
40
+ r: number
41
+ l: number
42
+ color: ColorRepresentation
43
+ opacity?: number
44
+ depthTest?: boolean
45
+ }
46
+
47
+ let { r, l, color, opacity = 1, depthTest = true }: Props = $props()
48
+
49
+ const { invalidate } = useThrelte()
50
+ const material = new MeshToonMaterial()
51
+ const lineMaterial = new LineBasicMaterial()
52
+
53
+ $effect(() => {
54
+ material.color.set(color)
55
+ lineMaterial.color.set(darkenColor(color, 10))
56
+ invalidate()
57
+ })
58
+
59
+ $effect(() => {
60
+ const isTransparent = opacity < 1
61
+ material.opacity = opacity
62
+ material.depthWrite = opacity === 1
63
+ material.depthTest = depthTest
64
+ lineMaterial.depthTest = depthTest
65
+ if (material.transparent !== isTransparent) {
66
+ material.transparent = isTransparent
67
+ material.needsUpdate = true
68
+ }
69
+ invalidate()
70
+ })
71
+
72
+ const midsection = $derived(Math.max(0, l - 2 * r))
73
+ const halfMid = $derived(midsection / 2)
74
+ </script>
75
+
76
+ {#if midsection > 0}
77
+ <T.Mesh scale={[r, r, midsection]}>
78
+ <T
79
+ is={unitCylinder}
80
+ dispose={false}
81
+ />
82
+ <T is={material} />
83
+ <T.LineSegments
84
+ raycast={() => null}
85
+ bvh={{ enabled: false }}
86
+ >
87
+ <T
88
+ is={unitCylinderEdges}
89
+ dispose={false}
90
+ />
91
+ <T is={lineMaterial} />
92
+ </T.LineSegments>
93
+ </T.Mesh>
94
+ {/if}
95
+
96
+ <T.Mesh
97
+ position={[0, 0, halfMid]}
98
+ scale={r}
99
+ >
100
+ <T
101
+ is={unitHemisphere}
102
+ dispose={false}
103
+ />
104
+ <T is={material} />
105
+ <T.LineSegments
106
+ raycast={() => null}
107
+ bvh={{ enabled: false }}
108
+ >
109
+ <T
110
+ is={unitHemisphereEdges}
111
+ dispose={false}
112
+ />
113
+ <T is={lineMaterial} />
114
+ </T.LineSegments>
115
+ </T.Mesh>
116
+
117
+ <T.Mesh
118
+ position={[0, 0, -halfMid]}
119
+ rotation={[Math.PI, 0, 0]}
120
+ scale={[r, r, r]}
121
+ >
122
+ <T
123
+ is={unitHemisphere}
124
+ dispose={false}
125
+ />
126
+ <T is={material} />
127
+ <T.LineSegments
128
+ raycast={() => null}
129
+ bvh={{ enabled: false }}
130
+ >
131
+ <T
132
+ is={unitHemisphereEdges}
133
+ dispose={false}
134
+ />
135
+ <T is={lineMaterial} />
136
+ </T.LineSegments>
137
+ </T.Mesh>
@@ -0,0 +1,19 @@
1
+ import type { ColorRepresentation } from 'three';
2
+ interface Props {
3
+ r: number;
4
+ l: number;
5
+ color: ColorRepresentation;
6
+ opacity?: number;
7
+ depthTest?: boolean;
8
+ }
9
+ /**
10
+ * A compound capsule via a shared open-ended unit cylinder and two
11
+ * hemispheres, each scaled per `r` and `l`, so dimension changes update
12
+ * transforms only and cause no geometry rebuild.
13
+ *
14
+ * Viam's capsule `l` is the *total* length, including the rounded caps, so the
15
+ * midsection has length `l - 2r`.
16
+ */
17
+ declare const Capsule: import("svelte").Component<Props, {}, "">;
18
+ type Capsule = ReturnType<typeof Capsule>;
19
+ export default Capsule;
@@ -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 = useTrait(() => entity, traits.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 = useTrait(() => entity, traits.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 = useTrait(() => entity, traits.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 = useTrait(() => entity, traits.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)
@@ -1,20 +1,36 @@
1
+ <script
2
+ module
3
+ lang="ts"
4
+ >
5
+ import { BoxGeometry, EdgesGeometry, SphereGeometry } from 'three'
6
+
7
+ /**
8
+ * Shared unit geometries — every mesh references these and sets
9
+ * dimensions through `mesh.scale`, so resizing never rebuilds GPU buffers.
10
+ */
11
+ const unitBox = new BoxGeometry(1, 1, 1)
12
+ const unitSphere = new SphereGeometry(1, 16, 12)
13
+ const unitBoxEdges = new EdgesGeometry(unitBox, 0)
14
+ const unitSphereEdges = new EdgesGeometry(unitSphere, 0)
15
+ </script>
16
+
1
17
  <script lang="ts">
2
18
  import type { Pose } from '@viamrobotics/sdk'
3
19
  import type { Entity } from 'koota'
4
20
 
5
21
  import { T, type Props as ThrelteProps, useThrelte } from '@threlte/core'
6
22
  import { type Snippet } from 'svelte'
7
- import { BufferGeometry, Color, DoubleSide, FrontSide, Material, Mesh } from 'three'
23
+ import { Color, DoubleSide, FrontSide, Group, Material, Mesh } from 'three'
8
24
 
9
25
  import { asColor } from '../../buffer'
10
26
  import { colors, darkenColor } from '../../color'
11
27
  import { traits, useTrait } from '../../ecs'
12
- import { CapsuleGeometry } from '../../three/CapsuleGeometry'
13
28
  import { poseToObject3d } from '../../transform'
14
29
 
15
30
  import AxesHelper from '../AxesHelper.svelte'
31
+ import Capsule from './Capsule.svelte'
16
32
 
17
- interface Props extends ThrelteProps<Mesh> {
33
+ interface Props extends Omit<ThrelteProps<Mesh>, 'ref'> {
18
34
  entity: Entity
19
35
  color?: string
20
36
  center?: Pose
@@ -56,6 +72,8 @@
56
72
 
57
73
  const currentOpacity = $derived(opacity.current ?? 0.7)
58
74
 
75
+ const isCapsule = $derived(capsule.current !== undefined)
76
+
59
77
  let material = $state.raw<Material>(new Material())
60
78
  $effect(() => {
61
79
  const isTransparent = currentOpacity < 1
@@ -69,83 +87,118 @@
69
87
  })
70
88
 
71
89
  const mesh = new Mesh()
72
- $effect.pre(() => {
90
+ const group = new Group()
91
+
92
+ $effect(() => {
93
+ const target = isCapsule ? group : mesh
73
94
  if (center) {
74
- poseToObject3d(center, mesh)
95
+ poseToObject3d(center, target)
75
96
  invalidate()
76
97
  }
77
98
  })
78
99
 
79
- let geo = $state.raw<BufferGeometry>()
80
- $effect.pre(() => {
81
- if (!box.current && !sphere.current && !capsule.current && !bufferGeometry.current) {
82
- geo = undefined
100
+ $effect(() => {
101
+ if (box.current) {
102
+ const { x, y, z } = box.current
103
+ mesh.scale.set(x * 0.001, y * 0.001, z * 0.001)
104
+ } else if (sphere.current) {
105
+ mesh.scale.setScalar((sphere.current.r ?? 0) * 0.001)
106
+ } else {
107
+ mesh.scale.set(1, 1, 1)
83
108
  }
109
+ invalidate()
84
110
  })
85
-
86
- const oncreate = (bufferGeometry: BufferGeometry) => {
87
- geo = bufferGeometry
88
- }
89
111
  </script>
90
112
 
91
- <T
92
- is={mesh}
93
- name={entity}
94
- userData.name={name}
95
- renderOrder={renderOrder.current}
96
- {...rest}
97
- >
98
- {#if box.current}
99
- {@const { x, y, z } = box.current ?? { x: 0, y: 0, z: 0 }}
100
- <T.BoxGeometry
101
- args={[x * 0.001, y * 0.001, z * 0.001]}
102
- {oncreate}
103
- />
104
- {:else if sphere.current}
105
- {@const { r } = sphere.current ?? { r: 0 }}
106
- <T.SphereGeometry
107
- args={[r * 0.001]}
108
- {oncreate}
109
- />
110
- {:else if capsule.current}
111
- {@const { r, l } = capsule.current ?? { r: 0, l: 0 }}
112
- <T
113
- is={CapsuleGeometry}
114
- args={[r * 0.001, l * 0.001]}
115
- {oncreate}
113
+ {#if isCapsule}
114
+ {@const { r, l } = capsule.current ?? { r: 0, l: 0 }}
115
+ <T
116
+ is={group}
117
+ name={entity}
118
+ userData.name={name}
119
+ renderOrder={renderOrder.current}
120
+ {...rest}
121
+ >
122
+ <Capsule
123
+ r={r * 0.001}
124
+ l={l * 0.001}
125
+ {color}
126
+ opacity={currentOpacity}
127
+ depthTest={materialProps.current?.depthTest ?? true}
116
128
  />
117
- {:else if bufferGeometry.current}
118
- <T
119
- is={bufferGeometry.current}
120
- {oncreate}
129
+
130
+ {@render children?.()}
131
+ </T>
132
+ {:else}
133
+ <T
134
+ is={mesh}
135
+ name={entity}
136
+ userData.name={name}
137
+ renderOrder={renderOrder.current}
138
+ {...rest}
139
+ >
140
+ {#if box.current}
141
+ <T
142
+ is={unitBox}
143
+ dispose={false}
144
+ />
145
+ <T.LineSegments
146
+ raycast={() => null}
147
+ bvh={{ enabled: false }}
148
+ >
149
+ <T
150
+ is={unitBoxEdges}
151
+ dispose={false}
152
+ />
153
+ <T.LineBasicMaterial color={darkenColor(color, 10)} />
154
+ </T.LineSegments>
155
+ {:else if sphere.current}
156
+ <T
157
+ is={unitSphere}
158
+ dispose={false}
159
+ />
160
+ <T.LineSegments
161
+ raycast={() => null}
162
+ bvh={{ enabled: false }}
163
+ >
164
+ <T
165
+ is={unitSphereEdges}
166
+ dispose={false}
167
+ />
168
+ <T.LineBasicMaterial color={darkenColor(color, 10)} />
169
+ </T.LineSegments>
170
+ {:else if bufferGeometry.current}
171
+ <T is={bufferGeometry.current}>
172
+ {#snippet children({ ref: geo })}
173
+ <!--
174
+ TODO(mp) currently some bufferGeometries are coming in empty,
175
+ this is a quick fix but this should be handled upstream
176
+ -->
177
+ {#if geo.getAttribute('position').array.length > 0}
178
+ <T.LineSegments
179
+ raycast={() => null}
180
+ bvh={{ enabled: false }}
181
+ >
182
+ <T.EdgesGeometry args={[geo, 0]} />
183
+ <T.LineBasicMaterial color={darkenColor(color, 10)} />
184
+ </T.LineSegments>
185
+ {/if}
186
+ {/snippet}
187
+ </T>
188
+ {/if}
189
+
190
+ <T.MeshToonMaterial
191
+ {color}
192
+ side={bufferGeometry.current ? DoubleSide : FrontSide}
193
+ depthTest={materialProps.current?.depthTest ?? true}
194
+ oncreate={(m) => {
195
+ material = m
196
+ }}
121
197
  />
122
- {/if}
123
-
124
- <T.MeshToonMaterial
125
- {color}
126
- side={bufferGeometry.current ? DoubleSide : FrontSide}
127
- depthTest={materialProps.current?.depthTest ?? true}
128
- oncreate={(m) => {
129
- material = m
130
- }}
131
- />
132
198
 
133
- <!--
134
- TODO(mp) currently some bufferGeometries are coming in empty,
135
- this is a quick fix but this should be handled upstream
136
- -->
137
- {#if geo && geo.getAttribute('position').array.length > 0}
138
- <T.LineSegments
139
- raycast={() => null}
140
- bvh={{ enabled: false }}
141
- >
142
- <T.EdgesGeometry args={[geo, 0]} />
143
- <T.LineBasicMaterial color={darkenColor(color, 10)} />
144
- </T.LineSegments>
145
- {/if}
146
-
147
- {@render children?.()}
148
- </T>
199
+ {@render children?.()}
200
+ </T>
201
+ {/if}
149
202
 
150
203
  {#if showAxesHelper.current}
151
204
  <AxesHelper
@@ -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 = useTrait(() => entity, traits.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 = useTrait(() => entity, traits.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, spawnSnapshotEntities } from '../snapshot'
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 entities: SnapshotEntity[] = []
39
+ let entitiesByUuid = new Map<string, SnapshotEntity>()
40
+ let unkeyedEntities: SnapshotEntity[] = []
40
41
 
41
42
  $effect(() => {
42
- world.id.toString()
43
- snapshot.uuid.toString()
43
+ void snapshot
44
44
 
45
45
  untrack(() => {
46
- entities = spawnSnapshotEntities(world, snapshot)
47
- for (const spawned of entities) {
48
- relationships.apply(spawned.entity, spawned.relationships)
49
- const uuid = spawned.entity.get(traits.UUID)
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 spawned of entities) {
87
- if (world.has(spawned.entity)) spawned.entity.destroy()
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>