@viamrobotics/motion-tools 1.25.4 → 1.25.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Scene.svelte +5 -1
- package/dist/components/overlay/Details.svelte +18 -7
- package/dist/components/overlay/FloatingPanel.svelte +9 -1
- package/dist/ecs/hierarchy.d.ts +6 -0
- package/dist/ecs/hierarchy.js +12 -2
- package/dist/ecs/useWorld.js +8 -0
- package/dist/hooks/useLogs.svelte.js +29 -24
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
3
|
|
|
4
|
-
import { T } from '@threlte/core'
|
|
4
|
+
import { T, useThrelte } from '@threlte/core'
|
|
5
5
|
import { Environment, Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
|
|
6
6
|
import { useXR } from '@threlte/xr'
|
|
7
7
|
import { ShaderMaterial, Vector3 } from 'three'
|
|
@@ -29,10 +29,14 @@
|
|
|
29
29
|
|
|
30
30
|
let { children }: Props = $props()
|
|
31
31
|
|
|
32
|
+
const threlte = useThrelte()
|
|
32
33
|
const settings = useSettings()
|
|
33
34
|
const focusedObject3d = useFocusedObject3d()
|
|
34
35
|
const origin = useOrigin()
|
|
35
36
|
|
|
37
|
+
// @ts-expect-error This is for debugging
|
|
38
|
+
globalThis.__threlte__ = threlte
|
|
39
|
+
|
|
36
40
|
const { raycaster, enabled } = interactivity({
|
|
37
41
|
filter: (intersections) => {
|
|
38
42
|
const match = intersections.find((intersection) => {
|
|
@@ -477,13 +477,24 @@
|
|
|
477
477
|
<div>
|
|
478
478
|
<strong class="font-semibold">parent frame</strong>
|
|
479
479
|
{#if showEditFrameOptions}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
480
|
+
<!--
|
|
481
|
+
Remount on entity change. svelte-tweakpane-ui's List runs
|
|
482
|
+
`listBlade.value = value` on the still-mounted blade before its
|
|
483
|
+
`options` prop has propagated, so the new entity's parent name
|
|
484
|
+
(absent from the previous entity's option set) hits Tweakpane's
|
|
485
|
+
ListConstraint, snaps to the first option, and fires a change
|
|
486
|
+
event that handleParentChange interprets as a user pick — silently
|
|
487
|
+
reparenting the clicked frame.
|
|
488
|
+
-->
|
|
489
|
+
{#key entity}
|
|
490
|
+
<div aria-label="mutable parent frame">
|
|
491
|
+
<List
|
|
492
|
+
options={configFrames.getParentFrameOptions(name.current ?? '') ?? []}
|
|
493
|
+
value={parent.current ?? 'world'}
|
|
494
|
+
on:change={handleParentChange}
|
|
495
|
+
/>
|
|
496
|
+
</div>
|
|
497
|
+
{/key}
|
|
487
498
|
{:else}
|
|
488
499
|
<div class="mt-0.5 flex gap-3">
|
|
489
500
|
{@render ImmutableField({
|
|
@@ -89,11 +89,19 @@
|
|
|
89
89
|
</div>
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
|
+
<!--
|
|
93
|
+
Skip rendering the body subtree while collapsed. zag-js controls
|
|
94
|
+
visibility via attributes (the panel chrome stays mounted), but the
|
|
95
|
+
children don't need to react to upstream state when the user can't see them.
|
|
96
|
+
Children mount fresh on open.
|
|
97
|
+
-->
|
|
92
98
|
<div
|
|
93
99
|
{...api.getBodyProps()}
|
|
94
100
|
class="relative h-[calc(100%-33px)]"
|
|
95
101
|
>
|
|
96
|
-
{
|
|
102
|
+
{#if isOpen}
|
|
103
|
+
{@render children()}
|
|
104
|
+
{/if}
|
|
97
105
|
</div>
|
|
98
106
|
|
|
99
107
|
{#if resizable}
|
package/dist/ecs/hierarchy.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export declare const parentTraits: (name: string | undefined) => ConfigurableTra
|
|
|
10
10
|
* Set or clear an entity's parent. Strips any existing `ChildOf` or `Orphan`,
|
|
11
11
|
* then writes `Orphan(name)` (the resolver converts it to `ChildOf` on the
|
|
12
12
|
* next reactive flush). Pass `undefined` or `'world'` to detach to root.
|
|
13
|
+
*
|
|
14
|
+
* Short-circuits when the effective parent name (via resolved `ChildOf` or
|
|
15
|
+
* pending `Orphan`) already matches `name`. Network-backed reconcilers call
|
|
16
|
+
* this every refetch tick on stable entities; the demote-then-re-resolve
|
|
17
|
+
* dance otherwise flips `useParentName` to `undefined` and back, remounting
|
|
18
|
+
* every `<Portal id={parent.current}>` subtree per tick.
|
|
13
19
|
*/
|
|
14
20
|
export declare const setParent: (entity: Entity, name: string | undefined) => void;
|
|
15
21
|
/** The parent entity, or `undefined` at the world root or while orphaned. */
|
package/dist/ecs/hierarchy.js
CHANGED
|
@@ -16,15 +16,25 @@ export const parentTraits = (name) => {
|
|
|
16
16
|
* Set or clear an entity's parent. Strips any existing `ChildOf` or `Orphan`,
|
|
17
17
|
* then writes `Orphan(name)` (the resolver converts it to `ChildOf` on the
|
|
18
18
|
* next reactive flush). Pass `undefined` or `'world'` to detach to root.
|
|
19
|
+
*
|
|
20
|
+
* Short-circuits when the effective parent name (via resolved `ChildOf` or
|
|
21
|
+
* pending `Orphan`) already matches `name`. Network-backed reconcilers call
|
|
22
|
+
* this every refetch tick on stable entities; the demote-then-re-resolve
|
|
23
|
+
* dance otherwise flips `useParentName` to `undefined` and back, remounting
|
|
24
|
+
* every `<Portal id={parent.current}>` subtree per tick.
|
|
19
25
|
*/
|
|
20
26
|
export const setParent = (entity, name) => {
|
|
27
|
+
const desired = !name || name === 'world' ? undefined : name;
|
|
21
28
|
const target = entity.targetFor(ChildOf);
|
|
29
|
+
const current = (target?.isAlive() ? target.get(Name) : undefined) ?? entity.get(Orphan);
|
|
30
|
+
if (current === desired)
|
|
31
|
+
return;
|
|
22
32
|
if (target)
|
|
23
33
|
entity.remove(ChildOf(target));
|
|
24
34
|
entity.remove(Orphan);
|
|
25
|
-
if (
|
|
35
|
+
if (desired === undefined)
|
|
26
36
|
return;
|
|
27
|
-
entity.add(Orphan(
|
|
37
|
+
entity.add(Orphan(desired));
|
|
28
38
|
};
|
|
29
39
|
/** The parent entity, or `undefined` at the world root or while orphaned. */
|
|
30
40
|
export const getParentEntity = (entity) => entity.targetFor(ChildOf);
|
package/dist/ecs/useWorld.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { createWorld } from 'koota';
|
|
2
2
|
import { getContext, setContext } from 'svelte';
|
|
3
|
+
import * as relations from './relations';
|
|
4
|
+
import * as traits from './traits';
|
|
3
5
|
export const WORLD_CONTEXT_KEY = Symbol('koota-context');
|
|
4
6
|
export function provideWorld() {
|
|
5
7
|
const world = createWorld();
|
|
8
|
+
// @ts-expect-error This is for debugging.
|
|
9
|
+
globalThis.__koota__ = {
|
|
10
|
+
world,
|
|
11
|
+
traits,
|
|
12
|
+
relations,
|
|
13
|
+
};
|
|
6
14
|
setContext(WORLD_CONTEXT_KEY, world);
|
|
7
15
|
}
|
|
8
16
|
export function useWorld() {
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { getContext, setContext, untrack } from 'svelte';
|
|
2
2
|
import { MathUtils } from 'three';
|
|
3
3
|
const key = Symbol('logs-context');
|
|
4
|
+
const MAX_LOGS = 200;
|
|
4
5
|
export const provideLogs = () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Plain insertion-ordered Map keyed by `${level}|${timestamp}|${message}`
|
|
7
|
+
// drives storage; a single `$state` version counter drives reactivity.
|
|
8
|
+
// Hot path is `Map.set` (or in-place `count++`) plus `version++` — no
|
|
9
|
+
// array allocation per add. The display arrays are materialized lazily
|
|
10
|
+
// in `$derived.by`, so a closed logs panel costs nothing.
|
|
11
|
+
const entries = new Map();
|
|
12
|
+
let version = $state(0);
|
|
8
13
|
const intl = new Intl.DateTimeFormat('en-US', {
|
|
9
14
|
dateStyle: 'short',
|
|
10
15
|
timeStyle: 'short',
|
|
11
16
|
});
|
|
17
|
+
const dedupKey = (timestamp, level, message) => `${level}\0${timestamp}\0${message}`;
|
|
18
|
+
const all = $derived.by(() => {
|
|
19
|
+
void version;
|
|
20
|
+
const out = [...entries.values()];
|
|
21
|
+
out.reverse();
|
|
22
|
+
return out;
|
|
23
|
+
});
|
|
24
|
+
const errors = $derived(all.filter((l) => l.level === 'error'));
|
|
25
|
+
const warnings = $derived(all.filter((l) => l.level === 'warn'));
|
|
12
26
|
setContext(key, {
|
|
13
27
|
get current() {
|
|
14
|
-
return
|
|
28
|
+
return all;
|
|
15
29
|
},
|
|
16
30
|
get errors() {
|
|
17
31
|
return errors;
|
|
@@ -22,35 +36,26 @@ export const provideLogs = () => {
|
|
|
22
36
|
add(message, level = 'info') {
|
|
23
37
|
untrack(() => {
|
|
24
38
|
const timestamp = intl.format(Date.now());
|
|
25
|
-
const
|
|
39
|
+
const k = dedupKey(timestamp, level, message);
|
|
40
|
+
const match = entries.get(k);
|
|
26
41
|
if (match) {
|
|
27
42
|
match.count += 1;
|
|
28
43
|
}
|
|
29
44
|
else {
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
entries.set(k, {
|
|
46
|
+
uuid: MathUtils.generateUUID(),
|
|
32
47
|
message,
|
|
33
48
|
count: 1,
|
|
34
49
|
level,
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
else if (level === 'warn') {
|
|
42
|
-
warnings.unshift(log);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (logs.length > 200) {
|
|
46
|
-
const log = logs.pop();
|
|
47
|
-
if (log?.level === 'error') {
|
|
48
|
-
errors.splice(errors.indexOf(log), 1);
|
|
49
|
-
}
|
|
50
|
-
else if (log?.level === 'warn') {
|
|
51
|
-
warnings.splice(warnings.indexOf(log), 1);
|
|
50
|
+
timestamp,
|
|
51
|
+
});
|
|
52
|
+
if (entries.size > MAX_LOGS) {
|
|
53
|
+
const oldestKey = entries.keys().next().value;
|
|
54
|
+
if (oldestKey !== undefined)
|
|
55
|
+
entries.delete(oldestKey);
|
|
52
56
|
}
|
|
53
57
|
}
|
|
58
|
+
version++;
|
|
54
59
|
});
|
|
55
60
|
},
|
|
56
61
|
});
|