@viamrobotics/motion-tools 1.15.0 → 1.15.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.
@@ -147,6 +147,47 @@ export declare class Orientation extends Message<Orientation> {
147
147
  static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Orientation;
148
148
  static equals(a: Orientation | PlainMessage<Orientation> | undefined, b: Orientation | PlainMessage<Orientation> | undefined): boolean;
149
149
  }
150
+ /**
151
+ * @generated from message viam.common.v1.PoseCloud
152
+ */
153
+ export declare class PoseCloud extends Message<PoseCloud> {
154
+ /**
155
+ * @generated from field: double x = 1;
156
+ */
157
+ x: number;
158
+ /**
159
+ * @generated from field: double y = 2;
160
+ */
161
+ y: number;
162
+ /**
163
+ * @generated from field: double z = 3;
164
+ */
165
+ z: number;
166
+ /**
167
+ * @generated from field: double o_x = 4;
168
+ */
169
+ oX: number;
170
+ /**
171
+ * @generated from field: double o_y = 5;
172
+ */
173
+ oY: number;
174
+ /**
175
+ * @generated from field: double o_z = 6;
176
+ */
177
+ oZ: number;
178
+ /**
179
+ * @generated from field: double theta = 7;
180
+ */
181
+ theta: number;
182
+ constructor(data?: PartialMessage<PoseCloud>);
183
+ static readonly runtime: typeof proto3;
184
+ static readonly typeName = "viam.common.v1.PoseCloud";
185
+ static readonly fields: FieldList;
186
+ static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PoseCloud;
187
+ static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): PoseCloud;
188
+ static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): PoseCloud;
189
+ static equals(a: PoseCloud | PlainMessage<PoseCloud> | undefined, b: PoseCloud | PlainMessage<PoseCloud> | undefined): boolean;
190
+ }
150
191
  /**
151
192
  * PoseInFrame contains a pose and the reference frame in which it was observed
152
193
  *
@@ -161,6 +202,10 @@ export declare class PoseInFrame extends Message<PoseInFrame> {
161
202
  * @generated from field: viam.common.v1.Pose pose = 2;
162
203
  */
163
204
  pose?: Pose;
205
+ /**
206
+ * @generated from field: optional viam.common.v1.PoseCloud goal_cloud = 3;
207
+ */
208
+ goalCloud?: PoseCloud;
164
209
  constructor(data?: PartialMessage<PoseInFrame>);
165
210
  static readonly runtime: typeof proto3;
166
211
  static readonly typeName = "viam.common.v1.PoseInFrame";
@@ -205,6 +205,66 @@ export class Orientation extends Message {
205
205
  return proto3.util.equals(Orientation, a, b);
206
206
  }
207
207
  }
208
+ /**
209
+ * @generated from message viam.common.v1.PoseCloud
210
+ */
211
+ export class PoseCloud extends Message {
212
+ /**
213
+ * @generated from field: double x = 1;
214
+ */
215
+ x = 0;
216
+ /**
217
+ * @generated from field: double y = 2;
218
+ */
219
+ y = 0;
220
+ /**
221
+ * @generated from field: double z = 3;
222
+ */
223
+ z = 0;
224
+ /**
225
+ * @generated from field: double o_x = 4;
226
+ */
227
+ oX = 0;
228
+ /**
229
+ * @generated from field: double o_y = 5;
230
+ */
231
+ oY = 0;
232
+ /**
233
+ * @generated from field: double o_z = 6;
234
+ */
235
+ oZ = 0;
236
+ /**
237
+ * @generated from field: double theta = 7;
238
+ */
239
+ theta = 0;
240
+ constructor(data) {
241
+ super();
242
+ proto3.util.initPartial(data, this);
243
+ }
244
+ static runtime = proto3;
245
+ static typeName = "viam.common.v1.PoseCloud";
246
+ static fields = proto3.util.newFieldList(() => [
247
+ { no: 1, name: "x", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ },
248
+ { no: 2, name: "y", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ },
249
+ { no: 3, name: "z", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ },
250
+ { no: 4, name: "o_x", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ },
251
+ { no: 5, name: "o_y", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ },
252
+ { no: 6, name: "o_z", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ },
253
+ { no: 7, name: "theta", kind: "scalar", T: 1 /* ScalarType.DOUBLE */ },
254
+ ]);
255
+ static fromBinary(bytes, options) {
256
+ return new PoseCloud().fromBinary(bytes, options);
257
+ }
258
+ static fromJson(jsonValue, options) {
259
+ return new PoseCloud().fromJson(jsonValue, options);
260
+ }
261
+ static fromJsonString(jsonString, options) {
262
+ return new PoseCloud().fromJsonString(jsonString, options);
263
+ }
264
+ static equals(a, b) {
265
+ return proto3.util.equals(PoseCloud, a, b);
266
+ }
267
+ }
208
268
  /**
209
269
  * PoseInFrame contains a pose and the reference frame in which it was observed
210
270
  *
@@ -219,6 +279,10 @@ export class PoseInFrame extends Message {
219
279
  * @generated from field: viam.common.v1.Pose pose = 2;
220
280
  */
221
281
  pose;
282
+ /**
283
+ * @generated from field: optional viam.common.v1.PoseCloud goal_cloud = 3;
284
+ */
285
+ goalCloud;
222
286
  constructor(data) {
223
287
  super();
224
288
  proto3.util.initPartial(data, this);
@@ -228,6 +292,7 @@ export class PoseInFrame extends Message {
228
292
  static fields = proto3.util.newFieldList(() => [
229
293
  { no: 1, name: "reference_frame", kind: "scalar", T: 9 /* ScalarType.STRING */ },
230
294
  { no: 2, name: "pose", kind: "message", T: Pose },
295
+ { no: 3, name: "goal_cloud", kind: "message", T: PoseCloud, opt: true },
231
296
  ]);
232
297
  static fromBinary(bytes, options) {
233
298
  return new PoseInFrame().fromBinary(bytes, options);
@@ -29,6 +29,7 @@
29
29
 
30
30
  import FileDrop from './FileDrop/FileDrop.svelte'
31
31
  import HoveredEntities from './hover/HoveredEntities.svelte'
32
+ import AddFrames from './overlay/AddFrames.svelte'
32
33
  import LiveUpdatesBanner from './overlay/LiveUpdatesBanner.svelte'
33
34
  import Logs from './overlay/Logs.svelte'
34
35
  import ArmPositions from './overlay/widgets/ArmPositions.svelte'
@@ -144,6 +145,7 @@
144
145
 
145
146
  <Settings />
146
147
  <Logs />
148
+ <AddFrames />
147
149
  </div>
148
150
  {/snippet}
149
151
  </SceneProviders>
@@ -63,7 +63,11 @@ Renders a Viam Geometry object
63
63
 
64
64
  <Portal id={parent.current}>
65
65
  {#if model}
66
- <T is={model} />
66
+ <T
67
+ is={model}
68
+ name={entity}
69
+ {...events}
70
+ />
67
71
  {/if}
68
72
 
69
73
  {#if settings.current.renderArmModels.includes('colliders') || !model}
@@ -93,7 +93,6 @@
93
93
  isOpen
94
94
  exitable={false}
95
95
  title="Lasso"
96
- strategy="absolute"
97
96
  defaultSize={{ width: 445, height: 100 }}
98
97
  defaultPosition={{ x: rect.width / 2 - 200, y: rect.height - 10 - 100 }}
99
98
  >
@@ -16,19 +16,7 @@
16
16
  const selectedEntity = useSelectedEntity()
17
17
  const selectedObject3d = useSelectedObject3d()
18
18
 
19
- const object = $derived.by(() => {
20
- if (!selectedObject3d.current) {
21
- return
22
- }
23
-
24
- // Create a clone in the case of meshes, which could be frames with geometries,
25
- // so that our bounding box doesn't include children
26
- if (isInstanceOf(selectedObject3d.current, 'Mesh')) {
27
- return selectedObject3d.current?.clone(false)
28
- }
29
-
30
- return selectedObject3d.current
31
- })
19
+ const object = $derived(selectedObject3d.current)
32
20
 
33
21
  const { start, stop } = useTask(
34
22
  () => {
@@ -44,10 +32,6 @@
44
32
  mesh.getBoundingBoxAt(selectedEntity.instance, box3)
45
33
  obb.fromBox3(box3)
46
34
  obbHelper.setFromOBB(obb)
47
- } else if (isInstanceOf(selectedObject3d.current, 'Mesh')) {
48
- selectedObject3d.current?.getWorldPosition(object.position)
49
- selectedObject3d.current?.getWorldQuaternion(object.quaternion)
50
- obbHelper.setFromObject(object)
51
35
  } else {
52
36
  obbHelper.setFromObject(object)
53
37
  }
@@ -0,0 +1,64 @@
1
+ <script lang="ts">
2
+ import { Portal } from '@threlte/extras'
3
+ import { Button } from '@viamrobotics/prime-core'
4
+
5
+ import { useFramelessComponents } from '../../hooks/useFramelessComponents.svelte'
6
+ import { usePartConfig } from '../../hooks/usePartConfig.svelte'
7
+ import { usePartID } from '../../hooks/usePartID.svelte'
8
+
9
+ import DashboardButton from './dashboard/Button.svelte'
10
+ import FloatingPanel from './FloatingPanel.svelte'
11
+
12
+ const partID = usePartID()
13
+ const framelessComponents = useFramelessComponents()
14
+ const partConfig = usePartConfig()
15
+
16
+ let selectedComponent = $derived(framelessComponents.current[0] ?? '')
17
+
18
+ let isOpen = $state(false)
19
+ </script>
20
+
21
+ {#if partID.current && partConfig.hasEditPermissions}
22
+ <Portal id="dashboard">
23
+ <fieldset>
24
+ <DashboardButton
25
+ active
26
+ icon="axis-arrow"
27
+ description="Add frames"
28
+ onclick={() => {
29
+ isOpen = !isOpen
30
+ }}
31
+ />
32
+ </fieldset>
33
+ </Portal>
34
+
35
+ <FloatingPanel
36
+ {isOpen}
37
+ defaultSize={{ width: 300, height: 150 }}
38
+ >
39
+ <div class="flex h-full flex-col items-center justify-center gap-2 overflow-auto p-3 text-xs">
40
+ {#if framelessComponents.current.length > 0}
41
+ <select
42
+ class="border-light hover:border-gray-6 focus:border-gray-9 h-7.5 w-full appearance-none border bg-white px-2 py-1.5 text-xs leading-tight"
43
+ bind:value={selectedComponent}
44
+ >
45
+ {#each framelessComponents.current as component (component)}
46
+ <option>{component}</option>
47
+ {/each}
48
+ </select>
49
+
50
+ <Button
51
+ icon="plus"
52
+ onclick={() => {
53
+ partConfig.createFrame(selectedComponent)
54
+ isOpen = false
55
+ }}
56
+ >
57
+ Add frame
58
+ </Button>
59
+ {:else}
60
+ <p class="text-center">No components without frames</p>
61
+ {/if}
62
+ </div>
63
+ </FloatingPanel>
64
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const AddFrames: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type AddFrames = ReturnType<typeof AddFrames>;
3
+ export default AddFrames;
@@ -638,9 +638,9 @@
638
638
  {/if}
639
639
  </div>
640
640
 
641
- <h3 class="text-subtle-2 pt-3 pb-2">Relationships</h3>
642
-
643
641
  {#if linkedEntities.current.length > 0}
642
+ <h3 class="text-subtle-2 pt-3 pb-2">Relationships</h3>
643
+
644
644
  <div>
645
645
  <div class="mt-0.5 flex flex-col gap-1">
646
646
  <strong class="font-semibold">Linked entities</strong>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte'
3
3
 
4
+ import { useThrelte } from '@threlte/core'
4
5
  import { Icon } from '@viamrobotics/prime-core'
5
6
  import * as floatingPanel from '@zag-js/floating-panel'
6
7
  import { normalizeProps, useMachine } from '@zag-js/svelte'
@@ -12,7 +13,6 @@
12
13
  exitable?: boolean
13
14
  resizable?: boolean
14
15
  persistRect?: boolean
15
- strategy?: 'absolute' | 'fixed'
16
16
  isOpen?: boolean
17
17
  children: Snippet
18
18
  }
@@ -20,6 +20,7 @@
20
20
  let {
21
21
  title = '',
22
22
  defaultSize = { width: 700, height: 500 },
23
+ defaultPosition,
23
24
  exitable = true,
24
25
  resizable = false,
25
26
  persistRect = true,
@@ -28,12 +29,19 @@
28
29
  ...props
29
30
  }: Props = $props()
30
31
 
32
+ const { dom } = useThrelte()
33
+
31
34
  const id = $props.id()
32
35
  const floatingPanelService = useMachine(floatingPanel.machine, () => ({
33
36
  id,
34
37
  defaultSize,
38
+ defaultPosition: defaultPosition ?? {
39
+ x: dom.clientWidth / 2 - defaultSize.width / 2 + dom.clientLeft,
40
+ y: dom.clientHeight / 2 - defaultSize.width / 2 + dom.clientTop,
41
+ },
35
42
  resizable,
36
43
  allowOverflow: false,
44
+ strategy: 'absolute' as const,
37
45
  persistRect,
38
46
  open: isOpen,
39
47
  ...props,
@@ -58,12 +66,12 @@
58
66
  {...api.getHeaderProps()}
59
67
  class="border-medium flex justify-between border-b p-2"
60
68
  >
61
- <p
69
+ <h3
62
70
  {...api.getTitleProps()}
63
71
  class="text-gray-7 text-xs"
64
72
  >
65
73
  {title}
66
- </p>
74
+ </h3>
67
75
 
68
76
  {#if exitable}
69
77
  <div
@@ -12,7 +12,6 @@ interface Props {
12
12
  exitable?: boolean;
13
13
  resizable?: boolean;
14
14
  persistRect?: boolean;
15
- strategy?: 'absolute' | 'fixed';
16
15
  isOpen?: boolean;
17
16
  children: Snippet;
18
17
  }
@@ -176,32 +176,27 @@
176
176
  {/if}
177
177
  {/snippet}
178
178
 
179
- <div class="root-node">
180
- <div {...api.getRootProps() as object}>
181
- <div {...api.getTreeProps()}>
182
- {#if rootChildren.length === 0}
183
- <p class="text-subtle-2 px-2 py-4">No objects displayed</p>
184
- {:else if rootChildren.length > 200}
185
- <VirtualList
186
- class="w-full"
187
- style="height:{Math.min(8, Math.max(rootChildren.length, 5)) * 32}px;"
188
- items={rootChildren}
189
- >
190
- {#snippet vl_slot({ index, item })}
191
- {@render treeNode({ node: item, indexPath: [Number(index)], api })}
192
- {/snippet}
193
- </VirtualList>
194
- {:else}
195
- <div
196
- style="height:{Math.min(8, Math.max(rootChildren.length, 5)) * 32}px;"
197
- class="overflow-auto"
198
- >
199
- {#each rootChildren as node, index (node.entity)}
200
- {@render treeNode({ node, indexPath: [Number(index)], api })}
201
- {/each}
202
- </div>
203
- {/if}
204
- </div>
179
+ <div
180
+ {...api.getRootProps()}
181
+ class="h-full overflow-auto text-xs"
182
+ >
183
+ <div {...api.getTreeProps()}>
184
+ {#if rootChildren.length === 0}
185
+ <p class="text-subtle-2 px-2 py-4">No objects displayed</p>
186
+ {:else if rootChildren.length > 200}
187
+ <VirtualList
188
+ class="w-full"
189
+ items={rootChildren}
190
+ >
191
+ {#snippet vl_slot({ index, item })}
192
+ {@render treeNode({ node: item, indexPath: [Number(index)], api })}
193
+ {/snippet}
194
+ </VirtualList>
195
+ {:else}
196
+ {#each rootChildren as node, index (node.entity)}
197
+ {@render treeNode({ node, indexPath: [Number(index)], api })}
198
+ {/each}
199
+ {/if}
205
200
  </div>
206
201
  </div>
207
202
 
@@ -2,25 +2,18 @@
2
2
  import { type Entity, IsExcluded } from 'koota'
3
3
 
4
4
  import { traits, useQuery, useWorld } from '../../../ecs'
5
- import { useEnvironment } from '../../../hooks/useEnvironment.svelte'
6
5
  import { useFrames } from '../../../hooks/useFrames.svelte'
7
- import { usePartConfig } from '../../../hooks/usePartConfig.svelte'
8
- import { usePartID } from '../../../hooks/usePartID.svelte'
9
6
  import { useSelectedEntity } from '../../../hooks/useSelection.svelte'
10
7
 
11
8
  import FloatingPanel from '../FloatingPanel.svelte'
12
- import AddFrames from './AddFrames.svelte'
13
9
  import { buildTreeNodes, type TreeNode } from './buildTree'
14
10
  import Tree from './Tree.svelte'
15
11
  import { provideTreeExpandedContext } from './useExpanded.svelte'
16
12
 
17
13
  provideTreeExpandedContext()
18
14
 
19
- const partID = usePartID()
20
15
  const selectedEntity = useSelectedEntity()
21
16
 
22
- const environment = useEnvironment()
23
- const partConfig = usePartConfig()
24
17
  const frames = useFrames()
25
18
  const world = useWorld()
26
19
 
@@ -49,19 +42,13 @@
49
42
  exitable={false}
50
43
  resizable
51
44
  >
52
- <div class="text-xs">
53
- <Tree
54
- {rootNode}
55
- {nodeMap}
56
- onSelectionChange={(event) => {
57
- const value = event.selectedValue[0]
58
-
59
- selectedEntity.set(value ? (Number(value) as Entity) : undefined)
60
- }}
61
- />
62
-
63
- {#if environment.current.isStandalone && partID.current && partConfig.hasEditPermissions}
64
- <AddFrames />
65
- {/if}
66
- </div>
45
+ <Tree
46
+ {rootNode}
47
+ {nodeMap}
48
+ onSelectionChange={(event) => {
49
+ const value = event.selectedValue[0]
50
+
51
+ selectedEntity.set(value ? (Number(value) as Entity) : undefined)
52
+ }}
53
+ />
67
54
  </FloatingPanel>
@@ -9,7 +9,6 @@ export declare class OBBHelper extends LineSegments2 {
9
9
  elements: number[];
10
10
  };
11
11
  }): this;
12
- /** Set from a Mesh/Object3D assuming no shears. Uses geometry's local bbox + world rotation/scale. */
13
- setFromObject(object: Object3D): this;
12
+ setFromObject(root: Object3D): this;
14
13
  dispose(): void;
15
14
  }
@@ -1,14 +1,42 @@
1
- import { BoxGeometry, BufferGeometry, EdgesGeometry, Matrix3, Matrix4, Mesh, Object3D, Quaternion, Vector3, } from 'three';
1
+ import { Box3, BoxGeometry, BufferGeometry, EdgesGeometry, Matrix4, Mesh, Object3D, Quaternion, Vector3, } from 'three';
2
2
  import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
3
3
  import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js';
4
4
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
5
+ const box = new Box3();
6
+ const childBox = new Box3();
7
+ const inverseRootMatrixWorld = new Matrix4();
8
+ const rootMatrixWorld = new Matrix4();
9
+ const relativeMatrix = new Matrix4();
10
+ const scaleMatrix = new Matrix4();
5
11
  const center = new Vector3();
6
- const half = new Vector3();
7
12
  const size = new Vector3();
13
+ const basis = new Matrix4();
8
14
  const quaternion = new Quaternion();
9
- const scale = new Vector3();
10
- const absScale = new Vector3();
11
- const worldCenter = new Vector3();
15
+ const corners = [
16
+ new Vector3(),
17
+ new Vector3(),
18
+ new Vector3(),
19
+ new Vector3(),
20
+ new Vector3(),
21
+ new Vector3(),
22
+ new Vector3(),
23
+ new Vector3(),
24
+ ];
25
+ const expandBoxByTransformedBox = (box, childBox, matrix) => {
26
+ const min = childBox.min;
27
+ const max = childBox.max;
28
+ corners[0].set(min.x, min.y, min.z).applyMatrix4(matrix);
29
+ corners[1].set(min.x, min.y, max.z).applyMatrix4(matrix);
30
+ corners[2].set(min.x, max.y, min.z).applyMatrix4(matrix);
31
+ corners[3].set(min.x, max.y, max.z).applyMatrix4(matrix);
32
+ corners[4].set(max.x, min.y, min.z).applyMatrix4(matrix);
33
+ corners[5].set(max.x, min.y, max.z).applyMatrix4(matrix);
34
+ corners[6].set(max.x, max.y, min.z).applyMatrix4(matrix);
35
+ corners[7].set(max.x, max.y, max.z).applyMatrix4(matrix);
36
+ for (const corner of corners) {
37
+ box.expandByPoint(corner);
38
+ }
39
+ };
12
40
  export class OBBHelper extends LineSegments2 {
13
41
  constructor(color = 0x000000, linewidth = 2) {
14
42
  const edges = new EdgesGeometry(new BoxGeometry());
@@ -27,52 +55,45 @@ export class OBBHelper extends LineSegments2 {
27
55
  this.renderOrder = 999;
28
56
  }
29
57
  setFromOBB(obb) {
30
- // position/rotation
31
- const basis = new Matrix4().setFromMatrix3(obb.rotation);
58
+ basis.setFromMatrix3(obb.rotation);
32
59
  quaternion.setFromRotationMatrix(basis);
33
- // scale = full size
34
60
  size.copy(obb.halfSize).multiplyScalar(2);
35
- // compose
36
61
  this.matrix.compose(obb.center, quaternion, size);
37
- this.matrixWorld.copy(this.matrix); // no parent updates if used standalone
62
+ this.matrixWorld.copy(this.matrix);
38
63
  return this;
39
64
  }
40
- /** Set from a Mesh/Object3D assuming no shears. Uses geometry's local bbox + world rotation/scale. */
41
- setFromObject(object) {
42
- // Find a geometry to read bbox from
43
- let geometry;
44
- if (object.geometry) {
45
- geometry = object.geometry;
46
- }
47
- else {
48
- // try the first mesh child
49
- object.traverse((child) => {
50
- if (!geometry && child.geometry) {
51
- geometry = child.geometry;
52
- }
53
- });
54
- }
55
- if (!geometry) {
65
+ setFromObject(root) {
66
+ root.updateWorldMatrix(true, true);
67
+ rootMatrixWorld.copy(root.matrixWorld);
68
+ inverseRootMatrixWorld.copy(rootMatrixWorld).invert();
69
+ box.makeEmpty();
70
+ root.traverse((child) => {
71
+ const mesh = child;
72
+ const geometry = mesh.geometry;
73
+ if (!geometry)
74
+ return;
75
+ if (!geometry.boundingBox) {
76
+ geometry.computeBoundingBox();
77
+ }
78
+ if (!geometry.boundingBox)
79
+ return;
80
+ // Transform this mesh's local bounding box into root-local space
81
+ relativeMatrix.multiplyMatrices(inverseRootMatrixWorld, mesh.matrixWorld);
82
+ childBox.copy(geometry.boundingBox);
83
+ expandBoxByTransformedBox(box, childBox, relativeMatrix);
84
+ });
85
+ if (box.isEmpty()) {
56
86
  console.warn('[OBBHelper] No geometry found on object to compute OBB.');
57
87
  return this;
58
88
  }
59
- if (!geometry.boundingBox) {
60
- geometry.computeBoundingBox();
61
- }
62
- if (geometry.boundingBox) {
63
- geometry.boundingBox.getCenter(center);
64
- // half size in local space
65
- geometry.boundingBox.getSize(size).multiplyScalar(0.5);
66
- }
67
- object.getWorldQuaternion(quaternion);
68
- object.getWorldScale(scale);
69
- // non-uniform scale supported (no shear): enlarge halfSize by |scale|
70
- half.copy(size).multiply(absScale.set(Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)));
71
- worldCenter.copy(center);
72
- object.localToWorld(worldCenter);
73
- // compose transform (unit box -> oriented box)
74
- const fullSize = half.multiplyScalar(2);
75
- this.matrix.compose(worldCenter, quaternion, fullSize);
89
+ box.getCenter(center);
90
+ box.getSize(size);
91
+ // Place the helper at the center of the bounding box, in root-local space
92
+ this.matrix.makeTranslation(center.x, center.y, center.z);
93
+ // Then inherit the root's full world transform
94
+ this.matrix.premultiply(rootMatrixWorld);
95
+ // Scale the unit box to the OBB extents in root-local axes
96
+ this.matrix.multiply(scaleMatrix.makeScale(size.x, size.y, size.z));
76
97
  this.matrixWorld.copy(this.matrix);
77
98
  return this;
78
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.15.0",
3
+ "version": "1.15.2",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,8 +17,8 @@
17
17
  "@skeletonlabs/skeleton": "3.2.0",
18
18
  "@skeletonlabs/skeleton-svelte": "1.5.1",
19
19
  "@sveltejs/adapter-static": "3.0.9",
20
- "@sveltejs/kit": "2.49.5",
21
- "@sveltejs/package": "2.5.0",
20
+ "@sveltejs/kit": "2.55.0",
21
+ "@sveltejs/package": "2.5.7",
22
22
  "@sveltejs/vite-plugin-svelte": "6.1.4",
23
23
  "@tailwindcss/forms": "0.5.10",
24
24
  "@tailwindcss/vite": "4.1.13",
@@ -63,8 +63,8 @@
63
63
  "prettier-plugin-tailwindcss": "0.6.14",
64
64
  "publint": "0.3.12",
65
65
  "runed": "0.31.1",
66
- "svelte": "5.45.9",
67
- "svelte-check": "4.3.1",
66
+ "svelte": "5.53.13",
67
+ "svelte-check": "4.4.5",
68
68
  "svelte-virtuallists": "1.4.2",
69
69
  "tailwindcss": "4.1.13",
70
70
  "three": "0.182.0",
@@ -1,32 +0,0 @@
1
- <script lang="ts">
2
- import { IconButton } from '@viamrobotics/prime-core'
3
-
4
- import { useFramelessComponents } from '../../../hooks/useFramelessComponents.svelte'
5
- import { usePartConfig } from '../../../hooks/usePartConfig.svelte'
6
-
7
- import Drawer from './Drawer.svelte'
8
-
9
- const framelessComponents = useFramelessComponents()
10
- const partConfig = usePartConfig()
11
- </script>
12
-
13
- <Drawer name="Add frames">
14
- <div class="flex max-h-64 flex-col gap-2 overflow-auto p-3">
15
- {#if framelessComponents.current.length > 0}
16
- <ul class="space-y-1">
17
- {#each framelessComponents.current as component (component)}
18
- <li class="flex items-center gap-2 text-xs text-gray-700">
19
- {component}
20
- <IconButton
21
- label="Add frame"
22
- icon="plus"
23
- onclick={() => partConfig.createFrame(component)}
24
- />
25
- </li>
26
- {/each}
27
- </ul>
28
- {:else}
29
- No components without frames
30
- {/if}
31
- </div>
32
- </Drawer>
@@ -1,18 +0,0 @@
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 AddFrames: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {}, {}, string>;
17
- type AddFrames = InstanceType<typeof AddFrames>;
18
- export default AddFrames;