@needle-tools/addressing 0.3.4 → 0.4.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 +62 -0
- package/dist/address.d.ts +5 -0
- package/dist/address.js +25 -3
- package/dist/document/assets.d.ts +27 -0
- package/dist/document/assets.js +75 -0
- package/dist/document/collectionSelector.d.ts +9 -0
- package/dist/document/collectionSelector.js +29 -0
- package/dist/document/evaluate.d.ts +72 -0
- package/dist/document/evaluate.js +971 -0
- package/dist/document/index.d.ts +8 -0
- package/dist/document/index.js +8 -0
- package/dist/document/inspection.d.ts +30 -0
- package/dist/document/inspection.js +130 -0
- package/dist/document/runtime.d.ts +71 -0
- package/dist/document/runtime.js +1 -0
- package/dist/document/types.d.ts +225 -0
- package/dist/document/types.js +43 -0
- package/dist/document/validation.d.ts +36 -0
- package/dist/document/validation.js +219 -0
- package/dist/document/write.d.ts +31 -0
- package/dist/document/write.js +106 -0
- package/dist/id.d.ts +2 -0
- package/dist/id.js +4 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/loadedGltf.d.ts +32 -0
- package/dist/loadedGltf.js +62 -0
- package/dist/resolve.js +8 -6
- package/package.json +9 -1
- package/src/address.ts +28 -3
- package/src/document/assets.ts +77 -0
- package/src/document/collectionSelector.ts +37 -0
- package/src/document/evaluate.ts +998 -0
- package/src/document/index.ts +8 -0
- package/src/document/inspection.ts +123 -0
- package/src/document/runtime.ts +66 -0
- package/src/document/schema.json +263 -0
- package/src/document/types.ts +249 -0
- package/src/document/validation.ts +222 -0
- package/src/document/write.ts +112 -0
- package/src/id.ts +5 -0
- package/src/index.ts +3 -0
- package/src/loadedGltf.ts +93 -0
- package/src/resolve.ts +9 -7
package/Readme.md
CHANGED
|
@@ -6,6 +6,62 @@ Persistent addressing for glTF assets and three.js scene graphs: deterministic
|
|
|
6
6
|
Zero runtime dependencies, no three.js import (scene graphs are accessed structurally),
|
|
7
7
|
usable in browsers, node, and workers.
|
|
8
8
|
|
|
9
|
+
## Project documents
|
|
10
|
+
|
|
11
|
+
`@needle-tools/addressing/document` exports the document types, evaluator,
|
|
12
|
+
single-def/over application, optional inspection index, and schema validator.
|
|
13
|
+
The JSON Schema is available at `@needle-tools/addressing/document/schema.json`.
|
|
14
|
+
The document format is unchanged by this package split.
|
|
15
|
+
|
|
16
|
+
The shared writer exports `writePropertyOver`, `captureDocumentValue`,
|
|
17
|
+
`documentOverKey`, and `mergeDocumentOvers`. These encode property edits, retain
|
|
18
|
+
addresses, remove net-undo edits, and merge saved overs. Hosts still supply node
|
|
19
|
+
reference encoding, live-target checks, asset assembly, and component serialization.
|
|
20
|
+
Non-finite numbers and unsupported values produce a writer issue instead of being
|
|
21
|
+
silently changed by JSON serialization.
|
|
22
|
+
|
|
23
|
+
`DocumentAssetAssembly` registers deduplicated asset sources and shared material
|
|
24
|
+
or HDR entities in the writer's asset/reference tables. Material assignments reuse
|
|
25
|
+
saved entity identities by source and material ID, even when asset keys change.
|
|
26
|
+
Hosts supply the captured entity IDs and resolve asset ownership; the assembly
|
|
27
|
+
contains no engine or editor dependencies.
|
|
28
|
+
|
|
29
|
+
The evaluator accepts a `DocumentRuntime` host. It owns asset caching, reference
|
|
30
|
+
placement, derived glTF IDs, target resolution, defs, property and parent overs,
|
|
31
|
+
entity sharing, and issue reporting. The host supplies loading, object creation,
|
|
32
|
+
cloning, and lifecycle scheduling. Needle Engine provides its own adapter;
|
|
33
|
+
plain three.js can use the same evaluator:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Color, Object3D, Scene } from "three";
|
|
37
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
38
|
+
import { evaluateProjectDocument, type DocumentRuntime } from "@needle-tools/addressing/document";
|
|
39
|
+
|
|
40
|
+
const scene = new Scene();
|
|
41
|
+
const runtime: DocumentRuntime = {
|
|
42
|
+
scene,
|
|
43
|
+
renderer, // your existing renderer, for $renderer opinions
|
|
44
|
+
createNode: () => new Object3D(),
|
|
45
|
+
createColor: value => new Color().fromArray(value),
|
|
46
|
+
parse: (data, src) => new GLTFLoader().parseAsync(data, new URL(".", new URL(src, documentUrl)).href),
|
|
47
|
+
instantiate: node => (node as Object3D).clone(),
|
|
48
|
+
// Plain objects have no component lifecycle or template registrations.
|
|
49
|
+
parkTemplate() {},
|
|
50
|
+
activateInstance() {},
|
|
51
|
+
disposeTemplate() {},
|
|
52
|
+
schedule: work => work(),
|
|
53
|
+
loadEnvironment, // your existing HDR/PMREM loader: (url, options) => Promise<Texture | null>
|
|
54
|
+
};
|
|
55
|
+
const result = await evaluateProjectDocument(runtime, document, { baseUrl: documentUrl });
|
|
56
|
+
await result.completed;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Supply `createDefContext` and the component access hooks if the host supports
|
|
60
|
+
components. Without them, component defs are reported as unsupported; node defs
|
|
61
|
+
and ordinary three.js properties still work. A skeletal or component-aware host
|
|
62
|
+
must supply its matching clone implementation. The default addressing entry point
|
|
63
|
+
does not import the document evaluator or any three.js runtime.
|
|
64
|
+
|
|
9
65
|
## Ids
|
|
10
66
|
|
|
11
67
|
Every glTF root-collection element (nodes, scenes, animations, cameras, materials,
|
|
@@ -34,6 +90,12 @@ An address captures every fallback at creation time, while the target is known
|
|
|
34
90
|
1. `id` — exact match (`extras` in glTF JSON, `userData` in live scene graphs)
|
|
35
91
|
2. `anchor` — survives asset updates
|
|
36
92
|
3. `path` — best-effort structural match
|
|
93
|
+
|
|
94
|
+
Node paths qualify same-named siblings with `~2n` (zero-based occurrence):
|
|
95
|
+
`Cube~21` names the second `Cube`. Literal tildes remain escaped as `~0`.
|
|
96
|
+
Unqualified ambiguous paths fail to resolve rather than picking the first child.
|
|
97
|
+
These paths assume the original sibling order; use authored anchors or persistent
|
|
98
|
+
IDs when the source scene changes.
|
|
37
99
|
4. **reconciliation** — never automatic: `reconcileInGraph` returns ranked candidates
|
|
38
100
|
with confidence + reasons; the caller decides thresholds and whether to re-anchor.
|
|
39
101
|
|
package/dist/address.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type NeedleAddress = {
|
|
|
23
23
|
* {@link splitPath}. Empty SEGMENTS are load-bearing (positional walk over
|
|
24
24
|
* unnamed nodes), so `""` is a valid one-segment path — test `!== undefined`,
|
|
25
25
|
* never truthiness. ABSENT means "no path rung".
|
|
26
|
+
* A `~2n` suffix selects the zero-based occurrence among same-named siblings.
|
|
26
27
|
*/
|
|
27
28
|
path?: string;
|
|
28
29
|
};
|
|
@@ -44,6 +45,10 @@ export type GraphNode = {
|
|
|
44
45
|
export declare function createGltfElementAddress(json: GltfJson, type: StampedElementType | string, index: number): NeedleAddress | null;
|
|
45
46
|
/** Build an address for a live scene-graph object under `root`. */
|
|
46
47
|
export declare function createObjectAddress(root: GraphNode, target: GraphNode): NeedleAddress | null;
|
|
48
|
+
export declare function splitNodePath(path: string): Array<{
|
|
49
|
+
name: string;
|
|
50
|
+
occurrence?: number;
|
|
51
|
+
}>;
|
|
47
52
|
export declare function collectionKeyForType(type: string): string;
|
|
48
53
|
/** path from root (exclusive) to target (inclusive), or null if target is not under root */
|
|
49
54
|
export declare function findPath(root: GraphNode, target: GraphNode): GraphNode[] | null;
|
package/dist/address.js
CHANGED
|
@@ -45,7 +45,7 @@ export function createGltfElementAddress(json, type, index) {
|
|
|
45
45
|
if (type === "node") {
|
|
46
46
|
const segments = gltfNodeNamePath(json, index);
|
|
47
47
|
if (segments)
|
|
48
|
-
address.path =
|
|
48
|
+
address.path = segments.join("/");
|
|
49
49
|
}
|
|
50
50
|
return address;
|
|
51
51
|
}
|
|
@@ -64,9 +64,28 @@ export function createObjectAddress(root, target) {
|
|
|
64
64
|
// empty name dropped — see createGltfElementAddress; path segments stay verbatim
|
|
65
65
|
if (target.name)
|
|
66
66
|
address.name = target.name;
|
|
67
|
-
|
|
67
|
+
let parent = root;
|
|
68
|
+
address.path = nodes.map(node => {
|
|
69
|
+
const siblings = parent.children ?? [];
|
|
70
|
+
parent = node;
|
|
71
|
+
return nodePathSegment(node.name ?? "", siblings, node);
|
|
72
|
+
}).join("/");
|
|
68
73
|
return address;
|
|
69
74
|
}
|
|
75
|
+
/** `~2n` selects a same-named sibling; literal tildes were already escaped as `~0`. */
|
|
76
|
+
function nodePathSegment(name, siblings, node) {
|
|
77
|
+
const matches = siblings.filter(sibling => (sibling.name ?? "") === name);
|
|
78
|
+
const escaped = encodePathSegment(name);
|
|
79
|
+
return matches.length > 1 ? `${escaped}~2${matches.indexOf(node)}` : escaped;
|
|
80
|
+
}
|
|
81
|
+
export function splitNodePath(path) {
|
|
82
|
+
return path.split("/").map(segment => {
|
|
83
|
+
const qualifier = /~2(0|[1-9][0-9]*)$/.exec(segment);
|
|
84
|
+
return qualifier
|
|
85
|
+
? { name: decodePathSegment(segment.slice(0, qualifier.index)), occurrence: Number(qualifier[1]) }
|
|
86
|
+
: { name: decodePathSegment(segment) };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
70
89
|
export function collectionKeyForType(type) {
|
|
71
90
|
// glTF collection keys are the pluralized type names; only "mesh" is irregular
|
|
72
91
|
return type === "mesh" ? "meshes" : `${type}s`;
|
|
@@ -90,7 +109,10 @@ function gltfNodeNamePath(json, nodeIndex) {
|
|
|
90
109
|
if (seen.has(current))
|
|
91
110
|
return null; // malformed (cyclic) input
|
|
92
111
|
seen.add(current);
|
|
93
|
-
|
|
112
|
+
const parent = parents.get(current);
|
|
113
|
+
const siblingIndices = parent !== undefined ? nodes[parent]?.children ?? []
|
|
114
|
+
: [...new Set((Array.isArray(json.scenes) ? json.scenes : []).flatMap(scene => scene.nodes ?? []))];
|
|
115
|
+
path.unshift(nodePathSegment(typeof nodes[current]?.name === "string" ? nodes[current].name : "", siblingIndices.map(i => nodes[i]), nodes[current]));
|
|
94
116
|
current = parents.get(current);
|
|
95
117
|
}
|
|
96
118
|
return path;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ProjectDocument, ProjectDocumentAsset, ProjectDocumentReference } from "./types.js";
|
|
2
|
+
/** Asset keys and shared resource identities for one document assembly. */
|
|
3
|
+
export declare class DocumentAssetAssembly {
|
|
4
|
+
private readonly assets;
|
|
5
|
+
private readonly references;
|
|
6
|
+
private readonly assetBySrc;
|
|
7
|
+
private readonly referenceById;
|
|
8
|
+
private readonly materialBySource;
|
|
9
|
+
private readonly baseMaterialBySource;
|
|
10
|
+
constructor(assets: Record<string, ProjectDocumentAsset>, references: ProjectDocumentReference[], base?: ProjectDocument | null);
|
|
11
|
+
addAsset(input: {
|
|
12
|
+
src: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
kind: string;
|
|
15
|
+
identity?: {
|
|
16
|
+
id: string;
|
|
17
|
+
provider?: string;
|
|
18
|
+
};
|
|
19
|
+
}): string;
|
|
20
|
+
materialEntity(asset: string, material: {
|
|
21
|
+
id: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
}, entityId: string): string;
|
|
24
|
+
environmentEntity(asset: string, entityId: string): string;
|
|
25
|
+
private addReference;
|
|
26
|
+
private materialKey;
|
|
27
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Asset keys and shared resource identities for one document assembly. */
|
|
2
|
+
export class DocumentAssetAssembly {
|
|
3
|
+
constructor(assets, references, base) {
|
|
4
|
+
this.assets = assets;
|
|
5
|
+
this.references = references;
|
|
6
|
+
this.assetBySrc = new Map();
|
|
7
|
+
this.referenceById = new Map();
|
|
8
|
+
this.materialBySource = new Map();
|
|
9
|
+
this.baseMaterialBySource = new Map();
|
|
10
|
+
for (const [key, asset] of Object.entries(assets))
|
|
11
|
+
this.assetBySrc.set(asset.src, key);
|
|
12
|
+
for (const reference of references) {
|
|
13
|
+
this.referenceById.set(reference.id, reference);
|
|
14
|
+
const key = this.materialKey(reference, assets);
|
|
15
|
+
if (key)
|
|
16
|
+
this.materialBySource.set(key, reference);
|
|
17
|
+
}
|
|
18
|
+
for (const reference of base?.references ?? []) {
|
|
19
|
+
const key = this.materialKey(reference, base?.assets ?? {});
|
|
20
|
+
if (key)
|
|
21
|
+
this.baseMaterialBySource.set(key, reference);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
addAsset(input) {
|
|
25
|
+
const existing = this.assetBySrc.get(input.src);
|
|
26
|
+
if (existing !== undefined)
|
|
27
|
+
return existing;
|
|
28
|
+
const filename = input.src.split("?")[0].split("/").pop() || "material";
|
|
29
|
+
const dot = filename.lastIndexOf(".");
|
|
30
|
+
const stem = input.name || (dot > 0 ? filename.substring(0, dot) : filename);
|
|
31
|
+
let key = stem;
|
|
32
|
+
let n = 2;
|
|
33
|
+
while (key in this.assets)
|
|
34
|
+
key = `${stem}-${n++}`;
|
|
35
|
+
this.assets[key] = { src: input.src, kind: input.kind, ...(input.identity ? { asset: { ...input.identity } } : {}) };
|
|
36
|
+
this.assetBySrc.set(input.src, key);
|
|
37
|
+
return key;
|
|
38
|
+
}
|
|
39
|
+
materialEntity(asset, material, entityId) {
|
|
40
|
+
if (!this.assets[asset])
|
|
41
|
+
throw new Error(`Unknown document asset: ${asset}`);
|
|
42
|
+
const key = JSON.stringify([this.assets[asset].src, material.id]);
|
|
43
|
+
const existing = this.materialBySource.get(key);
|
|
44
|
+
if (existing)
|
|
45
|
+
return existing.id;
|
|
46
|
+
const base = this.baseMaterialBySource.get(key);
|
|
47
|
+
const reference = base ? { ...base, asset } : {
|
|
48
|
+
id: entityId, asset, address: { type: "material", id: material.id, ...(material.name ? { name: material.name } : {}) },
|
|
49
|
+
};
|
|
50
|
+
this.addReference(reference);
|
|
51
|
+
this.materialBySource.set(key, reference);
|
|
52
|
+
return reference.id;
|
|
53
|
+
}
|
|
54
|
+
environmentEntity(asset, entityId) {
|
|
55
|
+
if (!this.assets[asset])
|
|
56
|
+
throw new Error(`Unknown document asset: ${asset}`);
|
|
57
|
+
this.addReference({ id: entityId, asset, address: { type: "hdr" } });
|
|
58
|
+
return entityId;
|
|
59
|
+
}
|
|
60
|
+
addReference(reference) {
|
|
61
|
+
const existing = this.referenceById.get(reference.id);
|
|
62
|
+
if (existing) {
|
|
63
|
+
if (existing.asset !== reference.asset || existing.address?.type !== reference.address?.type || existing.address?.id !== reference.address?.id) {
|
|
64
|
+
throw new Error(`Document reference id '${reference.id}' already identifies another resource`);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.references.push(reference);
|
|
69
|
+
this.referenceById.set(reference.id, reference);
|
|
70
|
+
}
|
|
71
|
+
materialKey(reference, assets) {
|
|
72
|
+
return reference.address?.type === "material" && reference.address.id && assets[reference.asset]
|
|
73
|
+
? JSON.stringify([assets[reference.asset].src, reference.address.id]) : null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface DocumentCollectionSelectorOptions {
|
|
2
|
+
matchesComponentType?(component: object, name: string): boolean;
|
|
3
|
+
matchesComponentGuid?(component: object, guid: string): boolean;
|
|
4
|
+
deriveComponentGuid?(referenceId: string, fileGuid: string): string;
|
|
5
|
+
referenceId?: string;
|
|
6
|
+
report?(message: string): void;
|
|
7
|
+
}
|
|
8
|
+
/** Resolve an index or field qualifier; ambiguous identities never select an arbitrary member. */
|
|
9
|
+
export declare function resolveDocumentCollectionElement(collection: readonly unknown[], selector: string, options?: DocumentCollectionSelectorOptions): unknown;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Resolve an index or field qualifier; ambiguous identities never select an arbitrary member. */
|
|
2
|
+
export function resolveDocumentCollectionElement(collection, selector, options = {}) {
|
|
3
|
+
if (/^\d+$/.test(selector))
|
|
4
|
+
return collection[Number(selector)];
|
|
5
|
+
const qualifier = /^([A-Za-z_][A-Za-z0-9_-]*):([\s\S]*)$/.exec(selector);
|
|
6
|
+
if (!qualifier) {
|
|
7
|
+
options.report?.(`"${selector}" is neither an index nor a <field>:<value> qualifier`);
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const [, field, value] = qualifier;
|
|
11
|
+
const elements = collection.filter((element) => element !== null && typeof element === "object");
|
|
12
|
+
let matches = elements.filter(element => {
|
|
13
|
+
if (field === "type")
|
|
14
|
+
return options.matchesComponentType?.(element, value) ?? element.constructor?.name === value;
|
|
15
|
+
if (field === "guid")
|
|
16
|
+
return options.matchesComponentGuid?.(element, value)
|
|
17
|
+
?? (element.guid === value || element.needle_editor_guid === value);
|
|
18
|
+
return element[field] === value;
|
|
19
|
+
});
|
|
20
|
+
if (!matches.length && field === "guid" && options.referenceId && options.deriveComponentGuid) {
|
|
21
|
+
const derived = options.deriveComponentGuid(options.referenceId, value);
|
|
22
|
+
matches = elements.filter(element => element.guid === derived);
|
|
23
|
+
}
|
|
24
|
+
if (matches.length > 1) {
|
|
25
|
+
options.report?.(`"${selector}" matched ${matches.length} elements ? the document must name one unambiguously`);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return matches[0];
|
|
29
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type NeedleAddress } from "../index.js";
|
|
2
|
+
import { type ProjectDocument, type ProjectDocumentTarget, type ProjectEvaluationIssue, type ProjectEvaluationResult, type EvaluateProjectDocumentOptions } from "./types.js";
|
|
3
|
+
import type { DocumentNode as Object3D, DocumentModel as Model, DocumentRuntime as Context, DocumentDefContext as SerializationContext } from "./runtime.js";
|
|
4
|
+
/** Shared asset/entity preparation; does not place nodes into the host scene. */
|
|
5
|
+
export declare function createDocumentAssetLoader(context: Pick<Context, "parse" | "parkTemplate" | "loadEnvironment">, document: ProjectDocument, options: EvaluateProjectDocumentOptions | undefined, issue: (arc: ProjectEvaluationIssue["arc"], message: string) => void): {
|
|
6
|
+
loadParsedAsset: (assetId: string, arc?: ProjectEvaluationIssue["arc"]) => Promise<Model | null>;
|
|
7
|
+
entities: Map<string, Promise<object | null>>;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate a project document into the given context. One-shot; never throws for
|
|
11
|
+
* content issues (they are collected in the result), only for a non-document input.
|
|
12
|
+
*
|
|
13
|
+
* Order (plans/project-format-v0.md): load + place references (document order) →
|
|
14
|
+
* mounts → [pre-setup phase:] asset component deserialization (the engine's own
|
|
15
|
+
* queue, FIFO) → defs → overs. Resolution is scoped to the target reference's
|
|
16
|
+
* subtree; misses are reported and skipped (exact resolution only in v0).
|
|
17
|
+
*/
|
|
18
|
+
export declare function evaluateProjectDocument(context: Context, document: ProjectDocument, options?: EvaluateProjectDocumentOptions): Promise<ProjectEvaluationResult>;
|
|
19
|
+
export declare function splitPath(path: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The serialization context ONE def application (or a whole document's worth)
|
|
22
|
+
* deserializes through: rooted at the COMPOSITION root so `{guid}` references in
|
|
23
|
+
* def data resolve across the whole composed result (cross-reference wiring).
|
|
24
|
+
* `baseUrl`: URL-string component fields (e.g. a tracked image's remote object) go
|
|
25
|
+
* through the EXISTING AssetReference.getOrCreate deserialization — it needs a
|
|
26
|
+
* source id to resolve document-relative urls against (no new loading path).
|
|
27
|
+
*/
|
|
28
|
+
export declare function createDefSerializationContext(context: Context, root: Object3D, baseUrl?: string): SerializationContext;
|
|
29
|
+
/**
|
|
30
|
+
* Apply ONE def to the live composition — create/adopt the node, then
|
|
31
|
+
* instantiate/adopt its components. Exported as the single-arc primitive:
|
|
32
|
+
* document evaluation loops over it, and the op-bridge APPLICATOR applies a
|
|
33
|
+
* creation op's carried def through exactly this function (one implementation,
|
|
34
|
+
* plans/operation-bridge.md fixture 2).
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyDef(def: NonNullable<ProjectDocument["defs"]>[number], root: Object3D, references: Map<string, Object3D>, issue: (arc: "def", message: string) => void, serializationContext: SerializationContext): void;
|
|
37
|
+
/**
|
|
38
|
+
* The shared state a sequence of over applications rides — constructed once per
|
|
39
|
+
* document evaluation AND once per op-bridge apply session (the applicator,
|
|
40
|
+
* plans/operation-bridge.md fixture 2):
|
|
41
|
+
* - async entity chaining: entity-targeted overs and entity-ref values await the
|
|
42
|
+
* entity's instantiation; overs touching the same entity keep their order among
|
|
43
|
+
* themselves by chaining on the entity's tail promise. Non-async overs apply
|
|
44
|
+
* synchronously (pre-awake); `settled()` awaits the async remainder.
|
|
45
|
+
* - deactivated subtrees ("active": false → removeFromParent), per scope ref:
|
|
46
|
+
* materials are RESOURCES, not graph nodes — a material used only by a
|
|
47
|
+
* deactivated mesh must stay addressable (doc order legally deactivates first
|
|
48
|
+
* and patches after), so material resolution searches these detached roots too.
|
|
49
|
+
*/
|
|
50
|
+
export type OverApplyState = {
|
|
51
|
+
/** Bind original-scene addresses before any rename, removal or reparent changes their paths. */
|
|
52
|
+
sceneTargets?: ReadonlyMap<ProjectDocumentTarget, Object3D | null>;
|
|
53
|
+
readonly entities: Map<string, Promise<object | null>>;
|
|
54
|
+
readonly deactivatedRoots: Map<string | undefined, Object3D[]>;
|
|
55
|
+
chainOnEntity(refId: string, work: (entity: object | null) => void): void;
|
|
56
|
+
settled(): Promise<void>;
|
|
57
|
+
};
|
|
58
|
+
export declare function createOverApplyState(entities: Map<string, Promise<object | null>>): OverApplyState;
|
|
59
|
+
/**
|
|
60
|
+
* Apply ONE over to the live composition — the single-arc primitive: document
|
|
61
|
+
* evaluation loops over it, and the op-bridge APPLICATOR applies a remote
|
|
62
|
+
* property/assign/deactivation op through exactly this function (one
|
|
63
|
+
* implementation, plans/operation-bridge.md fixture 2).
|
|
64
|
+
*/
|
|
65
|
+
export declare function applyOver(context: Context, over: NonNullable<ProjectDocument["overs"]>[number], root: Object3D, references: Map<string, Object3D>, state: OverApplyState, issue: (arc: "over", message: string) => void): void;
|
|
66
|
+
/**
|
|
67
|
+
* Material entity resolution incl. MATERIAL-ONLY glbs (a promoted material
|
|
68
|
+
* library entry: materials but no scene/meshes). The loader never
|
|
69
|
+
* instantiates an unreferenced material, so a scene traversal finds nothing —
|
|
70
|
+
* pull the materials from the parser instead. Exported for tests.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveMaterialEntity(model: Model, address: NeedleAddress): Promise<object | null>;
|