@viamrobotics/motion-tools 1.26.1 → 1.27.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.
Files changed (58) hide show
  1. package/dist/FrameConfigUpdater.svelte.js +42 -29
  2. package/dist/assert.d.ts +13 -0
  3. package/dist/assert.js +20 -0
  4. package/dist/buf/common/v1/common_pb.d.ts +19 -0
  5. package/dist/buf/common/v1/common_pb.js +32 -0
  6. package/dist/components/BatchedArrows.svelte +43 -45
  7. package/dist/components/Entities/Arrows/Arrows.svelte +35 -29
  8. package/dist/components/Entities/Entities.svelte +3 -8
  9. package/dist/components/Entities/Frame.svelte +31 -32
  10. package/dist/components/Entities/Frame.svelte.d.ts +0 -2
  11. package/dist/components/Entities/GLTF.svelte +27 -36
  12. package/dist/components/Entities/Geometry.svelte +35 -24
  13. package/dist/components/Entities/Line.svelte +37 -43
  14. package/dist/components/Entities/Mesh.svelte +12 -18
  15. package/dist/components/Entities/Points.svelte +25 -28
  16. package/dist/components/Entities/Pose.svelte +17 -24
  17. package/dist/components/Entities/Pose.svelte.d.ts +1 -4
  18. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +40 -41
  19. package/dist/components/Scene.svelte +7 -1
  20. package/dist/components/SceneProviders.svelte +2 -1
  21. package/dist/components/SelectedTransformControls.svelte +57 -34
  22. package/dist/components/StaticGeometries.svelte +1 -1
  23. package/dist/components/hover/HoveredEntity.svelte +33 -3
  24. package/dist/components/hover/LinkedHoveredEntity.svelte +2 -3
  25. package/dist/components/overlay/Details.svelte +72 -94
  26. package/dist/components/overlay/__tests__/__fixtures__/entity.js +14 -17
  27. package/dist/components/overlay/left-pane/Tree.svelte +9 -9
  28. package/dist/components/overlay/left-pane/Tree.svelte.d.ts +1 -2
  29. package/dist/components/overlay/left-pane/TreeContainer.svelte +4 -15
  30. package/dist/components/overlay/left-pane/TreeNode.svelte +1 -1
  31. package/dist/components/overlay/left-pane/TreeNode.svelte.d.ts +1 -1
  32. package/dist/components/overlay/left-pane/useTree.svelte.d.ts +14 -0
  33. package/dist/components/overlay/left-pane/useTree.svelte.js +63 -0
  34. package/dist/draw.js +24 -9
  35. package/dist/ecs/index.d.ts +1 -0
  36. package/dist/ecs/index.js +1 -0
  37. package/dist/ecs/provideWorldMatrix.svelte.d.ts +8 -0
  38. package/dist/ecs/provideWorldMatrix.svelte.js +13 -0
  39. package/dist/ecs/traits.d.ts +41 -50
  40. package/dist/ecs/traits.js +57 -29
  41. package/dist/ecs/useTrait.svelte.d.ts +1 -6
  42. package/dist/ecs/useTrait.svelte.js +21 -13
  43. package/dist/ecs/worldMatrix.d.ts +10 -0
  44. package/dist/ecs/worldMatrix.js +138 -0
  45. package/dist/editing/FrameEditSession.js +31 -18
  46. package/dist/hooks/use3DModels.svelte.js +1 -1
  47. package/dist/hooks/useConfigFrames.svelte.js +12 -0
  48. package/dist/hooks/useDrawAPI.svelte.js +14 -6
  49. package/dist/hooks/useDrawService.svelte.js +4 -7
  50. package/dist/hooks/useFrames.svelte.js +23 -11
  51. package/dist/hooks/useGeometries.svelte.js +11 -3
  52. package/dist/hooks/usePartConfig.svelte.js +43 -6
  53. package/dist/hooks/useWorldState.svelte.js +10 -2
  54. package/dist/plugins/bvh.svelte.js +37 -26
  55. package/dist/transform.js +55 -21
  56. package/package.json +3 -3
  57. package/dist/components/overlay/left-pane/buildTree.d.ts +0 -13
  58. package/dist/components/overlay/left-pane/buildTree.js +0 -48
@@ -1,4 +1,6 @@
1
1
  import { hierarchy, traits } from './ecs';
2
+ import { createPose, matrixToPose, poseToMatrix } from './transform';
3
+ const tempPose = createPose();
2
4
  export class FrameConfigUpdater {
3
5
  updateFrame;
4
6
  removeFrame;
@@ -10,19 +12,22 @@ export class FrameConfigUpdater {
10
12
  const { x, y, z } = position;
11
13
  if (x === undefined && y === undefined && z === undefined)
12
14
  return;
13
- const change = {};
15
+ const current = entity.get(traits.EditedMatrix);
16
+ if (!current)
17
+ return;
18
+ matrixToPose(current, tempPose);
14
19
  if (x !== undefined)
15
- change.x = x;
20
+ tempPose.x = x;
16
21
  if (y !== undefined)
17
- change.y = y;
22
+ tempPose.y = y;
18
23
  if (z !== undefined)
19
- change.z = z;
20
- entity.set(traits.EditedPose, change);
24
+ tempPose.z = z;
25
+ poseToMatrix(tempPose, current);
26
+ entity.changed(traits.EditedMatrix);
21
27
  const name = entity.get(traits.Name);
22
28
  const parent = hierarchy.getParentName(entity) ?? 'world';
23
- const updatedPose = entity.get(traits.EditedPose);
24
- if (name && updatedPose) {
25
- this.updateFrame(name, parent, updatedPose);
29
+ if (name) {
30
+ this.updateFrame(name, parent, { ...tempPose });
26
31
  }
27
32
  };
28
33
  updateLocalOrientation = (entity, orientation) => {
@@ -30,27 +35,32 @@ export class FrameConfigUpdater {
30
35
  if (oX === undefined && oY === undefined && oZ === undefined && theta === undefined) {
31
36
  return;
32
37
  }
33
- const change = {};
38
+ const current = entity.get(traits.EditedMatrix);
39
+ if (!current)
40
+ return;
41
+ matrixToPose(current, tempPose);
34
42
  if (oX !== undefined)
35
- change.oX = oX;
43
+ tempPose.oX = oX;
36
44
  if (oY !== undefined)
37
- change.oY = oY;
45
+ tempPose.oY = oY;
38
46
  if (oZ !== undefined)
39
- change.oZ = oZ;
47
+ tempPose.oZ = oZ;
40
48
  if (theta !== undefined)
41
- change.theta = theta;
42
- entity.set(traits.EditedPose, change);
49
+ tempPose.theta = theta;
50
+ poseToMatrix(tempPose, current);
51
+ entity.changed(traits.EditedMatrix);
43
52
  const name = entity.get(traits.Name);
44
53
  const parent = hierarchy.getParentName(entity) ?? 'world';
45
- const updatedPose = entity.get(traits.EditedPose);
46
- if (name && updatedPose) {
47
- this.updateFrame(name, parent, updatedPose);
54
+ if (name) {
55
+ this.updateFrame(name, parent, { ...tempPose });
48
56
  }
49
57
  };
50
58
  updateGeometry = (entity, geometry) => {
51
59
  const name = entity.get(traits.Name);
52
60
  const parent = hierarchy.getParentName(entity) ?? 'world';
53
- const pose = entity.get(traits.EditedPose);
61
+ const matrix = entity.get(traits.EditedMatrix);
62
+ if (matrix)
63
+ matrixToPose(matrix, tempPose);
54
64
  if (geometry?.type === 'box') {
55
65
  const { x, y, z } = geometry;
56
66
  if (x === undefined && y === undefined && z === undefined)
@@ -64,8 +74,8 @@ export class FrameConfigUpdater {
64
74
  change.z = z;
65
75
  entity.set(traits.Box, change);
66
76
  const box = entity.get(traits.Box);
67
- if (name && box && pose) {
68
- this.updateFrame(name, parent, pose, { type: 'box', ...box });
77
+ if (name && box && matrix) {
78
+ this.updateFrame(name, parent, { ...tempPose }, { type: 'box', ...box });
69
79
  }
70
80
  }
71
81
  else if (geometry?.type === 'sphere') {
@@ -74,8 +84,8 @@ export class FrameConfigUpdater {
74
84
  return;
75
85
  entity.set(traits.Sphere, { r });
76
86
  const sphere = entity.get(traits.Sphere);
77
- if (name && sphere && pose) {
78
- this.updateFrame(name, parent, pose, { type: 'sphere', ...sphere });
87
+ if (name && sphere && matrix) {
88
+ this.updateFrame(name, parent, { ...tempPose }, { type: 'sphere', ...sphere });
79
89
  }
80
90
  }
81
91
  else if (geometry?.type === 'capsule') {
@@ -89,16 +99,17 @@ export class FrameConfigUpdater {
89
99
  change.l = l;
90
100
  entity.set(traits.Capsule, change);
91
101
  const capsule = entity.get(traits.Capsule);
92
- if (name && capsule && pose) {
93
- this.updateFrame(name, parent, pose, { type: 'capsule', ...capsule });
102
+ if (name && capsule && matrix) {
103
+ this.updateFrame(name, parent, { ...tempPose }, { type: 'capsule', ...capsule });
94
104
  }
95
105
  }
96
106
  };
97
107
  setFrameParent = (entity, parentName) => {
98
108
  const name = entity.get(traits.Name);
99
- const pose = entity.get(traits.EditedPose);
100
- if (name && pose) {
101
- this.updateFrame(name, parentName, pose);
109
+ const matrix = entity.get(traits.EditedMatrix);
110
+ if (name && matrix) {
111
+ matrixToPose(matrix, tempPose);
112
+ this.updateFrame(name, parentName, { ...tempPose });
102
113
  }
103
114
  };
104
115
  deleteFrame = (entity) => {
@@ -110,9 +121,11 @@ export class FrameConfigUpdater {
110
121
  setGeometryType = (entity, type) => {
111
122
  const name = entity.get(traits.Name);
112
123
  const parent = hierarchy.getParentName(entity) ?? 'world';
113
- const pose = entity.get(traits.EditedPose);
114
- if (!name || !pose)
124
+ const matrix = entity.get(traits.EditedMatrix);
125
+ if (!name || !matrix)
115
126
  return;
127
+ matrixToPose(matrix, tempPose);
128
+ const pose = { ...tempPose };
116
129
  if (type === 'none') {
117
130
  this.updateFrame(name, parent, pose, { type: 'none' });
118
131
  }
@@ -0,0 +1,13 @@
1
+ export declare class AssertionError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ /**
5
+ * Assert that a value is defined.
6
+ *
7
+ * @example
8
+ * const stringify = (value: number | undefined): number => {
9
+ * assertExists(value)
10
+ * return `${value}` // TS now knows that value is of type `number`
11
+ * }
12
+ */
13
+ export declare const assertExists: <T>(value: T, message: string) => asserts value is NonNullable<T>;
package/dist/assert.js ADDED
@@ -0,0 +1,20 @@
1
+ export class AssertionError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'AssertionError';
5
+ }
6
+ }
7
+ /**
8
+ * Assert that a value is defined.
9
+ *
10
+ * @example
11
+ * const stringify = (value: number | undefined): number => {
12
+ * assertExists(value)
13
+ * return `${value}` // TS now knows that value is of type `number`
14
+ * }
15
+ */
16
+ export const assertExists = (value, message) => {
17
+ if (value === null || value === undefined) {
18
+ throw new AssertionError(message);
19
+ }
20
+ };
@@ -855,6 +855,25 @@ export declare class Get3DModelsResponse extends Message<Get3DModelsResponse> {
855
855
  static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Get3DModelsResponse;
856
856
  static equals(a: Get3DModelsResponse | PlainMessage<Get3DModelsResponse> | undefined, b: Get3DModelsResponse | PlainMessage<Get3DModelsResponse> | undefined): boolean;
857
857
  }
858
+ /**
859
+ * @generated from message viam.common.v1.GetWorldPoseResponse
860
+ */
861
+ export declare class GetWorldPoseResponse extends Message<GetWorldPoseResponse> {
862
+ /**
863
+ * Pose of the component in the world reference frame
864
+ *
865
+ * @generated from field: viam.common.v1.Pose pose = 1;
866
+ */
867
+ pose?: Pose;
868
+ constructor(data?: PartialMessage<GetWorldPoseResponse>);
869
+ static readonly runtime: typeof proto3;
870
+ static readonly typeName = "viam.common.v1.GetWorldPoseResponse";
871
+ static readonly fields: FieldList;
872
+ static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetWorldPoseResponse;
873
+ static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetWorldPoseResponse;
874
+ static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetWorldPoseResponse;
875
+ static equals(a: GetWorldPoseResponse | PlainMessage<GetWorldPoseResponse> | undefined, b: GetWorldPoseResponse | PlainMessage<GetWorldPoseResponse> | undefined): boolean;
876
+ }
858
877
  /**
859
878
  * @generated from message viam.common.v1.GetReadingsRequest
860
879
  */
@@ -1260,6 +1260,38 @@ export class Get3DModelsResponse extends Message {
1260
1260
  return proto3.util.equals(Get3DModelsResponse, a, b);
1261
1261
  }
1262
1262
  }
1263
+ /**
1264
+ * @generated from message viam.common.v1.GetWorldPoseResponse
1265
+ */
1266
+ export class GetWorldPoseResponse extends Message {
1267
+ /**
1268
+ * Pose of the component in the world reference frame
1269
+ *
1270
+ * @generated from field: viam.common.v1.Pose pose = 1;
1271
+ */
1272
+ pose;
1273
+ constructor(data) {
1274
+ super();
1275
+ proto3.util.initPartial(data, this);
1276
+ }
1277
+ static runtime = proto3;
1278
+ static typeName = "viam.common.v1.GetWorldPoseResponse";
1279
+ static fields = proto3.util.newFieldList(() => [
1280
+ { no: 1, name: "pose", kind: "message", T: Pose },
1281
+ ]);
1282
+ static fromBinary(bytes, options) {
1283
+ return new GetWorldPoseResponse().fromBinary(bytes, options);
1284
+ }
1285
+ static fromJson(jsonValue, options) {
1286
+ return new GetWorldPoseResponse().fromJson(jsonValue, options);
1287
+ }
1288
+ static fromJsonString(jsonString, options) {
1289
+ return new GetWorldPoseResponse().fromJsonString(jsonString, options);
1290
+ }
1291
+ static equals(a, b) {
1292
+ return proto3.util.equals(GetWorldPoseResponse, a, b);
1293
+ }
1294
+ }
1263
1295
  /**
1264
1296
  * @generated from message viam.common.v1.GetReadingsRequest
1265
1297
  */
@@ -2,103 +2,101 @@
2
2
  import type { Entity } from 'koota'
3
3
 
4
4
  import { T } from '@threlte/core'
5
- import { Portal } from '@threlte/extras'
6
- import { Color, Vector3 } from 'three'
5
+ import { Color, Quaternion, Vector3 } from 'three'
7
6
 
8
- import { hierarchy, traits, useWorld } from '../ecs'
7
+ import { traits, useWorld } from '../ecs'
9
8
  import { BatchedArrow } from '../three/BatchedArrow'
9
+ import { OrientationVector } from '../three/OrientationVector'
10
10
 
11
- const arrowBatchMap = $state<Record<string, BatchedArrow>>({
12
- world: new BatchedArrow(),
13
- })
14
- const batchEntries = $derived(Object.entries(arrowBatchMap))
11
+ const batched = new BatchedArrow()
15
12
 
16
13
  const world = useWorld()
17
14
 
18
15
  const direction = new Vector3()
19
16
  const origin = new Vector3()
20
17
  const color = new Color()
18
+ const tempQuat = new Quaternion()
19
+ const tempScale = new Vector3()
20
+ const tempOv = new OrientationVector()
21
+
22
+ /**
23
+ * Decompose the entity's `WorldMatrix` directly into the arrow's world
24
+ * origin (translation) and direction (OV components from the rotation).
25
+ */
26
+ const decompose = (entity: Entity): boolean => {
27
+ const worldMatrix = entity.get(traits.WorldMatrix)
28
+ if (!worldMatrix) return false
29
+ worldMatrix.decompose(origin, tempQuat, tempScale)
30
+ tempOv.setFromQuaternion(tempQuat)
31
+ direction.set(tempOv.x, tempOv.y, tempOv.z)
32
+ return true
33
+ }
21
34
 
22
35
  const onAdd = (entity: Entity) => {
23
- const parent = hierarchy.getParentName(entity) ?? 'world'
24
-
25
- arrowBatchMap[parent] ??= new BatchedArrow()
26
- const batched = arrowBatchMap[parent]
27
-
28
- const pose = entity.get(traits.Pose)
29
36
  const colorRGB = entity.get(traits.Color)
30
37
 
38
+ if (!decompose(entity)) {
39
+ direction.set(0, 0, 0)
40
+ origin.set(0, 0, 0)
41
+ }
42
+
31
43
  const instanceID = batched.addArrow(
32
- direction.set(pose?.oX ?? 0, pose?.oY ?? 0, pose?.oZ ?? 0),
33
- origin.set(pose?.x ?? 0, pose?.y ?? 0, pose?.z ?? 0).multiplyScalar(0.001),
44
+ direction,
45
+ origin,
34
46
  colorRGB ? color.set(colorRGB.r, colorRGB.g, colorRGB.b) : color.set('yellow')
35
47
  )
36
48
 
37
49
  entity.add(traits.Instance({ instanceID, meshID: batched.mesh.id }))
38
50
  }
39
51
 
40
- const onPoseChange = (entity: Entity) => {
52
+ const onWorldMatrixChange = (entity: Entity) => {
41
53
  if (!entity.has(traits.Arrow)) return
42
54
 
43
- const parent = hierarchy.getParentName(entity) ?? 'world'
44
- const batch = arrowBatchMap[parent]
45
55
  const instanceID = entity.get(traits.Instance)?.instanceID
46
- const pose = entity.get(traits.Pose)
47
-
48
- if (instanceID && instanceID !== -1 && pose) {
49
- batch?.updateArrow(
50
- instanceID,
51
- direction.set(pose.oX, pose.oY, pose.oZ),
52
- origin.set(pose.x, pose.y, pose.z).multiplyScalar(0.001)
53
- )
56
+
57
+ if (instanceID && instanceID !== -1 && decompose(entity)) {
58
+ batched.updateArrow(instanceID, direction, origin)
54
59
  }
55
60
  }
56
61
 
57
62
  const onColorChange = (entity: Entity) => {
58
63
  if (!entity.has(traits.Arrow)) return
59
64
 
60
- const parent = hierarchy.getParentName(entity) ?? 'world'
61
- const batch = arrowBatchMap[parent]
62
65
  const instanceID = entity.get(traits.Instance)?.instanceID
63
66
  const colorRGB = entity.get(traits.Color)
64
67
 
65
68
  if (instanceID && instanceID !== -1 && colorRGB) {
66
69
  color.set(colorRGB.r, colorRGB.g, colorRGB.b)
67
- batch.mesh.setColorAt(instanceID, color)
70
+ batched.mesh.setColorAt(instanceID, color)
68
71
  }
69
72
  }
70
73
 
71
74
  const onInstanceRemove = (entity: Entity) => {
72
75
  const instance = entity.get(traits.Instance)
73
-
74
- for (const [, batch] of batchEntries) {
75
- if (batch.mesh.id === instance?.meshID) {
76
- batch.removeArrow(instance.instanceID)
77
- }
76
+ if (instance && instance.meshID === batched.mesh.id) {
77
+ batched.removeArrow(instance.instanceID)
78
78
  }
79
79
  }
80
80
 
81
81
  $effect(() => {
82
82
  const unsubAdd = world.onAdd(traits.Arrow, onAdd)
83
83
  const unsubRemove = world.onRemove(traits.Instance, onInstanceRemove)
84
- const unsubPoseChange = world.onChange(traits.Pose, onPoseChange)
84
+ const unsubMatrixAdd = world.onAdd(traits.WorldMatrix, onWorldMatrixChange)
85
+ const unsubMatrixChange = world.onChange(traits.WorldMatrix, onWorldMatrixChange)
85
86
  const unsubColorChange = world.onChange(traits.Color, onColorChange)
86
87
 
87
88
  return () => {
88
89
  unsubAdd()
89
90
  unsubRemove()
90
- unsubPoseChange()
91
+ unsubMatrixAdd()
92
+ unsubMatrixChange()
91
93
  unsubColorChange()
92
94
  }
93
95
  })
94
96
  </script>
95
97
 
96
- {#each batchEntries as [parent, batch] (parent)}
97
- <Portal id={parent}>
98
- <T
99
- is={batch.mesh}
100
- dispose={false}
101
- bvh={{ enabled: false }}
102
- />
103
- </Portal>
104
- {/each}
98
+ <T
99
+ is={batched.mesh}
100
+ dispose={false}
101
+ bvh={{ enabled: false }}
102
+ />
@@ -1,14 +1,13 @@
1
1
  <script lang="ts">
2
2
  import type { Entity } from 'koota'
3
3
 
4
- import { T } from '@threlte/core'
5
- import { Portal } from '@threlte/extras'
4
+ import { T, useThrelte } from '@threlte/core'
6
5
 
7
6
  import type { InstancedArrows } from '../../../three/InstancedArrows/InstancedArrows'
8
7
 
9
8
  import AxesHelper from '../../AxesHelper.svelte'
10
9
  import { useEntityEvents } from '../hooks/useEntityEvents.svelte'
11
- import { traits, useParentName, useTrait } from '../../../ecs'
10
+ import { traits, useTrait } from '../../../ecs'
12
11
  import { useFocusedEntity, useSelectedEntity } from '../../../hooks/useSelection.svelte'
13
12
  import { meshBoundsRaycast, raycast } from '../../../three/InstancedArrows/raycast'
14
13
 
@@ -19,7 +18,8 @@
19
18
 
20
19
  let { entity, arrows }: Props = $props()
21
20
 
22
- const parent = useParentName(() => entity)
21
+ const { invalidate } = useThrelte()
22
+ const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
23
23
  const invisible = useTrait(() => entity, traits.Invisible)
24
24
  const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
25
25
 
@@ -35,32 +35,38 @@
35
35
  }
36
36
  return meshBoundsRaycast
37
37
  })
38
+
39
+ $effect.pre(() => {
40
+ arrows.matrixAutoUpdate = false
41
+ if (!worldMatrix.current) return
42
+ arrows.matrix.copy(worldMatrix.current)
43
+ arrows.updateMatrixWorld()
44
+ invalidate()
45
+ })
38
46
  </script>
39
47
 
40
- <Portal id={parent.current}>
48
+ <T
49
+ is={arrows}
50
+ name={entity}
51
+ {...events}
52
+ raycast={raycastFunction}
53
+ visible={invisible.current !== true}
54
+ >
41
55
  <T
42
- is={arrows}
43
- name={entity}
44
- {...events}
45
- raycast={raycastFunction}
46
- visible={invisible.current !== true}
47
- >
48
- <T
49
- is={arrows.headMesh}
50
- bvh={{ enabled: false }}
51
- raycast={() => null}
52
- />
53
- <T
54
- is={arrows.shaftMesh}
55
- bvh={{ enabled: false }}
56
- raycast={() => null}
56
+ is={arrows.headMesh}
57
+ bvh={{ enabled: false }}
58
+ raycast={() => null}
59
+ />
60
+ <T
61
+ is={arrows.shaftMesh}
62
+ bvh={{ enabled: false }}
63
+ raycast={() => null}
64
+ />
65
+ {#if showAxesHelper.current}
66
+ <AxesHelper
67
+ name={entity}
68
+ width={3}
69
+ length={0.1}
57
70
  />
58
- {#if showAxesHelper.current}
59
- <AxesHelper
60
- name={entity}
61
- width={3}
62
- length={0.1}
63
- />
64
- {/if}
65
- </T>
66
- </Portal>
71
+ {/if}
72
+ </T>
@@ -64,14 +64,9 @@
64
64
 
65
65
  {#each machineFramesEntities.current as entity (entity)}
66
66
  <Pose {entity}>
67
- {#snippet children({ pose })}
68
- <Frame
69
- {pose}
70
- {entity}
71
- >
72
- <Label text={entity.get(traits.Name)} />
73
- </Frame>
74
- {/snippet}
67
+ <Frame {entity}>
68
+ <Label text={entity.get(traits.Name)} />
69
+ </Frame>
75
70
  </Pose>
76
71
  {/each}
77
72
 
@@ -10,39 +10,34 @@ Renders a Viam Frame object
10
10
  </script>
11
11
 
12
12
  <script lang="ts">
13
- import type { Pose } from '@viamrobotics/sdk'
14
13
  import type { Entity } from 'koota'
15
14
  import type { Snippet } from 'svelte'
16
15
 
17
16
  import { T, useThrelte } from '@threlte/core'
18
- import { Portal, PortalTarget } from '@threlte/extras'
19
17
  import { Group, type Object3D } from 'three'
20
18
 
21
19
  import { asColor } from '../../buffer'
22
20
  import { colors, resourceColors } from '../../color'
23
- import { traits, useParentName, useTrait } from '../../ecs'
21
+ import { traits, useTrait } from '../../ecs'
24
22
  import { useResourceByName } from '../../hooks/useResourceByName.svelte'
25
- import { poseToObject3d } from '../../transform'
26
23
 
27
24
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
28
25
  import Mesh from './Mesh.svelte'
29
26
 
30
27
  interface Props {
31
28
  entity: Entity
32
- pose?: Pose
33
29
  children?: Snippet<[{ ref: Object3D }]>
34
30
  }
35
31
 
36
- let { entity, pose, children }: Props = $props()
32
+ let { entity, children }: Props = $props()
37
33
 
38
34
  const { invalidate } = useThrelte()
39
35
  const resourceByName = useResourceByName()
40
36
 
41
37
  const name = useTrait(() => entity, traits.Name)
42
- const parent = useParentName(() => entity)
43
38
  const entityColors = useTrait(() => entity, traits.Colors)
44
39
  const entityColor = useTrait(() => entity, traits.Color)
45
- const entityPose = useTrait(() => entity, traits.Pose)
40
+ const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
46
41
  const center = useTrait(() => entity, traits.Center)
47
42
  const invisible = useTrait(() => entity, traits.Invisible)
48
43
 
@@ -68,32 +63,36 @@ Renders a Viam Frame object
68
63
  })
69
64
 
70
65
  const group = new Group()
66
+ group.matrixAutoUpdate = false
71
67
 
72
- const resolvedPose = $derived(pose ?? entityPose.current)
73
68
  $effect.pre(() => {
74
- if (resolvedPose) {
75
- poseToObject3d(resolvedPose, group)
76
- invalidate()
77
- }
69
+ if (!worldMatrix.current) return
70
+
71
+ group.matrix.copy(worldMatrix.current)
72
+
73
+ /**
74
+ * Keep position/quaternion/scale in sync with matrix so TransformControls
75
+ * (which reads/writes those fields) sees the entity's actual transform on
76
+ * drag start. Without this, the gizmo applies its drag delta against an
77
+ * identity baseline and the frame snaps to identity on first onChange.
78
+ */
79
+ group.matrix.decompose(group.position, group.quaternion, group.scale)
80
+
81
+ group.updateMatrixWorld()
82
+ invalidate()
78
83
  })
79
84
  </script>
80
85
 
81
- <Portal id={parent.current}>
82
- <T
83
- is={group}
84
- visible={invisible.current !== true}
85
- >
86
- <Mesh
87
- {entity}
88
- {color}
89
- {...events}
90
- center={center.current}
91
- />
92
-
93
- {#if name.current}
94
- <PortalTarget id={name.current} />
95
- {/if}
96
-
97
- {@render children?.({ ref: group })}
98
- </T>
99
- </Portal>
86
+ <T
87
+ is={group}
88
+ visible={invisible.current !== true}
89
+ >
90
+ <Mesh
91
+ {entity}
92
+ {color}
93
+ {...events}
94
+ center={center.current}
95
+ />
96
+
97
+ {@render children?.({ ref: group })}
98
+ </T>
@@ -1,10 +1,8 @@
1
- import type { Pose } from '@viamrobotics/sdk';
2
1
  import type { Entity } from 'koota';
3
2
  import type { Snippet } from 'svelte';
4
3
  import { type Object3D } from 'three';
5
4
  interface Props {
6
5
  entity: Entity;
7
- pose?: Pose;
8
6
  children?: Snippet<[{
9
7
  ref: Object3D;
10
8
  }]>;