@viamrobotics/motion-tools 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/FrameConfigUpdater.svelte.js +1 -1
  2. package/dist/attribute.js +10 -2
  3. package/dist/buf/draw/v1/service_connect.d.ts +14 -25
  4. package/dist/buf/draw/v1/service_connect.js +14 -25
  5. package/dist/buf/draw/v1/service_pb.d.ts +61 -76
  6. package/dist/buf/draw/v1/service_pb.js +58 -111
  7. package/dist/buffer.d.ts +56 -7
  8. package/dist/buffer.js +70 -12
  9. package/dist/color.js +3 -3
  10. package/dist/components/App.svelte +1 -5
  11. package/dist/components/Camera.svelte +1 -7
  12. package/dist/components/Camera.svelte.d.ts +0 -1
  13. package/dist/components/CameraControls.svelte +2 -2
  14. package/dist/components/{Arrows → Entities/Arrows}/ArrowGroups.svelte +3 -3
  15. package/dist/components/{Arrows → Entities/Arrows}/Arrows.svelte +6 -6
  16. package/dist/components/{Arrows → Entities/Arrows}/Arrows.svelte.d.ts +1 -1
  17. package/dist/components/{Entities.svelte → Entities/Entities.svelte} +7 -3
  18. package/dist/components/Entities/Frame.svelte +86 -0
  19. package/dist/components/{Frame.svelte.d.ts → Entities/Frame.svelte.d.ts} +1 -0
  20. package/dist/components/{GLTF.svelte → Entities/GLTF.svelte} +5 -5
  21. package/dist/components/Entities/Geometry.svelte +75 -0
  22. package/dist/components/Entities/Geometry.svelte.d.ts +10 -0
  23. package/dist/components/{Label.svelte → Entities/Label.svelte} +1 -1
  24. package/dist/components/Entities/Line.svelte +90 -0
  25. package/dist/components/Entities/Mesh.svelte +130 -0
  26. package/dist/components/Entities/Mesh.svelte.d.ts +4 -0
  27. package/dist/components/{Points.svelte → Entities/Points.svelte} +5 -5
  28. package/dist/components/{Pose.svelte → Entities/Pose.svelte} +3 -3
  29. package/dist/{hooks/useObjectEvents.svelte.d.ts → components/Entities/hooks/useEntityEvents.svelte.d.ts} +1 -1
  30. package/dist/{hooks/useObjectEvents.svelte.js → components/Entities/hooks/useEntityEvents.svelte.js} +6 -6
  31. package/dist/components/FileDrop/file-names.js +6 -3
  32. package/dist/components/FileDrop/snapshot-dropper.js +8 -4
  33. package/dist/components/FileDrop/useFileDrop.svelte.js +9 -6
  34. package/dist/components/Lasso/Lasso.svelte +4 -4
  35. package/dist/components/PCD.svelte +14 -6
  36. package/dist/components/PCD.svelte.d.ts +2 -0
  37. package/dist/components/Scene.svelte +1 -3
  38. package/dist/components/Selected.svelte +2 -0
  39. package/dist/components/StaticGeometries.svelte +1 -1
  40. package/dist/components/overlay/AddRelationship.svelte +1 -1
  41. package/dist/components/overlay/LiveUpdatesBanner.svelte +4 -6
  42. package/dist/components/overlay/left-pane/buildTree.js +15 -0
  43. package/dist/components/overlay/settings/Settings.svelte +11 -13
  44. package/dist/components/overlay/widgets/Camera.svelte +11 -9
  45. package/dist/components/xr/ArmTeleop.svelte +33 -33
  46. package/dist/components/xr/CameraFeed.svelte +21 -23
  47. package/dist/components/xr/JointLimitsWidget.svelte +19 -5
  48. package/dist/components/xr/XRConfigPanel.svelte +17 -16
  49. package/dist/components/xr/XRControllerSettings.svelte +5 -4
  50. package/dist/components/xr/XRToast.svelte +11 -6
  51. package/dist/ecs/relations.d.ts +1 -0
  52. package/dist/ecs/relations.js +1 -0
  53. package/dist/ecs/traits.d.ts +2 -19
  54. package/dist/ecs/traits.js +33 -6
  55. package/dist/ecs/useQuery.svelte.js +3 -3
  56. package/dist/format.js +1 -1
  57. package/dist/hooks/use3DModels.svelte.js +36 -38
  58. package/dist/hooks/useConfigFrames.svelte.js +2 -7
  59. package/dist/hooks/useDrawAPI.svelte.js +1 -1
  60. package/dist/hooks/useFramelessComponents.svelte.js +1 -1
  61. package/dist/hooks/useFrames.svelte.js +62 -56
  62. package/dist/hooks/useGeometries.svelte.js +59 -36
  63. package/dist/hooks/useLinked.svelte.js +5 -4
  64. package/dist/hooks/usePartConfig.svelte.js +16 -17
  65. package/dist/hooks/usePointcloudObjects.svelte.js +107 -62
  66. package/dist/hooks/usePointclouds.svelte.js +50 -32
  67. package/dist/hooks/usePose.svelte.js +3 -7
  68. package/dist/hooks/useResizable.svelte.js +4 -3
  69. package/dist/hooks/useSettings.svelte.js +2 -2
  70. package/dist/hooks/useWeblabs.svelte.js +3 -2
  71. package/dist/hooks/useWorldState.svelte.js +31 -28
  72. package/dist/loaders/pcd/index.js +2 -1
  73. package/dist/loaders/pcd/worker.inline.d.ts +1 -1
  74. package/dist/loaders/pcd/worker.inline.js +1 -1
  75. package/dist/loaders/pcd/worker.js +1 -1
  76. package/dist/metadata.d.ts +22 -0
  77. package/dist/metadata.js +66 -0
  78. package/dist/snapshot.d.ts +20 -0
  79. package/dist/snapshot.js +72 -28
  80. package/dist/three/InstancedArrows/InstancedArrows.js +1 -1
  81. package/dist/three/InstancedArrows/box.js +1 -1
  82. package/dist/three/InstancedArrows/raycast.js +12 -12
  83. package/dist/three/OBBHelper.d.ts +3 -2
  84. package/dist/three/OBBHelper.js +17 -5
  85. package/package.json +19 -11
  86. package/dist/WorldObject.svelte.d.ts +0 -27
  87. package/dist/WorldObject.svelte.js +0 -114
  88. package/dist/components/Frame.svelte +0 -89
  89. package/dist/components/Geometry.svelte +0 -211
  90. package/dist/components/Geometry.svelte.d.ts +0 -19
  91. package/dist/components/Line.svelte +0 -43
  92. /package/dist/components/{Arrows → Entities/Arrows}/ArrowGroups.svelte.d.ts +0 -0
  93. /package/dist/components/{Entities.svelte.d.ts → Entities/Entities.svelte.d.ts} +0 -0
  94. /package/dist/components/{GLTF.svelte.d.ts → Entities/GLTF.svelte.d.ts} +0 -0
  95. /package/dist/components/{Label.svelte.d.ts → Entities/Label.svelte.d.ts} +0 -0
  96. /package/dist/components/{Line.svelte.d.ts → Entities/Line.svelte.d.ts} +0 -0
  97. /package/dist/components/{LineDots.svelte → Entities/LineDots.svelte} +0 -0
  98. /package/dist/components/{LineDots.svelte.d.ts → Entities/LineDots.svelte.d.ts} +0 -0
  99. /package/dist/components/{LineGeometry.svelte → Entities/LineGeometry.svelte} +0 -0
  100. /package/dist/components/{LineGeometry.svelte.d.ts → Entities/LineGeometry.svelte.d.ts} +0 -0
  101. /package/dist/components/{Points.svelte.d.ts → Entities/Points.svelte.d.ts} +0 -0
  102. /package/dist/components/{Pose.svelte.d.ts → Entities/Pose.svelte.d.ts} +0 -0
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ import { T, useThrelte, type Props as ThrelteProps } from '@threlte/core'
3
+ import { type Snippet } from 'svelte'
4
+ import { BufferGeometry, Color, DoubleSide, FrontSide, Mesh } from 'three'
5
+ import { CapsuleGeometry } from '../../three/CapsuleGeometry'
6
+ import { colors, darkenColor } from '../../color'
7
+ import AxesHelper from '../AxesHelper.svelte'
8
+ import type { Entity } from 'koota'
9
+ import { traits, useTrait } from '../../ecs'
10
+ import { poseToObject3d } from '../../transform'
11
+ import type { Pose } from '@viamrobotics/sdk'
12
+
13
+ interface Props extends ThrelteProps<Mesh> {
14
+ entity: Entity
15
+ color?: string
16
+ center?: Pose
17
+ children?: Snippet
18
+ }
19
+
20
+ let { entity, color: overrideColor, center, children, ...rest }: Props = $props()
21
+
22
+ const colorUtil = new Color()
23
+
24
+ const { invalidate } = useThrelte()
25
+ const name = useTrait(() => entity, traits.Name)
26
+ const entityColor = useTrait(() => entity, traits.Color)
27
+ const opacity = useTrait(() => entity, traits.Opacity)
28
+ const box = useTrait(() => entity, traits.Box)
29
+ const capsule = useTrait(() => entity, traits.Capsule)
30
+ const sphere = useTrait(() => entity, traits.Sphere)
31
+ const bufferGeometry = useTrait(() => entity, traits.BufferGeometry)
32
+ const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
33
+ const materialProps = useTrait(() => entity, traits.Material)
34
+ const renderOrder = useTrait(() => entity, traits.RenderOrder)
35
+
36
+ const color = $derived.by(() => {
37
+ if (overrideColor) {
38
+ return overrideColor
39
+ }
40
+
41
+ if (entityColor.current) {
42
+ return colorUtil.setRGB(entityColor.current.r, entityColor.current.g, entityColor.current.b)
43
+ }
44
+
45
+ return colors.default
46
+ })
47
+
48
+ const mesh = new Mesh()
49
+
50
+ $effect.pre(() => {
51
+ if (center) {
52
+ poseToObject3d(center, mesh)
53
+ invalidate()
54
+ }
55
+ })
56
+
57
+ let geo = $state.raw<BufferGeometry>()
58
+
59
+ const oncreate = (bufferGeometry: BufferGeometry) => {
60
+ geo = bufferGeometry
61
+ }
62
+ </script>
63
+
64
+ <T
65
+ is={mesh}
66
+ name={entity}
67
+ userData.name={name}
68
+ renderOrder={renderOrder.current}
69
+ {...rest}
70
+ >
71
+ {#if box.current}
72
+ {@const { x, y, z } = box.current ?? { x: 0, y: 0, z: 0 }}
73
+ <T.BoxGeometry
74
+ args={[x * 0.001, y * 0.001, z * 0.001]}
75
+ {oncreate}
76
+ />
77
+ {:else if sphere.current}
78
+ {@const { r } = sphere.current ?? { r: 0 }}
79
+ <T.SphereGeometry
80
+ args={[r * 0.001]}
81
+ {oncreate}
82
+ />
83
+ {:else if capsule.current}
84
+ {@const { r, l } = capsule.current ?? { r: 0, l: 0 }}
85
+ <T
86
+ is={CapsuleGeometry}
87
+ args={[r * 0.001, l * 0.001]}
88
+ {oncreate}
89
+ />
90
+ {:else if bufferGeometry.current}
91
+ <T
92
+ is={bufferGeometry.current}
93
+ {oncreate}
94
+ />
95
+ {/if}
96
+
97
+ {@const currentOpacity = opacity.current ?? 0.7}
98
+ <T.MeshToonMaterial
99
+ {color}
100
+ side={bufferGeometry.current ? DoubleSide : FrontSide}
101
+ transparent={currentOpacity < 1}
102
+ depthWrite={currentOpacity === 1}
103
+ opacity={currentOpacity}
104
+ depthTest={materialProps.current?.depthTest ?? true}
105
+ />
106
+
107
+ <!--
108
+ TODO(mp) currently some bufferGeometries are coming in empty,
109
+ this is a quick fix but this should be handled upstream
110
+ -->
111
+ {#if geo && geo.getAttribute('position').array.length > 0}
112
+ <T.LineSegments
113
+ raycast={() => null}
114
+ bvh={{ enabled: false }}
115
+ >
116
+ <T.EdgesGeometry args={[geo, 0]} />
117
+ <T.LineBasicMaterial color={darkenColor(color, 10)} />
118
+ </T.LineSegments>
119
+ {/if}
120
+
121
+ {@render children?.()}
122
+ </T>
123
+
124
+ {#if showAxesHelper.current}
125
+ <AxesHelper
126
+ name={entity}
127
+ width={3}
128
+ length={0.1}
129
+ />
130
+ {/if}
@@ -0,0 +1,4 @@
1
+ import { Mesh } from 'three';
2
+ declare const Mesh: any;
3
+ type Mesh = ReturnType<typeof Mesh>;
4
+ export default Mesh;
@@ -2,12 +2,12 @@
2
2
  import { Points, PointsMaterial, OrthographicCamera } from 'three'
3
3
  import { T, useTask, useThrelte } from '@threlte/core'
4
4
  import { Portal } from '@threlte/extras'
5
- import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
6
- import { poseToObject3d } from '../transform'
7
- import { useSettings } from '../hooks/useSettings.svelte'
5
+ import { useEntityEvents } from './hooks/useEntityEvents.svelte'
6
+ import { poseToObject3d } from '../../transform'
7
+ import { useSettings } from '../../hooks/useSettings.svelte'
8
8
  import type { Snippet } from 'svelte'
9
9
  import type { Entity } from 'koota'
10
- import { traits, useTrait } from '../ecs'
10
+ import { traits, useTrait } from '../../ecs'
11
11
 
12
12
  interface Props {
13
13
  entity: Entity
@@ -93,7 +93,7 @@
93
93
  }
94
94
  })
95
95
 
96
- const events = useObjectEvents(() => entity)
96
+ const events = useEntityEvents(() => entity)
97
97
 
98
98
  const { start, stop } = useTask(
99
99
  () => {
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
- import { traits, useTrait } from '../ecs'
3
- import { usePose } from '../hooks/usePose.svelte'
4
- import { matrixToPose, poseToMatrix } from '../transform'
2
+ import { traits, useTrait } from '../../ecs'
3
+ import { usePose } from '../../hooks/usePose.svelte'
4
+ import { matrixToPose, poseToMatrix } from '../../transform'
5
5
  import type { Pose } from '@viamrobotics/sdk'
6
6
  import type { Entity } from 'koota'
7
7
  import type { Snippet } from 'svelte'
@@ -1,6 +1,6 @@
1
1
  import { type IntersectionEvent } from '@threlte/extras';
2
2
  import type { Entity } from 'koota';
3
- export declare const useObjectEvents: (entity: () => Entity | undefined) => {
3
+ export declare const useEntityEvents: (entity: () => Entity | undefined) => {
4
4
  readonly visible: boolean;
5
5
  onpointerenter: (event: IntersectionEvent<MouseEvent>) => void;
6
6
  onpointermove: (event: IntersectionEvent<MouseEvent>) => void;
@@ -1,11 +1,11 @@
1
1
  import { useCursor } from '@threlte/extras';
2
- import { useFocusedEntity, useSelectedEntity } from './useSelection.svelte';
3
- import { useVisibility } from './useVisibility.svelte';
2
+ import { useFocusedEntity, useSelectedEntity } from '../../../hooks/useSelection.svelte';
3
+ import { useVisibility } from '../../../hooks/useVisibility.svelte';
4
4
  import { Vector2 } from 'three';
5
- import { traits } from '../ecs';
6
- import { updateHoverInfo } from '../HoverUpdater.svelte';
7
- import { createPose, matrixToPose, poseToMatrix } from '../transform';
8
- export const useObjectEvents = (entity) => {
5
+ import { traits } from '../../../ecs';
6
+ import { updateHoverInfo } from '../../../HoverUpdater.svelte';
7
+ import { createPose, matrixToPose, poseToMatrix } from '../../../transform';
8
+ export const useEntityEvents = (entity) => {
9
9
  const down = new Vector2();
10
10
  const selectedEntity = useSelectedEntity();
11
11
  const focusedEntity = useFocusedEntity();
@@ -26,13 +26,14 @@ const hasPrefix = (name) => {
26
26
  };
27
27
  const validatePrefix = (extension, prefix) => {
28
28
  switch (prefix) {
29
- case Prefixes.Snapshot:
29
+ case Prefixes.Snapshot: {
30
30
  if (extension !== Extensions.JSON &&
31
31
  extension !== Extensions.PB &&
32
32
  extension !== Extensions.PB_GZ) {
33
33
  return new FileNameError(`Only ${Extensions.JSON}, ${Extensions.PB} and ${Extensions.PB_GZ} snapshot files are supported.`);
34
34
  }
35
35
  break;
36
+ }
36
37
  }
37
38
  return undefined;
38
39
  };
@@ -76,12 +77,14 @@ export const readFile = (file, reader, extension) => {
76
77
  if (!extension)
77
78
  return;
78
79
  switch (extension) {
79
- case Extensions.JSON:
80
+ case Extensions.JSON: {
80
81
  return reader.readAsText(file);
82
+ }
81
83
  case Extensions.PCD:
82
84
  case Extensions.PLY:
83
85
  case Extensions.PB:
84
- case Extensions.PB_GZ:
86
+ case Extensions.PB_GZ: {
85
87
  return reader.readAsArrayBuffer(file);
88
+ }
86
89
  }
87
90
  };
@@ -81,16 +81,20 @@ const decodeGzip = async (params) => {
81
81
  };
82
82
  export const snapshotDropper = async (params) => {
83
83
  switch (params.extension) {
84
- case 'json':
84
+ case 'json': {
85
85
  return decodeJson(params);
86
- case 'pb':
86
+ }
87
+ case 'pb': {
87
88
  return decodeBinary(params);
88
- case 'pb.gz':
89
+ }
90
+ case 'pb.gz': {
89
91
  return decodeGzip(params);
90
- default:
92
+ }
93
+ default: {
91
94
  return {
92
95
  success: false,
93
96
  error: new FileDropperError(`Only ${Extensions.JSON}, ${Extensions.PB} and ${Extensions.PB_GZ} snapshot files are supported.`),
94
97
  };
98
+ }
95
99
  }
96
100
  };
@@ -4,14 +4,17 @@ import { plyDropper } from './ply-dropper';
4
4
  import { snapshotDropper } from './snapshot-dropper';
5
5
  const createFileDropper = (extension, prefix) => {
6
6
  switch (prefix) {
7
- case Prefixes.Snapshot:
7
+ case Prefixes.Snapshot: {
8
8
  return snapshotDropper;
9
+ }
9
10
  }
10
11
  switch (extension) {
11
- case Extensions.PCD:
12
+ case Extensions.PCD: {
12
13
  return pcdDropper;
13
- case Extensions.PLY:
14
+ }
15
+ case Extensions.PLY: {
14
16
  return plyDropper;
17
+ }
15
18
  }
16
19
  return undefined;
17
20
  };
@@ -74,11 +77,11 @@ export const useFileDrop = (onSuccess, onError) => {
74
77
  prefix,
75
78
  content,
76
79
  });
77
- if (!result.success) {
78
- handleError(result.error.message);
80
+ if (result.success) {
81
+ onSuccess(result);
79
82
  }
80
83
  else {
81
- onSuccess(result);
84
+ handleError(result.error.message);
82
85
  }
83
86
  });
84
87
  readFile(file, reader, extension);
@@ -245,16 +245,16 @@
245
245
  }
246
246
 
247
247
  $effect(() => {
248
- window.addEventListener('keydown', onkeydown)
249
- window.addEventListener('keyup', onkeyup)
248
+ globalThis.addEventListener('keydown', onkeydown)
249
+ globalThis.addEventListener('keyup', onkeyup)
250
250
  dom.addEventListener('pointerdown', onpointerdown)
251
251
  dom.addEventListener('pointermove', onpointermove)
252
252
  dom.addEventListener('pointerup', onpointerup)
253
253
  dom.addEventListener('pointerleave', onpointerleave)
254
254
 
255
255
  return () => {
256
- window.removeEventListener('keydown', onkeydown)
257
- window.removeEventListener('keyup', onkeyup)
256
+ globalThis.removeEventListener('keydown', onkeydown)
257
+ globalThis.removeEventListener('keyup', onkeyup)
258
258
  dom.removeEventListener('pointerdown', onpointerdown)
259
259
  dom.removeEventListener('pointermove', onpointermove)
260
260
  dom.removeEventListener('pointerup', onpointerup)
@@ -2,13 +2,15 @@
2
2
  import { parsePcdInWorker } from '../lib'
3
3
  import { traits, useWorld } from '../ecs'
4
4
  import { createBufferGeometry } from '../attribute'
5
- import type { Entity } from 'koota'
5
+ import type { ConfigurableTrait, Entity } from 'koota'
6
6
 
7
7
  interface Props {
8
8
  data: Uint8Array
9
+ name?: string
10
+ renderOrder?: number
9
11
  }
10
12
 
11
- let { data }: Props = $props()
13
+ let { data, name, renderOrder }: Props = $props()
12
14
 
13
15
  const world = useWorld()
14
16
 
@@ -18,11 +20,17 @@
18
20
  parsePcdInWorker(data).then(({ positions, colors }) => {
19
21
  const geometry = createBufferGeometry(positions, colors)
20
22
 
21
- entity = world.spawn(
22
- traits.Name('Random points'),
23
+ const entityTraits: ConfigurableTrait[] = [
24
+ traits.Name(name ?? 'Random points'),
23
25
  traits.Points,
24
- traits.BufferGeometry(geometry)
25
- )
26
+ traits.BufferGeometry(geometry),
27
+ ]
28
+
29
+ if (renderOrder) {
30
+ entityTraits.push(traits.RenderOrder(renderOrder))
31
+ }
32
+
33
+ entity = world.spawn(...entityTraits)
26
34
  })
27
35
 
28
36
  return () => {
@@ -1,5 +1,7 @@
1
1
  interface Props {
2
2
  data: Uint8Array;
3
+ name?: string;
4
+ renderOrder?: number;
3
5
  }
4
6
  declare const PCD: import("svelte").Component<Props, {}, "">;
5
7
  type PCD = ReturnType<typeof PCD>;
@@ -2,7 +2,7 @@
2
2
  import { ShaderMaterial, Vector3 } from 'three'
3
3
  import { T } from '@threlte/core'
4
4
  import { Environment, Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
5
- import Entities from './Entities.svelte'
5
+ import Entities from './Entities/Entities.svelte'
6
6
  import Selected from './Selected.svelte'
7
7
  import Focus from './Focus.svelte'
8
8
  import StaticGeometries from './StaticGeometries.svelte'
@@ -17,7 +17,6 @@
17
17
  import MeasureTool from './MeasureTool/MeasureTool.svelte'
18
18
  import PointerMissBox from './PointerMissBox.svelte'
19
19
  import BatchedArrows from './BatchedArrows.svelte'
20
- import Arrows from './Arrows/ArrowGroups.svelte'
21
20
  import hdrImage from '../assets/ferndale_studio_11_1k.hdr'
22
21
 
23
22
  interface Props {
@@ -103,7 +102,6 @@
103
102
 
104
103
  <Entities />
105
104
  <BatchedArrows />
106
- <Arrows />
107
105
  </T.Group>
108
106
 
109
107
  {@render children?.()}
@@ -46,6 +46,8 @@
46
46
  selectedObject3d.current?.getWorldPosition(object.position)
47
47
  selectedObject3d.current?.getWorldQuaternion(object.quaternion)
48
48
  obbHelper.setFromObject(object)
49
+ } else {
50
+ obbHelper.setFromObject(object)
49
51
  }
50
52
 
51
53
  invalidate()
@@ -12,7 +12,7 @@
12
12
  import { PressedKeys } from 'runed'
13
13
  import { quaternionToPose, vector3ToPose } from '../transform'
14
14
  import { Quaternion, Vector3 } from 'three'
15
- import Frame from './Frame.svelte'
15
+ import Frame from './Entities/Frame.svelte'
16
16
  import { useSettings } from '../hooks/useSettings.svelte'
17
17
  import { useWorld, traits } from '../ecs'
18
18
  import type { Entity } from 'koota'
@@ -17,7 +17,7 @@
17
17
  return allEntities.current
18
18
  .map((e: Entity) => e.get(traits.Name))
19
19
  .filter((n: string | undefined): n is string => n !== undefined && n !== currentEntityName)
20
- .sort()
20
+ .toSorted()
21
21
  })
22
22
 
23
23
  let showRelationshipOptions = $state(false)
@@ -13,12 +13,10 @@
13
13
 
14
14
  <svelte:window
15
15
  onkeydown={(event) => {
16
- if (event.metaKey) {
17
- if (event.key.toLowerCase() === 's') {
18
- event.preventDefault()
19
- event.stopImmediatePropagation()
20
- partConfig.save()
21
- }
16
+ if (event.metaKey && event.key.toLowerCase() === 's') {
17
+ event.preventDefault()
18
+ event.stopImmediatePropagation()
19
+ partConfig.save()
22
20
  }
23
21
  }}
24
22
  />
@@ -1,4 +1,7 @@
1
1
  import { traits } from '../../../ecs';
2
+ function sortNodes(nodes) {
3
+ nodes.sort((a, b) => a.entity.get(traits.Name)?.localeCompare(b.entity.get(traits.Name) ?? '') ?? 0);
4
+ }
2
5
  /**
3
6
  * Creates a tree representing parent child / relationships from a set of frames.
4
7
  */
@@ -29,5 +32,17 @@ export const buildTreeNodes = (entities) => {
29
32
  }
30
33
  }
31
34
  }
35
+ for (const node of rootNodes) {
36
+ if (!node.children)
37
+ continue;
38
+ sortNodes(node.children);
39
+ }
40
+ for (const node of childNodes) {
41
+ if (!node.children)
42
+ continue;
43
+ sortNodes(node.children);
44
+ }
45
+ sortNodes(rootNodes);
46
+ sortNodes(childNodes);
32
47
  return { rootNodes, nodeMap };
33
48
  };
@@ -285,19 +285,17 @@
285
285
  <Switch
286
286
  on={isWidgetOpen}
287
287
  on:change={(event) => {
288
- if (event.detail) {
289
- settings.current.openCameraWidgets = {
290
- ...settings.current.openCameraWidgets,
291
- [partID.current]: [...currentRobotCameraWidgets, camera.name],
292
- }
293
- } else {
294
- settings.current.openCameraWidgets = {
295
- ...settings.current.openCameraWidgets,
296
- [partID.current]: currentRobotCameraWidgets.filter(
297
- (widget) => widget !== camera.name
298
- ),
299
- }
300
- }
288
+ settings.current.openCameraWidgets = event.detail
289
+ ? {
290
+ ...settings.current.openCameraWidgets,
291
+ [partID.current]: [...currentRobotCameraWidgets, camera.name],
292
+ }
293
+ : {
294
+ ...settings.current.openCameraWidgets,
295
+ [partID.current]: currentRobotCameraWidgets.filter(
296
+ (widget) => widget !== camera.name
297
+ ),
298
+ }
301
299
  }}
302
300
  />
303
301
  </div>
@@ -31,12 +31,14 @@
31
31
  let fpsInterval: ReturnType<typeof setInterval> | undefined
32
32
  let fpsCounterActive = false
33
33
 
34
+ const cleanup = () => {
35
+ if (fpsInterval) clearInterval(fpsInterval)
36
+ fpsCounterActive = false
37
+ }
38
+
34
39
  // Cleanup on destroy
35
40
  $effect(() => {
36
- return () => {
37
- if (fpsInterval) clearInterval(fpsInterval)
38
- fpsCounterActive = false
39
- }
41
+ return cleanup
40
42
  })
41
43
 
42
44
  const onMediaLoad = (e: Event) => {
@@ -89,8 +91,8 @@
89
91
  resolutions = options.map((opt) => ({ width: opt.width, height: opt.height }))
90
92
  isLoading = false
91
93
  })
92
- .catch((e) => {
93
- error = e instanceof Error ? e.message : 'Failed to get stream options'
94
+ .catch((error_) => {
95
+ error = error_ instanceof Error ? error_.message : 'Failed to get stream options'
94
96
  isLoading = false
95
97
  })
96
98
  }
@@ -101,13 +103,13 @@
101
103
  if (!target.value || !streamClient) return
102
104
 
103
105
  const [w, h] = target.value.split('x').map(Number)
104
- if (isNaN(w) || isNaN(h)) return
106
+ if (Number.isNaN(w) || Number.isNaN(h)) return
105
107
 
106
108
  try {
107
109
  await streamClient.setOptions(name, w, h)
108
110
  error = undefined
109
- } catch (err) {
110
- error = err instanceof Error ? err.message : 'Failed to set resolution'
111
+ } catch (error_) {
112
+ error = error_ instanceof Error ? error_.message : 'Failed to set resolution'
111
113
  }
112
114
  }
113
115
  </script>