@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
package/README.md
CHANGED
|
@@ -1,119 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Viam Visualization
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
3D visualization interface for spatial data using Viam's APIs — frame systems, geometries, point clouds, drawings — for motion-related monitoring, testing, and debugging.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Documentation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
to your machines if required.
|
|
7
|
+
📚 **[viamrobotics.github.io/visualization](https://viamrobotics.github.io/visualization/)** is the canonical guide. It covers:
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
- [Running locally](https://viamrobotics.github.io/visualization/guides/local-usage/) — set up the app and drive it from Go via `client/api`.
|
|
10
|
+
- [Embedding `<MotionTools />`](https://viamrobotics.github.io/visualization/guides/embedding/) — drop the visualizer into your own Svelte app.
|
|
11
|
+
- [Implementing WorldStateStoreService](https://viamrobotics.github.io/visualization/guides/worldstatestore/) — produce `Transform`s for a Viam WSS module with `draw`.
|
|
12
|
+
- API reference for [`client/api`](https://viamrobotics.github.io/visualization/api/client-api/) and [`draw`](https://viamrobotics.github.io/visualization/api/draw/).
|
|
13
|
+
- [v1 → v2 migration guide](https://viamrobotics.github.io/visualization/migration/v1-to-v2/).
|
|
14
|
+
- A live [playground](https://viamrobotics.github.io/visualization/playground/) rendering a baked snapshot.
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
## Quick start
|
|
13
17
|
|
|
14
18
|
```bash
|
|
15
|
-
make setup
|
|
19
|
+
make setup # one-time: install Node 22, pnpm, bun, Go, buf, project deps
|
|
20
|
+
make up # http://localhost:5173
|
|
16
21
|
```
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
For manual setup, machine configs, multiple instances, and troubleshooting see the [local-usage guide](https://viamrobotics.github.io/visualization/guides/local-usage/).
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
2. Install **pnpm** package manager
|
|
22
|
-
3. Install **bun** runtime
|
|
23
|
-
4. Install **Go** and **buf** (for protobuf generation)
|
|
24
|
-
5. Install all project dependencies
|
|
25
|
-
6. Generate protobuf code
|
|
25
|
+
## Contributing
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#### Manual setup
|
|
30
|
-
|
|
31
|
-
If the above does not work for you, or if you prefer to install dependencies manually:
|
|
32
|
-
|
|
33
|
-
1. [Install fnm](https://github.com/Schniz/fnm#installation): `curl -fsSL https://fnm.vercel.app/install | bash`
|
|
34
|
-
2. Install Node.js: `fnm install 22 && fnm use 22`
|
|
35
|
-
3. [Install pnpm](https://pnpm.io/installation): `curl -fsSL https://get.pnpm.io/install.sh | sh -`
|
|
36
|
-
4. [Install bun](https://bun.sh/docs/installation): `curl -fsSL https://bun.sh/install | bash`
|
|
37
|
-
5. [Install Go](https://go.dev/doc/install)
|
|
38
|
-
6. [Install buf](https://buf.build/docs/installation): download from GitHub releases
|
|
39
|
-
7. Install Go tools: `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest`
|
|
40
|
-
8. Install dependencies: `pnpm install`
|
|
41
|
-
9. Generate protobufs: `make proto`
|
|
42
|
-
|
|
43
|
-
### Env files for machine configs
|
|
44
|
-
|
|
45
|
-
To add a list of connection configs in an `.env.local` file, use the following format:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
VITE_CONFIGS='
|
|
49
|
-
{
|
|
50
|
-
"fleet-rover-01": {
|
|
51
|
-
"host": "fleet-rover-01-main.ve4ba7w5qr.viam.cloud",
|
|
52
|
-
"partId": "myPartID",
|
|
53
|
-
"apiKeyId": "myApiKeyId",
|
|
54
|
-
"apiKeyValue": "MyApiKeyValue",
|
|
55
|
-
"signalingAddress": "https://app.viam.com:443"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
'
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Running the app locally
|
|
62
|
-
|
|
63
|
-
After setup completes, you can start a local app server with:
|
|
27
|
+
Run the dev server with HMR:
|
|
64
28
|
|
|
65
29
|
```bash
|
|
66
|
-
make up
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
This starts the app as a static site. The build part of the process will only run if you have not built the app yet as a part of `make up`, or your build is out of date.
|
|
70
|
-
|
|
71
|
-
#### Running multiple apps
|
|
72
|
-
|
|
73
|
-
If you want to be able to run multiple versions of the app, you can configure how the servers run. The `make up` command can accept two options:
|
|
74
|
-
|
|
75
|
-
1. `STATIC_PORT` is the port for the static file server, and defaults to `5173`
|
|
76
|
-
2. `WS_PORT` is the port for the websocket server used to communicate with the draw client API
|
|
77
|
-
|
|
78
|
-
> [!NOTE]
|
|
79
|
-
> The `WS_PORT` is not fully configurable at the moment, so passing it will only affect where the frontend listens for the websocket server, but calls with the draw client API are currently hardcoded to point to `"http://localhost:3000/"`. If this is a feature you require, please submit a request to the viz team!
|
|
80
|
-
|
|
81
|
-
To run two apps using the same web socket server, run:
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
# in one terminal
|
|
85
|
-
make up
|
|
86
|
-
|
|
87
|
-
# in another terminal
|
|
88
|
-
make up STATIC_PORT=5174
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The apps should be available on `http://localhost:5173/` and `http://localhost:5174/`, and calls to the draw client API should render in both.
|
|
92
|
-
|
|
93
|
-
### Local development
|
|
94
|
-
|
|
95
|
-
If you are contributing to `motion-tools`, you should just run the development web server with:
|
|
96
|
-
|
|
97
|
-
```
|
|
98
30
|
pnpm dev
|
|
99
31
|
```
|
|
100
32
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
To visit the visualizer, go to `http://localhost:5173/`
|
|
104
|
-
|
|
105
|
-
Open the machine config page (bottom right) and enter in connection details to visualize a specific machine. You can also add machine configs from an env file (see below).
|
|
106
|
-
|
|
107
|
-
## Executing drawing commands
|
|
108
|
-
|
|
109
|
-
The visualizer includes a golang package that allows executing commands to the visualizer.
|
|
110
|
-
|
|
111
|
-
The list of available commands [can be found here](https://pkg.go.dev/github.com/viam-labs/motion-tools@v0.9.0/client/client).
|
|
33
|
+
See [CLAUDE.md](CLAUDE.md) for contributor conventions.
|
|
112
34
|
|
|
113
35
|
## Programmatic camera control
|
|
114
36
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
To do this, open the Javascript console while using the visualizer and call methods or set properties on the `cameraControls` object.
|
|
118
|
-
|
|
119
|
-
The following APIs are available: https://github.com/yomotsu/camera-controls?tab=readme-ov-file#properties
|
|
37
|
+
The visualizer exposes a `cameraControls` object on `window`. Open the browser console and call methods on it to move the camera or tweak its settings at runtime. Full API: <https://github.com/yomotsu/camera-controls#properties>.
|
|
@@ -23,6 +23,5 @@ export declare class FrameConfigUpdater {
|
|
|
23
23
|
setFrameParent: (entity: Entity, parentName: string) => void;
|
|
24
24
|
deleteFrame: (entity: Entity) => void;
|
|
25
25
|
setGeometryType: (entity: Entity, type: "none" | "box" | "sphere" | "capsule") => void;
|
|
26
|
-
private sanitizeFloatValue;
|
|
27
26
|
}
|
|
28
27
|
export {};
|
|
@@ -7,9 +7,7 @@ export class FrameConfigUpdater {
|
|
|
7
7
|
this.removeFrame = removeFrame;
|
|
8
8
|
}
|
|
9
9
|
updateLocalPosition = (entity, position) => {
|
|
10
|
-
const x =
|
|
11
|
-
const y = this.sanitizeFloatValue(position.y);
|
|
12
|
-
const z = this.sanitizeFloatValue(position.z);
|
|
10
|
+
const { x, y, z } = position;
|
|
13
11
|
if (x === undefined && y === undefined && z === undefined)
|
|
14
12
|
return;
|
|
15
13
|
const change = {};
|
|
@@ -28,10 +26,7 @@ export class FrameConfigUpdater {
|
|
|
28
26
|
}
|
|
29
27
|
};
|
|
30
28
|
updateLocalOrientation = (entity, orientation) => {
|
|
31
|
-
const oX =
|
|
32
|
-
const oY = this.sanitizeFloatValue(orientation.oY);
|
|
33
|
-
const oZ = this.sanitizeFloatValue(orientation.oZ);
|
|
34
|
-
const theta = this.sanitizeFloatValue(orientation.theta);
|
|
29
|
+
const { oX, oY, oZ, theta } = orientation;
|
|
35
30
|
if (oX === undefined && oY === undefined && oZ === undefined && theta === undefined) {
|
|
36
31
|
return;
|
|
37
32
|
}
|
|
@@ -57,9 +52,7 @@ export class FrameConfigUpdater {
|
|
|
57
52
|
const parent = entity.get(traits.Parent) ?? 'world';
|
|
58
53
|
const pose = entity.get(traits.EditedPose);
|
|
59
54
|
if (geometry?.type === 'box') {
|
|
60
|
-
const x =
|
|
61
|
-
const y = this.sanitizeFloatValue(geometry.y);
|
|
62
|
-
const z = this.sanitizeFloatValue(geometry.z);
|
|
55
|
+
const { x, y, z } = geometry;
|
|
63
56
|
if (x === undefined && y === undefined && z === undefined)
|
|
64
57
|
return;
|
|
65
58
|
const change = {};
|
|
@@ -76,7 +69,7 @@ export class FrameConfigUpdater {
|
|
|
76
69
|
}
|
|
77
70
|
}
|
|
78
71
|
else if (geometry?.type === 'sphere') {
|
|
79
|
-
const r =
|
|
72
|
+
const { r } = geometry;
|
|
80
73
|
if (r === undefined)
|
|
81
74
|
return;
|
|
82
75
|
entity.set(traits.Sphere, { r });
|
|
@@ -86,8 +79,7 @@ export class FrameConfigUpdater {
|
|
|
86
79
|
}
|
|
87
80
|
}
|
|
88
81
|
else if (geometry?.type === 'capsule') {
|
|
89
|
-
const r =
|
|
90
|
-
const l = this.sanitizeFloatValue(geometry.l);
|
|
82
|
+
const { r, l } = geometry;
|
|
91
83
|
if (r === undefined && l === undefined)
|
|
92
84
|
return;
|
|
93
85
|
const change = {};
|
|
@@ -95,7 +87,7 @@ export class FrameConfigUpdater {
|
|
|
95
87
|
change.r = r;
|
|
96
88
|
if (l !== undefined)
|
|
97
89
|
change.l = l;
|
|
98
|
-
entity.set(traits.Capsule,
|
|
90
|
+
entity.set(traits.Capsule, change);
|
|
99
91
|
const capsule = entity.get(traits.Capsule);
|
|
100
92
|
if (name && capsule && pose) {
|
|
101
93
|
this.updateFrame(name, parent, pose, { type: 'capsule', ...capsule });
|
|
@@ -134,14 +126,4 @@ export class FrameConfigUpdater {
|
|
|
134
126
|
this.updateFrame(name, parent, pose, { type: 'capsule', r: 20, l: 100 });
|
|
135
127
|
}
|
|
136
128
|
};
|
|
137
|
-
sanitizeFloatValue = (value) => {
|
|
138
|
-
if (value === undefined) {
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
const num = Number.parseFloat(value.toFixed(2));
|
|
142
|
-
if (Number.isNaN(num)) {
|
|
143
|
-
return undefined;
|
|
144
|
-
}
|
|
145
|
-
return value;
|
|
146
|
-
};
|
|
147
129
|
}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
interface Props {
|
|
49
49
|
partID?: string
|
|
50
|
-
|
|
50
|
+
inputBindingsEnabled?: boolean
|
|
51
51
|
localConfigProps?: LocalConfigProps
|
|
52
52
|
drawConnectionConfig?: DrawConnectionConfig
|
|
53
53
|
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
let {
|
|
76
76
|
partID = '',
|
|
77
|
-
|
|
77
|
+
inputBindingsEnabled = true,
|
|
78
78
|
localConfigProps,
|
|
79
79
|
cameraPose,
|
|
80
80
|
drawConnectionConfig,
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
const { isPresenting } = useXR()
|
|
92
92
|
|
|
93
93
|
$effect(() => {
|
|
94
|
-
|
|
94
|
+
environment.current.inputBindingsEnabled = inputBindingsEnabled
|
|
95
95
|
})
|
|
96
96
|
|
|
97
97
|
createPartIDContext(() => partID)
|
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import Button from './overlay/dashboard/Button.svelte'
|
|
6
6
|
import { useCameraControls, useTransformControls } from '../hooks/useControls.svelte'
|
|
7
|
-
import {
|
|
7
|
+
import { useEnvironment } from '../hooks/useEnvironment.svelte'
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import InputBindings from './InputBindings.svelte'
|
|
10
10
|
|
|
11
11
|
const cameraControls = useCameraControls()
|
|
12
|
-
const
|
|
12
|
+
const environment = useEnvironment()
|
|
13
13
|
const transformControls = useTransformControls()
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const inputBindingsEnabled = $derived(environment.current.inputBindingsEnabled)
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<Portal id="dashboard">
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
}}
|
|
38
38
|
>
|
|
39
39
|
{#snippet children({ ref }: { ref: CameraControlsRef })}
|
|
40
|
-
{#if
|
|
41
|
-
<
|
|
40
|
+
{#if inputBindingsEnabled}
|
|
41
|
+
<InputBindings cameraControls={ref} />
|
|
42
42
|
{/if}
|
|
43
43
|
<Gizmo placement="bottom-right" />
|
|
44
44
|
{/snippet}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { traits, useTrait } from '../../ecs'
|
|
7
7
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
8
8
|
import { usePose } from '../../hooks/usePose.svelte'
|
|
9
|
-
import {
|
|
9
|
+
import { composeRenderedPose } from '../../transform'
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
entity: Entity
|
|
@@ -25,22 +25,27 @@
|
|
|
25
25
|
() => parent.current
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
if (pose.current === undefined
|
|
30
|
-
return editedPose.current
|
|
31
|
-
}
|
|
28
|
+
$effect.pre(() => {
|
|
29
|
+
if (pose.current === undefined) return
|
|
32
30
|
|
|
33
|
-
if (
|
|
34
|
-
|
|
31
|
+
if (entity.has(traits.LivePose)) {
|
|
32
|
+
entity.set(traits.LivePose, pose.current)
|
|
33
|
+
} else {
|
|
34
|
+
entity.add(traits.LivePose(pose.current))
|
|
35
35
|
}
|
|
36
|
+
})
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// Always render through the live blend: live × network⁻¹ × edited. With
|
|
39
|
+
// `edited === network` (no edits) this collapses to `live`, so the rendered
|
|
40
|
+
// pose tracks the robot's kinematics-resolved position. With edits, the
|
|
41
|
+
// formula composes the staged delta on top of live. Input handlers that
|
|
42
|
+
// drive edits (gizmo onChange, Details panel) compute `edited` such that
|
|
43
|
+
// the blend renders to the user's intent.
|
|
44
|
+
const resolvedPose = $derived.by(() => {
|
|
45
|
+
if (pose.current === undefined || partConfig.hasPendingSave) return editedPose.current
|
|
46
|
+
if (!entityPose.current || !editedPose.current) return undefined
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
const resultMatrix = poseUsePose.multiply(poseNetworkInverse).multiply(poseLocalEditedPose)
|
|
43
|
-
return matrixToPose(resultMatrix)
|
|
48
|
+
return composeRenderedPose(pose.current, entityPose.current, editedPose.current)
|
|
44
49
|
})
|
|
45
50
|
</script>
|
|
46
51
|
|
|
@@ -2,6 +2,9 @@ import { Extensions, parseFileName, Prefixes, readFile } from './file-names';
|
|
|
2
2
|
import { pcdDropper } from './pcd-dropper';
|
|
3
3
|
import { plyDropper } from './ply-dropper';
|
|
4
4
|
import { snapshotDropper } from './snapshot-dropper';
|
|
5
|
+
const hasDraggedFiles = (dataTransfer) => {
|
|
6
|
+
return dataTransfer?.types?.includes('Files') ?? false;
|
|
7
|
+
};
|
|
5
8
|
const createFileDropper = (extension, prefix) => {
|
|
6
9
|
switch (prefix) {
|
|
7
10
|
case Prefixes.Snapshot: {
|
|
@@ -22,11 +25,15 @@ export const useFileDrop = (onSuccess, onError) => {
|
|
|
22
25
|
let dropState = $state('inactive');
|
|
23
26
|
// prevent default to allow drop
|
|
24
27
|
const ondragenter = (event) => {
|
|
28
|
+
if (!hasDraggedFiles(event.dataTransfer))
|
|
29
|
+
return;
|
|
25
30
|
event.preventDefault();
|
|
26
31
|
dropState = 'hovering';
|
|
27
32
|
};
|
|
28
33
|
// prevent default to allow drop
|
|
29
34
|
const ondragover = (event) => {
|
|
35
|
+
if (!hasDraggedFiles(event.dataTransfer))
|
|
36
|
+
return;
|
|
30
37
|
event.preventDefault();
|
|
31
38
|
};
|
|
32
39
|
const ondragleave = (event) => {
|
|
@@ -40,10 +47,17 @@ export const useFileDrop = (onSuccess, onError) => {
|
|
|
40
47
|
dropState = 'inactive';
|
|
41
48
|
};
|
|
42
49
|
const ondrop = (event) => {
|
|
50
|
+
const { dataTransfer } = event;
|
|
51
|
+
if (dataTransfer === null || !hasDraggedFiles(dataTransfer)) {
|
|
52
|
+
dropState = 'inactive';
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
43
55
|
event.preventDefault();
|
|
44
|
-
|
|
56
|
+
const { files } = dataTransfer;
|
|
57
|
+
if (files.length === 0) {
|
|
58
|
+
dropState = 'inactive';
|
|
45
59
|
return;
|
|
46
|
-
|
|
60
|
+
}
|
|
47
61
|
let completed = 0;
|
|
48
62
|
for (const file of files) {
|
|
49
63
|
const fileName = parseFileName(file.name);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { CameraControlsRef } from '@threlte/extras'
|
|
3
3
|
|
|
4
4
|
import { isInstanceOf, useTask } from '@threlte/core'
|
|
5
|
+
import { useGamepad, useInputMap, useKeyboard } from '@threlte/extras'
|
|
5
6
|
import { PressedKeys } from 'runed'
|
|
6
7
|
import { MathUtils, Vector3 } from 'three'
|
|
7
8
|
|
|
@@ -22,19 +23,33 @@
|
|
|
22
23
|
|
|
23
24
|
const settings = useSettings()
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
const keyboard = useKeyboard()
|
|
27
|
+
const gamepad = useGamepad()
|
|
28
|
+
const input = useInputMap(
|
|
29
|
+
({ key, gamepadAxis, gamepadButton }) => ({
|
|
30
|
+
truckLeft: [key('a'), gamepadAxis('leftStick', 'x', -1)],
|
|
31
|
+
truckRight: [key('d'), gamepadAxis('leftStick', 'x', 1)],
|
|
32
|
+
forward: [key('w'), gamepadAxis('leftStick', 'y', -1)],
|
|
33
|
+
backward: [key('s'), gamepadAxis('leftStick', 'y', 1)],
|
|
34
|
+
dollyIn: [key('r'), gamepadButton('rightBumper')],
|
|
35
|
+
dollyOut: [key('f'), gamepadButton('leftBumper')],
|
|
36
|
+
rotateLeft: [key('arrowleft'), gamepadAxis('rightStick', 'x', -1)],
|
|
37
|
+
rotateRight: [key('arrowright'), gamepadAxis('rightStick', 'x', 1)],
|
|
38
|
+
tiltUp: [key('arrowup'), gamepadAxis('rightStick', 'y', 1)],
|
|
39
|
+
tiltDown: [key('arrowdown'), gamepadAxis('rightStick', 'y', -1)],
|
|
40
|
+
}),
|
|
41
|
+
{ keyboard, gamepad }
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const truckAxis = $derived(input.axis('truckLeft', 'truckRight'))
|
|
45
|
+
const forwardAxis = $derived(input.axis('backward', 'forward'))
|
|
46
|
+
const dollyAxis = $derived(input.axis('dollyOut', 'dollyIn'))
|
|
47
|
+
const yawAxis = $derived(input.axis('rotateLeft', 'rotateRight'))
|
|
48
|
+
const pitchAxis = $derived(input.axis('tiltUp', 'tiltDown'))
|
|
49
|
+
|
|
50
|
+
const anyKeysPressed = $derived(
|
|
51
|
+
truckAxis !== 0 || forwardAxis !== 0 || dollyAxis !== 0 || yawAxis !== 0 || pitchAxis !== 0
|
|
52
|
+
)
|
|
38
53
|
|
|
39
54
|
const target = new Vector3()
|
|
40
55
|
|
|
@@ -70,7 +85,7 @@
|
|
|
70
85
|
const dt = delta * 1000
|
|
71
86
|
|
|
72
87
|
// Disallow keyboard navigation if the user is holding down the meta key
|
|
73
|
-
if (meta) {
|
|
88
|
+
if (keyboard.key('meta').pressed) {
|
|
74
89
|
return
|
|
75
90
|
}
|
|
76
91
|
|
|
@@ -80,64 +95,41 @@
|
|
|
80
95
|
const dollySpeed = 0.005 * dt
|
|
81
96
|
const zoomSpeed = 0.5 * dt
|
|
82
97
|
|
|
83
|
-
if (
|
|
84
|
-
cameraControls.truck(
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (d) {
|
|
88
|
-
cameraControls.truck(moveSpeed * dt, 0, true)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (w) {
|
|
92
|
-
cameraControls.forward(moveSpeed * dt, true)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (s) {
|
|
96
|
-
cameraControls.forward(-moveSpeed * dt, true)
|
|
98
|
+
if (truckAxis !== 0) {
|
|
99
|
+
cameraControls.truck(truckAxis * moveSpeed * dt, 0, true)
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
cameraControls.dolly(dollySpeed, true)
|
|
102
|
-
} else {
|
|
103
|
-
cameraControls.zoom(zoomSpeed, true)
|
|
104
|
-
}
|
|
102
|
+
if (forwardAxis !== 0) {
|
|
103
|
+
cameraControls.forward(forwardAxis * moveSpeed * dt, true)
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
if (
|
|
106
|
+
if (dollyAxis !== 0) {
|
|
108
107
|
if (isInstanceOf(cameraControls.camera, 'PerspectiveCamera')) {
|
|
109
|
-
cameraControls.dolly(
|
|
108
|
+
cameraControls.dolly(dollyAxis * dollySpeed, true)
|
|
110
109
|
} else {
|
|
111
|
-
cameraControls.zoom(
|
|
110
|
+
cameraControls.zoom(dollyAxis * zoomSpeed, true)
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
if (
|
|
116
|
-
cameraControls.rotate(
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (right) {
|
|
120
|
-
cameraControls.rotate(rotateSpeed, 0, true)
|
|
114
|
+
if (yawAxis !== 0) {
|
|
115
|
+
cameraControls.rotate(yawAxis * rotateSpeed, 0, true)
|
|
121
116
|
}
|
|
122
117
|
|
|
123
|
-
if (
|
|
124
|
-
cameraControls.rotate(0,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (down) {
|
|
128
|
-
cameraControls.rotate(0, tiltSpeed, true)
|
|
118
|
+
if (pitchAxis !== 0) {
|
|
119
|
+
cameraControls.rotate(0, pitchAxis * tiltSpeed, true)
|
|
129
120
|
}
|
|
130
121
|
},
|
|
131
122
|
{
|
|
123
|
+
after: input.task,
|
|
132
124
|
running: () => anyKeysPressed,
|
|
133
125
|
autoInvalidate: false,
|
|
134
126
|
}
|
|
135
127
|
)
|
|
136
128
|
|
|
129
|
+
const keys = new PressedKeys()
|
|
130
|
+
|
|
137
131
|
keys.onKeys('escape', () => {
|
|
138
|
-
|
|
139
|
-
focusedEntity.set()
|
|
140
|
-
}
|
|
132
|
+
focusedEntity.set()
|
|
141
133
|
})
|
|
142
134
|
|
|
143
135
|
keys.onKeys('c', () => {
|
|
@@ -157,30 +149,11 @@
|
|
|
157
149
|
settings.current.transformMode = 'scale'
|
|
158
150
|
})
|
|
159
151
|
|
|
160
|
-
keys.onKeys('
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* Handler for any keybindings that need to access the event object
|
|
166
|
-
*/
|
|
167
|
-
const onkeydown = (event: KeyboardEvent) => {
|
|
168
|
-
const key = event.key.toLowerCase()
|
|
169
|
-
|
|
170
|
-
if (key === 'h') {
|
|
171
|
-
if (!entity) return
|
|
172
|
-
|
|
173
|
-
event.stopImmediatePropagation()
|
|
174
|
-
|
|
175
|
-
if (entity.has(traits.Invisible)) {
|
|
176
|
-
entity.remove(traits.Invisible)
|
|
177
|
-
} else {
|
|
178
|
-
entity.add(traits.Invisible)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return
|
|
152
|
+
keys.onKeys('h', () => {
|
|
153
|
+
if (entity?.has(traits.Invisible)) {
|
|
154
|
+
entity.remove(traits.Invisible)
|
|
155
|
+
} else {
|
|
156
|
+
entity?.add(traits.Invisible)
|
|
182
157
|
}
|
|
183
|
-
}
|
|
158
|
+
})
|
|
184
159
|
</script>
|
|
185
|
-
|
|
186
|
-
<svelte:window {onkeydown} />
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CameraControlsRef } from '@threlte/extras';
|
|
2
|
+
interface Props {
|
|
3
|
+
cameraControls: CameraControlsRef;
|
|
4
|
+
}
|
|
5
|
+
declare const InputBindings: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type InputBindings = ReturnType<typeof InputBindings>;
|
|
7
|
+
export default InputBindings;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import Entities from './Entities/Entities.svelte'
|
|
11
11
|
import Focus from './Focus.svelte'
|
|
12
12
|
import Selected from './Selected.svelte'
|
|
13
|
+
import SelectedTransformControls from './SelectedTransformControls.svelte'
|
|
13
14
|
import StaticGeometries from './StaticGeometries.svelte'
|
|
14
15
|
import { useFocusedObject3d } from '../hooks/useSelection.svelte'
|
|
15
16
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
|
|
78
79
|
<StaticGeometries />
|
|
79
80
|
<Selected />
|
|
81
|
+
<SelectedTransformControls />
|
|
80
82
|
|
|
81
83
|
{#if !$isPresenting && settings.current.grid}
|
|
82
84
|
<Grid
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
} from '../hooks/useControls.svelte'
|
|
13
13
|
import { provideDrawAPI } from '../hooks/useDrawAPI.svelte'
|
|
14
14
|
import { provideDrawService } from '../hooks/useDrawService.svelte'
|
|
15
|
+
import { provideFrameEditSession } from '../hooks/useFrameEditSession.svelte'
|
|
15
16
|
import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
|
|
16
17
|
import { provideFrames } from '../hooks/useFrames.svelte'
|
|
17
18
|
import { provideGeometries } from '../hooks/useGeometries.svelte'
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
|
|
48
49
|
provideResourceByName(() => partID.current)
|
|
49
50
|
provideConfigFrames()
|
|
51
|
+
provideFrameEditSession(() => partID.current)
|
|
50
52
|
provideFrames(() => partID.current)
|
|
51
53
|
provideGeometries(() => partID.current)
|
|
52
54
|
provide3DModels(() => partID.current)
|