@viamrobotics/motion-tools 1.11.0 → 1.11.1
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.
- package/dist/components/App.svelte +3 -1
- package/dist/components/Focus.svelte +1 -8
- package/dist/components/Geometry.svelte +0 -1
- package/dist/components/Lasso/Lasso.svelte +153 -0
- package/dist/components/Lasso/Lasso.svelte.d.ts +6 -0
- package/dist/components/Lasso/Line.svelte +44 -0
- package/dist/components/Lasso/Line.svelte.d.ts +11 -0
- package/dist/components/PointerMissBox.svelte +0 -1
- package/dist/components/Points.svelte +1 -1
- package/dist/components/Scene.svelte +3 -6
- package/dist/components/hover/HoveredEntityTooltip.svelte +2 -1
- package/dist/components/xr/CameraFeed.svelte +3 -0
- package/dist/components/xr/CameraFeed.svelte.d.ts +1 -0
- package/dist/components/xr/JointLimitsWidget.svelte +25 -22
- package/dist/components/xr/XR.svelte +28 -20
- package/dist/plugins/bvh.svelte.d.ts +8 -0
- package/dist/plugins/bvh.svelte.js +69 -0
- package/dist/ply.d.ts +1 -1
- package/dist/ply.js +5 -0
- package/package.json +4 -1
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import Camera from './overlay/widgets/Camera.svelte'
|
|
30
30
|
import HoveredEntities from './hover/HoveredEntities.svelte'
|
|
31
31
|
import Settings from './overlay/settings/Settings.svelte'
|
|
32
|
+
import { useXR } from '@threlte/xr'
|
|
32
33
|
|
|
33
34
|
interface LocalConfigProps {
|
|
34
35
|
getLocalPartConfig: () => Struct
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
const settings = provideSettings()
|
|
68
69
|
const environment = provideEnvironment()
|
|
69
70
|
const currentRobotCameraWidgets = $derived(settings.current.openCameraWidgets[partID] || [])
|
|
71
|
+
const { isPresenting } = useXR()
|
|
70
72
|
|
|
71
73
|
$effect(() => {
|
|
72
74
|
settings.current.enableKeybindings = enableKeybindings
|
|
@@ -148,7 +150,7 @@
|
|
|
148
150
|
<ArmPositions />
|
|
149
151
|
{/if}
|
|
150
152
|
|
|
151
|
-
{#if !focus}
|
|
153
|
+
{#if !focus && !$isPresenting}
|
|
152
154
|
{#each currentRobotCameraWidgets as cameraName (cameraName)}
|
|
153
155
|
<Camera name={cameraName} />
|
|
154
156
|
{/each}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { T } from '@threlte/core'
|
|
3
|
+
import type { IntersectionEvent } from '@threlte/extras'
|
|
4
|
+
import Line from './Line.svelte'
|
|
5
|
+
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
6
|
+
import earcut from 'earcut'
|
|
7
|
+
import { Box3, BufferAttribute, Vector3 } from 'three'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
debug?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { debug = true }: Props = $props()
|
|
14
|
+
|
|
15
|
+
const controls = useCameraControls()
|
|
16
|
+
|
|
17
|
+
const box3 = new Box3()
|
|
18
|
+
const a = new Vector3()
|
|
19
|
+
const b = new Vector3()
|
|
20
|
+
const c = new Vector3()
|
|
21
|
+
|
|
22
|
+
let drawing = false
|
|
23
|
+
|
|
24
|
+
let position = $state<[number, number, number]>([0, 0, 0])
|
|
25
|
+
let lassos = $state<
|
|
26
|
+
{
|
|
27
|
+
positions: number[]
|
|
28
|
+
indices: Uint16Array
|
|
29
|
+
boxes: Box3[]
|
|
30
|
+
min: { x: number; y: number }
|
|
31
|
+
max: { x: number; y: number }
|
|
32
|
+
}[]
|
|
33
|
+
>([])
|
|
34
|
+
|
|
35
|
+
const onpointerdown = (event: IntersectionEvent<PointerEvent>) => {
|
|
36
|
+
drawing = true
|
|
37
|
+
|
|
38
|
+
const { x, y } = event.point
|
|
39
|
+
|
|
40
|
+
lassos.push({
|
|
41
|
+
positions: [x, y, 0],
|
|
42
|
+
indices: new Uint16Array(),
|
|
43
|
+
boxes: [],
|
|
44
|
+
min: { x, y },
|
|
45
|
+
max: { x, y },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (controls.current) {
|
|
49
|
+
controls.current.enabled = false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const onpointermove = (event: IntersectionEvent<PointerEvent>) => {
|
|
54
|
+
event.point.toArray(position)
|
|
55
|
+
|
|
56
|
+
if (!drawing) return
|
|
57
|
+
|
|
58
|
+
let line = lassos.at(-1)
|
|
59
|
+
|
|
60
|
+
if (!line) return
|
|
61
|
+
|
|
62
|
+
const { x, y } = event.point
|
|
63
|
+
line.positions.push(x, y, 0)
|
|
64
|
+
|
|
65
|
+
if (x < line.min.x) line.min.x = x
|
|
66
|
+
else if (x > line.max.x) line.max.x = x
|
|
67
|
+
|
|
68
|
+
if (y < line.min.y) line.min.y = y
|
|
69
|
+
else if (y > line.max.y) line.max.y = y
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const onpointerup = () => {
|
|
73
|
+
drawing = false
|
|
74
|
+
|
|
75
|
+
let lasso = lassos.at(-1)
|
|
76
|
+
|
|
77
|
+
if (!lasso) return
|
|
78
|
+
|
|
79
|
+
const [x, y] = lasso.positions
|
|
80
|
+
|
|
81
|
+
if (controls.current) {
|
|
82
|
+
controls.current.enabled = true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Close the loop
|
|
86
|
+
lasso.positions.push(x, y, 0)
|
|
87
|
+
|
|
88
|
+
const { positions } = lasso
|
|
89
|
+
|
|
90
|
+
const indices = earcut(positions, undefined, 3)
|
|
91
|
+
lasso.indices = new Uint16Array(indices)
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < indices.length; i += 6) {
|
|
94
|
+
const stride = 3
|
|
95
|
+
const ia = indices[i + 0] * stride
|
|
96
|
+
const ib = indices[i + 1] * stride
|
|
97
|
+
const ic = indices[i + 2] * stride
|
|
98
|
+
|
|
99
|
+
a.set(positions[ia + 0], positions[ia + 1], positions[ia + 2])
|
|
100
|
+
b.set(positions[ib + 0], positions[ib + 1], positions[ib + 2])
|
|
101
|
+
c.set(positions[ic + 0], positions[ic + 1], positions[ic + 2])
|
|
102
|
+
box3.setFromPoints([a, b, c])
|
|
103
|
+
|
|
104
|
+
lasso.boxes.push(box3.clone())
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<T.Mesh
|
|
110
|
+
{onpointerdown}
|
|
111
|
+
{onpointerup}
|
|
112
|
+
{onpointermove}
|
|
113
|
+
>
|
|
114
|
+
<T.PlaneGeometry args={[7, 7, 10, 10]} />
|
|
115
|
+
<T.MeshBasicMaterial
|
|
116
|
+
wireframe
|
|
117
|
+
color="blue"
|
|
118
|
+
transparent
|
|
119
|
+
opacity={debug ? 1 : 0}
|
|
120
|
+
/>
|
|
121
|
+
</T.Mesh>
|
|
122
|
+
|
|
123
|
+
{#each lassos as lasso (lasso)}
|
|
124
|
+
<Line positions={lasso.positions} />
|
|
125
|
+
|
|
126
|
+
{#if debug}
|
|
127
|
+
{#if lasso.indices.length > 0}
|
|
128
|
+
<T.Mesh>
|
|
129
|
+
<T.BufferGeometry
|
|
130
|
+
oncreate={(ref) => {
|
|
131
|
+
ref.setIndex(new BufferAttribute(lasso.indices, 1))
|
|
132
|
+
ref.setAttribute('position', new BufferAttribute(new Float32Array(lasso.positions), 3))
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
<T.MeshBasicMaterial
|
|
136
|
+
wireframe
|
|
137
|
+
color="green"
|
|
138
|
+
/>
|
|
139
|
+
</T.Mesh>
|
|
140
|
+
{/if}
|
|
141
|
+
|
|
142
|
+
{#each lasso.boxes as box (box)}
|
|
143
|
+
<T.Box3Helper args={[box, 'lightgreen']} />
|
|
144
|
+
{/each}
|
|
145
|
+
|
|
146
|
+
<T.Box3Helper
|
|
147
|
+
args={[
|
|
148
|
+
new Box3(a.set(lasso.min.x, lasso.min.y, 0), b.set(lasso.max.x, lasso.max.y, 0)),
|
|
149
|
+
'red',
|
|
150
|
+
]}
|
|
151
|
+
/>
|
|
152
|
+
{/if}
|
|
153
|
+
{/each}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { T, useThrelte } from '@threlte/core'
|
|
3
|
+
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
|
|
4
|
+
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
|
|
5
|
+
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
|
|
6
|
+
import { untrack } from 'svelte'
|
|
7
|
+
|
|
8
|
+
let { positions } = $props()
|
|
9
|
+
|
|
10
|
+
let geometry = $state.raw(new LineGeometry())
|
|
11
|
+
|
|
12
|
+
const material = new LineMaterial()
|
|
13
|
+
const line = new Line2()
|
|
14
|
+
material.linewidth = 1
|
|
15
|
+
material.color.set('red')
|
|
16
|
+
material.depthTest = false
|
|
17
|
+
material.depthWrite = false
|
|
18
|
+
|
|
19
|
+
const { invalidate } = useThrelte()
|
|
20
|
+
|
|
21
|
+
$effect(() => {
|
|
22
|
+
untrack(() => {
|
|
23
|
+
geometry = new LineGeometry()
|
|
24
|
+
invalidate()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!positions || positions.length === 0) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
untrack(() => {
|
|
32
|
+
geometry.setPositions(positions)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<T
|
|
38
|
+
is={line}
|
|
39
|
+
frustumCulled={false}
|
|
40
|
+
renderOrder={Number.POSITIVE_INFINITY}
|
|
41
|
+
>
|
|
42
|
+
<T is={geometry} />
|
|
43
|
+
<T is={material} />
|
|
44
|
+
</T>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default Line;
|
|
2
|
+
type Line = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const Line: import("svelte").Component<{
|
|
7
|
+
positions: any;
|
|
8
|
+
}, {}, "">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
positions: any;
|
|
11
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Vector3 } from 'three'
|
|
3
3
|
import { T } from '@threlte/core'
|
|
4
|
-
import { Grid, interactivity, PerfMonitor,
|
|
4
|
+
import { Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
|
|
5
5
|
import Entities from './Entities.svelte'
|
|
6
6
|
import Selected from './Selected.svelte'
|
|
7
7
|
import Focus from './Focus.svelte'
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { useFocusedObject3d } from '../hooks/useSelection.svelte'
|
|
11
11
|
import type { Snippet } from 'svelte'
|
|
12
12
|
import { useXR } from '@threlte/xr'
|
|
13
|
-
|
|
13
|
+
import { bvh } from '../plugins/bvh.svelte'
|
|
14
14
|
import { useOrigin } from './xr/useOrigin.svelte'
|
|
15
15
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
16
16
|
import CameraControls from './CameraControls.svelte'
|
|
@@ -43,10 +43,7 @@
|
|
|
43
43
|
enabled.set(!settings.current.enableMeasure)
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
raycaster
|
|
47
|
-
raycaster.params.Points.threshold = 0.005
|
|
48
|
-
|
|
49
|
-
bvh(() => ({ helper: false }))
|
|
46
|
+
bvh(raycaster, () => ({ helper: false }))
|
|
50
47
|
|
|
51
48
|
const focusedObject = $derived(focusedObject3d.current)
|
|
52
49
|
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
position={[hoverInfo.x, hoverInfo.y, hoverInfo.z]}
|
|
15
15
|
class="pointer-events-none"
|
|
16
16
|
zIndexRange={[3, 0]}
|
|
17
|
+
center
|
|
17
18
|
>
|
|
18
19
|
<div
|
|
19
|
-
class="border-medium pointer-events-none relative -
|
|
20
|
+
class="border-medium pointer-events-none relative -translate-y-1/2 border bg-white px-3 py-2.5 text-xs shadow-md"
|
|
20
21
|
>
|
|
21
22
|
<!-- Arrow -->
|
|
22
23
|
<div
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
offset?: { x?: number; y?: number; z?: number }
|
|
11
11
|
scale?: number
|
|
12
12
|
enableProfiling?: boolean
|
|
13
|
+
onAspectChange?: (aspect: number) => void
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
let {
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
offset = {},
|
|
18
19
|
scale = 0.7,
|
|
19
20
|
enableProfiling = false,
|
|
21
|
+
onAspectChange,
|
|
20
22
|
}: CameraFeedProps = $props()
|
|
21
23
|
|
|
22
24
|
const partID = usePartID()
|
|
@@ -77,6 +79,7 @@
|
|
|
77
79
|
const onReady = () => {
|
|
78
80
|
const videoReadyTime = performance.now()
|
|
79
81
|
aspect = video.videoWidth / video.videoHeight
|
|
82
|
+
onAspectChange?.(aspect)
|
|
80
83
|
|
|
81
84
|
if (!texture) {
|
|
82
85
|
texture = new VideoTexture(video)
|
|
@@ -11,12 +11,7 @@
|
|
|
11
11
|
rotationY?: number
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
let {
|
|
15
|
-
armName,
|
|
16
|
-
offset = {},
|
|
17
|
-
scale = 0.6,
|
|
18
|
-
rotationY = -15 * (Math.PI / 180),
|
|
19
|
-
}: JointLimitsWidgetProps = $props()
|
|
14
|
+
let { armName, offset = {}, scale = 0.6, rotationY = 0 }: JointLimitsWidgetProps = $props()
|
|
20
15
|
|
|
21
16
|
const armClient = useArmClient()
|
|
22
17
|
const armKinematics = useArmKinematics()
|
|
@@ -63,10 +58,11 @@
|
|
|
63
58
|
})
|
|
64
59
|
})
|
|
65
60
|
|
|
66
|
-
// Canvas setup
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
61
|
+
// Canvas setup — use 2x resolution for sharper XR text
|
|
62
|
+
const RESOLUTION_SCALE = 4
|
|
63
|
+
const CANVAS_WIDTH = 800 * RESOLUTION_SCALE
|
|
64
|
+
const HEADER_HEIGHT = 80 * RESOLUTION_SCALE
|
|
65
|
+
const ROW_HEIGHT = 120 * RESOLUTION_SCALE
|
|
70
66
|
let canvasHeight = $derived(HEADER_HEIGHT + (jointData?.length ?? 0) * ROW_HEIGHT)
|
|
71
67
|
|
|
72
68
|
let canvas: HTMLCanvasElement | undefined = $state()
|
|
@@ -105,19 +101,21 @@
|
|
|
105
101
|
|
|
106
102
|
// Render header with arm name
|
|
107
103
|
function renderHeader(ctx: CanvasRenderingContext2D, width: number) {
|
|
104
|
+
const s = RESOLUTION_SCALE
|
|
105
|
+
|
|
108
106
|
// Header background
|
|
109
107
|
ctx.fillStyle = '#0a0a0a'
|
|
110
108
|
ctx.fillRect(0, 0, width, HEADER_HEIGHT)
|
|
111
109
|
|
|
112
110
|
// Arm name
|
|
113
111
|
ctx.fillStyle = '#ffffff'
|
|
114
|
-
ctx.font =
|
|
112
|
+
ctx.font = `bold ${36 * s}px monospace`
|
|
115
113
|
ctx.textBaseline = 'middle'
|
|
116
|
-
ctx.fillText(armName, 20, HEADER_HEIGHT / 2)
|
|
114
|
+
ctx.fillText(armName, 20 * s, HEADER_HEIGHT / 2)
|
|
117
115
|
|
|
118
116
|
// Separator line
|
|
119
117
|
ctx.strokeStyle = '#444444'
|
|
120
|
-
ctx.lineWidth = 4
|
|
118
|
+
ctx.lineWidth = 4 * s
|
|
121
119
|
ctx.beginPath()
|
|
122
120
|
ctx.moveTo(0, HEADER_HEIGHT)
|
|
123
121
|
ctx.lineTo(width, HEADER_HEIGHT)
|
|
@@ -131,6 +129,7 @@
|
|
|
131
129
|
width: number,
|
|
132
130
|
height: number
|
|
133
131
|
) {
|
|
132
|
+
const s = RESOLUTION_SCALE
|
|
134
133
|
const rowHeight = (height - HEADER_HEIGHT) / joints.length
|
|
135
134
|
|
|
136
135
|
joints.forEach((joint, index) => {
|
|
@@ -142,15 +141,15 @@
|
|
|
142
141
|
|
|
143
142
|
// Joint label
|
|
144
143
|
ctx.fillStyle = '#ffffff'
|
|
145
|
-
ctx.font =
|
|
144
|
+
ctx.font = `bold ${32 * s}px monospace`
|
|
146
145
|
ctx.textBaseline = 'middle'
|
|
147
|
-
ctx.fillText(joint.jointId, 20, y + rowHeight / 2)
|
|
146
|
+
ctx.fillText(joint.jointId, 20 * s, y + rowHeight / 2)
|
|
148
147
|
|
|
149
148
|
// Progress bar dimensions
|
|
150
|
-
const barX = 240
|
|
151
|
-
const barY = y + (rowHeight - 60) / 2
|
|
152
|
-
const barWidth = 360
|
|
153
|
-
const barHeight = 60
|
|
149
|
+
const barX = 240 * s
|
|
150
|
+
const barY = y + (rowHeight - 60 * s) / 2
|
|
151
|
+
const barWidth = 360 * s
|
|
152
|
+
const barHeight = 60 * s
|
|
154
153
|
|
|
155
154
|
// Progress bar background
|
|
156
155
|
ctx.fillStyle = '#333333'
|
|
@@ -164,13 +163,17 @@
|
|
|
164
163
|
|
|
165
164
|
// Progress bar border
|
|
166
165
|
ctx.strokeStyle = '#666666'
|
|
167
|
-
ctx.lineWidth = 4
|
|
166
|
+
ctx.lineWidth = 4 * s
|
|
168
167
|
ctx.strokeRect(barX, barY, barWidth, barHeight)
|
|
169
168
|
|
|
170
169
|
// Current value text
|
|
171
170
|
ctx.fillStyle = '#ffffff'
|
|
172
|
-
ctx.font =
|
|
173
|
-
ctx.fillText(
|
|
171
|
+
ctx.font = `${28 * s}px monospace`
|
|
172
|
+
ctx.fillText(
|
|
173
|
+
`${joint.currentPosition.toFixed(1)}°`,
|
|
174
|
+
barX + barWidth + 20 * s,
|
|
175
|
+
y + rowHeight / 2
|
|
176
|
+
)
|
|
174
177
|
})
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
11
11
|
import XRToast from './XRToast.svelte'
|
|
12
12
|
import { useOrigin } from './useOrigin.svelte'
|
|
13
|
+
import { SvelteMap } from 'svelte/reactivity'
|
|
13
14
|
|
|
14
15
|
const { ...rest } = $props()
|
|
15
16
|
|
|
@@ -27,6 +28,16 @@
|
|
|
27
28
|
return openWidgets[currentPartID] || []
|
|
28
29
|
})
|
|
29
30
|
|
|
31
|
+
// Track camera aspect ratios to compute proper spacing
|
|
32
|
+
const cameraAspects = new SvelteMap<string, number>()
|
|
33
|
+
|
|
34
|
+
const CAMERA_SCALE = 0.8
|
|
35
|
+
const CAMERA_GAP = 0.15 // gap between feed edges
|
|
36
|
+
|
|
37
|
+
// Compute spacing from the widest camera feed (default 16:9 before any aspect is known)
|
|
38
|
+
const maxAspect = $derived(cameraAspects.size > 0 ? Math.max(...cameraAspects.values()) : 16 / 9)
|
|
39
|
+
const feedSpacing = $derived(maxAspect * CAMERA_SCALE + CAMERA_GAP)
|
|
40
|
+
|
|
30
41
|
// Get arms assigned to controllers
|
|
31
42
|
const controllerConfig = $derived(settings.current.xrController)
|
|
32
43
|
const leftArmName = $derived(controllerConfig.left.armName)
|
|
@@ -42,37 +53,34 @@
|
|
|
42
53
|
origin.set([0, 0, 0])
|
|
43
54
|
}}
|
|
44
55
|
>
|
|
45
|
-
<!-- Render
|
|
46
|
-
{#
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
<!-- Render camera feeds only when presenting to avoid conflicting with overlay Camera widgets -->
|
|
57
|
+
{#if $isPresenting}
|
|
58
|
+
{#each enabledCameras as cameraName, index (cameraName)}
|
|
59
|
+
{@const centerOffset = ((enabledCameras.length - 1) * feedSpacing) / 2}
|
|
60
|
+
<CameraFeed
|
|
61
|
+
resourceName={cameraName}
|
|
62
|
+
offset={{ x: index * feedSpacing - centerOffset, y: 1.5, z: -2.5 }}
|
|
63
|
+
scale={CAMERA_SCALE}
|
|
64
|
+
enableProfiling={false}
|
|
65
|
+
onAspectChange={(a) => {
|
|
66
|
+
cameraAspects.set(cameraName, a)
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
{/each}
|
|
70
|
+
{/if}
|
|
56
71
|
|
|
57
72
|
<!-- Render joint limits widgets only for arms assigned to controllers, on the matching side -->
|
|
58
73
|
{#if leftArmName}
|
|
59
|
-
{@const spacing = 1.2}
|
|
60
|
-
{@const centerOffset = ((enabledCameras.length - 1) * spacing) / 2}
|
|
61
|
-
{@const widgetX = -(centerOffset + spacing + 0.3)}
|
|
62
74
|
<JointLimitsWidget
|
|
63
75
|
armName={leftArmName}
|
|
64
|
-
offset={{ x:
|
|
76
|
+
offset={{ x: -0.5, y: 2.5, z: -2.5 }}
|
|
65
77
|
scale={0.6}
|
|
66
|
-
rotationY={15 * (Math.PI / 180)}
|
|
67
78
|
/>
|
|
68
79
|
{/if}
|
|
69
80
|
{#if rightArmName}
|
|
70
|
-
{@const spacing = 1.2}
|
|
71
|
-
{@const centerOffset = ((enabledCameras.length - 1) * spacing) / 2}
|
|
72
|
-
{@const widgetX = centerOffset + spacing + 0.3}
|
|
73
81
|
<JointLimitsWidget
|
|
74
82
|
armName={rightArmName}
|
|
75
|
-
offset={{ x:
|
|
83
|
+
offset={{ x: 0.5, y: 2.5, z: -2.5 }}
|
|
76
84
|
scale={0.6}
|
|
77
85
|
/>
|
|
78
86
|
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Raycaster } from 'three';
|
|
2
|
+
import { type BVHOptions } from 'three-mesh-bvh';
|
|
3
|
+
interface Options extends BVHOptions {
|
|
4
|
+
helper?: boolean;
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const bvh: (raycaster: Raycaster, options?: () => Options) => void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { injectPlugin, isInstanceOf } from '@threlte/core';
|
|
2
|
+
import { BatchedMesh, Points, Mesh } from 'three';
|
|
3
|
+
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, computeBatchedBoundsTree, disposeBatchedBoundsTree, PointsBVH, SAH, BVHHelper, } from 'three-mesh-bvh';
|
|
4
|
+
export const bvh = (raycaster, options) => {
|
|
5
|
+
const bvhOptions = $derived({
|
|
6
|
+
strategy: SAH,
|
|
7
|
+
verbose: false,
|
|
8
|
+
setBoundingBox: true,
|
|
9
|
+
maxDepth: 20,
|
|
10
|
+
maxLeafSize: 10,
|
|
11
|
+
indirect: false,
|
|
12
|
+
helper: false,
|
|
13
|
+
...options?.(),
|
|
14
|
+
});
|
|
15
|
+
raycaster.firstHitOnly = true;
|
|
16
|
+
raycaster.params.Points.threshold = 0.005;
|
|
17
|
+
injectPlugin('bvh', (args) => {
|
|
18
|
+
const { props } = $derived(args);
|
|
19
|
+
const opts = $derived(props.bvh ? { ...bvhOptions, ...props.bvh } : bvhOptions);
|
|
20
|
+
$effect(() => {
|
|
21
|
+
const { ref } = args;
|
|
22
|
+
if (opts.enabled === false) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (isInstanceOf(ref, 'Points')) {
|
|
26
|
+
ref.geometry.computeBoundsTree = computeBoundsTree;
|
|
27
|
+
ref.geometry.disposeBoundsTree = disposeBoundsTree;
|
|
28
|
+
ref.raycast = acceleratedRaycast;
|
|
29
|
+
computeBoundsTree.call(ref.geometry, { type: PointsBVH, ...opts });
|
|
30
|
+
const helper = opts.helper ? new BVHHelper(ref) : undefined;
|
|
31
|
+
if (helper)
|
|
32
|
+
ref.add(helper);
|
|
33
|
+
return () => {
|
|
34
|
+
ref.raycast = Points.prototype.raycast;
|
|
35
|
+
if (helper)
|
|
36
|
+
ref.remove(helper);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
else if (isInstanceOf(ref, 'BatchedMesh')) {
|
|
40
|
+
/* @ts-expect-error Some sort of ambient type is conflicing here, likely from @threlte/extras */
|
|
41
|
+
ref.geometry.computeBoundsTree = computeBatchedBoundsTree;
|
|
42
|
+
ref.geometry.disposeBoundsTree = disposeBatchedBoundsTree;
|
|
43
|
+
ref.raycast = acceleratedRaycast;
|
|
44
|
+
const helper = opts.helper ? new BVHHelper(ref) : undefined;
|
|
45
|
+
if (helper)
|
|
46
|
+
ref.add(helper);
|
|
47
|
+
return () => {
|
|
48
|
+
ref.raycast = BatchedMesh.prototype.raycast;
|
|
49
|
+
if (helper)
|
|
50
|
+
ref.remove(helper);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else if (isInstanceOf(ref, 'Mesh')) {
|
|
54
|
+
ref.geometry.computeBoundsTree = computeBoundsTree;
|
|
55
|
+
ref.geometry.disposeBoundsTree = disposeBoundsTree;
|
|
56
|
+
ref.raycast = acceleratedRaycast;
|
|
57
|
+
computeBoundsTree.call(ref.geometry, opts);
|
|
58
|
+
const helper = opts.helper ? new BVHHelper(ref) : undefined;
|
|
59
|
+
if (helper)
|
|
60
|
+
ref.add(helper);
|
|
61
|
+
return () => {
|
|
62
|
+
ref.raycast = Mesh.prototype.raycast;
|
|
63
|
+
if (helper)
|
|
64
|
+
ref.remove(helper);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
};
|
package/dist/ply.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BufferGeometry } from 'three';
|
|
2
2
|
export declare const parsePlyInput: (mesh: string | Uint8Array) => BufferGeometry;
|
package/dist/ply.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BufferGeometry } from 'three';
|
|
1
2
|
import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
|
|
2
3
|
const plyLoader = new PLYLoader();
|
|
3
4
|
export const parsePlyInput = (mesh) => {
|
|
@@ -5,6 +6,10 @@ export const parsePlyInput = (mesh) => {
|
|
|
5
6
|
if (typeof mesh === 'string') {
|
|
6
7
|
return plyLoader.parse(atob(mesh));
|
|
7
8
|
}
|
|
9
|
+
// First, determine if ply has any geometry
|
|
10
|
+
if (mesh.length === 0) {
|
|
11
|
+
return new BufferGeometry();
|
|
12
|
+
}
|
|
8
13
|
// Case 2: detect text vs binary PLY in Uint8Array
|
|
9
14
|
const header = new TextDecoder().decode(mesh.slice(0, 50));
|
|
10
15
|
const isAscii = header.includes('format ascii');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.1",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@threlte/rapier": "3.2.0",
|
|
31
31
|
"@threlte/xr": "1.0.8",
|
|
32
32
|
"@types/bun": "1.2.21",
|
|
33
|
+
"@types/earcut": "^3.0.0",
|
|
33
34
|
"@types/lodash-es": "4.17.12",
|
|
34
35
|
"@types/three": "0.181.0",
|
|
35
36
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
|
@@ -132,9 +133,11 @@
|
|
|
132
133
|
"@connectrpc/connect-web": "1.7.0",
|
|
133
134
|
"@neodrag/svelte": "^2.3.3",
|
|
134
135
|
"@tanstack/svelte-query-devtools": "^6.0.2",
|
|
136
|
+
"earcut": "^3.0.2",
|
|
135
137
|
"expr-eval": "^2.0.2",
|
|
136
138
|
"koota": "0.6.5",
|
|
137
139
|
"lodash-es": "4.17.23",
|
|
140
|
+
"three-mesh-bvh": "^0.9.8",
|
|
138
141
|
"uuid-tool": "^2.0.3"
|
|
139
142
|
},
|
|
140
143
|
"scripts": {
|