@viamrobotics/motion-tools 1.26.1 → 1.26.2

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 (51) hide show
  1. package/dist/FrameConfigUpdater.svelte.js +42 -29
  2. package/dist/buf/common/v1/common_pb.d.ts +19 -0
  3. package/dist/buf/common/v1/common_pb.js +32 -0
  4. package/dist/components/BatchedArrows.svelte +31 -15
  5. package/dist/components/Entities/Entities.svelte +3 -8
  6. package/dist/components/Entities/Frame.svelte +25 -9
  7. package/dist/components/Entities/Frame.svelte.d.ts +0 -2
  8. package/dist/components/Entities/GLTF.svelte +5 -4
  9. package/dist/components/Entities/Line.svelte +5 -4
  10. package/dist/components/Entities/Mesh.svelte +12 -18
  11. package/dist/components/Entities/Points.svelte +5 -4
  12. package/dist/components/Entities/Pose.svelte +17 -24
  13. package/dist/components/Entities/Pose.svelte.d.ts +1 -4
  14. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +40 -41
  15. package/dist/components/SceneProviders.svelte +2 -1
  16. package/dist/components/SelectedTransformControls.svelte +57 -34
  17. package/dist/components/StaticGeometries.svelte +1 -1
  18. package/dist/components/hover/HoveredEntity.svelte +33 -3
  19. package/dist/components/hover/LinkedHoveredEntity.svelte +2 -3
  20. package/dist/components/overlay/Details.svelte +72 -94
  21. package/dist/components/overlay/__tests__/__fixtures__/entity.js +14 -17
  22. package/dist/components/overlay/left-pane/Tree.svelte +9 -9
  23. package/dist/components/overlay/left-pane/Tree.svelte.d.ts +1 -2
  24. package/dist/components/overlay/left-pane/TreeContainer.svelte +4 -15
  25. package/dist/components/overlay/left-pane/TreeNode.svelte +1 -1
  26. package/dist/components/overlay/left-pane/TreeNode.svelte.d.ts +1 -1
  27. package/dist/components/overlay/left-pane/useTree.svelte.d.ts +14 -0
  28. package/dist/components/overlay/left-pane/useTree.svelte.js +63 -0
  29. package/dist/draw.js +21 -7
  30. package/dist/ecs/index.d.ts +1 -0
  31. package/dist/ecs/index.js +1 -0
  32. package/dist/ecs/provideWorldMatrix.svelte.d.ts +8 -0
  33. package/dist/ecs/provideWorldMatrix.svelte.js +13 -0
  34. package/dist/ecs/traits.d.ts +41 -45
  35. package/dist/ecs/traits.js +57 -28
  36. package/dist/ecs/useTrait.svelte.d.ts +1 -6
  37. package/dist/ecs/useTrait.svelte.js +21 -13
  38. package/dist/ecs/worldMatrix.d.ts +10 -0
  39. package/dist/ecs/worldMatrix.js +148 -0
  40. package/dist/editing/FrameEditSession.js +31 -18
  41. package/dist/hooks/use3DModels.svelte.js +1 -1
  42. package/dist/hooks/useConfigFrames.svelte.js +12 -0
  43. package/dist/hooks/useDrawAPI.svelte.js +14 -6
  44. package/dist/hooks/useFrames.svelte.js +23 -11
  45. package/dist/hooks/useGeometries.svelte.js +4 -2
  46. package/dist/hooks/usePartConfig.svelte.js +38 -3
  47. package/dist/hooks/useWorldState.svelte.js +10 -2
  48. package/dist/transform.js +55 -21
  49. package/package.json +3 -3
  50. package/dist/components/overlay/left-pane/buildTree.d.ts +0 -13
  51. 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
  }
@@ -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
  */
@@ -3,10 +3,11 @@
3
3
 
4
4
  import { T } from '@threlte/core'
5
5
  import { Portal } from '@threlte/extras'
6
- import { Color, Vector3 } from 'three'
6
+ import { Color, Quaternion, Vector3 } from 'three'
7
7
 
8
8
  import { hierarchy, traits, useWorld } from '../ecs'
9
9
  import { BatchedArrow } from '../three/BatchedArrow'
10
+ import { OrientationVector } from '../three/OrientationVector'
10
11
 
11
12
  const arrowBatchMap = $state<Record<string, BatchedArrow>>({
12
13
  world: new BatchedArrow(),
@@ -18,6 +19,22 @@
18
19
  const direction = new Vector3()
19
20
  const origin = new Vector3()
20
21
  const color = new Color()
22
+ const tempQuat = new Quaternion()
23
+ const tempScale = new Vector3()
24
+ const tempOv = new OrientationVector()
25
+
26
+ /**
27
+ * Decompose the matrix directly into the arrow's direction
28
+ * (OV components from the rotation) and origin (translation)
29
+ */
30
+ const decompose = (entity: Entity): boolean => {
31
+ const matrix = entity.get(traits.Matrix)
32
+ if (!matrix) return false
33
+ matrix.decompose(origin, tempQuat, tempScale)
34
+ tempOv.setFromQuaternion(tempQuat)
35
+ direction.set(tempOv.x, tempOv.y, tempOv.z)
36
+ return true
37
+ }
21
38
 
22
39
  const onAdd = (entity: Entity) => {
23
40
  const parent = hierarchy.getParentName(entity) ?? 'world'
@@ -25,32 +42,31 @@
25
42
  arrowBatchMap[parent] ??= new BatchedArrow()
26
43
  const batched = arrowBatchMap[parent]
27
44
 
28
- const pose = entity.get(traits.Pose)
29
45
  const colorRGB = entity.get(traits.Color)
30
46
 
47
+ if (!decompose(entity)) {
48
+ direction.set(0, 0, 0)
49
+ origin.set(0, 0, 0)
50
+ }
51
+
31
52
  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),
53
+ direction,
54
+ origin,
34
55
  colorRGB ? color.set(colorRGB.r, colorRGB.g, colorRGB.b) : color.set('yellow')
35
56
  )
36
57
 
37
58
  entity.add(traits.Instance({ instanceID, meshID: batched.mesh.id }))
38
59
  }
39
60
 
40
- const onPoseChange = (entity: Entity) => {
61
+ const onMatrixChange = (entity: Entity) => {
41
62
  if (!entity.has(traits.Arrow)) return
42
63
 
43
64
  const parent = hierarchy.getParentName(entity) ?? 'world'
44
65
  const batch = arrowBatchMap[parent]
45
66
  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
- )
67
+
68
+ if (instanceID && instanceID !== -1 && decompose(entity)) {
69
+ batch?.updateArrow(instanceID, direction, origin)
54
70
  }
55
71
  }
56
72
 
@@ -81,13 +97,13 @@
81
97
  $effect(() => {
82
98
  const unsubAdd = world.onAdd(traits.Arrow, onAdd)
83
99
  const unsubRemove = world.onRemove(traits.Instance, onInstanceRemove)
84
- const unsubPoseChange = world.onChange(traits.Pose, onPoseChange)
100
+ const unsubMatrixChange = world.onChange(traits.Matrix, onMatrixChange)
85
101
  const unsubColorChange = world.onChange(traits.Color, onColorChange)
86
102
 
87
103
  return () => {
88
104
  unsubAdd()
89
105
  unsubRemove()
90
- unsubPoseChange()
106
+ unsubMatrixChange()
91
107
  unsubColorChange()
92
108
  }
93
109
  })
@@ -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,7 +10,6 @@ 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
 
@@ -22,18 +21,17 @@ Renders a Viam Frame object
22
21
  import { colors, resourceColors } from '../../color'
23
22
  import { traits, useParentName, useTrait } from '../../ecs'
24
23
  import { useResourceByName } from '../../hooks/useResourceByName.svelte'
25
- import { poseToObject3d } from '../../transform'
24
+ import { composeLocalMatrix } from '../../transform'
26
25
 
27
26
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
28
27
  import Mesh from './Mesh.svelte'
29
28
 
30
29
  interface Props {
31
30
  entity: Entity
32
- pose?: Pose
33
31
  children?: Snippet<[{ ref: Object3D }]>
34
32
  }
35
33
 
36
- let { entity, pose, children }: Props = $props()
34
+ let { entity, children }: Props = $props()
37
35
 
38
36
  const { invalidate } = useThrelte()
39
37
  const resourceByName = useResourceByName()
@@ -42,7 +40,9 @@ Renders a Viam Frame object
42
40
  const parent = useParentName(() => entity)
43
41
  const entityColors = useTrait(() => entity, traits.Colors)
44
42
  const entityColor = useTrait(() => entity, traits.Color)
45
- const entityPose = useTrait(() => entity, traits.Pose)
43
+ const matrix = useTrait(() => entity, traits.Matrix)
44
+ const editedMatrix = useTrait(() => entity, traits.EditedMatrix)
45
+ const liveMatrix = useTrait(() => entity, traits.LiveMatrix)
46
46
  const center = useTrait(() => entity, traits.Center)
47
47
  const invisible = useTrait(() => entity, traits.Invisible)
48
48
 
@@ -68,13 +68,29 @@ Renders a Viam Frame object
68
68
  })
69
69
 
70
70
  const group = new Group()
71
+ group.matrixAutoUpdate = false
71
72
 
72
- const resolvedPose = $derived(pose ?? entityPose.current)
73
73
  $effect.pre(() => {
74
- if (resolvedPose) {
75
- poseToObject3d(resolvedPose, group)
76
- invalidate()
74
+ if (liveMatrix.current && matrix.current && editedMatrix.current) {
75
+ composeLocalMatrix(liveMatrix.current, matrix.current, editedMatrix.current, group.matrix)
76
+ } else if (editedMatrix.current) {
77
+ group.matrix.copy(editedMatrix.current)
78
+ } else if (matrix.current) {
79
+ group.matrix.copy(matrix.current)
80
+ } else {
81
+ return
77
82
  }
83
+
84
+ /**
85
+ * Keep position/quaternion/scale in sync with matrix so TransformControls
86
+ * (which reads/writes those fields) sees the entity's actual transform on
87
+ * drag start. Without this, the gizmo applies its drag delta against an
88
+ * identity baseline and the frame snaps to identity on first onChange.
89
+ */
90
+ group.matrix.decompose(group.position, group.quaternion, group.scale)
91
+
92
+ group.updateMatrixWorld()
93
+ invalidate()
78
94
  })
79
95
  </script>
80
96
 
@@ -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
  }]>;
@@ -20,7 +20,6 @@
20
20
  import { Group, type Object3D } from 'three'
21
21
 
22
22
  import { traits, useParentName, useTrait } from '../../ecs'
23
- import { poseToObject3d } from '../../transform'
24
23
 
25
24
  import AxesHelper from '../AxesHelper.svelte'
26
25
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
@@ -36,7 +35,7 @@
36
35
 
37
36
  const name = useTrait(() => entity, traits.Name)
38
37
  const parent = useParentName(() => entity)
39
- const pose = useTrait(() => entity, traits.Pose)
38
+ const matrix = useTrait(() => entity, traits.Matrix)
40
39
  const gltfTrait = useTrait(() => entity, traits.GLTF)
41
40
  const scale = useTrait(() => entity, traits.Scale)
42
41
  const invisible = useTrait(() => entity, traits.Invisible)
@@ -46,10 +45,12 @@
46
45
  const animationName = $derived(gltfTrait.current?.animationName)
47
46
 
48
47
  const group = new Group()
48
+ group.matrixAutoUpdate = false
49
49
 
50
50
  $effect.pre(() => {
51
- if (pose.current) {
52
- poseToObject3d(pose.current, group)
51
+ if (matrix.current) {
52
+ group.matrix.copy(matrix.current)
53
+ group.updateMatrixWorld()
53
54
  }
54
55
  })
55
56
 
@@ -8,7 +8,6 @@
8
8
 
9
9
  import { isVertexColors, STRIDE } from '../../buffer'
10
10
  import { traits, useParentName, useTrait } from '../../ecs'
11
- import { poseToObject3d } from '../../transform'
12
11
 
13
12
  import AxesHelper from '../AxesHelper.svelte'
14
13
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
@@ -25,7 +24,7 @@
25
24
  const { invalidate } = useThrelte()
26
25
  const name = useTrait(() => entity, traits.Name)
27
26
  const parent = useParentName(() => entity)
28
- const pose = useTrait(() => entity, traits.Pose)
27
+ const matrix = useTrait(() => entity, traits.Matrix)
29
28
  const color = useTrait(() => entity, traits.Color)
30
29
  const colors = useTrait(() => entity, traits.Colors)
31
30
  const dotColors = useTrait(() => entity, traits.DotColors)
@@ -63,10 +62,12 @@
63
62
  const currentOpacity = $derived(opacity.current ?? 0.7)
64
63
 
65
64
  const mesh = new Line2()
65
+ mesh.matrixAutoUpdate = false
66
66
 
67
67
  $effect.pre(() => {
68
- if (pose.current) {
69
- poseToObject3d(pose.current, mesh)
68
+ if (matrix.current) {
69
+ mesh.matrix.copy(matrix.current)
70
+ mesh.updateMatrixWorld()
70
71
  invalidate()
71
72
  }
72
73
  })
@@ -137,9 +137,18 @@
137
137
  renderOrder={renderOrder.current}
138
138
  {...rest}
139
139
  >
140
- {#if box.current}
140
+ {#if box.current || sphere.current}
141
+ {@const meshGeometry = box.current ? unitBox : unitSphere}
142
+ {@const edgesGeometry = box.current ? unitBoxEdges : unitSphereEdges}
143
+ <!--
144
+ Switch via a derived `is` on the same <T> so `useAttach`'s effect
145
+ cleanup runs before the new attach. Splitting these across two
146
+ branches of an {#if}/{:else if} races mount-new against unmount-old:
147
+ the new attach saves `mesh.geometry`, then the old cleanup restores
148
+ it to the pre-attach value (null), leaving the mesh geometryless.
149
+ -->
141
150
  <T
142
- is={unitBox}
151
+ is={meshGeometry}
143
152
  dispose={false}
144
153
  />
145
154
  <T.LineSegments
@@ -147,22 +156,7 @@
147
156
  bvh={{ enabled: false }}
148
157
  >
149
158
  <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}
159
+ is={edgesGeometry}
166
160
  dispose={false}
167
161
  />
168
162
  <T.LineBasicMaterial color={darkenColor(color, 10)} />
@@ -9,7 +9,6 @@
9
9
  import { asColor, isSingleColor } from '../../buffer'
10
10
  import { traits, useParentName, useTrait } from '../../ecs'
11
11
  import { useSettings } from '../../hooks/useSettings.svelte'
12
- import { poseToObject3d } from '../../transform'
13
12
 
14
13
  import AxesHelper from '../AxesHelper.svelte'
15
14
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
@@ -25,7 +24,7 @@
25
24
  const settings = useSettings()
26
25
 
27
26
  const parent = useParentName(() => entity)
28
- const pose = useTrait(() => entity, traits.Pose)
27
+ const matrix = useTrait(() => entity, traits.Matrix)
29
28
  const geometry = useTrait(() => entity, traits.BufferGeometry)
30
29
  const entityColor = useTrait(() => entity, traits.Color)
31
30
  const colors = useTrait(() => entity, traits.Colors)
@@ -42,6 +41,7 @@
42
41
  const orthographic = $derived(settings.current.cameraMode === 'orthographic')
43
42
 
44
43
  const points = new Points()
44
+ points.matrixAutoUpdate = false
45
45
  const material = points.material as PointsMaterial
46
46
  material.toneMapped = false
47
47
 
@@ -98,8 +98,9 @@
98
98
  })
99
99
 
100
100
  $effect.pre(() => {
101
- if (pose.current) {
102
- poseToObject3d(pose.current, points)
101
+ if (matrix.current) {
102
+ points.matrix.copy(matrix.current)
103
+ points.updateMatrixWorld()
103
104
  }
104
105
  })
105
106
 
@@ -1,52 +1,45 @@
1
1
  <script lang="ts">
2
- import type { Pose } from '@viamrobotics/sdk'
3
2
  import type { Entity } from 'koota'
4
3
  import type { Snippet } from 'svelte'
5
4
 
5
+ import { Matrix4 } from 'three'
6
+
6
7
  import { traits, useParentName, useTrait } from '../../ecs'
7
- import { usePartConfig } from '../../hooks/usePartConfig.svelte'
8
8
  import { usePose } from '../../hooks/usePose.svelte'
9
- import { composeRenderedPose } from '../../transform'
9
+ import { poseToMatrix } from '../../transform'
10
10
 
11
11
  interface Props {
12
12
  entity: Entity
13
- children: Snippet<[{ pose: Pose | undefined }]>
13
+ children: Snippet
14
14
  }
15
15
  let { entity, children }: Props = $props()
16
16
 
17
- const partConfig = usePartConfig()
18
17
  const name = useTrait(() => entity, traits.Name)
19
18
  const parent = useParentName(() => entity)
20
- const editedPose = useTrait(() => entity, traits.EditedPose)
21
- const entityPose = useTrait(() => entity, traits.Pose)
22
19
 
23
20
  const pose = usePose(
24
21
  () => name.current,
25
22
  () => parent.current
26
23
  )
27
24
 
25
+ /**
26
+ * Mirror the robot's live kinematics-resolved pose into LiveMatrix so
27
+ * Frame.svelte can compose the rendered transform via
28
+ * `composeLocalMatrix(live, baseline, edited)`. Mutate the stored
29
+ * `Matrix4` in place when present and notify via `entity.changed` —
30
+ * allocate only on first add.
31
+ */
28
32
  $effect.pre(() => {
29
33
  if (pose.current === undefined) return
30
34
 
31
- if (entity.has(traits.LivePose)) {
32
- entity.set(traits.LivePose, pose.current)
35
+ const live = entity.get(traits.LiveMatrix)
36
+ if (live) {
37
+ poseToMatrix(pose.current, live)
38
+ entity.changed(traits.LiveMatrix)
33
39
  } else {
34
- entity.add(traits.LivePose(pose.current))
40
+ entity.add(traits.LiveMatrix(poseToMatrix(pose.current, new Matrix4())))
35
41
  }
36
42
  })
37
-
38
- // Always render through the live blend: live × network⁻¹ × edited. With
39
- // `edited === network` (no edits) this collapses to `live`, so the rendered
40
- // pose tracks the robot's kinematics-resolved position. With edits, the
41
- // formula composes the staged delta on top of live. Input handlers that
42
- // drive edits (gizmo onChange, Details panel) compute `edited` such that
43
- // the blend renders to the user's intent.
44
- const resolvedPose = $derived.by(() => {
45
- if (pose.current === undefined || partConfig.hasPendingSave) return editedPose.current
46
- if (!entityPose.current || !editedPose.current) return undefined
47
-
48
- return composeRenderedPose(pose.current, entityPose.current, editedPose.current)
49
- })
50
43
  </script>
51
44
 
52
- {@render children({ pose: resolvedPose })}
45
+ {@render children()}
@@ -1,11 +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
  interface Props {
5
4
  entity: Entity;
6
- children: Snippet<[{
7
- pose: Pose | undefined;
8
- }]>;
5
+ children: Snippet;
9
6
  }
10
7
  declare const Pose: import("svelte").Component<Props, {}, "">;
11
8
  type Pose = ReturnType<typeof Pose>;