@viamrobotics/motion-tools 1.21.0 → 1.23.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.
- package/README.md +18 -100
- package/dist/FrameConfigUpdater.svelte.d.ts +0 -1
- package/dist/FrameConfigUpdater.svelte.js +6 -24
- package/dist/components/App.svelte +3 -3
- package/dist/components/App.svelte.d.ts +1 -1
- package/dist/components/CameraControls.svelte +6 -6
- package/dist/components/Entities/Pose.svelte +18 -13
- package/dist/components/FileDrop/useFileDrop.svelte.js +16 -2
- package/dist/components/{KeyboardControls.svelte → InputBindings.svelte} +50 -77
- package/dist/components/InputBindings.svelte.d.ts +7 -0
- package/dist/components/PointerMissBox.svelte +1 -1
- package/dist/components/Scene.svelte +2 -0
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/SelectedTransformControls.svelte +227 -0
- package/dist/components/SelectedTransformControls.svelte.d.ts +3 -0
- package/dist/components/StaticGeometries.svelte +3 -56
- package/dist/components/overlay/Details.svelte +82 -54
- package/dist/components/overlay/dashboard/Button.svelte +4 -2
- package/dist/components/overlay/dashboard/Button.svelte.d.ts +1 -1
- package/dist/components/overlay/dashboard/Dashboard.svelte +43 -33
- package/dist/ecs/traits.d.ts +15 -0
- package/dist/ecs/traits.js +7 -0
- package/dist/editing/FrameEditSession.d.ts +37 -0
- package/dist/editing/FrameEditSession.js +178 -0
- package/dist/hooks/useEnvironment.svelte.d.ts +1 -0
- package/dist/hooks/useEnvironment.svelte.js +1 -0
- package/dist/hooks/useFrameEditSession.svelte.d.ts +15 -0
- package/dist/hooks/useFrameEditSession.svelte.js +36 -0
- package/dist/hooks/useFrames.svelte.js +45 -5
- package/dist/hooks/usePartConfig.svelte.js +10 -0
- package/dist/hooks/useSettings.svelte.d.ts +1 -3
- package/dist/hooks/useSettings.svelte.js +1 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/transform.js +13 -0
- package/package.json +8 -6
- package/dist/components/KeyboardControls.svelte.d.ts +0 -7
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { TransformControls } from '@threlte/extras'
|
|
3
|
+
import { Quaternion, Vector3 } from 'three'
|
|
4
|
+
|
|
5
|
+
import type { FrameEditSession } from '../editing/FrameEditSession'
|
|
6
|
+
|
|
7
|
+
import { traits, useTrait } from '../ecs'
|
|
8
|
+
import { useTransformControls } from '../hooks/useControls.svelte'
|
|
9
|
+
import { useEnvironment } from '../hooks/useEnvironment.svelte'
|
|
10
|
+
import { useFrameEditSession } from '../hooks/useFrameEditSession.svelte'
|
|
11
|
+
import { useSelectedEntity, useSelectedObject3d } from '../hooks/useSelection.svelte'
|
|
12
|
+
import { useSettings } from '../hooks/useSettings.svelte'
|
|
13
|
+
import {
|
|
14
|
+
composeEditedPoseForRenderedPose,
|
|
15
|
+
createPose,
|
|
16
|
+
quaternionToPose,
|
|
17
|
+
vector3ToPose,
|
|
18
|
+
} from '../transform'
|
|
19
|
+
|
|
20
|
+
const settings = useSettings()
|
|
21
|
+
const environment = useEnvironment()
|
|
22
|
+
const transformControls = useTransformControls()
|
|
23
|
+
const selectedEntity = useSelectedEntity()
|
|
24
|
+
const selectedObject3d = useSelectedObject3d()
|
|
25
|
+
const sessions = useFrameEditSession()
|
|
26
|
+
|
|
27
|
+
const mode = $derived(settings.current.transformMode)
|
|
28
|
+
const entity = $derived(selectedEntity.current)
|
|
29
|
+
const transformable = useTrait(() => entity, traits.Transformable)
|
|
30
|
+
const networkPose = useTrait(() => entity, traits.Pose)
|
|
31
|
+
const livePose = useTrait(() => entity, traits.LivePose)
|
|
32
|
+
const box = useTrait(() => entity, traits.Box)
|
|
33
|
+
const sphere = useTrait(() => entity, traits.Sphere)
|
|
34
|
+
const capsule = useTrait(() => entity, traits.Capsule)
|
|
35
|
+
const hasScalableGeometry = $derived(
|
|
36
|
+
box.current !== undefined || sphere.current !== undefined || capsule.current !== undefined
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// Mesh sets name={entity} on its inner mesh, so useSelectedObject3d resolves
|
|
40
|
+
// to that mesh — not the parent Frame Group we actually want to drive. Walk
|
|
41
|
+
// up to the Group so translate/rotate/scale apply to the whole frame, not
|
|
42
|
+
// the geometry inside it.
|
|
43
|
+
const ref = $derived(selectedObject3d.current?.parent ?? selectedObject3d.current)
|
|
44
|
+
|
|
45
|
+
const activeMode = $derived.by(() => {
|
|
46
|
+
if (mode === 'none' || !transformable.current) return undefined
|
|
47
|
+
// Scale only does anything for primitive geometries the gizmo can size.
|
|
48
|
+
if (mode === 'scale' && !hasScalableGeometry) return undefined
|
|
49
|
+
return mode
|
|
50
|
+
})
|
|
51
|
+
const isSphereScale = $derived(activeMode === 'scale' && sphere.current !== undefined)
|
|
52
|
+
const isCapsuleScale = $derived(activeMode === 'scale' && capsule.current !== undefined)
|
|
53
|
+
|
|
54
|
+
const quaternion = new Quaternion()
|
|
55
|
+
const vector3 = new Vector3()
|
|
56
|
+
const refPose = createPose()
|
|
57
|
+
|
|
58
|
+
let session: FrameEditSession | undefined
|
|
59
|
+
let scaleStart:
|
|
60
|
+
| { type: 'box'; x: number; y: number; z: number }
|
|
61
|
+
| { type: 'sphere'; r: number }
|
|
62
|
+
| { type: 'capsule'; r: number; l: number }
|
|
63
|
+
| undefined
|
|
64
|
+
|
|
65
|
+
const captureScaleStart = () => {
|
|
66
|
+
if (!entity || activeMode !== 'scale') {
|
|
67
|
+
scaleStart = undefined
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const box = entity.get(traits.Box)
|
|
72
|
+
if (box) {
|
|
73
|
+
scaleStart = { type: 'box', ...box }
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sphere = entity.get(traits.Sphere)
|
|
78
|
+
if (sphere) {
|
|
79
|
+
scaleStart = { type: 'sphere', ...sphere }
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const capsule = entity.get(traits.Capsule)
|
|
84
|
+
if (capsule) {
|
|
85
|
+
scaleStart = { type: 'capsule', ...capsule }
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
scaleStart = undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const onMouseDown = () => {
|
|
93
|
+
if (entity?.has(traits.FramesAPI)) {
|
|
94
|
+
session = sessions.begin([entity])
|
|
95
|
+
}
|
|
96
|
+
captureScaleStart()
|
|
97
|
+
environment.current.viewerMode = 'edit'
|
|
98
|
+
transformControls.setActive(true)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const onChange = () => {
|
|
102
|
+
if (!ref || !entity || !activeMode) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isFrameEntity = entity.has(traits.FramesAPI)
|
|
107
|
+
|
|
108
|
+
if (activeMode === 'translate' || activeMode === 'rotate') {
|
|
109
|
+
if (isFrameEntity) {
|
|
110
|
+
stageFrameTransform()
|
|
111
|
+
} else {
|
|
112
|
+
const pose = entity.get(traits.Pose)
|
|
113
|
+
if (pose) {
|
|
114
|
+
if (activeMode === 'translate') {
|
|
115
|
+
vector3ToPose(ref.getWorldPosition(vector3), pose)
|
|
116
|
+
} else {
|
|
117
|
+
quaternionToPose(ref.getWorldQuaternion(quaternion), pose)
|
|
118
|
+
ref.quaternion.copy(quaternion)
|
|
119
|
+
}
|
|
120
|
+
entity.set(traits.Pose, pose)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// scale → bake the gizmo's scale factor into the geometry trait,
|
|
125
|
+
// then reset the object's scale so subsequent drags start from 1.
|
|
126
|
+
if (!scaleStart) {
|
|
127
|
+
captureScaleStart()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (scaleStart?.type === 'box') {
|
|
131
|
+
const next = {
|
|
132
|
+
x: scaleStart.x * ref.scale.x,
|
|
133
|
+
y: scaleStart.y * ref.scale.y,
|
|
134
|
+
z: scaleStart.z * ref.scale.z,
|
|
135
|
+
}
|
|
136
|
+
if (isFrameEntity) {
|
|
137
|
+
session?.stageGeometry(entity, { type: 'box', ...next })
|
|
138
|
+
} else {
|
|
139
|
+
entity.set(traits.Box, next)
|
|
140
|
+
}
|
|
141
|
+
} else if (scaleStart?.type === 'sphere') {
|
|
142
|
+
const next = { r: scaleStart.r * ref.scale.x }
|
|
143
|
+
if (isFrameEntity) {
|
|
144
|
+
session?.stageGeometry(entity, { type: 'sphere', ...next })
|
|
145
|
+
} else {
|
|
146
|
+
entity.set(traits.Sphere, next)
|
|
147
|
+
}
|
|
148
|
+
} else if (scaleStart?.type === 'capsule') {
|
|
149
|
+
const next = { r: scaleStart.r * ref.scale.x, l: scaleStart.l * ref.scale.y }
|
|
150
|
+
if (isFrameEntity) {
|
|
151
|
+
session?.stageGeometry(entity, { type: 'capsule', ...next })
|
|
152
|
+
} else {
|
|
153
|
+
entity.set(traits.Capsule, next)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
ref.scale.setScalar(1)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const onMouseUp = () => {
|
|
162
|
+
session?.commit()
|
|
163
|
+
session = undefined
|
|
164
|
+
scaleStart = undefined
|
|
165
|
+
transformControls.setActive(false)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Pose.svelte renders frame entities through the live blend
|
|
169
|
+
// render = M(live) × M(network)⁻¹ × M(edited)
|
|
170
|
+
// so for the user's drag to render where they pulled the gizmo to, EditedPose
|
|
171
|
+
// must satisfy
|
|
172
|
+
// M(edited) = M(network) × M(live)⁻¹ × M(ref)
|
|
173
|
+
// where M(ref) is the gizmo-driven group's parent-relative matrix in mm.
|
|
174
|
+
// When live ≈ network (no kinematic offset), this collapses to M(edited) =
|
|
175
|
+
// M(ref) — the same as the naive writeback. When they diverge (e.g. an arm
|
|
176
|
+
// whose joints have moved away from its config pose), this composition is
|
|
177
|
+
// what keeps the rendering anchored to the user's pointer instead of
|
|
178
|
+
// shearing through the live × baseline⁻¹ offset.
|
|
179
|
+
const stageFrameTransform = () => {
|
|
180
|
+
if (!ref || !entity) return
|
|
181
|
+
|
|
182
|
+
vector3ToPose(ref.position, refPose)
|
|
183
|
+
quaternionToPose(ref.quaternion, refPose)
|
|
184
|
+
|
|
185
|
+
const live = livePose.current
|
|
186
|
+
const network = networkPose.current
|
|
187
|
+
|
|
188
|
+
if (!live || !network) {
|
|
189
|
+
// No live pose available — Pose.svelte's blend short-circuits to
|
|
190
|
+
// editedPose, so naive writeback is correct.
|
|
191
|
+
if (activeMode === 'translate') {
|
|
192
|
+
session?.stagePose(entity, {
|
|
193
|
+
x: refPose.x,
|
|
194
|
+
y: refPose.y,
|
|
195
|
+
z: refPose.z,
|
|
196
|
+
})
|
|
197
|
+
} else if (activeMode === 'rotate') {
|
|
198
|
+
session?.stagePose(entity, {
|
|
199
|
+
oX: refPose.oX,
|
|
200
|
+
oY: refPose.oY,
|
|
201
|
+
oZ: refPose.oZ,
|
|
202
|
+
theta: refPose.theta,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
session?.stagePose(entity, composeEditedPoseForRenderedPose(network, live, refPose))
|
|
209
|
+
}
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
{#if ref && entity && activeMode}
|
|
213
|
+
{#key entity}
|
|
214
|
+
<TransformControls
|
|
215
|
+
object={ref}
|
|
216
|
+
mode={activeMode}
|
|
217
|
+
translationSnap={settings.current.snapping ? 0.1 : undefined}
|
|
218
|
+
rotationSnap={settings.current.snapping ? Math.PI / 24 : undefined}
|
|
219
|
+
scaleSnap={settings.current.snapping ? 0.1 : undefined}
|
|
220
|
+
showY={!isSphereScale}
|
|
221
|
+
showZ={!isSphereScale && !isCapsuleScale}
|
|
222
|
+
onmouseDown={onMouseDown}
|
|
223
|
+
onobjectChange={onChange}
|
|
224
|
+
onmouseUp={onMouseUp}
|
|
225
|
+
/>
|
|
226
|
+
{/key}
|
|
227
|
+
{/if}
|
|
@@ -8,22 +8,15 @@
|
|
|
8
8
|
<script lang="ts">
|
|
9
9
|
import type { Entity } from 'koota'
|
|
10
10
|
|
|
11
|
-
import { TransformControls } from '@threlte/extras'
|
|
12
11
|
import { PressedKeys } from 'runed'
|
|
13
12
|
import { SvelteSet } from 'svelte/reactivity'
|
|
14
|
-
import { Quaternion, Vector3 } from 'three'
|
|
15
13
|
|
|
16
14
|
import { traits, useWorld } from '../ecs'
|
|
17
|
-
import { useTransformControls } from '../hooks/useControls.svelte'
|
|
18
15
|
import { useSelectedEntity } from '../hooks/useSelection.svelte'
|
|
19
|
-
import { useSettings } from '../hooks/useSettings.svelte'
|
|
20
|
-
import { quaternionToPose, vector3ToPose } from '../transform'
|
|
21
16
|
|
|
22
17
|
import Frame from './Entities/Frame.svelte'
|
|
23
18
|
|
|
24
19
|
const world = useWorld()
|
|
25
|
-
const settings = useSettings()
|
|
26
|
-
const transformControls = useTransformControls()
|
|
27
20
|
const selectedEntity = useSelectedEntity()
|
|
28
21
|
|
|
29
22
|
const entities = new SvelteSet<Entity>()
|
|
@@ -31,11 +24,6 @@
|
|
|
31
24
|
[...entities].find((entity) => entity === selectedEntity.current)
|
|
32
25
|
)
|
|
33
26
|
|
|
34
|
-
const mode = $derived(settings.current.transformMode)
|
|
35
|
-
|
|
36
|
-
const quaternion = new Quaternion()
|
|
37
|
-
const vector3 = new Vector3()
|
|
38
|
-
|
|
39
27
|
const keys = new PressedKeys()
|
|
40
28
|
|
|
41
29
|
keys.onKeys('=', () => {
|
|
@@ -43,7 +31,8 @@
|
|
|
43
31
|
traits.Name(`custom geometry ${++index}`),
|
|
44
32
|
traits.Pose,
|
|
45
33
|
traits.Box({ x: 100, y: 100, z: 100 }),
|
|
46
|
-
traits.Removable
|
|
34
|
+
traits.Removable,
|
|
35
|
+
traits.Transformable
|
|
47
36
|
)
|
|
48
37
|
|
|
49
38
|
entities.add(entity)
|
|
@@ -57,50 +46,8 @@
|
|
|
57
46
|
selectedEntity.set()
|
|
58
47
|
}
|
|
59
48
|
})
|
|
60
|
-
|
|
61
|
-
$effect(() => {
|
|
62
|
-
settings.current.transforming = selectedCustomGeometry !== undefined
|
|
63
|
-
})
|
|
64
49
|
</script>
|
|
65
50
|
|
|
66
51
|
{#each entities as entity (entity)}
|
|
67
|
-
<Frame {entity}
|
|
68
|
-
{#snippet children({ ref })}
|
|
69
|
-
{#if selectedEntity.current === entity}
|
|
70
|
-
{#key mode}
|
|
71
|
-
<TransformControls
|
|
72
|
-
object={ref}
|
|
73
|
-
{mode}
|
|
74
|
-
translationSnap={settings.current.snapping ? 0.1 : undefined}
|
|
75
|
-
rotationSnap={settings.current.snapping ? Math.PI / 24 : undefined}
|
|
76
|
-
scaleSnap={settings.current.snapping ? 0.1 : undefined}
|
|
77
|
-
onmouseDown={() => {
|
|
78
|
-
transformControls.setActive(true)
|
|
79
|
-
}}
|
|
80
|
-
onmouseUp={() => {
|
|
81
|
-
transformControls.setActive(false)
|
|
82
|
-
|
|
83
|
-
const pose = entity.get(traits.Pose)
|
|
84
|
-
const box = entity.get(traits.Box)
|
|
85
|
-
|
|
86
|
-
if (pose && mode === 'translate') {
|
|
87
|
-
vector3ToPose(ref.getWorldPosition(vector3), pose)
|
|
88
|
-
entity.set(traits.Pose, pose)
|
|
89
|
-
} else if (pose && mode === 'rotate') {
|
|
90
|
-
quaternionToPose(ref.getWorldQuaternion(quaternion), pose)
|
|
91
|
-
ref.quaternion.copy(quaternion)
|
|
92
|
-
entity.set(traits.Pose, pose)
|
|
93
|
-
} else if (box && mode === 'scale') {
|
|
94
|
-
box.x *= ref.scale.x
|
|
95
|
-
box.y *= ref.scale.y
|
|
96
|
-
box.z *= ref.scale.z
|
|
97
|
-
entity.set(traits.Box, box)
|
|
98
|
-
ref.scale.setScalar(1)
|
|
99
|
-
}
|
|
100
|
-
}}
|
|
101
|
-
/>
|
|
102
|
-
{/key}
|
|
103
|
-
{/if}
|
|
104
|
-
{/snippet}
|
|
105
|
-
</Frame>
|
|
52
|
+
<Frame {entity} />
|
|
106
53
|
{/each}
|
|
@@ -11,12 +11,6 @@
|
|
|
11
11
|
const quaternion = new Quaternion()
|
|
12
12
|
const ov = new OrientationVector()
|
|
13
13
|
const euler = new Euler()
|
|
14
|
-
|
|
15
|
-
ThemeUtils.setGlobalDefaultTheme({
|
|
16
|
-
...ThemeUtils.presets.light,
|
|
17
|
-
baseBackgroundColor: '#fbfbfc',
|
|
18
|
-
baseShadowColor: 'transparent',
|
|
19
|
-
})
|
|
20
14
|
</script>
|
|
21
15
|
|
|
22
16
|
<script lang="ts">
|
|
@@ -24,7 +18,7 @@
|
|
|
24
18
|
import type { Snippet } from 'svelte'
|
|
25
19
|
|
|
26
20
|
import { draggable } from '@neodrag/svelte'
|
|
27
|
-
import { isInstanceOf, useTask } from '@threlte/core'
|
|
21
|
+
import { isInstanceOf, useTask, useThrelte } from '@threlte/core'
|
|
28
22
|
import { Button, Icon, Tooltip } from '@viamrobotics/prime-core'
|
|
29
23
|
import { Check, Copy } from 'lucide-svelte'
|
|
30
24
|
import {
|
|
@@ -68,6 +62,7 @@
|
|
|
68
62
|
const { details }: Props = $props()
|
|
69
63
|
|
|
70
64
|
const world = useWorld()
|
|
65
|
+
const { invalidate } = useThrelte()
|
|
71
66
|
const drawService = useDrawService()
|
|
72
67
|
const controls = useCameraControls()
|
|
73
68
|
const resourceByName = useResourceByName()
|
|
@@ -92,6 +87,7 @@
|
|
|
92
87
|
const removable = useTrait(() => entity, traits.Removable)
|
|
93
88
|
const points = useTrait(() => entity, traits.Points)
|
|
94
89
|
const arrows = useTrait(() => entity, traits.Arrows)
|
|
90
|
+
const opacity = useTrait(() => entity, traits.Opacity)
|
|
95
91
|
|
|
96
92
|
const framesAPI = useTrait(() => entity, traits.FramesAPI)
|
|
97
93
|
const isFrameNode = $derived(!!framesAPI.current)
|
|
@@ -139,8 +135,6 @@
|
|
|
139
135
|
}
|
|
140
136
|
})
|
|
141
137
|
|
|
142
|
-
const formatTwoDecimals = (value: number) => value.toFixed(2)
|
|
143
|
-
|
|
144
138
|
const detailConfigUpdater = new FrameConfigUpdater(partConfig.updateFrame, partConfig.deleteFrame)
|
|
145
139
|
|
|
146
140
|
const handlePositionChange = (event: PointChangeEvent) => {
|
|
@@ -205,6 +199,23 @@
|
|
|
205
199
|
detailConfigUpdater.updateGeometry(entity, { type: 'capsule', l: event.detail.value })
|
|
206
200
|
}
|
|
207
201
|
|
|
202
|
+
const opacityValue = $derived(opacity.current ?? 1)
|
|
203
|
+
|
|
204
|
+
const handleOpacityChange = (event: SliderChangeEvent) => {
|
|
205
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
206
|
+
const next = event.detail.value
|
|
207
|
+
// No trait === fully opaque, so drop the trait when the user returns to 1
|
|
208
|
+
// instead of leaving an Opacity(1) entry on the entity.
|
|
209
|
+
if (next >= 1) {
|
|
210
|
+
entity.remove(traits.Opacity)
|
|
211
|
+
} else if (entity.has(traits.Opacity)) {
|
|
212
|
+
entity.set(traits.Opacity, next)
|
|
213
|
+
} else {
|
|
214
|
+
entity.add(traits.Opacity(next))
|
|
215
|
+
}
|
|
216
|
+
invalidate()
|
|
217
|
+
}
|
|
218
|
+
|
|
208
219
|
const handleParentChange = (event: ListChangeEvent) => {
|
|
209
220
|
if (event.detail.origin !== 'internal' || !entity) return
|
|
210
221
|
const value = event.detail.value as string
|
|
@@ -295,6 +306,12 @@
|
|
|
295
306
|
2
|
|
296
307
|
)
|
|
297
308
|
}
|
|
309
|
+
|
|
310
|
+
ThemeUtils.setGlobalDefaultTheme({
|
|
311
|
+
...ThemeUtils.presets.light,
|
|
312
|
+
baseBackgroundColor: '#fbfbfc',
|
|
313
|
+
baseShadowColor: 'transparent',
|
|
314
|
+
})
|
|
298
315
|
</script>
|
|
299
316
|
|
|
300
317
|
{#snippet ImmutableField({
|
|
@@ -321,9 +338,7 @@
|
|
|
321
338
|
{#if entity}
|
|
322
339
|
<div
|
|
323
340
|
id="details-panel"
|
|
324
|
-
class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2
|
|
325
|
-
? 'w-80'
|
|
326
|
-
: 'w-60'} border p-2 text-xs dark:text-black"
|
|
341
|
+
class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2 w-70 border p-2 text-xs dark:text-black"
|
|
327
342
|
use:draggable={{
|
|
328
343
|
bounds: 'body',
|
|
329
344
|
handle: dragElement,
|
|
@@ -492,7 +507,6 @@
|
|
|
492
507
|
y: localPose.current.y,
|
|
493
508
|
z: localPose.current.z,
|
|
494
509
|
}}
|
|
495
|
-
format={formatTwoDecimals}
|
|
496
510
|
on:change={handlePositionChange}
|
|
497
511
|
/>
|
|
498
512
|
</div>
|
|
@@ -531,7 +545,6 @@
|
|
|
531
545
|
z: localPose.current.oZ,
|
|
532
546
|
w: localPose.current.theta,
|
|
533
547
|
}}
|
|
534
|
-
format={formatTwoDecimals}
|
|
535
548
|
on:change={handleOrientationOVChange}
|
|
536
549
|
/>
|
|
537
550
|
</TabPage>
|
|
@@ -578,48 +591,49 @@
|
|
|
578
591
|
<div aria-label="mutable geometry">
|
|
579
592
|
<TabGroup bind:selectedIndex={geometryTabIndex}>
|
|
580
593
|
<TabPage title="None" />
|
|
581
|
-
<TabPage title="Box"
|
|
582
|
-
|
|
583
|
-
|
|
594
|
+
<TabPage title="Box">
|
|
595
|
+
{#if box.current}
|
|
596
|
+
<div aria-label="mutable box dimensions">
|
|
597
|
+
<Point
|
|
598
|
+
value={{
|
|
599
|
+
x: box.current.x,
|
|
600
|
+
y: box.current.y,
|
|
601
|
+
z: box.current.z,
|
|
602
|
+
}}
|
|
603
|
+
on:change={handleBoxChange}
|
|
604
|
+
/>
|
|
605
|
+
</div>
|
|
606
|
+
{/if}
|
|
607
|
+
</TabPage>
|
|
608
|
+
<TabPage title="Sphere">
|
|
609
|
+
{#if sphere.current}
|
|
610
|
+
<div aria-label="mutable sphere dimensions">
|
|
611
|
+
<Slider
|
|
612
|
+
label="r"
|
|
613
|
+
value={sphere.current.r}
|
|
614
|
+
on:change={handleSphereRChange}
|
|
615
|
+
/>
|
|
616
|
+
</div>
|
|
617
|
+
{/if}
|
|
618
|
+
</TabPage>
|
|
619
|
+
<TabPage title="Capsule">
|
|
620
|
+
{#if capsule.current}
|
|
621
|
+
<div aria-label="mutable capsule dimensions">
|
|
622
|
+
<Slider
|
|
623
|
+
label="r"
|
|
624
|
+
value={capsule.current.r}
|
|
625
|
+
on:change={handleCapsuleRChange}
|
|
626
|
+
/>
|
|
627
|
+
<Slider
|
|
628
|
+
label="l"
|
|
629
|
+
value={capsule.current.l}
|
|
630
|
+
on:change={handleCapsuleLChange}
|
|
631
|
+
/>
|
|
632
|
+
</div>
|
|
633
|
+
{/if}
|
|
634
|
+
</TabPage>
|
|
584
635
|
</TabGroup>
|
|
585
636
|
</div>
|
|
586
|
-
{#if geometryTabIndex === 1 && box.current}
|
|
587
|
-
<div aria-label="mutable box dimensions">
|
|
588
|
-
<Point
|
|
589
|
-
value={{
|
|
590
|
-
x: box.current.x,
|
|
591
|
-
y: box.current.y,
|
|
592
|
-
z: box.current.z,
|
|
593
|
-
}}
|
|
594
|
-
format={formatTwoDecimals}
|
|
595
|
-
on:change={handleBoxChange}
|
|
596
|
-
/>
|
|
597
|
-
</div>
|
|
598
|
-
{:else if geometryTabIndex === 2 && sphere.current}
|
|
599
|
-
<div aria-label="mutable sphere dimensions">
|
|
600
|
-
<Slider
|
|
601
|
-
label="r"
|
|
602
|
-
value={sphere.current.r}
|
|
603
|
-
format={formatTwoDecimals}
|
|
604
|
-
on:change={handleSphereRChange}
|
|
605
|
-
/>
|
|
606
|
-
</div>
|
|
607
|
-
{:else if geometryTabIndex === 3 && capsule.current}
|
|
608
|
-
<div aria-label="mutable capsule dimensions">
|
|
609
|
-
<Slider
|
|
610
|
-
label="r"
|
|
611
|
-
value={capsule.current.r}
|
|
612
|
-
format={formatTwoDecimals}
|
|
613
|
-
on:change={handleCapsuleRChange}
|
|
614
|
-
/>
|
|
615
|
-
<Slider
|
|
616
|
-
label="l"
|
|
617
|
-
value={capsule.current.l}
|
|
618
|
-
format={formatTwoDecimals}
|
|
619
|
-
on:change={handleCapsuleLChange}
|
|
620
|
-
/>
|
|
621
|
-
</div>
|
|
622
|
-
{/if}
|
|
623
637
|
</div>
|
|
624
638
|
{:else if box.current}
|
|
625
639
|
<div>
|
|
@@ -673,6 +687,20 @@
|
|
|
673
687
|
</div>
|
|
674
688
|
{/if}
|
|
675
689
|
|
|
690
|
+
<div>
|
|
691
|
+
<strong class="font-semibold">opacity</strong>
|
|
692
|
+
<div aria-label="mutable opacity">
|
|
693
|
+
<Slider
|
|
694
|
+
value={opacityValue}
|
|
695
|
+
min={0}
|
|
696
|
+
max={1}
|
|
697
|
+
step={0.01}
|
|
698
|
+
format={(v) => v.toFixed(2)}
|
|
699
|
+
on:change={handleOpacityChange}
|
|
700
|
+
/>
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
703
|
+
|
|
676
704
|
{#if isInstanceOf(object3d, 'Points')}
|
|
677
705
|
<div>
|
|
678
706
|
<strong class="font-semibold">points</strong>
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements'
|
|
3
3
|
|
|
4
4
|
import { Icon, type IconName, Tooltip } from '@viamrobotics/prime-core'
|
|
5
|
-
import { Ruler } from 'lucide-svelte'
|
|
5
|
+
import { MousePointer2, Ruler } from 'lucide-svelte'
|
|
6
6
|
|
|
7
7
|
interface Props extends HTMLButtonAttributes {
|
|
8
|
-
icon: IconName | 'ruler'
|
|
8
|
+
icon: IconName | 'ruler' | 'mouse-pointer'
|
|
9
9
|
active?: boolean
|
|
10
10
|
description: string
|
|
11
11
|
hotkey?: string
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
>
|
|
49
49
|
{#if icon === 'ruler'}
|
|
50
50
|
<Ruler size="16" />
|
|
51
|
+
{:else if icon === 'mouse-pointer'}
|
|
52
|
+
<MousePointer2 size="16" />
|
|
51
53
|
{:else}
|
|
52
54
|
<Icon name={icon} />
|
|
53
55
|
{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements';
|
|
2
2
|
import { type IconName } from '@viamrobotics/prime-core';
|
|
3
3
|
interface Props extends HTMLButtonAttributes {
|
|
4
|
-
icon: IconName | 'ruler';
|
|
4
|
+
icon: IconName | 'ruler' | 'mouse-pointer';
|
|
5
5
|
active?: boolean;
|
|
6
6
|
description: string;
|
|
7
7
|
hotkey?: string;
|
|
@@ -38,40 +38,50 @@
|
|
|
38
38
|
</fieldset>
|
|
39
39
|
|
|
40
40
|
<!-- transform -->
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
41
|
+
<fieldset class="flex">
|
|
42
|
+
<Button
|
|
43
|
+
icon="mouse-pointer"
|
|
44
|
+
active={settings.current.transformMode === 'none'}
|
|
45
|
+
description="No transform controls"
|
|
46
|
+
hotkey="0"
|
|
47
|
+
onclick={() => {
|
|
48
|
+
settings.current.transformMode = 'none'
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
<Button
|
|
52
|
+
icon="cursor-move"
|
|
53
|
+
active={settings.current.transformMode === 'translate'}
|
|
54
|
+
description="Translate"
|
|
55
|
+
hotkey="1"
|
|
56
|
+
class="-ml-px"
|
|
57
|
+
onclick={() => {
|
|
58
|
+
settings.current.transformMode = 'translate'
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
<Button
|
|
62
|
+
icon="sync"
|
|
63
|
+
active={settings.current.transformMode === 'rotate'}
|
|
64
|
+
description="Rotate"
|
|
65
|
+
hotkey="2"
|
|
66
|
+
class="-ml-px"
|
|
67
|
+
onclick={() => {
|
|
68
|
+
settings.current.transformMode = 'rotate'
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
<Button
|
|
72
|
+
icon="resize"
|
|
73
|
+
active={settings.current.transformMode === 'scale'}
|
|
74
|
+
description="Scale"
|
|
75
|
+
hotkey="3"
|
|
76
|
+
class="-ml-px"
|
|
77
|
+
onclick={() => {
|
|
78
|
+
settings.current.transformMode = 'scale'
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
</fieldset>
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
<!-- snapping -->
|
|
84
|
+
{#if settings.current.transformMode !== 'none'}
|
|
75
85
|
<fieldset class="flex">
|
|
76
86
|
<Button
|
|
77
87
|
icon={settings.current.snapping ? 'magnet' : 'magnet-off'}
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -23,6 +23,15 @@ export declare const EditedPose: import("koota").Trait<{
|
|
|
23
23
|
oZ: number;
|
|
24
24
|
theta: number;
|
|
25
25
|
}>;
|
|
26
|
+
export declare const LivePose: import("koota").Trait<{
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
z: number;
|
|
30
|
+
oX: number;
|
|
31
|
+
oY: number;
|
|
32
|
+
oZ: number;
|
|
33
|
+
theta: number;
|
|
34
|
+
}>;
|
|
26
35
|
export declare const Center: import("koota").Trait<{
|
|
27
36
|
x: number;
|
|
28
37
|
y: number;
|
|
@@ -146,6 +155,12 @@ export declare const SnapshotAPI: import("koota").Trait<() => boolean>;
|
|
|
146
155
|
* Marker trait for entities created from user-dropped files (PLY, PCD, etc.)
|
|
147
156
|
*/
|
|
148
157
|
export declare const DroppedFile: import("koota").Trait<() => boolean>;
|
|
158
|
+
/**
|
|
159
|
+
* Marker trait for entities the dashboard's TransformControls may attach to —
|
|
160
|
+
* editable frames and ad-hoc custom geometries. Other entity kinds (lines,
|
|
161
|
+
* points, batched arrows, etc.) are deliberately excluded.
|
|
162
|
+
*/
|
|
163
|
+
export declare const Transformable: import("koota").Trait<() => boolean>;
|
|
149
164
|
export declare const ShowAxesHelper: import("koota").Trait<() => boolean>;
|
|
150
165
|
/**
|
|
151
166
|
* Marker trait for entities that should be rendered in screen space (CSS pixels)
|