@viamrobotics/motion-tools 1.31.0 → 1.33.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 (130) hide show
  1. package/dist/components/App.svelte +64 -53
  2. package/dist/components/App.svelte.d.ts +14 -7
  3. package/dist/components/Entities/Arrows/Arrows.svelte +4 -7
  4. package/dist/components/Entities/hooks/useEntityEvents.svelte.d.ts +0 -1
  5. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +30 -16
  6. package/dist/components/InputBindings.svelte +0 -43
  7. package/dist/components/KeyboardBindings.svelte +38 -0
  8. package/dist/components/KeyboardBindings.svelte.d.ts +18 -0
  9. package/dist/components/PointerMissBox.svelte +6 -3
  10. package/dist/components/Scene.svelte +43 -61
  11. package/dist/components/SceneProviders.svelte +2 -7
  12. package/dist/components/SceneProviders.svelte.d.ts +1 -3
  13. package/dist/components/Selected.svelte +20 -27
  14. package/dist/components/SelectedTransformControls.svelte +8 -7
  15. package/dist/components/StaticGeometries.svelte +3 -5
  16. package/dist/components/hover/HoveredEntities.svelte +15 -14
  17. package/dist/components/hover/HoveredEntities.svelte.d.ts +17 -2
  18. package/dist/components/hover/HoveredEntity.svelte +8 -5
  19. package/dist/components/hover/HoveredEntity.svelte.d.ts +5 -1
  20. package/dist/components/hover/LinkedHoveredEntity.svelte +7 -11
  21. package/dist/components/hover/LinkedHoveredEntity.svelte.d.ts +1 -0
  22. package/dist/components/overlay/Details.svelte +22 -37
  23. package/dist/components/overlay/Details.svelte.d.ts +3 -1
  24. package/dist/components/overlay/controls/Controls.svelte +0 -2
  25. package/dist/components/overlay/dashboard/Button.svelte +5 -3
  26. package/dist/components/overlay/dashboard/Button.svelte.d.ts +1 -1
  27. package/dist/components/overlay/left-pane/Tree.svelte +13 -10
  28. package/dist/components/overlay/left-pane/TreeContainer.svelte +9 -4
  29. package/dist/components/overlay/left-pane/TreeNode.svelte +6 -4
  30. package/dist/components/overlay/settings/ConnectionSettings.svelte +42 -0
  31. package/dist/components/overlay/settings/ConnectionSettings.svelte.d.ts +18 -0
  32. package/dist/components/overlay/settings/DebugSettings.svelte +13 -0
  33. package/dist/components/{xr/frame-configure/Controllers.svelte.d.ts → overlay/settings/DebugSettings.svelte.d.ts} +3 -3
  34. package/dist/components/overlay/settings/PointcloudSettings.svelte +61 -0
  35. package/dist/components/overlay/settings/PointcloudSettings.svelte.d.ts +3 -0
  36. package/dist/components/overlay/settings/SceneSettings.svelte +110 -0
  37. package/dist/components/overlay/settings/SceneSettings.svelte.d.ts +18 -0
  38. package/dist/components/overlay/settings/Settings.svelte +27 -312
  39. package/dist/components/overlay/settings/Settings.svelte.d.ts +8 -1
  40. package/dist/components/overlay/settings/Tabs.svelte +5 -3
  41. package/dist/components/overlay/settings/Tabs.svelte.d.ts +3 -3
  42. package/dist/components/overlay/settings/VisionSettings.svelte +31 -0
  43. package/dist/components/overlay/settings/VisionSettings.svelte.d.ts +3 -0
  44. package/dist/components/overlay/settings/WeblabSettings.svelte +27 -0
  45. package/dist/components/overlay/settings/WeblabSettings.svelte.d.ts +18 -0
  46. package/dist/components/overlay/settings/WidgetSettings.svelte +49 -0
  47. package/dist/components/overlay/settings/WidgetSettings.svelte.d.ts +3 -0
  48. package/dist/components/overlay/widgets/FramePov.svelte +1 -12
  49. package/dist/draw.d.ts +1 -0
  50. package/dist/draw.js +1 -1
  51. package/dist/ecs/index.d.ts +1 -0
  52. package/dist/ecs/index.js +1 -0
  53. package/dist/ecs/traits.d.ts +22 -5
  54. package/dist/ecs/traits.js +33 -4
  55. package/dist/ecs/useTag.svelte.d.ts +5 -0
  56. package/dist/ecs/useTag.svelte.js +43 -0
  57. package/dist/hooks/useEnvironment.svelte.d.ts +1 -1
  58. package/dist/hooks/useLinked.svelte.js +7 -8
  59. package/dist/hooks/useMouseRaycaster.svelte.d.ts +4 -3
  60. package/dist/hooks/useMouseRaycaster.svelte.js +1 -0
  61. package/dist/hooks/useSettings.svelte.d.ts +1 -1
  62. package/dist/plugins/Focus/Focus.svelte +45 -0
  63. package/dist/plugins/Focus/Focus.svelte.d.ts +3 -0
  64. package/dist/plugins/Focus/FocusBox.svelte +75 -0
  65. package/dist/plugins/Focus/FocusBox.svelte.d.ts +3 -0
  66. package/dist/plugins/Focus/provideFocus.svelte.d.ts +1 -0
  67. package/dist/plugins/Focus/provideFocus.svelte.js +61 -0
  68. package/dist/{components → plugins}/MeasureTool/MeasureTool.svelte +6 -8
  69. package/dist/plugins/Selection/SelectionTool.svelte +10 -3
  70. package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte +3 -5
  71. package/dist/plugins/XR/DebugPanel.svelte +29 -0
  72. package/dist/plugins/XR/DebugPanel.svelte.d.ts +3 -0
  73. package/dist/plugins/XR/OriginMarker.svelte +341 -0
  74. package/dist/plugins/XR/PendingEditsPanel.svelte +60 -0
  75. package/dist/plugins/XR/PendingEditsPanel.svelte.d.ts +18 -0
  76. package/dist/plugins/XR/WristDisplay.svelte +60 -0
  77. package/dist/plugins/XR/WristDisplay.svelte.d.ts +19 -0
  78. package/dist/{components/xr → plugins/XR}/XR.svelte +69 -23
  79. package/dist/plugins/XR/XRPlugins.svelte +9 -0
  80. package/dist/plugins/XR/XRPlugins.svelte.d.ts +26 -0
  81. package/dist/plugins/XR/XRSettings.svelte +240 -0
  82. package/dist/plugins/XR/XRSettings.svelte.d.ts +3 -0
  83. package/dist/{components/xr → plugins/XR}/XRToast.svelte +6 -9
  84. package/dist/plugins/XR/debug.svelte.d.ts +7 -0
  85. package/dist/plugins/XR/debug.svelte.js +13 -0
  86. package/dist/plugins/XR/frame-configure/Controllers.svelte +413 -0
  87. package/dist/plugins/XR/teleop/Controllers.svelte.d.ts +3 -0
  88. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.d.ts +4 -0
  89. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.js +22 -0
  90. package/dist/plugins/XR/useOrigin.svelte.d.ts +24 -0
  91. package/dist/plugins/XR/useOrigin.svelte.js +50 -0
  92. package/dist/plugins/index.d.ts +4 -0
  93. package/dist/plugins/index.js +4 -0
  94. package/dist/three/OBBHelper.js +1 -0
  95. package/dist/three/arrow.d.ts +2 -0
  96. package/dist/three/arrow.js +3 -1
  97. package/package.json +16 -4
  98. package/dist/components/Focus.svelte +0 -46
  99. package/dist/components/Focus.svelte.d.ts +0 -7
  100. package/dist/components/xr/OriginMarker.svelte +0 -151
  101. package/dist/components/xr/XRControllerSettings.svelte +0 -242
  102. package/dist/components/xr/XRControllerSettings.svelte.d.ts +0 -3
  103. package/dist/components/xr/frame-configure/Controllers.svelte +0 -6
  104. package/dist/components/xr/useOrigin.svelte.d.ts +0 -9
  105. package/dist/components/xr/useOrigin.svelte.js +0 -27
  106. package/dist/hooks/useSelection.svelte.d.ts +0 -33
  107. package/dist/hooks/useSelection.svelte.js +0 -94
  108. /package/dist/{components → plugins}/MeasureTool/MeasurePoint.svelte +0 -0
  109. /package/dist/{components → plugins}/MeasureTool/MeasurePoint.svelte.d.ts +0 -0
  110. /package/dist/{components → plugins}/MeasureTool/MeasureTool.svelte.d.ts +0 -0
  111. /package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte.d.ts +0 -0
  112. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte +0 -0
  113. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte.d.ts +0 -0
  114. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte +0 -0
  115. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte.d.ts +0 -0
  116. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte +0 -0
  117. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte.d.ts +0 -0
  118. /package/dist/{components/xr → plugins/XR}/OriginMarker.svelte.d.ts +0 -0
  119. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte +0 -0
  120. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte.d.ts +0 -0
  121. /package/dist/{components/xr → plugins/XR}/XR.svelte.d.ts +0 -0
  122. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte +0 -0
  123. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte.d.ts +0 -0
  124. /package/dist/{components/xr → plugins/XR}/XRToast.svelte.d.ts +0 -0
  125. /package/dist/{components/xr/teleop → plugins/XR/frame-configure}/Controllers.svelte.d.ts +0 -0
  126. /package/dist/{components/xr → plugins/XR}/math.d.ts +0 -0
  127. /package/dist/{components/xr → plugins/XR}/math.js +0 -0
  128. /package/dist/{components/xr → plugins/XR}/teleop/Controllers.svelte +0 -0
  129. /package/dist/{components/xr → plugins/XR}/toasts.svelte.d.ts +0 -0
  130. /package/dist/{components/xr → plugins/XR}/toasts.svelte.js +0 -0
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import { Switch } from '@viamrobotics/prime-core'
3
+ import { useResourceNames } from '@viamrobotics/svelte-sdk'
4
+
5
+ import { usePartID } from '../../../hooks/usePartID.svelte'
6
+ import { useSettings } from '../../../hooks/useSettings.svelte'
7
+
8
+ const partID = usePartID()
9
+
10
+ const visionServices = useResourceNames(() => partID.current, 'vision')
11
+ const settings = useSettings()
12
+ const { disabledVisionServices } = $derived(settings.current)
13
+ </script>
14
+
15
+ <div class="text-gray-9 flex flex-col gap-1 text-xs">
16
+ <h3 class="border-gray-3 border-b py-1 text-sm"><strong>Enabled vision services</strong></h3>
17
+
18
+ {#each visionServices.current as visionService (visionService)}
19
+ <div class="flex items-center justify-between py-0.5">
20
+ {visionService.name}
21
+ <Switch
22
+ on={disabledVisionServices[visionService.name] !== true}
23
+ on:change={(event) => {
24
+ disabledVisionServices[visionService.name] = !event.detail
25
+ }}
26
+ />
27
+ </div>
28
+ {:else}
29
+ No vision services detected
30
+ {/each}
31
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const VisionSettings: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type VisionSettings = ReturnType<typeof VisionSettings>;
3
+ export default VisionSettings;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import { Switch } from '@viamrobotics/prime-core'
3
+ import { onMount } from 'svelte'
4
+
5
+ import { useWeblabs, WEBLABS_EXPERIMENTS } from '../../../hooks/useWeblabs.svelte'
6
+
7
+ const weblabs = useWeblabs()
8
+ const knownWeblabs = Object.keys(WEBLABS_EXPERIMENTS)
9
+
10
+ onMount(() => {
11
+ weblabs.load(knownWeblabs)
12
+ })
13
+ </script>
14
+
15
+ <div class="flex flex-col gap-1 text-xs">
16
+ {#each knownWeblabs as experiment (experiment)}
17
+ <label class="flex items-center justify-between gap-2 py-0.5">
18
+ {experiment}
19
+ <Switch
20
+ on={weblabs.isActive(experiment)}
21
+ on:change={() => weblabs.toggle(experiment)}
22
+ />
23
+ </label>
24
+ {:else}
25
+ No weblabs defined
26
+ {/each}
27
+ </div>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const WeblabSettings: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type WeblabSettings = InstanceType<typeof WeblabSettings>;
18
+ export default WeblabSettings;
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ import { Switch } from '@viamrobotics/prime-core'
3
+ import { useResourceNames } from '@viamrobotics/svelte-sdk'
4
+
5
+ import { usePartID } from '../../../hooks/usePartID.svelte'
6
+ import { useSettings } from '../../../hooks/useSettings.svelte'
7
+
8
+ const partID = usePartID()
9
+ const cameras = useResourceNames(() => partID.current, 'camera')
10
+ const settings = useSettings()
11
+
12
+ const currentRobotCameraWidgets = $derived(
13
+ settings.current.openCameraWidgets[partID.current] || []
14
+ )
15
+ </script>
16
+
17
+ <div class="text-gray-9 flex flex-col gap-1 text-xs">
18
+ <label class="flex items-center justify-between gap-2 py-1">
19
+ Arm positions
20
+ <Switch bind:on={settings.current.enableArmPositionsWidget} />
21
+ </label>
22
+
23
+ <h3 class="border-gray-3 border-b py-1 text-sm"><strong>Camera widgets</strong></h3>
24
+
25
+ {#each cameras.current as camera (camera)}
26
+ {@const isWidgetOpen = currentRobotCameraWidgets.includes(camera.name)}
27
+ <div class="flex items-center justify-between gap-2 py-0.5">
28
+ <span class="min-w-0 truncate">{camera.name}</span>
29
+ <Switch
30
+ on={isWidgetOpen}
31
+ on:change={(event) => {
32
+ settings.current.openCameraWidgets = event.detail
33
+ ? {
34
+ ...settings.current.openCameraWidgets,
35
+ [partID.current]: [...currentRobotCameraWidgets, camera.name],
36
+ }
37
+ : {
38
+ ...settings.current.openCameraWidgets,
39
+ [partID.current]: currentRobotCameraWidgets.filter(
40
+ (widget) => widget !== camera.name
41
+ ),
42
+ }
43
+ }}
44
+ />
45
+ </div>
46
+ {:else}
47
+ No cameras detected
48
+ {/each}
49
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const WidgetSettings: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type WidgetSettings = ReturnType<typeof WidgetSettings>;
3
+ export default WidgetSettings;
@@ -7,7 +7,6 @@
7
7
  import { usePartID } from '../../../hooks/usePartID.svelte'
8
8
  import { useSettings } from '../../../hooks/useSettings.svelte'
9
9
 
10
- import { useOrigin } from '../../xr/useOrigin.svelte'
11
10
  import Button from '../dashboard/Button.svelte'
12
11
  import FloatingPanel from '../FloatingPanel.svelte'
13
12
 
@@ -20,7 +19,6 @@
20
19
  const { scene, renderer: mainRenderer, renderStage, invalidate } = useThrelte()
21
20
  const settings = useSettings()
22
21
  const partID = usePartID()
23
- const origin = useOrigin()
24
22
 
25
23
  // Three.js cameras look down -Z; Viam camera frames conventionally have the
26
24
  // optical axis along +Z with image-down along +Y. A 180° rotation around X
@@ -53,7 +51,6 @@
53
51
  const orthoHeight = $derived(BASE_ORTHO_HEIGHT / orthoZoom)
54
52
 
55
53
  const composed = new Matrix4()
56
- const originMat = new Matrix4()
57
54
 
58
55
  $effect(() => {
59
56
  if (!canvasEl) return
@@ -121,15 +118,7 @@
121
118
 
122
119
  const povCamera = cameraMode === 'perspective' ? perspectiveCamera : orthographicCamera
123
120
 
124
- // Compose origin × worldMatrix × VIAM_TO_THREE_CAMERA. The frame
125
- // entities' WorldMatrix lives in ECS world space; the rendered scene
126
- // is wrapped in a T.Group that applies `origin` on top, so the POV
127
- // camera needs the same origin transform to share coordinate space
128
- // with the meshes it's rendering.
129
- originMat
130
- .makeRotationZ(origin.rotation)
131
- .setPosition(origin.position[0], origin.position[1], origin.position[2])
132
- composed.copy(originMat).multiply(worldMat).multiply(VIAM_TO_THREE_CAMERA)
121
+ composed.multiplyMatrices(worldMat, VIAM_TO_THREE_CAMERA)
133
122
  composed.decompose(povCamera.position, povCamera.quaternion, povCamera.scale)
134
123
 
135
124
  const aspect = width / height
package/dist/draw.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { Transform as TransformProto } from './buf/common/v1/common_pb';
4
4
  import type { Drawing } from './buf/draw/v1/drawing_pb';
5
5
  import type { Relationship } from './metadata';
6
6
  import { type Metadata } from './metadata';
7
+ export declare const DEFAULT_LINE_WIDTH = 5;
7
8
  export type Transform = TransformWithUUID | TransformProto;
8
9
  export declare const uuidBytesToString: (bytes: Uint8Array | undefined) => string | undefined;
9
10
  export declare const uuidStringToBytes: (uuid: string) => Uint8Array<ArrayBuffer>;
package/dist/draw.js CHANGED
@@ -11,7 +11,7 @@ import { ColorFormat } from './buf/draw/v1/metadata_pb';
11
11
  import { isPointCloud } from './geometry';
12
12
  const vec3 = new Vector3();
13
13
  const rgb = { r: 0, g: 0, b: 0 };
14
- const DEFAULT_LINE_WIDTH = 5;
14
+ export const DEFAULT_LINE_WIDTH = 5;
15
15
  const DEFAULT_POINT_SIZE = 10;
16
16
  const DEFAULT_NURBS_DEGREE = 3;
17
17
  const DEFAULT_NURBS_WEIGHT = 1;
@@ -1,6 +1,7 @@
1
1
  export { provideWorld, useWorld } from './useWorld';
2
2
  export { useQuery } from './useQuery.svelte';
3
3
  export { useTrait } from './useTrait.svelte';
4
+ export { useTag } from './useTag.svelte';
4
5
  export { useTarget } from './useTarget.svelte';
5
6
  export { useParentName } from './useParentName.svelte';
6
7
  export { provideHierarchy } from './provideHierarchy.svelte';
package/dist/ecs/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { provideWorld, useWorld } from './useWorld';
2
2
  export { useQuery } from './useQuery.svelte';
3
3
  export { useTrait } from './useTrait.svelte';
4
+ export { useTag } from './useTag.svelte';
4
5
  export { useTarget } from './useTarget.svelte';
5
6
  export { useParentName } from './useParentName.svelte';
6
7
  export { provideHierarchy } from './provideHierarchy.svelte';
@@ -1,5 +1,5 @@
1
1
  import type { GLTF as ThreeGltf } from 'three/examples/jsm/loaders/GLTFLoader.js';
2
- import { Geometry as ViamGeometry } from '@viamrobotics/sdk';
2
+ import { type Pose, Geometry as ViamGeometry } from '@viamrobotics/sdk';
3
3
  import { type Entity } from 'koota';
4
4
  import { Matrix4, BufferGeometry as ThreeBufferGeometry } from 'three';
5
5
  export declare const Name: import("koota").Trait<() => string>;
@@ -65,6 +65,13 @@ export declare const InstancedMatrix: import("koota").Trait<() => {
65
65
  }>;
66
66
  export declare const Hovered: import("koota").Trait<() => boolean>;
67
67
  export declare const Invisible: import("koota").Trait<() => boolean>;
68
+ /**
69
+ * Suppresses the default frame-style world/local pose and parent-frame blocks
70
+ * in the details panel. Entities that render their own pose UI via the
71
+ * `details-extensions` portal target (e.g. gizmo plugin entities) opt in by
72
+ * adding this trait.
73
+ */
74
+ export declare const CustomDetails: import("koota").Trait<() => boolean>;
68
75
  /**
69
76
  * True when the entity itself, or any of its parents up the `ChildOf`
70
77
  * chain, has `Invisible`. Maintained by `provideInheritedInvisible`;
@@ -76,11 +83,14 @@ export declare const InheritedInvisible: import("koota").Trait<() => boolean>;
76
83
  * Represents that an entity is composed of many instances, so that the treeview and
77
84
  * details panel may display all instances
78
85
  */
79
- export declare const Instanced: import("koota").Trait<() => boolean>;
86
+ export declare const InstanceId: import("koota").Trait<() => number>;
80
87
  export declare const Instance: import("koota").Trait<{
81
88
  meshID: number;
82
89
  instanceID: number;
83
90
  }>;
91
+ export declare const Instances: import("koota").Trait<{
92
+ count: number;
93
+ }>;
84
94
  export declare const RenderOrder: import("koota").Trait<() => number>;
85
95
  export declare const Opacity: import("koota").Trait<() => number>;
86
96
  /**
@@ -108,9 +118,6 @@ export declare const Colors: import("koota").Trait<() => Uint8Array<ArrayBufferL
108
118
  * Per-vertex opacity values packed as uint8 (0-255).
109
119
  */
110
120
  export declare const Opacities: import("koota").Trait<() => Uint8Array<ArrayBuffer>>;
111
- export declare const Instances: import("koota").Trait<{
112
- count: number;
113
- }>;
114
121
  export declare const Arrows: import("koota").Trait<{
115
122
  headAtPose: boolean;
116
123
  }>;
@@ -205,6 +212,10 @@ export declare const ChunkProgress: import("koota").Trait<{
205
212
  */
206
213
  export type InteractionLayerValue = 'selectTool';
207
214
  export declare const SelectToolInteractionLayer: import("koota").Trait<() => boolean>;
215
+ /**
216
+ * This entity is selected by the user
217
+ */
218
+ export declare const Selected: import("koota").TagTrait;
208
219
  /**
209
220
  * This entity can be safely removed from the scene by the user
210
221
  */
@@ -229,3 +240,9 @@ export declare const Geometry: (geometry: ViamGeometry) => import("koota").Trait
229
240
  r: number;
230
241
  }>] | [import("koota").Trait<() => ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>>, ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>];
231
242
  export declare const updateGeometryTrait: (entity: Entity, geometry?: ViamGeometry) => void;
243
+ /**
244
+ * Patches an entity's `Matrix` trait in-place via the `Pose` round-trip
245
+ * (`matrixToPose` → merge → `poseToMatrix`), then signals `entity.changed(Matrix)`.
246
+ * No-ops silently if the entity has no `Matrix` trait.
247
+ */
248
+ export declare const writeMatrix: (entity: Entity, patch: Partial<Pose>) => void;
@@ -6,6 +6,7 @@ import { ColorFormat } from '../buf/draw/v1/metadata_pb';
6
6
  import { createBox, createCapsule, createSphere } from '../geometry';
7
7
  import { parsePcdInWorker } from '../loaders/pcd';
8
8
  import { parsePlyInput } from '../ply';
9
+ import { createPose, matrixToPose, poseToMatrix } from '../transform';
9
10
  export const Name = trait(() => '');
10
11
  export const UUID = trait(() => '');
11
12
  /**
@@ -61,6 +62,13 @@ export const InstancedMatrix = trait(() => ({
61
62
  }));
62
63
  export const Hovered = trait(() => true);
63
64
  export const Invisible = trait(() => true);
65
+ /**
66
+ * Suppresses the default frame-style world/local pose and parent-frame blocks
67
+ * in the details panel. Entities that render their own pose UI via the
68
+ * `details-extensions` portal target (e.g. gizmo plugin entities) opt in by
69
+ * adding this trait.
70
+ */
71
+ export const CustomDetails = trait(() => true);
64
72
  /**
65
73
  * True when the entity itself, or any of its parents up the `ChildOf`
66
74
  * chain, has `Invisible`. Maintained by `provideInheritedInvisible`;
@@ -72,11 +80,14 @@ export const InheritedInvisible = trait(() => true);
72
80
  * Represents that an entity is composed of many instances, so that the treeview and
73
81
  * details panel may display all instances
74
82
  */
75
- export const Instanced = trait(() => true);
83
+ export const InstanceId = trait(() => -1);
76
84
  export const Instance = trait({
77
85
  meshID: -1,
78
86
  instanceID: -1,
79
87
  });
88
+ export const Instances = trait({
89
+ count: 0,
90
+ });
80
91
  export const RenderOrder = trait(() => 0);
81
92
  export const Opacity = trait(() => 1);
82
93
  /**
@@ -100,9 +111,6 @@ export const Colors = trait(() => new Uint8Array());
100
111
  * Per-vertex opacity values packed as uint8 (0-255).
101
112
  */
102
113
  export const Opacities = trait(() => new Uint8Array());
103
- export const Instances = trait({
104
- count: 0,
105
- });
106
114
  export const Arrows = trait({
107
115
  headAtPose: true,
108
116
  });
@@ -175,6 +183,10 @@ export const ReferenceFrame = trait(() => true);
175
183
  */
176
184
  export const ChunkProgress = trait({ loaded: 0, total: 0 });
177
185
  export const SelectToolInteractionLayer = trait(() => true);
186
+ /**
187
+ * This entity is selected by the user
188
+ */
189
+ export const Selected = trait();
178
190
  /**
179
191
  * This entity can be safely removed from the scene by the user
180
192
  */
@@ -250,6 +262,23 @@ export const updateGeometryTrait = (entity, geometry) => {
250
262
  updatePointCloud(entity, geometry.geometryType.value.pointCloud);
251
263
  }
252
264
  };
265
+ /**
266
+ * Patches an entity's `Matrix` trait in-place via the `Pose` round-trip
267
+ * (`matrixToPose` → merge → `poseToMatrix`), then signals `entity.changed(Matrix)`.
268
+ * No-ops silently if the entity has no `Matrix` trait.
269
+ */
270
+ export const writeMatrix = (entity, patch) => {
271
+ const matrix = entity.get(Matrix);
272
+ if (!matrix)
273
+ return;
274
+ const pose = matrixToPose(matrix, createPose());
275
+ const filtered = Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== undefined));
276
+ if (Object.keys(filtered).length === 0)
277
+ return;
278
+ Object.assign(pose, filtered);
279
+ poseToMatrix(pose, matrix);
280
+ entity.changed(Matrix);
281
+ };
253
282
  const updatePointCloud = (entity, pointCloud) => {
254
283
  parsePcdInWorker(new Uint8Array(pointCloud))
255
284
  .then((parsed) => {
@@ -0,0 +1,5 @@
1
+ import { type Entity, type TagTrait, type World } from 'koota';
2
+ export declare function isWorld(target: Entity | World): target is World;
3
+ export declare function useTag(target: () => Entity | World | undefined | null, tag: TagTrait): {
4
+ readonly current: boolean;
5
+ };
@@ -0,0 +1,43 @@
1
+ import { $internal as internal } from 'koota';
2
+ import { useWorld } from './useWorld';
3
+ export function isWorld(target) {
4
+ return typeof target?.spawn === 'function';
5
+ }
6
+ export function useTag(target, tag) {
7
+ const contextWorld = useWorld();
8
+ let value = $state(false);
9
+ $effect(() => {
10
+ const t = target();
11
+ if (!t) {
12
+ value = false;
13
+ return;
14
+ }
15
+ const world = isWorld(t) ? t : contextWorld;
16
+ // ???
17
+ // eslint-disable-next-line prefer-const
18
+ let entity;
19
+ /**
20
+ * Subscribe before reading worldEntity: world.onAdd triggers lazy
21
+ * registration so worldEntity is guaranteed to exist after this.
22
+ */
23
+ const onAddUnsub = world.onAdd(tag, (e) => {
24
+ if (e === entity)
25
+ value = true;
26
+ });
27
+ const onRemoveUnsub = world.onRemove(tag, (e) => {
28
+ if (e === entity)
29
+ value = false;
30
+ });
31
+ entity = isWorld(t) ? t[internal].worldEntity : t;
32
+ value = entity.has(tag);
33
+ return () => {
34
+ onAddUnsub();
35
+ onRemoveUnsub();
36
+ };
37
+ });
38
+ return {
39
+ get current() {
40
+ return value;
41
+ },
42
+ };
43
+ }
@@ -1,6 +1,6 @@
1
1
  export declare const ENVIRONMENT_CONTEXT_KEY: unique symbol;
2
2
  interface Environemnt {
3
- viewerMode: 'edit' | 'monitor' | 'focus';
3
+ viewerMode: 'edit' | 'monitor';
4
4
  isStandalone: boolean;
5
5
  inputBindingsEnabled: boolean;
6
6
  }
@@ -1,20 +1,19 @@
1
1
  import { getContext, setContext } from 'svelte';
2
- import { relations, useWorld } from '../ecs';
3
- import { useFocusedEntity, useSelectedEntity } from './useSelection.svelte';
2
+ import { relations, traits, useQuery, useWorld } from '../ecs';
4
3
  const linkedKey = Symbol('linked-context');
5
4
  export const provideLinkedEntities = () => {
6
5
  const world = useWorld();
7
- const selectedEntity = useSelectedEntity();
8
- const focusedEntity = useFocusedEntity();
9
- const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current);
10
- let linkedEntities = $derived(displayEntity?.targetsFor(relations.SubEntityLink) ?? []);
6
+ const selected = useQuery(traits.Selected);
7
+ let linkedEntities = $derived(selected.current
8
+ .flatMap((entity) => entity.targetFor(relations.SubEntityLink))
9
+ .filter((entity) => entity !== undefined));
11
10
  const unsubAdd = world.onAdd(relations.SubEntityLink, (entity, target) => {
12
- if (entity === displayEntity) {
11
+ if (selected.current.includes(entity)) {
13
12
  linkedEntities = [...linkedEntities, target];
14
13
  }
15
14
  });
16
15
  const unsubRemove = world.onRemove(relations.SubEntityLink, (entity, target) => {
17
- if (entity === displayEntity) {
16
+ if (selected.current.includes(entity)) {
18
17
  linkedEntities = linkedEntities.filter((e) => e !== target);
19
18
  }
20
19
  });
@@ -5,9 +5,10 @@ interface RaycastEvent<T extends EventNames> {
5
5
  intersections: Intersection[];
6
6
  }
7
7
  type Callback<T extends EventNames> = (event: RaycastEvent<T>) => void;
8
- export declare const useMouseRaycaster: (getOptions?: () => {
9
- enabled: boolean;
10
- }) => {
8
+ interface MouseRaycasterOptions {
9
+ enabled?: boolean;
10
+ }
11
+ export declare const useMouseRaycaster: (getOptions?: () => MouseRaycasterOptions) => {
11
12
  raycaster: Raycaster;
12
13
  onclick: (cb: Callback<"click">) => void;
13
14
  onmove: (cb: Callback<"move">) => void;
@@ -69,6 +69,7 @@ export const useMouseRaycaster = (getOptions) => {
69
69
  if (!options.enabled) {
70
70
  return;
71
71
  }
72
+ raycaster.firstHitOnly = true;
72
73
  dom.addEventListener('pointermove', onPointerMove, { passive: true });
73
74
  dom.addEventListener('pointerdown', onPointerDown, { passive: true });
74
75
  dom.addEventListener('pointerup', onPointerUp, { passive: true });
@@ -1,7 +1,7 @@
1
1
  import type { ColorRepresentation } from 'three';
2
2
  export interface Settings {
3
3
  cameraMode: 'orthographic' | 'perspective';
4
- interactionMode: 'navigate' | 'measure' | 'select';
4
+ interactionMode: 'navigate' | 'measure' | 'select' | 'gizmo';
5
5
  refreshRates: {
6
6
  poses: number;
7
7
  pointclouds: number;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import { Portal } from '@threlte/extras'
3
+ import { PressedKeys } from 'runed'
4
+
5
+ import Button from '../../components/overlay/dashboard/Button.svelte'
6
+ import { traits, useQuery } from '../../ecs'
7
+
8
+ import FocusBox from './FocusBox.svelte'
9
+ import { provideFocus } from './provideFocus.svelte'
10
+
11
+ let focusing = $state(false)
12
+
13
+ provideFocus(() => focusing)
14
+
15
+ const selected = useQuery(traits.Selected)
16
+
17
+ const canFocus = $derived(selected.current.length > 0 || focusing)
18
+
19
+ const keys = new PressedKeys()
20
+
21
+ keys.onKeys('/', () => {
22
+ if (selected.current.length > 0 && !focusing) {
23
+ focusing = true
24
+ } else if (focusing) {
25
+ focusing = false
26
+ }
27
+ })
28
+ </script>
29
+
30
+ <Portal id="dashboard">
31
+ <fieldset class="flex">
32
+ <Button
33
+ icon="focus"
34
+ active={focusing}
35
+ disabled={!canFocus}
36
+ description="Focus selection"
37
+ hotkey="/"
38
+ onclick={() => (focusing = !focusing)}
39
+ />
40
+ </fieldset>
41
+ </Portal>
42
+
43
+ {#if focusing}
44
+ <FocusBox />
45
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const Focus: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type Focus = ReturnType<typeof Focus>;
3
+ export default Focus;
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { T, useThrelte } from '@threlte/core'
3
+ import { Gizmo, TrackballControls } from '@threlte/extras'
4
+ import { untrack } from 'svelte'
5
+ import { Box3, Vector3 } from 'three'
6
+
7
+ import Camera from '../../components/Camera.svelte'
8
+ import { traits, useQuery } from '../../ecs'
9
+ import { useCameraControls } from '../../hooks/useControls.svelte'
10
+
11
+ const { scene } = useThrelte()
12
+ const cameraControls = useCameraControls()
13
+ const selected = useQuery(traits.Selected)
14
+
15
+ /**
16
+ * Save the main camera controls and their state the instant focus begins —
17
+ * this runs before the TrackballControls below swaps the shared context, so
18
+ * `current` is still the main controls. On teardown we hand `current` back to
19
+ * them (the trackball never restores it) and reset them to the saved view, so
20
+ * exiting focus returns the camera to where it was. camera-controls exposes
21
+ * saveState()/reset(); TrackballControls does not, which also narrows the type.
22
+ */
23
+ $effect.pre(() => {
24
+ const previousControls = untrack(() => cameraControls.current)
25
+ const restorableControls =
26
+ previousControls && 'saveState' in previousControls ? previousControls : undefined
27
+ restorableControls?.saveState()
28
+
29
+ return () => {
30
+ if (!restorableControls) return
31
+ cameraControls.set(restorableControls)
32
+ restorableControls.reset(false)
33
+ }
34
+ })
35
+
36
+ const box = new Box3()
37
+ const vec = new Vector3()
38
+
39
+ let center = $state.raw<[number, number, number]>([0, 0, 0])
40
+ let size = $state.raw<[number, number, number]>([0, 0, 0])
41
+
42
+ /**
43
+ * Frame the camera on the selection captured when focus was entered. Reading
44
+ * the selection untracked leaves this effect with no reactive dependencies,
45
+ * so it runs once on mount and the framing stays put — changing the selection
46
+ * while focused must not re-frame or reset the camera.
47
+ */
48
+ $effect(() => {
49
+ box.makeEmpty()
50
+ for (const entity of untrack(() => selected.current)) {
51
+ const object3d = scene.getObjectByName(entity as unknown as string)
52
+ if (object3d) {
53
+ box.expandByObject(object3d)
54
+ }
55
+ }
56
+
57
+ size = box.getSize(vec).toArray()
58
+ center = box.getCenter(vec).toArray()
59
+ })
60
+ </script>
61
+
62
+ <Camera position={[size[0] + 1, size[0] + 1, size[0] + 1]}>
63
+ <TrackballControls
64
+ target={center}
65
+ oncreate={(ref) => cameraControls.set(ref)}
66
+ >
67
+ <Gizmo placement="bottom-right" />
68
+ </TrackballControls>
69
+ </Camera>
70
+
71
+ <T.Box3Helper
72
+ args={[box, 'red']}
73
+ bvh={{ enabled: false }}
74
+ raycast={() => null}
75
+ />
@@ -0,0 +1,3 @@
1
+ declare const FocusBox: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type FocusBox = ReturnType<typeof FocusBox>;
3
+ export default FocusBox;
@@ -0,0 +1 @@
1
+ export declare const provideFocus: (focusing: () => boolean) => void;