@viamrobotics/motion-tools 0.19.2 → 1.0.3
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 +56 -26
- package/dist/FrameConfigUpdater.svelte.d.ts +11 -17
- package/dist/FrameConfigUpdater.svelte.js +109 -109
- package/dist/WorldObject.svelte.js +2 -15
- package/dist/common/v1/common_pb.d.ts +950 -0
- package/dist/common/v1/common_pb.js +1399 -0
- package/dist/components/App.svelte +38 -22
- package/dist/components/App.svelte.d.ts +1 -0
- package/dist/components/BatchedArrows.svelte +102 -0
- package/dist/components/BatchedArrows.svelte.d.ts +3 -0
- package/dist/components/CameraControls.svelte +2 -3
- package/dist/components/Details.svelte +364 -365
- package/dist/components/Entities.svelte +80 -0
- package/dist/components/{WorldObjects.svelte.d.ts → Entities.svelte.d.ts} +3 -3
- package/dist/components/FileDrop/FileDrop.svelte +96 -0
- package/dist/components/FileDrop/FileDrop.svelte.d.ts +4 -0
- package/dist/components/FileDrop/file-dropper.d.ts +36 -0
- package/dist/components/FileDrop/file-dropper.js +6 -0
- package/dist/components/FileDrop/file-names.d.ts +27 -0
- package/dist/components/FileDrop/file-names.js +85 -0
- package/dist/components/FileDrop/pcd-dropper.d.ts +2 -0
- package/dist/components/FileDrop/pcd-dropper.js +27 -0
- package/dist/components/FileDrop/ply-dropper.d.ts +2 -0
- package/dist/components/FileDrop/ply-dropper.js +27 -0
- package/dist/components/FileDrop/snapshot-dropper.d.ts +2 -0
- package/dist/components/FileDrop/snapshot-dropper.js +96 -0
- package/dist/components/FileDrop/useFileDrop.svelte.d.ts +9 -0
- package/dist/components/FileDrop/useFileDrop.svelte.js +97 -0
- package/dist/components/Focus.svelte +2 -3
- package/dist/components/Frame.svelte +41 -22
- package/dist/components/Frame.svelte.d.ts +4 -6
- package/dist/components/GLTF.svelte +36 -0
- package/dist/components/GLTF.svelte.d.ts +11 -0
- package/dist/components/Geometry2.svelte +206 -0
- package/dist/components/Geometry2.svelte.d.ts +18 -0
- package/dist/components/KeyboardControls.svelte +3 -3
- package/dist/components/Line.svelte +15 -14
- package/dist/components/Line.svelte.d.ts +2 -2
- package/dist/components/LiveUpdatesBanner.svelte +51 -15
- package/dist/components/MeasureTool.svelte +4 -5
- package/dist/components/Pointcloud.svelte +27 -14
- package/dist/components/Pointcloud.svelte.d.ts +2 -2
- package/dist/components/PointerMissBox.svelte +3 -3
- package/dist/components/Pose.svelte +31 -6
- package/dist/components/Pose.svelte.d.ts +2 -2
- package/dist/components/Scene.svelte +7 -6
- package/dist/components/SceneProviders.svelte +0 -6
- package/dist/components/Selected.svelte +22 -16
- package/dist/components/StaticGeometries.svelte +51 -27
- package/dist/components/Tree/Tree.svelte +51 -38
- package/dist/components/Tree/Tree.svelte.d.ts +3 -4
- package/dist/components/Tree/TreeContainer.svelte +75 -40
- package/dist/components/Tree/Widgets.svelte +2 -5
- package/dist/components/Tree/buildTree.d.ts +7 -6
- package/dist/components/Tree/buildTree.js +22 -41
- package/dist/components/__tests__/__fixtures__/entity.d.ts +2 -0
- package/dist/components/__tests__/__fixtures__/entity.js +20 -0
- package/dist/components/__tests__/__fixtures__/resource.d.ts +17 -0
- package/dist/components/__tests__/__fixtures__/resource.js +13 -0
- package/dist/components/dashboard/Dashboard.svelte +5 -3
- package/dist/components/dashboard/Dashboard.svelte.d.ts +7 -2
- package/dist/components/widgets/ArmPositions.svelte +19 -7
- package/dist/draw/v1/drawing_pb.d.ts +341 -0
- package/dist/draw/v1/drawing_pb.js +417 -0
- package/dist/draw/v1/metadata_pb.d.ts +23 -0
- package/dist/draw/v1/metadata_pb.js +39 -0
- package/dist/draw/v1/scene_pb.d.ts +230 -0
- package/dist/draw/v1/scene_pb.js +298 -0
- package/dist/draw/v1/snapshot_pb.d.ts +42 -0
- package/dist/draw/v1/snapshot_pb.js +61 -0
- package/dist/draw/v1/transforms_pb.d.ts +23 -0
- package/dist/draw/v1/transforms_pb.js +39 -0
- package/dist/ecs/index.d.ts +4 -0
- package/dist/ecs/index.js +4 -0
- package/dist/ecs/traits.d.ts +132 -0
- package/dist/ecs/traits.js +85 -0
- package/dist/ecs/useQuery.svelte.d.ts +4 -0
- package/dist/ecs/useQuery.svelte.js +49 -0
- package/dist/ecs/useTrait.svelte.d.ts +19 -0
- package/dist/ecs/useTrait.svelte.js +40 -0
- package/dist/ecs/useWorld.d.ts +4 -0
- package/dist/ecs/useWorld.js +10 -0
- package/dist/geometry.js +6 -6
- package/dist/hooks/__tests__/fixtures/ResizableTestWrapper.svelte +41 -0
- package/dist/hooks/__tests__/fixtures/ResizableTestWrapper.svelte.d.ts +6 -0
- package/dist/hooks/use3DModels.svelte.js +6 -4
- package/dist/hooks/useDrawAPI.svelte.d.ts +0 -10
- package/dist/hooks/useDrawAPI.svelte.js +143 -267
- package/dist/hooks/useFramelessComponents.svelte.js +1 -1
- package/dist/hooks/useFrames.svelte.d.ts +6 -2
- package/dist/hooks/useFrames.svelte.js +123 -19
- package/dist/hooks/useGeometries.svelte.d.ts +0 -2
- package/dist/hooks/useGeometries.svelte.js +49 -25
- package/dist/hooks/useObjectEvents.svelte.d.ts +3 -2
- package/dist/hooks/useObjectEvents.svelte.js +11 -7
- package/dist/hooks/usePartConfig.svelte.d.ts +1 -1
- package/dist/hooks/usePartConfig.svelte.js +2 -1
- package/dist/hooks/usePointclouds.svelte.d.ts +0 -2
- package/dist/hooks/usePointclouds.svelte.js +52 -21
- package/dist/hooks/usePose.svelte.js +15 -7
- package/dist/hooks/useResizable.svelte.d.ts +12 -0
- package/dist/hooks/useResizable.svelte.js +45 -0
- package/dist/hooks/useResourceByName.svelte.js +8 -5
- package/dist/hooks/useSelection.svelte.d.ts +13 -23
- package/dist/hooks/useSelection.svelte.js +45 -65
- package/dist/hooks/useVisibility.svelte.d.ts +2 -1
- package/dist/hooks/useWeblabs.svelte.d.ts +0 -1
- package/dist/hooks/useWeblabs.svelte.js +0 -1
- package/dist/hooks/useWorldState.svelte.d.ts +9 -0
- package/dist/hooks/useWorldState.svelte.js +158 -107
- package/dist/lib.d.ts +1 -0
- package/dist/lib.js +2 -0
- package/dist/three/BatchedArrow.d.ts +2 -3
- package/dist/three/BatchedArrow.js +3 -11
- package/dist/three/CapsuleGeometry.d.ts +1 -1
- package/dist/three/CapsuleGeometry.js +3 -1
- package/dist/transform.js +0 -15
- package/package.json +13 -7
- package/dist/components/FileDrop.svelte +0 -158
- package/dist/components/FileDrop.svelte.d.ts +0 -3
- package/dist/components/WorldObject.svelte +0 -28
- package/dist/components/WorldObject.svelte.d.ts +0 -11
- package/dist/components/WorldObjects.svelte +0 -159
- package/dist/components/WorldState.svelte +0 -92
- package/dist/components/WorldState.svelte.d.ts +0 -7
- package/dist/components/__tests__/__fixtures__/worldObject.svelte.d.ts +0 -2
- package/dist/components/__tests__/__fixtures__/worldObject.svelte.js +0 -35
- package/dist/components/portal/Portal.svelte +0 -25
- package/dist/components/portal/Portal.svelte.d.ts +0 -8
- package/dist/components/portal/PortalTarget.svelte +0 -18
- package/dist/components/portal/PortalTarget.svelte.d.ts +0 -6
- package/dist/components/portal/index.d.ts +0 -2
- package/dist/components/portal/index.js +0 -2
- package/dist/components/portal/usePortalContext.svelte.d.ts +0 -5
- package/dist/components/portal/usePortalContext.svelte.js +0 -5
- package/dist/hooks/useArrows.svelte.d.ts +0 -3
- package/dist/hooks/useArrows.svelte.js +0 -9
- package/dist/hooks/useDraggable.svelte.d.ts +0 -10
- package/dist/hooks/useDraggable.svelte.js +0 -36
- package/dist/hooks/useObjects.svelte.d.ts +0 -7
- package/dist/hooks/useObjects.svelte.js +0 -35
- package/dist/hooks/usePersistentUUIDs.svelte.d.ts +0 -5
- package/dist/hooks/usePersistentUUIDs.svelte.js +0 -13
- package/dist/hooks/useResourceByName.svelte.d.ts +0 -7
- package/dist/hooks/useStaticGeometries.svelte.d.ts +0 -9
- package/dist/hooks/useStaticGeometries.svelte.js +0 -47
- package/dist/workers/worldStateWorker.d.ts +0 -1
- package/dist/workers/worldStateWorker.js +0 -114
- package/dist/world-state-messages.d.ts +0 -23
- package/dist/world-state-messages.js +0 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Pose from './Pose.svelte'
|
|
3
|
+
import Frame from './Frame.svelte'
|
|
4
|
+
import Line from './Line.svelte'
|
|
5
|
+
import Pointcloud from './Pointcloud.svelte'
|
|
6
|
+
import GLTF from './GLTF.svelte'
|
|
7
|
+
import Label from './Label.svelte'
|
|
8
|
+
import { traits, useQuery } from '../ecs'
|
|
9
|
+
import { Or } from 'koota'
|
|
10
|
+
|
|
11
|
+
const frames = useQuery(traits.FramesAPI)
|
|
12
|
+
const geometries = useQuery(traits.GeometriesAPI)
|
|
13
|
+
const points = useQuery(traits.PointsGeometry)
|
|
14
|
+
const lines = useQuery(traits.LineGeometry)
|
|
15
|
+
const gltfs = useQuery(traits.GLTF)
|
|
16
|
+
const droppedMeshes = useQuery(traits.DroppedFile, traits.BufferGeometry)
|
|
17
|
+
const drawnMeshes = useQuery(
|
|
18
|
+
traits.DrawAPI,
|
|
19
|
+
Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
|
|
20
|
+
)
|
|
21
|
+
const worldStateMeshes = useQuery(
|
|
22
|
+
traits.WorldStateStoreAPI,
|
|
23
|
+
Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
|
|
24
|
+
)
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#each drawnMeshes.current as entity (entity)}
|
|
28
|
+
<Frame {entity}>
|
|
29
|
+
<Label text={entity.get(traits.Name)} />
|
|
30
|
+
</Frame>
|
|
31
|
+
{/each}
|
|
32
|
+
|
|
33
|
+
{#each worldStateMeshes.current as entity (entity)}
|
|
34
|
+
<Frame {entity}>
|
|
35
|
+
<Label text={entity.get(traits.Name)} />
|
|
36
|
+
</Frame>
|
|
37
|
+
{/each}
|
|
38
|
+
|
|
39
|
+
{#each droppedMeshes.current as entity (entity)}
|
|
40
|
+
<Frame {entity}>
|
|
41
|
+
<Label text={entity.get(traits.Name)} />
|
|
42
|
+
</Frame>
|
|
43
|
+
{/each}
|
|
44
|
+
|
|
45
|
+
{#each points.current as entity (entity)}
|
|
46
|
+
<Pointcloud {entity}>
|
|
47
|
+
<Label text={entity.get(traits.Name)} />
|
|
48
|
+
</Pointcloud>
|
|
49
|
+
{/each}
|
|
50
|
+
|
|
51
|
+
{#each frames.current as entity (entity)}
|
|
52
|
+
<Pose {entity}>
|
|
53
|
+
{#snippet children({ pose })}
|
|
54
|
+
<Frame
|
|
55
|
+
{pose}
|
|
56
|
+
{entity}
|
|
57
|
+
>
|
|
58
|
+
<Label text={entity.get(traits.Name)} />
|
|
59
|
+
</Frame>
|
|
60
|
+
{/snippet}
|
|
61
|
+
</Pose>
|
|
62
|
+
{/each}
|
|
63
|
+
|
|
64
|
+
{#each geometries.current as entity (entity)}
|
|
65
|
+
<Frame {entity}>
|
|
66
|
+
<Label text={entity.get(traits.Name)} />
|
|
67
|
+
</Frame>
|
|
68
|
+
{/each}
|
|
69
|
+
|
|
70
|
+
{#each lines.current as entity (entity)}
|
|
71
|
+
<Line {entity}>
|
|
72
|
+
<Label text={entity.get(traits.Name)} />
|
|
73
|
+
</Line>
|
|
74
|
+
{/each}
|
|
75
|
+
|
|
76
|
+
{#each gltfs.current as entity (entity)}
|
|
77
|
+
<GLTF {entity}>
|
|
78
|
+
<Label text={entity.get(traits.Name)} />
|
|
79
|
+
</GLTF>
|
|
80
|
+
{/each}
|
|
@@ -11,8 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
|
|
|
11
11
|
};
|
|
12
12
|
z_$$bindings?: Bindings;
|
|
13
13
|
}
|
|
14
|
-
declare const
|
|
14
|
+
declare const Entities: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
15
|
[evt: string]: CustomEvent<any>;
|
|
16
16
|
}, {}, {}, string>;
|
|
17
|
-
type
|
|
18
|
-
export default
|
|
17
|
+
type Entities = InstanceType<typeof Entities>;
|
|
18
|
+
export default Entities;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
3
|
+
import { useToast, ToastVariant } from '@viamrobotics/prime-core'
|
|
4
|
+
import { useFileDrop } from './useFileDrop.svelte'
|
|
5
|
+
import { useWorld } from '../../ecs/useWorld'
|
|
6
|
+
import type { FileDropperSuccess } from './file-dropper'
|
|
7
|
+
import { traits } from '../../ecs'
|
|
8
|
+
import { parseMetadata } from '../../WorldObject.svelte'
|
|
9
|
+
import type { Snapshot } from '../../draw/v1/snapshot_pb'
|
|
10
|
+
|
|
11
|
+
const props: HTMLAttributes<HTMLDivElement> = $props()
|
|
12
|
+
|
|
13
|
+
const world = useWorld()
|
|
14
|
+
const toast = useToast()
|
|
15
|
+
|
|
16
|
+
const addSnapshotToWorld = (snapshot: Snapshot) => {
|
|
17
|
+
for (const transform of snapshot.transforms) {
|
|
18
|
+
const entity = world.spawn(
|
|
19
|
+
traits.Name(transform.referenceFrame),
|
|
20
|
+
traits.Pose(transform.poseInObserverFrame?.pose),
|
|
21
|
+
traits.Parent(transform.poseInObserverFrame?.referenceFrame)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if (transform.physicalObject) {
|
|
25
|
+
entity.add(traits.Geometry(transform.physicalObject))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (transform.metadata) {
|
|
29
|
+
const metadata = parseMetadata(transform.metadata.fields)
|
|
30
|
+
if (metadata.color) {
|
|
31
|
+
entity.add(traits.Color(metadata.color))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const drawing of snapshot.drawings) {
|
|
37
|
+
world.spawn(
|
|
38
|
+
traits.Name(drawing.referenceFrame),
|
|
39
|
+
traits.Pose(drawing.poseInObserverFrame?.pose),
|
|
40
|
+
traits.Parent(drawing.poseInObserverFrame?.referenceFrame)
|
|
41
|
+
// TODO: Add shape
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if (drawing.metadata) {
|
|
45
|
+
// add shape colors
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fileDrop = useFileDrop(
|
|
51
|
+
(result: FileDropperSuccess) => {
|
|
52
|
+
switch (result.type) {
|
|
53
|
+
case 'snapshot': {
|
|
54
|
+
addSnapshotToWorld(result.snapshot)
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
case 'pcd':
|
|
58
|
+
world.spawn(
|
|
59
|
+
traits.Name(result.name),
|
|
60
|
+
traits.PointsGeometry(result.pcd.positions),
|
|
61
|
+
result.pcd.colors ? traits.VertexColors(result.pcd.colors) : traits.Color,
|
|
62
|
+
traits.DroppedFile
|
|
63
|
+
)
|
|
64
|
+
break
|
|
65
|
+
case 'ply':
|
|
66
|
+
world.spawn(
|
|
67
|
+
traits.Name(result.name),
|
|
68
|
+
traits.BufferGeometry(result.ply),
|
|
69
|
+
traits.DroppedFile
|
|
70
|
+
)
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
toast({ message: `${result.name} loaded.`, variant: ToastVariant.Success })
|
|
75
|
+
},
|
|
76
|
+
(message) => toast({ message, variant: ToastVariant.Danger })
|
|
77
|
+
)
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<svelte:window
|
|
81
|
+
ondragenter={fileDrop.ondragenter}
|
|
82
|
+
ondragleave={fileDrop.ondragleave}
|
|
83
|
+
ondragover={fileDrop.ondragover}
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
<div
|
|
87
|
+
class={{
|
|
88
|
+
'fixed inset-0 z-9999': true,
|
|
89
|
+
'pointer-events-none': fileDrop.dropState === 'inactive',
|
|
90
|
+
'bg-black/10': fileDrop.dropState !== 'inactive',
|
|
91
|
+
}}
|
|
92
|
+
role="region"
|
|
93
|
+
aria-label="File drop zone"
|
|
94
|
+
ondrop={fileDrop.ondrop}
|
|
95
|
+
{...props}
|
|
96
|
+
></div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Snapshot } from '../../draw/v1/snapshot_pb';
|
|
2
|
+
import type { SuccessMessage } from '../../loaders/pcd/worker';
|
|
3
|
+
import type { BufferGeometry } from 'three';
|
|
4
|
+
interface FileDropSuccess {
|
|
5
|
+
success: true;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SnapshotFileDropSuccess extends FileDropSuccess {
|
|
9
|
+
type: 'snapshot';
|
|
10
|
+
snapshot: Snapshot;
|
|
11
|
+
}
|
|
12
|
+
export interface PointcloudFileDropSuccess extends FileDropSuccess {
|
|
13
|
+
type: 'pcd';
|
|
14
|
+
pcd: SuccessMessage;
|
|
15
|
+
}
|
|
16
|
+
export interface PlyFileDropSuccess extends FileDropSuccess {
|
|
17
|
+
type: 'ply';
|
|
18
|
+
ply: BufferGeometry;
|
|
19
|
+
}
|
|
20
|
+
export declare class FileDropperError extends Error {
|
|
21
|
+
constructor(message: string, options?: ErrorOptions);
|
|
22
|
+
}
|
|
23
|
+
export type FileDropperSuccess = SnapshotFileDropSuccess | PointcloudFileDropSuccess | PlyFileDropSuccess;
|
|
24
|
+
export interface FileDropperFailure {
|
|
25
|
+
success: false;
|
|
26
|
+
error: FileDropperError;
|
|
27
|
+
}
|
|
28
|
+
export type FileDropperResult = FileDropperSuccess | FileDropperFailure;
|
|
29
|
+
export type FileDropperParams = {
|
|
30
|
+
name: string;
|
|
31
|
+
extension: string;
|
|
32
|
+
prefix: string | undefined;
|
|
33
|
+
content: string | ArrayBuffer | null | undefined;
|
|
34
|
+
};
|
|
35
|
+
export type FileDropper = (params: FileDropperParams) => Promise<FileDropperResult>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ValueOf } from 'type-fest';
|
|
2
|
+
export declare const Extensions: {
|
|
3
|
+
readonly JSON: "json";
|
|
4
|
+
readonly PCD: "pcd";
|
|
5
|
+
readonly PLY: "ply";
|
|
6
|
+
readonly PB: "pb";
|
|
7
|
+
readonly PB_GZ: "pb.gz";
|
|
8
|
+
};
|
|
9
|
+
export declare const Prefixes: {
|
|
10
|
+
readonly Snapshot: "snapshot";
|
|
11
|
+
};
|
|
12
|
+
declare class FileNameError extends Error {
|
|
13
|
+
constructor(message: string, options?: ErrorOptions);
|
|
14
|
+
}
|
|
15
|
+
interface ParseFileSuccess {
|
|
16
|
+
success: true;
|
|
17
|
+
extension: ValueOf<typeof Extensions>;
|
|
18
|
+
prefix: ValueOf<typeof Prefixes> | undefined;
|
|
19
|
+
}
|
|
20
|
+
interface ParseFileError {
|
|
21
|
+
success: false;
|
|
22
|
+
error: FileNameError;
|
|
23
|
+
}
|
|
24
|
+
type ParseFileResult = ParseFileSuccess | ParseFileError;
|
|
25
|
+
export declare const parseFileName: (filename: string) => ParseFileResult;
|
|
26
|
+
export declare const readFile: (file: File, reader: FileReader, extension: ValueOf<typeof Extensions> | undefined) => void;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const Extensions = {
|
|
2
|
+
JSON: 'json',
|
|
3
|
+
PCD: 'pcd',
|
|
4
|
+
PLY: 'ply',
|
|
5
|
+
PB: 'pb',
|
|
6
|
+
PB_GZ: 'pb.gz',
|
|
7
|
+
};
|
|
8
|
+
export const Prefixes = {
|
|
9
|
+
Snapshot: 'snapshot',
|
|
10
|
+
};
|
|
11
|
+
class FileNameError extends Error {
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.name = 'FileNameError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const isExtension = (extension) => {
|
|
18
|
+
return Object.values(Extensions).includes(extension);
|
|
19
|
+
};
|
|
20
|
+
const isPrefix = (prefix) => {
|
|
21
|
+
if (!prefix)
|
|
22
|
+
return false;
|
|
23
|
+
return Object.values(Prefixes).includes(prefix);
|
|
24
|
+
};
|
|
25
|
+
const validatePrefix = (extension, prefix) => {
|
|
26
|
+
switch (prefix) {
|
|
27
|
+
case Prefixes.Snapshot:
|
|
28
|
+
if (extension !== Extensions.JSON &&
|
|
29
|
+
extension !== Extensions.PB &&
|
|
30
|
+
extension !== Extensions.PB_GZ) {
|
|
31
|
+
return new FileNameError(`Only ${Extensions.JSON}, ${Extensions.PB} and ${Extensions.PB_GZ} snapshot files are supported.`);
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
};
|
|
37
|
+
export const parseFileName = (filename) => {
|
|
38
|
+
const [name, ...extensions] = filename.split('.');
|
|
39
|
+
const suffix = extensions.at(-1);
|
|
40
|
+
if (!suffix) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
error: new FileNameError('Could not determine file extension.'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const nested = extensions.at(-2);
|
|
47
|
+
let extension = suffix;
|
|
48
|
+
if (nested) {
|
|
49
|
+
const nestedExtension = `${nested}.${suffix}`;
|
|
50
|
+
if (isExtension(nestedExtension)) {
|
|
51
|
+
extension = nestedExtension;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!isExtension(extension)) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: new FileNameError(`Only ${Object.values(Extensions).join(', ')} files are supported.`),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const prefix = name.split('_').at(0);
|
|
61
|
+
if (isPrefix(prefix)) {
|
|
62
|
+
const error = validatePrefix(extension, prefix);
|
|
63
|
+
if (error) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { success: true, extension, prefix };
|
|
70
|
+
}
|
|
71
|
+
return { success: true, extension, prefix: undefined };
|
|
72
|
+
};
|
|
73
|
+
export const readFile = (file, reader, extension) => {
|
|
74
|
+
if (!extension)
|
|
75
|
+
return;
|
|
76
|
+
switch (extension) {
|
|
77
|
+
case Extensions.JSON:
|
|
78
|
+
return reader.readAsText(file);
|
|
79
|
+
case Extensions.PCD:
|
|
80
|
+
case Extensions.PLY:
|
|
81
|
+
case Extensions.PB:
|
|
82
|
+
case Extensions.PB_GZ:
|
|
83
|
+
return reader.readAsArrayBuffer(file);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isArrayBuffer } from 'lodash-es';
|
|
2
|
+
import { FileDropperError } from './file-dropper';
|
|
3
|
+
import { parsePcdInWorker } from '../../loaders/pcd';
|
|
4
|
+
export const pcdDropper = async (params) => {
|
|
5
|
+
const { name, content } = params;
|
|
6
|
+
if (!isArrayBuffer(content)) {
|
|
7
|
+
return {
|
|
8
|
+
success: false,
|
|
9
|
+
error: new FileDropperError(`${name} failed to load.`),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const result = await parsePcdInWorker(new Uint8Array(content));
|
|
14
|
+
return {
|
|
15
|
+
success: true,
|
|
16
|
+
name,
|
|
17
|
+
type: 'pcd',
|
|
18
|
+
pcd: result,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
error: new FileDropperError(`${name} failed to parse.`, { cause: error }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isArrayBuffer } from 'lodash-es';
|
|
2
|
+
import { FileDropperError } from './file-dropper';
|
|
3
|
+
import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js';
|
|
4
|
+
export const plyDropper = async (params) => {
|
|
5
|
+
const { name, content } = params;
|
|
6
|
+
if (!isArrayBuffer(content)) {
|
|
7
|
+
return {
|
|
8
|
+
success: false,
|
|
9
|
+
error: new FileDropperError(`${name} failed to load.`),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const geometry = new PLYLoader().parse(new TextDecoder().decode(content));
|
|
14
|
+
return {
|
|
15
|
+
success: true,
|
|
16
|
+
name,
|
|
17
|
+
type: 'ply',
|
|
18
|
+
ply: geometry,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
error: new FileDropperError(`${name} failed to parse.`, { cause: error }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Snapshot } from '../../draw/v1/snapshot_pb';
|
|
2
|
+
import { isArrayBuffer, isString } from 'lodash-es';
|
|
3
|
+
import { FileDropperError, } from './file-dropper';
|
|
4
|
+
import { Extensions } from './file-names';
|
|
5
|
+
const decodeJson = (params) => {
|
|
6
|
+
const { name, content } = params;
|
|
7
|
+
if (!isString(content)) {
|
|
8
|
+
return {
|
|
9
|
+
success: false,
|
|
10
|
+
error: new FileDropperError(`${name} failed to load.`),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const snapshot = Snapshot.fromJsonString(content);
|
|
15
|
+
return {
|
|
16
|
+
success: true,
|
|
17
|
+
name,
|
|
18
|
+
type: 'snapshot',
|
|
19
|
+
snapshot,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: new FileDropperError(`${name} failed to parse.`, { cause: error }),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const decodeBinary = (params) => {
|
|
30
|
+
const { name, content } = params;
|
|
31
|
+
if (!isArrayBuffer(content)) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: new FileDropperError(`${name} failed to load.`),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const snapshot = Snapshot.fromBinary(new Uint8Array(content));
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
name,
|
|
42
|
+
type: 'snapshot',
|
|
43
|
+
snapshot,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: new FileDropperError(`${name} failed to parse.`, { cause: error }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const decodeGzip = async (params) => {
|
|
54
|
+
const { name, content } = params;
|
|
55
|
+
if (!isArrayBuffer(content)) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: new FileDropperError(`${name} failed to load.`),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const decompressor = new DecompressionStream('gzip');
|
|
63
|
+
const blob = new Blob([content]);
|
|
64
|
+
const stream = blob.stream().pipeThrough(decompressor);
|
|
65
|
+
const response = await new Response(stream).blob();
|
|
66
|
+
const buffer = await response.arrayBuffer();
|
|
67
|
+
const snapshot = Snapshot.fromBinary(new Uint8Array(buffer));
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
name,
|
|
71
|
+
type: 'snapshot',
|
|
72
|
+
snapshot,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: new FileDropperError(`${name} failed to parse.`, { cause: error }),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
export const snapshotDropper = async (params) => {
|
|
83
|
+
switch (params.extension) {
|
|
84
|
+
case 'json':
|
|
85
|
+
return decodeJson(params);
|
|
86
|
+
case 'pb':
|
|
87
|
+
return decodeBinary(params);
|
|
88
|
+
case 'pb.gz':
|
|
89
|
+
return decodeGzip(params);
|
|
90
|
+
default:
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: new FileDropperError(`Only ${Extensions.JSON}, ${Extensions.PB} and ${Extensions.PB_GZ} snapshot files are supported.`),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FileDropperSuccess } from './file-dropper';
|
|
2
|
+
export type DropStates = 'inactive' | 'hovering' | 'loading';
|
|
3
|
+
export declare const useFileDrop: (onSuccess: (result: FileDropperSuccess) => void, onError: (message: string) => void) => {
|
|
4
|
+
readonly dropState: DropStates;
|
|
5
|
+
ondrop: (event: DragEvent) => void;
|
|
6
|
+
ondragenter: (event: DragEvent) => void;
|
|
7
|
+
ondragover: (event: DragEvent) => void;
|
|
8
|
+
ondragleave: (event: DragEvent) => void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Extensions, parseFileName, Prefixes, readFile } from './file-names';
|
|
2
|
+
import { pcdDropper } from './pcd-dropper';
|
|
3
|
+
import { plyDropper } from './ply-dropper';
|
|
4
|
+
import { snapshotDropper } from './snapshot-dropper';
|
|
5
|
+
const createFileDropper = (extension, prefix) => {
|
|
6
|
+
switch (prefix) {
|
|
7
|
+
case Prefixes.Snapshot:
|
|
8
|
+
return snapshotDropper;
|
|
9
|
+
}
|
|
10
|
+
switch (extension) {
|
|
11
|
+
case Extensions.PCD:
|
|
12
|
+
return pcdDropper;
|
|
13
|
+
case Extensions.PLY:
|
|
14
|
+
return plyDropper;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
};
|
|
18
|
+
export const useFileDrop = (onSuccess, onError) => {
|
|
19
|
+
let dropState = $state('inactive');
|
|
20
|
+
// prevent default to allow drop
|
|
21
|
+
const ondragenter = (event) => {
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
dropState = 'hovering';
|
|
24
|
+
};
|
|
25
|
+
// prevent default to allow drop
|
|
26
|
+
const ondragover = (event) => {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
};
|
|
29
|
+
const ondragleave = (event) => {
|
|
30
|
+
// only deactivate if really leaving the window
|
|
31
|
+
if (event.relatedTarget === null) {
|
|
32
|
+
dropState = 'inactive';
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const handleError = (error) => {
|
|
36
|
+
onError(error);
|
|
37
|
+
dropState = 'inactive';
|
|
38
|
+
};
|
|
39
|
+
const ondrop = (event) => {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
if (event.dataTransfer === null)
|
|
42
|
+
return;
|
|
43
|
+
const { files } = event.dataTransfer;
|
|
44
|
+
let completed = 0;
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const fileName = parseFileName(file.name);
|
|
47
|
+
if (!fileName.success) {
|
|
48
|
+
handleError(fileName.error.message);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const { extension, prefix } = fileName;
|
|
52
|
+
const reader = new FileReader();
|
|
53
|
+
reader.addEventListener('loadend', () => {
|
|
54
|
+
completed += 1;
|
|
55
|
+
if (completed === files.length) {
|
|
56
|
+
dropState = 'inactive';
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
reader.addEventListener('error', (event) => {
|
|
60
|
+
const error = event.target?.error?.message;
|
|
61
|
+
console.error(`${file.name} failed to load.`, error);
|
|
62
|
+
handleError(`${file.name} failed to load.`);
|
|
63
|
+
});
|
|
64
|
+
reader.addEventListener('load', async (event) => {
|
|
65
|
+
const content = event.target?.result;
|
|
66
|
+
const dropper = createFileDropper(extension, prefix);
|
|
67
|
+
if (!dropper) {
|
|
68
|
+
handleError(`${file.name} is not a supported file type.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const result = await dropper({
|
|
72
|
+
name: file.name,
|
|
73
|
+
extension,
|
|
74
|
+
prefix,
|
|
75
|
+
content,
|
|
76
|
+
});
|
|
77
|
+
if (!result.success) {
|
|
78
|
+
handleError(result.error.message);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
onSuccess(result);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
readFile(file, reader, extension);
|
|
85
|
+
dropState = 'loading';
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
get dropState() {
|
|
90
|
+
return dropState;
|
|
91
|
+
},
|
|
92
|
+
ondrop,
|
|
93
|
+
ondragenter,
|
|
94
|
+
ondragover,
|
|
95
|
+
ondragleave,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { T } from '@threlte/core'
|
|
3
|
-
import { TrackballControls, Gizmo } from '@threlte/extras'
|
|
3
|
+
import { TrackballControls, Gizmo, Portal } from '@threlte/extras'
|
|
4
4
|
import { Box3, type Object3D, Vector3 } from 'three'
|
|
5
5
|
import { TrackballControls as ThreeTrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
|
|
6
6
|
import Camera from './Camera.svelte'
|
|
7
|
-
import Portal from './portal/Portal.svelte'
|
|
8
7
|
import Button from './dashboard/Button.svelte'
|
|
9
8
|
|
|
10
9
|
interface Props {
|
|
@@ -46,7 +45,7 @@
|
|
|
46
45
|
bind:ref={controls}
|
|
47
46
|
target={center}
|
|
48
47
|
>
|
|
49
|
-
<Gizmo />
|
|
48
|
+
<Gizmo placement="bottom-right" />
|
|
50
49
|
</TrackballControls>
|
|
51
50
|
</Camera>
|
|
52
51
|
|