@viamrobotics/motion-tools 1.12.2 → 1.13.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/dist/assets/ferndale_studio_11_1k.hdr +0 -0
- package/dist/components/App.svelte +11 -25
- package/dist/components/App.svelte.d.ts +3 -3
- package/dist/components/Geometry.svelte +42 -42
- package/dist/components/Scene.svelte +4 -1
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Selected.svelte +9 -9
- package/dist/components/StaticGeometries.svelte +5 -2
- package/dist/components/overlay/Details.svelte +3 -3
- package/dist/components/overlay/FloatingPanel.svelte +3 -0
- package/dist/components/overlay/FloatingPanel.svelte.d.ts +1 -0
- package/dist/components/overlay/LiveUpdatesBanner.svelte +3 -3
- package/dist/components/overlay/Logs.svelte +75 -0
- package/dist/components/overlay/__tests__/__fixtures__/resource.d.ts +10 -1
- package/dist/components/overlay/__tests__/__fixtures__/resource.js +1 -0
- package/dist/components/overlay/dashboard/Dashboard.svelte +1 -1
- package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -3
- package/dist/hooks/use3DModels.svelte.js +10 -0
- package/dist/hooks/useConfigFrames.svelte.d.ts +9 -0
- package/dist/hooks/useConfigFrames.svelte.js +92 -0
- package/dist/hooks/useFramelessComponents.svelte.js +2 -2
- package/dist/hooks/useFrames.svelte.d.ts +1 -6
- package/dist/hooks/useFrames.svelte.js +39 -145
- package/dist/hooks/usePartConfig.svelte.d.ts +10 -19
- package/dist/hooks/usePartConfig.svelte.js +138 -165
- package/dist/hooks/useVisibility.svelte.js +0 -12
- package/package.json +1 -1
- package/dist/components/overlay/left-pane/Logs.svelte +0 -52
- /package/dist/components/overlay/{left-pane/Logs.svelte.d.ts → Logs.svelte.d.ts} +0 -0
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
import { createFrame } from '../frame';
|
|
2
2
|
import { createPoseFromFrame } from '../transform';
|
|
3
3
|
import { Struct, Pose } from '@viamrobotics/sdk';
|
|
4
|
+
import { createAppMutation, createAppQuery } from '@viamrobotics/svelte-sdk';
|
|
4
5
|
import { getContext, setContext } from 'svelte';
|
|
5
6
|
const key = Symbol('part-config-context');
|
|
6
|
-
export const providePartConfig = (params) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
_localPartConfig = new StandalonePartConfig(standalonePartConfigProps);
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
throw new Error('No part config provided');
|
|
17
|
-
}
|
|
7
|
+
export const providePartConfig = (partID, params) => {
|
|
8
|
+
const props = $derived(params());
|
|
9
|
+
const config = $derived(props ? useEmbeddedPartConfig(props) : useStandalonePartConfig(partID));
|
|
10
|
+
const getCurrent = () => {
|
|
11
|
+
return (config.current.toJson?.() ?? { components: [] });
|
|
12
|
+
};
|
|
13
|
+
const current = $derived(getCurrent());
|
|
18
14
|
const createFragmentFrame = (fragmentId, componentName) => {
|
|
19
|
-
const newConfig =
|
|
20
|
-
|
|
21
|
-
newConfig.fragment_mods = [];
|
|
22
|
-
}
|
|
15
|
+
const newConfig = getCurrent();
|
|
16
|
+
newConfig.fragment_mods ??= [];
|
|
23
17
|
let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
|
|
24
18
|
if (fragmentMod === undefined) {
|
|
25
19
|
fragmentMod = {
|
|
@@ -35,23 +29,19 @@ export const providePartConfig = (params) => {
|
|
|
35
29
|
},
|
|
36
30
|
};
|
|
37
31
|
fragmentMod.mods.push(frame);
|
|
38
|
-
|
|
39
|
-
_localPartConfig.setLocalPartConfig(configStruct);
|
|
32
|
+
config.set(newConfig);
|
|
40
33
|
};
|
|
41
34
|
const createPartFrame = (componentName) => {
|
|
42
|
-
const newConfig =
|
|
35
|
+
const newConfig = getCurrent();
|
|
43
36
|
const component = newConfig?.components?.find((comp) => comp.name === componentName);
|
|
44
37
|
if (component) {
|
|
45
38
|
component.frame = createFrame();
|
|
46
39
|
}
|
|
47
|
-
|
|
48
|
-
_localPartConfig.setLocalPartConfig(configStruct);
|
|
40
|
+
config.set(newConfig);
|
|
49
41
|
};
|
|
50
42
|
const updateFragmentFrame = (fragmentId, componentName, referenceFrame, framePosition, frameGeometry) => {
|
|
51
|
-
const newConfig =
|
|
52
|
-
|
|
53
|
-
newConfig.fragment_mods = [];
|
|
54
|
-
}
|
|
43
|
+
const newConfig = getCurrent();
|
|
44
|
+
newConfig.fragment_mods ??= [];
|
|
55
45
|
let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
|
|
56
46
|
if (fragmentMod === undefined) {
|
|
57
47
|
fragmentMod = {
|
|
@@ -99,11 +89,12 @@ export const providePartConfig = (params) => {
|
|
|
99
89
|
else {
|
|
100
90
|
fragmentMod.mods.push(frame);
|
|
101
91
|
}
|
|
102
|
-
|
|
92
|
+
config.set(newConfig);
|
|
103
93
|
};
|
|
104
94
|
const updatePartFrame = (componentName, referenceFrame, pose, geometry) => {
|
|
105
|
-
const newConfig =
|
|
106
|
-
const component = newConfig
|
|
95
|
+
const newConfig = getCurrent();
|
|
96
|
+
const component = newConfig.components?.find(({ name }) => name === componentName);
|
|
97
|
+
console.log('hi', newConfig, componentName);
|
|
107
98
|
if (!component) {
|
|
108
99
|
return;
|
|
109
100
|
}
|
|
@@ -131,24 +122,20 @@ export const providePartConfig = (params) => {
|
|
|
131
122
|
}
|
|
132
123
|
}
|
|
133
124
|
}
|
|
134
|
-
|
|
135
|
-
_localPartConfig.setLocalPartConfig(configStruct);
|
|
125
|
+
config.set(newConfig);
|
|
136
126
|
};
|
|
137
127
|
const deletePartFrame = (componentName) => {
|
|
138
|
-
const newConfig =
|
|
139
|
-
const component = newConfig?.components?.find((
|
|
128
|
+
const newConfig = getCurrent();
|
|
129
|
+
const component = newConfig?.components?.find(({ name }) => name === componentName);
|
|
140
130
|
if (!component) {
|
|
141
131
|
return;
|
|
142
132
|
}
|
|
143
133
|
delete component.frame;
|
|
144
|
-
|
|
145
|
-
_localPartConfig.setLocalPartConfig(configStruct);
|
|
134
|
+
config.set(newConfig);
|
|
146
135
|
};
|
|
147
136
|
const deleteFragmentFrame = (fragmentId, componentName) => {
|
|
148
|
-
const newConfig =
|
|
149
|
-
|
|
150
|
-
newConfig.fragment_mods = [];
|
|
151
|
-
}
|
|
137
|
+
const newConfig = getCurrent();
|
|
138
|
+
newConfig.fragment_mods ??= [];
|
|
152
139
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
140
|
let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
|
|
154
141
|
if (fragmentMod === undefined) {
|
|
@@ -164,12 +151,23 @@ export const providePartConfig = (params) => {
|
|
|
164
151
|
[modUnSetPath]: '',
|
|
165
152
|
},
|
|
166
153
|
});
|
|
167
|
-
|
|
168
|
-
_localPartConfig.setLocalPartConfig(configStruct);
|
|
154
|
+
config.set(newConfig);
|
|
169
155
|
};
|
|
170
156
|
setContext(key, {
|
|
157
|
+
get current() {
|
|
158
|
+
return current;
|
|
159
|
+
},
|
|
160
|
+
get componentNameToFragmentId() {
|
|
161
|
+
return config.componentNameToFragmentId;
|
|
162
|
+
},
|
|
163
|
+
get isDirty() {
|
|
164
|
+
return config.isDirty;
|
|
165
|
+
},
|
|
166
|
+
get hasEditPermissions() {
|
|
167
|
+
return config.hasEditPermissions;
|
|
168
|
+
},
|
|
171
169
|
updateFrame: (componentName, referenceFrame, framePosition, frameGeometry) => {
|
|
172
|
-
const fragmentId =
|
|
170
|
+
const fragmentId = config.componentNameToFragmentId[componentName];
|
|
173
171
|
if (fragmentId !== undefined) {
|
|
174
172
|
updateFragmentFrame(fragmentId, componentName, referenceFrame, framePosition, frameGeometry);
|
|
175
173
|
}
|
|
@@ -178,7 +176,7 @@ export const providePartConfig = (params) => {
|
|
|
178
176
|
}
|
|
179
177
|
},
|
|
180
178
|
deleteFrame: (componentName) => {
|
|
181
|
-
const fragmentId =
|
|
179
|
+
const fragmentId = config.componentNameToFragmentId[componentName];
|
|
182
180
|
if (fragmentId !== undefined) {
|
|
183
181
|
deleteFragmentFrame(fragmentId, componentName);
|
|
184
182
|
}
|
|
@@ -187,7 +185,7 @@ export const providePartConfig = (params) => {
|
|
|
187
185
|
}
|
|
188
186
|
},
|
|
189
187
|
createFrame: (componentName) => {
|
|
190
|
-
const fragmentId =
|
|
188
|
+
const fragmentId = config.componentNameToFragmentId[componentName];
|
|
191
189
|
if (fragmentId !== undefined) {
|
|
192
190
|
createFragmentFrame(fragmentId, componentName);
|
|
193
191
|
}
|
|
@@ -195,137 +193,112 @@ export const providePartConfig = (params) => {
|
|
|
195
193
|
createPartFrame(componentName);
|
|
196
194
|
}
|
|
197
195
|
},
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
196
|
+
save: () => config.save?.(),
|
|
197
|
+
discardChanges: () => config.discardChanges?.(),
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
export const usePartConfig = () => {
|
|
201
|
+
return getContext(key);
|
|
202
|
+
};
|
|
203
|
+
const useEmbeddedPartConfig = (props) => {
|
|
204
|
+
return {
|
|
205
|
+
hasEditPermissions: true,
|
|
206
|
+
get isDirty() {
|
|
207
|
+
return props.isDirty;
|
|
203
208
|
},
|
|
204
|
-
get
|
|
205
|
-
return
|
|
209
|
+
get current() {
|
|
210
|
+
return props.current ?? new Struct();
|
|
206
211
|
},
|
|
207
212
|
get componentNameToFragmentId() {
|
|
208
|
-
return
|
|
209
|
-
},
|
|
210
|
-
get isDirty() {
|
|
211
|
-
return _localPartConfig.isDirty();
|
|
213
|
+
return props.componentToFragId;
|
|
212
214
|
},
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
set(config) {
|
|
216
|
+
const struct = Struct.fromJson(config);
|
|
217
|
+
return props.setLocalPartConfig(struct);
|
|
215
218
|
},
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
export const usePartConfig = () => {
|
|
219
|
-
return getContext(key);
|
|
219
|
+
};
|
|
220
220
|
};
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
this._hasEditPermissions = true;
|
|
262
|
-
const configJson = JSON.parse(partResponse?.configJson ?? '{}');
|
|
263
|
-
this._networkPartConfig = Struct.fromJson(configJson);
|
|
264
|
-
this._localPartConfig = Struct.fromJson(configJson);
|
|
265
|
-
this._partName = partResponse?.part?.name;
|
|
266
|
-
const componentNameToFragmentId = {};
|
|
267
|
-
const fragmentRequests = [];
|
|
268
|
-
if (configJson.fragments) {
|
|
269
|
-
for (const fragmentId of configJson.fragments) {
|
|
270
|
-
//TODO: right now the json could be just a list of strings or an object with an id prop
|
|
271
|
-
const fragId = typeof fragmentId === 'string' ? fragmentId : fragmentId.id;
|
|
272
|
-
fragmentRequests.push(standalonePartConfigProps.viamClient()?.appClient.getFragment(fragId));
|
|
273
|
-
}
|
|
274
|
-
const fragementResponses = await Promise.all(fragmentRequests);
|
|
275
|
-
for (const fragmentResponse of fragementResponses) {
|
|
276
|
-
const fragmentId = fragmentResponse?.id;
|
|
277
|
-
if (!fragmentId) {
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
const components = fragmentResponse?.fragment?.fields['components']?.kind;
|
|
281
|
-
if (components?.case === 'listValue') {
|
|
282
|
-
for (const component of components.value.values) {
|
|
283
|
-
if (component.kind.case === 'structValue') {
|
|
284
|
-
const componentName = component.kind.value.fields['name'].kind;
|
|
285
|
-
if (componentName.case === 'stringValue') {
|
|
286
|
-
componentNameToFragmentId[componentName.value] = fragmentId;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
221
|
+
const useStandalonePartConfig = (partID) => {
|
|
222
|
+
const partQuery = createAppQuery('getRobotPart', () => [partID()], {
|
|
223
|
+
refetchInterval: false,
|
|
224
|
+
});
|
|
225
|
+
const partName = $derived(partQuery.data?.part?.name);
|
|
226
|
+
const configJSON = $derived.by(() => {
|
|
227
|
+
if (!partQuery.data?.configJson) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(partQuery.data.configJson);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
let networkPartConfig = $derived(configJSON ? Struct.fromJson(configJSON) : undefined);
|
|
238
|
+
let current = $state.raw();
|
|
239
|
+
let isDirty = $state(false);
|
|
240
|
+
const hasEditPermissions = $derived(networkPartConfig !== undefined);
|
|
241
|
+
const fragmentQueries = $derived((configJSON?.fragments ?? []).map((fragmentId) => {
|
|
242
|
+
const id = typeof fragmentId === 'string' ? fragmentId : fragmentId.id;
|
|
243
|
+
return createAppQuery('getFragment', () => [id], { refetchInterval: false });
|
|
244
|
+
}));
|
|
245
|
+
const componentNameToFragmentId = $derived.by(() => {
|
|
246
|
+
const results = {};
|
|
247
|
+
for (const query of fragmentQueries) {
|
|
248
|
+
if (!query.data) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const fragmentId = query.data.id;
|
|
252
|
+
const components = query.data?.fragment?.fields['components']?.kind;
|
|
253
|
+
if (components?.case === 'listValue') {
|
|
254
|
+
for (const component of components.value.values) {
|
|
255
|
+
if (component.kind.case === 'structValue') {
|
|
256
|
+
const componentName = component.kind.value.fields['name']?.kind;
|
|
257
|
+
if (componentName.case === 'stringValue') {
|
|
258
|
+
results[componentName.value] = fragmentId;
|
|
290
259
|
}
|
|
291
260
|
}
|
|
292
|
-
this._componentNameToFragmentId = componentNameToFragmentId;
|
|
293
261
|
}
|
|
294
|
-
}
|
|
295
|
-
initLocalConfig();
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
getLocalPartConfig() {
|
|
299
|
-
return this._localPartConfig ?? new Struct();
|
|
300
|
-
}
|
|
301
|
-
setLocalPartConfig(config) {
|
|
302
|
-
this._localPartConfig = config;
|
|
303
|
-
this._isDirty = true;
|
|
304
|
-
}
|
|
305
|
-
isDirty() {
|
|
306
|
-
return this._isDirty;
|
|
307
|
-
}
|
|
308
|
-
hasEditPermissions() {
|
|
309
|
-
return this._hasEditPermissions;
|
|
310
|
-
}
|
|
311
|
-
componentNameToFragmentId() {
|
|
312
|
-
return this._componentNameToFragmentId ?? {};
|
|
313
|
-
}
|
|
314
|
-
async saveLocalPartConfig() {
|
|
315
|
-
if (!this._localPartConfig || !this._partName) {
|
|
316
|
-
return;
|
|
262
|
+
}
|
|
317
263
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
async resetLocalPartConfig() {
|
|
325
|
-
if (!this._networkPartConfig) {
|
|
264
|
+
return results;
|
|
265
|
+
});
|
|
266
|
+
$effect.pre(() => {
|
|
267
|
+
if (!networkPartConfig) {
|
|
268
|
+
// no config returned here indicates this api key has no permission to update config
|
|
326
269
|
return;
|
|
327
270
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
271
|
+
current = networkPartConfig;
|
|
272
|
+
});
|
|
273
|
+
const updateRobotPartMutation = createAppMutation('updateRobotPart');
|
|
274
|
+
return {
|
|
275
|
+
get current() {
|
|
276
|
+
return current ?? new Struct();
|
|
277
|
+
},
|
|
278
|
+
get isDirty() {
|
|
279
|
+
return isDirty;
|
|
280
|
+
},
|
|
281
|
+
get hasEditPermissions() {
|
|
282
|
+
return hasEditPermissions;
|
|
283
|
+
},
|
|
284
|
+
get componentNameToFragmentId() {
|
|
285
|
+
return componentNameToFragmentId;
|
|
286
|
+
},
|
|
287
|
+
set(config) {
|
|
288
|
+
current = Struct.fromJson(config);
|
|
289
|
+
isDirty = true;
|
|
290
|
+
},
|
|
291
|
+
async save() {
|
|
292
|
+
if (!current || !partName) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
networkPartConfig = current;
|
|
296
|
+
await updateRobotPartMutation.mutateAsync([partID(), partName, current]);
|
|
297
|
+
isDirty = false;
|
|
298
|
+
},
|
|
299
|
+
discardChanges() {
|
|
300
|
+
current = networkPartConfig;
|
|
301
|
+
isDirty = false;
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
};
|
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { get, set } from 'idb-keyval';
|
|
2
1
|
import { getContext, setContext } from 'svelte';
|
|
3
2
|
import { SvelteMap } from 'svelte/reactivity';
|
|
4
3
|
const key = Symbol('object-visibility-context');
|
|
5
|
-
const idbKey = 'object-visibility';
|
|
6
4
|
export const provideVisibility = () => {
|
|
7
5
|
const map = new SvelteMap();
|
|
8
|
-
get(idbKey).then((entries) => {
|
|
9
|
-
if (entries) {
|
|
10
|
-
for (const [key, value] of entries) {
|
|
11
|
-
map.set(key, value);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
$effect(() => {
|
|
16
|
-
set(idbKey, [...map.entries()]);
|
|
17
|
-
});
|
|
18
6
|
setContext(key, map);
|
|
19
7
|
};
|
|
20
8
|
export const useVisibility = () => {
|
package/package.json
CHANGED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { useLogs } from '../../../hooks/useLogs.svelte'
|
|
3
|
-
import Drawer from './Drawer.svelte'
|
|
4
|
-
|
|
5
|
-
const logs = useLogs()
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
|
-
<Drawer name="Logs">
|
|
9
|
-
{#snippet titleAlert()}
|
|
10
|
-
{#if logs.warnings.length > 0}
|
|
11
|
-
<span class="mr-1 rounded bg-yellow-700 px-1 py-0.5 text-xs text-white">
|
|
12
|
-
{logs.warnings.length}
|
|
13
|
-
</span>
|
|
14
|
-
{/if}
|
|
15
|
-
|
|
16
|
-
{#if logs.errors.length > 0}
|
|
17
|
-
<span class="mr-1 rounded bg-red-700 px-1 py-0.5 text-xs text-white">
|
|
18
|
-
{logs.errors.length}
|
|
19
|
-
</span>
|
|
20
|
-
{/if}
|
|
21
|
-
{/snippet}
|
|
22
|
-
|
|
23
|
-
<div class="flex h-64 flex-col gap-2 overflow-auto p-3">
|
|
24
|
-
{#each logs.current as log (log.uuid)}
|
|
25
|
-
<div>
|
|
26
|
-
<div class="flex flex-wrap items-center gap-1.5">
|
|
27
|
-
<div
|
|
28
|
-
class={[
|
|
29
|
-
'h-2 w-2 rounded-full',
|
|
30
|
-
{
|
|
31
|
-
'bg-danger-dark': log.level === 'error',
|
|
32
|
-
'bg-amber-300': log.level === 'warn',
|
|
33
|
-
'bg-blue-400': log.level === 'info',
|
|
34
|
-
},
|
|
35
|
-
]}
|
|
36
|
-
></div>
|
|
37
|
-
<div class="text-subtle-2">{log.timestamp}</div>
|
|
38
|
-
</div>
|
|
39
|
-
<div>
|
|
40
|
-
{#if log.count > 1}
|
|
41
|
-
<span class="mr-1 rounded bg-green-700 px-1 py-0.5 text-xs text-white">
|
|
42
|
-
{log.count}
|
|
43
|
-
</span>
|
|
44
|
-
{/if}
|
|
45
|
-
{log.message}
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
{:else}
|
|
49
|
-
No logs
|
|
50
|
-
{/each}
|
|
51
|
-
</div>
|
|
52
|
-
</Drawer>
|
|
File without changes
|