lecodes-cli 0.13.0 → 0.13.2
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/index.js +51 -28
- package/package.json +3 -3
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk/scene/defineScene.ts +221 -109
- package/runtime/sdk/scene/editorPlugins.ts +22 -16
- package/runtime/sdk/scene/grammar.ts +49 -14
- package/runtime/sdk-types.json +1 -1
- package/runtime/sdk/compile/aspectMacro.ts +0 -42
- package/runtime/sdk/compile/assetIconMacro.ts +0 -382
- package/runtime/sdk/compile/assetMacro.ts +0 -45
- package/runtime/sdk/compile/assetName.ts +0 -50
- package/runtime/sdk/compile/bundler.ts +0 -244
- package/runtime/sdk/compile/compileProject.ts +0 -106
- package/runtime/sdk/compile/detectEntry.ts +0 -125
- package/runtime/sdk/compile/fontMacro.ts +0 -459
- package/runtime/sdk/compile/fontRegistry.ts +0 -78
- package/runtime/sdk/compile/header.ts +0 -55
- package/runtime/sdk/compile/index.ts +0 -53
- package/runtime/sdk/compile/libraryImports.ts +0 -52
- package/runtime/sdk/compile/sceneEditor.ts +0 -78
- package/runtime/sdk/compile/sfnt.ts +0 -98
- package/runtime/sdk/compile/sourcemap.ts +0 -25
|
@@ -17,31 +17,37 @@ import type { InspectorUI } from "../core/InspectorUI"
|
|
|
17
17
|
export type EditorRayHit = {
|
|
18
18
|
point: [number, number, number]
|
|
19
19
|
normal: [number, number, number]
|
|
20
|
-
/**
|
|
20
|
+
/** Absolute path of the scene-file node the hit belongs to ("city/in1/pt1") — null for
|
|
21
|
+
* ground-plane fallback hits. */
|
|
21
22
|
node: string | null
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
* The editor scripting API handed to windows and tools.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* The editor scripting API handed to windows and tools. Nodes are addressed by ABSOLUTE PATH —
|
|
27
|
+
* '/'-joined names from the scene root; a root node's path is its bare name. Doc-op methods write
|
|
28
|
+
* the scene DOCUMENT through the editor's normal commit path (undo, file, live patching all
|
|
29
|
+
* included); they return false / no-op when the document can't take the edit (sibling-name
|
|
30
|
+
* collision, unknown path).
|
|
28
31
|
*/
|
|
29
32
|
export type EditorApi = {
|
|
30
|
-
/** The currently selected node
|
|
33
|
+
/** The currently selected node path (or `path::part` key for asset internals), null when
|
|
34
|
+
* nothing is selected. */
|
|
31
35
|
readonly selection: string | null
|
|
32
|
-
select(
|
|
33
|
-
/** The scene document's nodes
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
select(path: string | null): void
|
|
37
|
+
/** The scene document's nodes: absolute path, sibling-unique display name, source kind
|
|
38
|
+
* (mesh / model / light / group / …). */
|
|
39
|
+
nodes(): { path: string, name: string, kind: string }[]
|
|
40
|
+
/** First unused "base", "base2", "base3", … name among the SIBLINGS under `parentPath`
|
|
41
|
+
* (scene root when omitted). */
|
|
42
|
+
uniqueName(base: string, parentPath?: string): string
|
|
43
|
+
/** Add a ROOT node from plain def data (scene-file grammar; a string `model` value means an
|
|
44
|
+
* asset path). `name` may not contain '/' or ':'. One undo step unless grouped by `transact`. */
|
|
39
45
|
addNode(name: string, def: Record<string, unknown>): boolean
|
|
40
46
|
/** Write one def prop (transforms apply live; anything else patches/re-runs the node). */
|
|
41
|
-
setProp(
|
|
42
|
-
removeNode(
|
|
43
|
-
/** Duplicate a node; returns the copy's
|
|
44
|
-
duplicate(
|
|
47
|
+
setProp(path: string, key: string, value: unknown): boolean
|
|
48
|
+
removeNode(path: string): void
|
|
49
|
+
/** Duplicate a node; returns the copy's path (null when it can't). */
|
|
50
|
+
duplicate(path: string): string | null
|
|
45
51
|
/** Raycast the scene under a viewport pixel (same hit rules as tool clicks). */
|
|
46
52
|
raycast(screenX: number, screenY: number): EditorRayHit | null
|
|
47
53
|
/** Group every doc edit inside `fn` into ONE undo step. */
|
|
@@ -43,39 +43,74 @@ export const make = <A extends Record<string, unknown>, N>(
|
|
|
43
43
|
): MakeEntry<A, N> => ({ __make: true, fn, args })
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Reference another scene node by
|
|
47
|
-
* `use(Road, { from: ref('pointA'), to: ref('pointB') })`. Resolves to the live node when
|
|
48
|
-
* attach — after EVERY node of the scene exists, so
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* Reference another scene node by PATH in an aspect's props:
|
|
47
|
+
* `use(Road, { from: ref('pointA'), to: ref('lane/pointB') })`. Resolves to the live node when
|
|
48
|
+
* aspects attach — after EVERY node of the scene exists, so declaration order doesn't matter.
|
|
49
|
+
* Resolution is scoped upward from the host node (like variable scoping): the host's own children
|
|
50
|
+
* first, then its siblings, then each ancestor's scope up to the scene root. The FIRST segment
|
|
51
|
+
* binds the scope; the remaining segments descend from there. Refs address def nodes only (no
|
|
52
|
+
* `::`/`name[i]` asset-internal segments); an unknown path resolves to `null` (type your aspect
|
|
53
|
+
* field `Node | null`). Scene files only — hand-written code passes nodes directly:
|
|
54
|
+
* `node.aspect(Road, { from: nodes.pointA })`.
|
|
51
55
|
*/
|
|
52
|
-
export const ref = <T = Node>(
|
|
56
|
+
export const ref = <T = Node>(path: string): T => ({ $ref: path } as unknown as T)
|
|
53
57
|
|
|
54
58
|
export const isNodeRef = (v: unknown): v is { $ref: string } =>
|
|
55
59
|
typeof v === "object" && v !== null && typeof (v as { $ref?: unknown }).$ref === "string"
|
|
56
60
|
|
|
57
|
-
/**
|
|
61
|
+
/** Resolve one ref string against a path-keyed nodes record, scoped upward from `hostPath` (own
|
|
62
|
+
* children → parent's scope (siblings + the host itself) → each ancestor scope → root, `""`).
|
|
63
|
+
* The ref's FIRST segment binds the scope — a match there is final even when the rest of the
|
|
64
|
+
* path doesn't exist (lexical shadowing). Returns the target's ABSOLUTE path, or null.
|
|
65
|
+
* `Object.hasOwn`, not indexing: a node named `constructor` must not resolve via the prototype. */
|
|
66
|
+
export const resolveRefPath = (
|
|
67
|
+
nodes: Record<string, unknown>, hostPath: string, ref: string,
|
|
68
|
+
): string | null => {
|
|
69
|
+
const i = ref.indexOf("/")
|
|
70
|
+
const first = i < 0 ? ref : ref.slice(0, i)
|
|
71
|
+
for (let scope = hostPath; ; ) {
|
|
72
|
+
const base = scope === "" ? "" : `${scope}/`
|
|
73
|
+
if (Object.hasOwn(nodes, base + first)) return Object.hasOwn(nodes, base + ref) ? base + ref : null
|
|
74
|
+
if (scope === "") return null
|
|
75
|
+
const cut = scope.lastIndexOf("/")
|
|
76
|
+
scope = cut < 0 ? "" : scope.slice(0, cut)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Swap `ref()` markers in aspect props for the live nodes (top level + one array level deep),
|
|
81
|
+
* resolved scoped-upward from `hostPath` ("" = root scope only — the 2D flat-map case). */
|
|
58
82
|
export const resolveRefs = <N>(
|
|
59
|
-
props: Record<string, unknown> | undefined, nodes: Record<string, N>,
|
|
83
|
+
props: Record<string, unknown> | undefined, nodes: Record<string, N>, hostPath = "",
|
|
60
84
|
): Record<string, unknown> | undefined => {
|
|
61
85
|
if (!props) return props
|
|
86
|
+
const lookup = (r: string): N | null => {
|
|
87
|
+
const p = resolveRefPath(nodes, hostPath, r)
|
|
88
|
+
return p === null ? null : nodes[p]
|
|
89
|
+
}
|
|
62
90
|
let out: Record<string, unknown> | undefined
|
|
63
91
|
for (const [ k, v ] of Object.entries(props)) {
|
|
64
92
|
if (isNodeRef(v)) {
|
|
65
|
-
;(out ??= { ...props })[k] =
|
|
93
|
+
;(out ??= { ...props })[k] = lookup(v.$ref)
|
|
66
94
|
} else if (Array.isArray(v) && v.some(isNodeRef)) {
|
|
67
|
-
;(out ??= { ...props })[k] = v.map((el) => (isNodeRef(el) ?
|
|
95
|
+
;(out ??= { ...props })[k] = v.map((el) => (isNodeRef(el) ? lookup(el.$ref) : el))
|
|
68
96
|
}
|
|
69
97
|
}
|
|
70
98
|
return out ?? props
|
|
71
99
|
}
|
|
72
100
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
101
|
+
/** ABSOLUTE paths of the nodes a props record's ref() values resolve to (editor dep tracking).
|
|
102
|
+
* An unresolved ref contributes no dep — the handle re-derives deps after structural changes. */
|
|
103
|
+
export const collectRefDeps = (
|
|
104
|
+
props: Record<string, unknown>, nodes: Record<string, unknown>, hostPath: string,
|
|
105
|
+
): Set<string> => {
|
|
75
106
|
const deps = new Set<string>()
|
|
107
|
+
const add = (r: string): void => {
|
|
108
|
+
const p = resolveRefPath(nodes, hostPath, r)
|
|
109
|
+
if (p !== null) deps.add(p)
|
|
110
|
+
}
|
|
76
111
|
for (const v of Object.values(props)) {
|
|
77
|
-
if (isNodeRef(v))
|
|
78
|
-
else if (Array.isArray(v)) for (const el of v) if (isNodeRef(el))
|
|
112
|
+
if (isNodeRef(v)) add(v.$ref)
|
|
113
|
+
else if (Array.isArray(v)) for (const el of v) if (isNodeRef(el)) add(el.$ref)
|
|
79
114
|
}
|
|
80
115
|
return deps
|
|
81
116
|
}
|