@loaders.gl/scene 5.0.0-alpha.1

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.
Files changed (42) hide show
  1. package/LICENSE +41 -0
  2. package/README.md +23 -0
  3. package/dist/dist.dev.js +831 -0
  4. package/dist/dist.min.js +14 -0
  5. package/dist/index.cjs +65 -0
  6. package/dist/index.cjs.map +7 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +5 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/lib/parse-usd.d.ts +5 -0
  12. package/dist/lib/parse-usd.d.ts.map +1 -0
  13. package/dist/lib/parse-usd.js +276 -0
  14. package/dist/lib/parse-usd.js.map +1 -0
  15. package/dist/lib/parse-usda.d.ts +4 -0
  16. package/dist/lib/parse-usda.d.ts.map +1 -0
  17. package/dist/lib/parse-usda.js +395 -0
  18. package/dist/lib/parse-usda.js.map +1 -0
  19. package/dist/lib/parse-usdz.d.ts +3 -0
  20. package/dist/lib/parse-usdz.d.ts.map +1 -0
  21. package/dist/lib/parse-usdz.js +50 -0
  22. package/dist/lib/parse-usdz.js.map +1 -0
  23. package/dist/lib/usd-types.d.ts +86 -0
  24. package/dist/lib/usd-types.d.ts.map +1 -0
  25. package/dist/lib/usd-types.js +5 -0
  26. package/dist/lib/usd-types.js.map +1 -0
  27. package/dist/usd-loader-types.d.ts +50 -0
  28. package/dist/usd-loader-types.d.ts.map +1 -0
  29. package/dist/usd-loader-types.js +35 -0
  30. package/dist/usd-loader-types.js.map +1 -0
  31. package/dist/usd-loader.d.ts +30 -0
  32. package/dist/usd-loader.d.ts.map +1 -0
  33. package/dist/usd-loader.js +17 -0
  34. package/dist/usd-loader.js.map +1 -0
  35. package/package.json +55 -0
  36. package/src/index.ts +15 -0
  37. package/src/lib/parse-usd.ts +349 -0
  38. package/src/lib/parse-usda.ts +458 -0
  39. package/src/lib/parse-usdz.ts +59 -0
  40. package/src/lib/usd-types.ts +103 -0
  41. package/src/usd-loader-types.ts +40 -0
  42. package/src/usd-loader.ts +26 -0
@@ -0,0 +1,458 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {
6
+ USDAssetPath,
7
+ USDAttribute,
8
+ USDPrim,
9
+ USDStage,
10
+ USDValue,
11
+ USDVariant
12
+ } from './usd-types';
13
+
14
+ type USDToken = {
15
+ /** Decoded token value. */
16
+ value: string;
17
+ /** One-based source line. */
18
+ line: number;
19
+ /** Lexical token category. */
20
+ kind: 'word' | 'number' | 'string' | 'asset' | 'path' | 'punctuation' | 'end';
21
+ };
22
+
23
+ type USDPrimContents = Pick<USDPrim, 'attributes' | 'metadata' | 'children' | 'variants'>;
24
+
25
+ const NUMBER_PATTERN = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/;
26
+ const QUALIFIERS = new Set([
27
+ 'uniform',
28
+ 'varying',
29
+ 'custom',
30
+ 'prepend',
31
+ 'append',
32
+ 'add',
33
+ 'delete',
34
+ 'reorder'
35
+ ]);
36
+
37
+ /** Parses an OpenUSD ASCII layer. */
38
+ export function parseUSDA(source: string, url?: string): USDStage {
39
+ if (!source.trimStart().startsWith('#usda')) {
40
+ throw new Error('OpenUSD ASCII layers must begin with the #usda header.');
41
+ }
42
+
43
+ return new USDAParser(source, url).parse();
44
+ }
45
+
46
+ /** Recursive-descent parser for the supported USDA syntax. */
47
+ class USDAParser {
48
+ /** Token stream for the source layer. */
49
+ private readonly tokenizer: USDTokenizer;
50
+ /** URL of the source layer. */
51
+ private readonly url: string | undefined;
52
+
53
+ /** Creates a USDA parser. */
54
+ constructor(source: string, url?: string) {
55
+ this.tokenizer = new USDTokenizer(source);
56
+ this.url = url;
57
+ }
58
+
59
+ /** Parses the complete USDA layer. */
60
+ parse(): USDStage {
61
+ const metadata = this.tokenizer.match('(') ? this.parseMetadata(')') : {};
62
+ const rootPrims: USDPrim[] = [];
63
+
64
+ while (!this.tokenizer.isAtEnd()) {
65
+ if (this.isPrimDeclaration()) {
66
+ rootPrims.push(this.parsePrim(''));
67
+ } else {
68
+ this.tokenizer.read();
69
+ }
70
+ }
71
+
72
+ return {
73
+ format: 'usda',
74
+ url: this.url,
75
+ metadata,
76
+ rootPrims,
77
+ layers: this.url ? [this.url] : []
78
+ };
79
+ }
80
+
81
+ /** Parses a prim and its descendants. */
82
+ private parsePrim(parentPath: string): USDPrim {
83
+ const declaration = this.tokenizer.read();
84
+ const specifier = declaration.value as USDPrim['specifier'];
85
+ const firstToken = this.tokenizer.read();
86
+ const hasExplicitType = this.tokenizer.peek().kind === 'string';
87
+ const type = hasExplicitType ? firstToken.value : '';
88
+ const name = hasExplicitType ? this.tokenizer.read().value : firstToken.value;
89
+ const path = `${parentPath}/${name}`;
90
+ const metadata = this.tokenizer.match('(') ? this.parseMetadata(')') : {};
91
+
92
+ this.tokenizer.expect('{');
93
+ const contents = this.parsePrimContents(path);
94
+
95
+ return {
96
+ name,
97
+ path,
98
+ sourceUrl: this.url,
99
+ type,
100
+ specifier,
101
+ attributes: contents.attributes,
102
+ metadata: {...metadata, ...contents.metadata},
103
+ variants: contents.variants,
104
+ children: contents.children
105
+ };
106
+ }
107
+
108
+ /** Parses the attributes, variants, and child prims inside a prim body. */
109
+ private parsePrimContents(parentPath: string): USDPrimContents {
110
+ const attributes: Record<string, USDAttribute> = {};
111
+ const metadata: Record<string, USDValue> = {};
112
+ const children: USDPrim[] = [];
113
+ const variants: Record<string, Record<string, USDVariant>> = {};
114
+
115
+ while (!this.tokenizer.isAtEnd() && !this.tokenizer.match('}')) {
116
+ if (this.isPrimDeclaration()) {
117
+ children.push(this.parsePrim(parentPath));
118
+ } else if (this.tokenizer.peek().value === 'variantSet') {
119
+ this.parseVariantSet(parentPath, variants);
120
+ } else {
121
+ const attribute = this.parseAttribute();
122
+ if (attribute) {
123
+ attributes[attribute.name] = attribute;
124
+ }
125
+ }
126
+ }
127
+
128
+ return {attributes, metadata, children, variants};
129
+ }
130
+
131
+ /** Parses one named variant set. */
132
+ private parseVariantSet(
133
+ parentPath: string,
134
+ variants: Record<string, Record<string, USDVariant>>
135
+ ): void {
136
+ this.tokenizer.expect('variantSet');
137
+ const variantSetName = this.tokenizer.read().value;
138
+ this.tokenizer.expect('=');
139
+ this.tokenizer.expect('{');
140
+ const variantSet: Record<string, USDVariant> = {};
141
+
142
+ while (!this.tokenizer.isAtEnd() && !this.tokenizer.match('}')) {
143
+ const variantName = this.tokenizer.read().value;
144
+ this.tokenizer.expect('{');
145
+ const contents = this.parsePrimContents(parentPath);
146
+ variantSet[variantName] = {
147
+ attributes: contents.attributes,
148
+ metadata: contents.metadata,
149
+ children: contents.children
150
+ };
151
+ }
152
+
153
+ variants[variantSetName] = variantSet;
154
+ }
155
+
156
+ /** Parses an attribute declaration at the current token. */
157
+ private parseAttribute(): USDAttribute | null {
158
+ const firstToken = this.tokenizer.read();
159
+ if (firstToken.kind === 'end') {
160
+ return null;
161
+ }
162
+
163
+ const declarationTokens = [firstToken];
164
+ while (!this.tokenizer.isAtEnd() && this.tokenizer.peek().line === firstToken.line) {
165
+ const nextToken = this.tokenizer.peek();
166
+ if (nextToken.value === '=') {
167
+ this.tokenizer.read();
168
+ break;
169
+ }
170
+ if (nextToken.value === '{' || nextToken.value === '}') {
171
+ return null;
172
+ }
173
+ declarationTokens.push(this.tokenizer.read());
174
+ }
175
+
176
+ const hasAssignment = this.tokenizer.previousValue === '=';
177
+ if (!hasAssignment) {
178
+ return null;
179
+ }
180
+
181
+ const significantTokens = declarationTokens.filter(token => !QUALIFIERS.has(token.value));
182
+ if (significantTokens.length === 0) {
183
+ return null;
184
+ }
185
+
186
+ const name = significantTokens[significantTokens.length - 1].value;
187
+ const type = significantTokens
188
+ .slice(0, -1)
189
+ .map(token => token.value)
190
+ .join('');
191
+ const value = this.parseValue();
192
+ const metadata = this.tokenizer.match('(') ? this.parseMetadata(')') : {};
193
+
194
+ return {name, type, value, metadata};
195
+ }
196
+
197
+ /** Parses a metadata dictionary through the requested terminator. */
198
+ private parseMetadata(terminator: ')' | '}'): Record<string, USDValue> {
199
+ const metadata: Record<string, USDValue> = {};
200
+
201
+ while (!this.tokenizer.isAtEnd() && !this.tokenizer.match(terminator)) {
202
+ this.tokenizer.match(',');
203
+ if (this.tokenizer.peek().value === terminator) {
204
+ this.tokenizer.read();
205
+ break;
206
+ }
207
+
208
+ const firstToken = this.tokenizer.read();
209
+ const declarationTokens = [firstToken];
210
+ while (!this.tokenizer.isAtEnd() && this.tokenizer.peek().line === firstToken.line) {
211
+ if (this.tokenizer.peek().value === '=' || this.tokenizer.peek().value === ':') {
212
+ this.tokenizer.read();
213
+ break;
214
+ }
215
+ if (this.tokenizer.peek().value === terminator) {
216
+ break;
217
+ }
218
+ declarationTokens.push(this.tokenizer.read());
219
+ }
220
+
221
+ if (this.tokenizer.previousValue !== '=' && this.tokenizer.previousValue !== ':') {
222
+ continue;
223
+ }
224
+
225
+ const name = declarationTokens[declarationTokens.length - 1].value;
226
+ metadata[name] = this.parseValue();
227
+ this.tokenizer.match(',');
228
+ }
229
+
230
+ return metadata;
231
+ }
232
+
233
+ /** Parses one scalar, list, tuple, dictionary, asset path, or scene path value. */
234
+ private parseValue(): USDValue {
235
+ const token = this.tokenizer.read();
236
+
237
+ if (token.value === '[') {
238
+ return this.parseList(']');
239
+ }
240
+ if (token.value === '(') {
241
+ return this.parseList(')');
242
+ }
243
+ if (token.value === '{') {
244
+ return this.parseMetadata('}');
245
+ }
246
+ if (token.kind === 'asset') {
247
+ const reference: USDAssetPath = {assetPath: token.value};
248
+ if (this.tokenizer.peek().kind === 'path' && this.tokenizer.peek().line === token.line) {
249
+ reference.primPath = this.tokenizer.read().value;
250
+ }
251
+ return reference;
252
+ }
253
+ if (token.kind === 'path') {
254
+ return {path: token.value};
255
+ }
256
+ if (token.kind === 'number') {
257
+ return Number(token.value);
258
+ }
259
+ if (token.value === 'true') {
260
+ return true;
261
+ }
262
+ if (token.value === 'false') {
263
+ return false;
264
+ }
265
+ if (token.value === 'None' || token.value === 'null') {
266
+ return null;
267
+ }
268
+
269
+ return token.value;
270
+ }
271
+
272
+ /** Parses a comma-separated list or tuple. */
273
+ private parseList(terminator: ']' | ')'): USDValue[] {
274
+ const values: USDValue[] = [];
275
+ while (!this.tokenizer.isAtEnd() && !this.tokenizer.match(terminator)) {
276
+ if (this.tokenizer.match(',')) {
277
+ continue;
278
+ }
279
+ values.push(this.parseValue());
280
+ this.tokenizer.match(',');
281
+ }
282
+ return values;
283
+ }
284
+
285
+ /** Returns whether the current token starts a prim declaration. */
286
+ private isPrimDeclaration(): boolean {
287
+ const value = this.tokenizer.peek().value;
288
+ return value === 'def' || value === 'over' || value === 'class';
289
+ }
290
+ }
291
+
292
+ /** Tokenizer for the supported USDA lexical syntax. */
293
+ class USDTokenizer {
294
+ /** Complete source text. */
295
+ private readonly source: string;
296
+ /** Current character offset. */
297
+ private offset = 0;
298
+ /** Current one-based line number. */
299
+ private line = 1;
300
+ /** Lookahead token, when one has been read. */
301
+ private bufferedToken: USDToken | null = null;
302
+ /** Value of the most recently consumed token. */
303
+ previousValue = '';
304
+
305
+ /** Creates a tokenizer for USDA source text. */
306
+ constructor(source: string) {
307
+ this.source = source;
308
+ }
309
+
310
+ /** Returns the next token without consuming it. */
311
+ peek(): USDToken {
312
+ this.bufferedToken ||= this.readToken();
313
+ return this.bufferedToken;
314
+ }
315
+
316
+ /** Consumes and returns the next token. */
317
+ read(): USDToken {
318
+ const token = this.peek();
319
+ this.bufferedToken = null;
320
+ this.previousValue = token.value;
321
+ return token;
322
+ }
323
+
324
+ /** Consumes a token when its value matches. */
325
+ match(value: string): boolean {
326
+ if (this.peek().value !== value) {
327
+ return false;
328
+ }
329
+ this.read();
330
+ return true;
331
+ }
332
+
333
+ /** Consumes an expected token or throws a line-numbered parse error. */
334
+ expect(value: string): void {
335
+ const token = this.read();
336
+ if (token.value !== value) {
337
+ throw new Error(`Expected "${value}" at USDA line ${token.line}, received "${token.value}".`);
338
+ }
339
+ }
340
+
341
+ /** Returns whether the token stream is exhausted. */
342
+ isAtEnd(): boolean {
343
+ return this.peek().kind === 'end';
344
+ }
345
+
346
+ /** Reads the next token from the current character offset. */
347
+ private readToken(): USDToken {
348
+ this.skipIgnoredText();
349
+ const line = this.line;
350
+ const character = this.source[this.offset];
351
+
352
+ if (character === undefined) {
353
+ return {value: '', line, kind: 'end'};
354
+ }
355
+ if ('{}[](),='.includes(character)) {
356
+ this.offset++;
357
+ return {value: character, line, kind: 'punctuation'};
358
+ }
359
+ if (character === '"' || character === "'") {
360
+ return {value: this.readQuotedString(character), line, kind: 'string'};
361
+ }
362
+ if (character === '@') {
363
+ return {value: this.readDelimitedValue('@'), line, kind: 'asset'};
364
+ }
365
+ if (character === '<') {
366
+ return {value: this.readDelimitedValue('>'), line, kind: 'path'};
367
+ }
368
+
369
+ const numericMatch = this.source.slice(this.offset).match(NUMBER_PATTERN);
370
+ if (numericMatch) {
371
+ this.offset += numericMatch[0].length;
372
+ return {value: numericMatch[0], line, kind: 'number'};
373
+ }
374
+
375
+ const startOffset = this.offset;
376
+ while (this.offset < this.source.length) {
377
+ const nextCharacter = this.source[this.offset];
378
+ if (/\s/.test(nextCharacter) || '{}[](),=@<>'.includes(nextCharacter)) {
379
+ break;
380
+ }
381
+ this.offset++;
382
+ }
383
+
384
+ if (this.offset === startOffset) {
385
+ this.offset++;
386
+ }
387
+
388
+ return {value: this.source.slice(startOffset, this.offset), line, kind: 'word'};
389
+ }
390
+
391
+ /** Skips whitespace, comments, and triple-quoted documentation blocks. */
392
+ private skipIgnoredText(): void {
393
+ while (this.offset < this.source.length) {
394
+ const character = this.source[this.offset];
395
+ if (character === '\n') {
396
+ this.line++;
397
+ this.offset++;
398
+ } else if (/\s/.test(character)) {
399
+ this.offset++;
400
+ } else if (character === '#') {
401
+ while (this.offset < this.source.length && this.source[this.offset] !== '\n') {
402
+ this.offset++;
403
+ }
404
+ } else if (character === '/' && this.source[this.offset + 1] === '*') {
405
+ this.offset += 2;
406
+ while (this.offset < this.source.length && !this.source.startsWith('*/', this.offset)) {
407
+ if (this.source[this.offset] === '\n') {
408
+ this.line++;
409
+ }
410
+ this.offset++;
411
+ }
412
+ this.offset += 2;
413
+ } else if (this.source.startsWith('"""', this.offset)) {
414
+ this.offset += 3;
415
+ while (this.offset < this.source.length && !this.source.startsWith('"""', this.offset)) {
416
+ if (this.source[this.offset] === '\n') {
417
+ this.line++;
418
+ }
419
+ this.offset++;
420
+ }
421
+ this.offset += 3;
422
+ } else {
423
+ break;
424
+ }
425
+ }
426
+ }
427
+
428
+ /** Reads a quoted string and decodes common escapes. */
429
+ private readQuotedString(delimiter: string): string {
430
+ this.offset++;
431
+ let value = '';
432
+ while (this.offset < this.source.length) {
433
+ const character = this.source[this.offset++];
434
+ if (character === delimiter) {
435
+ break;
436
+ }
437
+ if (character === '\\' && this.offset < this.source.length) {
438
+ const escapedCharacter = this.source[this.offset++];
439
+ value += escapedCharacter === 'n' ? '\n' : escapedCharacter;
440
+ } else {
441
+ value += character;
442
+ }
443
+ }
444
+ return value;
445
+ }
446
+
447
+ /** Reads an asset or scene path through its closing delimiter. */
448
+ private readDelimitedValue(delimiter: '@' | '>'): string {
449
+ this.offset++;
450
+ const startOffset = this.offset;
451
+ while (this.offset < this.source.length && this.source[this.offset] !== delimiter) {
452
+ this.offset++;
453
+ }
454
+ const value = this.source.slice(startOffset, this.offset);
455
+ this.offset++;
456
+ return value;
457
+ }
458
+ }
@@ -0,0 +1,59 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ const END_OF_CENTRAL_DIRECTORY = 0x06054b50;
6
+ const CENTRAL_DIRECTORY_ENTRY = 0x02014b50;
7
+ const LOCAL_FILE_HEADER = 0x04034b50;
8
+
9
+ /** Extracts stored files from an uncompressed USDZ ZIP archive. */
10
+ export function parseUSDZArchive(arrayBuffer: ArrayBuffer): Map<string, ArrayBuffer> {
11
+ const view = new DataView(arrayBuffer);
12
+ const directoryOffset = findCentralDirectory(view);
13
+ const entryCount = view.getUint16(directoryOffset + 10, true);
14
+ let entryOffset = view.getUint32(directoryOffset + 16, true);
15
+ const files = new Map<string, ArrayBuffer>();
16
+ const textDecoder = new TextDecoder();
17
+
18
+ for (let entryIndex = 0; entryIndex < entryCount; entryIndex++) {
19
+ if (view.getUint32(entryOffset, true) !== CENTRAL_DIRECTORY_ENTRY) {
20
+ throw new Error('USDZ archive contains an invalid ZIP central-directory entry.');
21
+ }
22
+
23
+ const compression = view.getUint16(entryOffset + 10, true);
24
+ const compressedLength = view.getUint32(entryOffset + 20, true);
25
+ const filenameLength = view.getUint16(entryOffset + 28, true);
26
+ const extraLength = view.getUint16(entryOffset + 30, true);
27
+ const commentLength = view.getUint16(entryOffset + 32, true);
28
+ const localOffset = view.getUint32(entryOffset + 42, true);
29
+ const filename = textDecoder.decode(
30
+ new Uint8Array(arrayBuffer, entryOffset + 46, filenameLength)
31
+ );
32
+
33
+ if (compression !== 0) {
34
+ throw new Error(`USDZ entry "${filename}" is compressed; USDZ requires stored ZIP entries.`);
35
+ }
36
+ if (view.getUint32(localOffset, true) !== LOCAL_FILE_HEADER) {
37
+ throw new Error(`USDZ entry "${filename}" has an invalid local ZIP header.`);
38
+ }
39
+
40
+ const localNameLength = view.getUint16(localOffset + 26, true);
41
+ const localExtraLength = view.getUint16(localOffset + 28, true);
42
+ const contentOffset = localOffset + 30 + localNameLength + localExtraLength;
43
+ files.set(filename, arrayBuffer.slice(contentOffset, contentOffset + compressedLength));
44
+ entryOffset += 46 + filenameLength + extraLength + commentLength;
45
+ }
46
+
47
+ return files;
48
+ }
49
+
50
+ /** Finds the ZIP end-of-central-directory record. */
51
+ function findCentralDirectory(view: DataView): number {
52
+ const minimumOffset = Math.max(0, view.byteLength - 65557);
53
+ for (let offset = view.byteLength - 22; offset >= minimumOffset; offset--) {
54
+ if (view.getUint32(offset, true) === END_OF_CENTRAL_DIRECTORY) {
55
+ return offset;
56
+ }
57
+ }
58
+ throw new Error('USDZ archive does not contain a ZIP end-of-central-directory record.');
59
+ }
@@ -0,0 +1,103 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {LoaderOptions} from '@loaders.gl/loader-utils';
6
+
7
+ /** Reference to an external OpenUSD layer and optional prim within that layer. */
8
+ export type USDAssetPath = {
9
+ /** URL or relative path to the referenced layer. */
10
+ assetPath: string;
11
+ /** Optional prim path within the referenced layer. */
12
+ primPath?: string;
13
+ };
14
+
15
+ /** Reference to a prim in an OpenUSD scene. */
16
+ export type USDScenePath = {
17
+ /** Absolute or relative scene path. */
18
+ path: string;
19
+ };
20
+
21
+ /** Value types represented by the OpenUSD ASCII parser. */
22
+ export type USDValue =
23
+ | string
24
+ | number
25
+ | boolean
26
+ | null
27
+ | USDAssetPath
28
+ | USDScenePath
29
+ | USDValue[]
30
+ | {[name: string]: USDValue};
31
+
32
+ /** Parsed OpenUSD attribute declaration. */
33
+ export type USDAttribute = {
34
+ /** Attribute name. */
35
+ name: string;
36
+ /** Authored OpenUSD type name. */
37
+ type: string;
38
+ /** Parsed attribute value. */
39
+ value: USDValue;
40
+ /** Attribute metadata. */
41
+ metadata: Record<string, USDValue>;
42
+ };
43
+
44
+ /** Opinions authored by one variant. */
45
+ export type USDVariant = {
46
+ /** Variant attributes. */
47
+ attributes: Record<string, USDAttribute>;
48
+ /** Variant metadata. */
49
+ metadata: Record<string, USDValue>;
50
+ /** Child prims introduced or overridden by the variant. */
51
+ children: USDPrim[];
52
+ };
53
+
54
+ /** Parsed and optionally composed OpenUSD prim. */
55
+ export type USDPrim = {
56
+ /** Prim name. */
57
+ name: string;
58
+ /** Absolute prim path. */
59
+ path: string;
60
+ /** URL of the layer that authored the prim. */
61
+ sourceUrl?: string;
62
+ /** Authored prim type, such as `Xform` or `Mesh`. */
63
+ type: string;
64
+ /** Prim specifier. */
65
+ specifier: 'def' | 'over' | 'class';
66
+ /** Authored attributes keyed by name. */
67
+ attributes: Record<string, USDAttribute>;
68
+ /** Prim metadata keyed by name. */
69
+ metadata: Record<string, USDValue>;
70
+ /** Variant sets keyed by set name and variant name. */
71
+ variants: Record<string, Record<string, USDVariant>>;
72
+ /** Child prims. */
73
+ children: USDPrim[];
74
+ };
75
+
76
+ /** Parsed OpenUSD stage. */
77
+ export type USDStage = {
78
+ /** Container format used for the root layer. */
79
+ format: 'usda' | 'usdz';
80
+ /** URL of the root layer when available. */
81
+ url?: string;
82
+ /** Root-layer metadata. */
83
+ metadata: Record<string, USDValue>;
84
+ /** Root prims in scene order. */
85
+ rootPrims: USDPrim[];
86
+ /** URLs of all layers used to compose the stage. */
87
+ layers: string[];
88
+ };
89
+
90
+ /** Options for {@link USDLoader}. */
91
+ export type USDLoaderOptions = LoaderOptions & {
92
+ /** OpenUSD-specific parser and composition options. */
93
+ usd?: {
94
+ /** Compose references and variants into the returned prim hierarchy. */
95
+ compose?: boolean;
96
+ /** Load referenced layers while composing the stage. */
97
+ loadReferences?: boolean;
98
+ /** Maximum recursive composition depth. */
99
+ maxReferenceDepth?: number;
100
+ /** Variant selections that override authored selections. */
101
+ variantSelections?: Record<string, string>;
102
+ };
103
+ };
@@ -0,0 +1,40 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Loader} from '@loaders.gl/loader-utils';
6
+ import type {USDLoaderOptions, USDStage} from './lib/usd-types';
7
+
8
+ // __VERSION__ is injected by babel-plugin-version-inline
9
+ // @ts-ignore TS2304: Cannot find name '__VERSION__'.
10
+ const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';
11
+
12
+ /** Loads the parser-bearing USD loader implementation. */
13
+ async function preload() {
14
+ const {USDLoaderWithParser} = await import('@loaders.gl/scene/usd-loader');
15
+ return USDLoaderWithParser;
16
+ }
17
+
18
+ /** Metadata-only loader for OpenUSD ASCII and uncompressed USDZ scenes. */
19
+ export const USDLoader = {
20
+ dataType: null as unknown as USDStage,
21
+ batchType: null as never,
22
+ name: 'Universal Scene Description',
23
+ id: 'usd',
24
+ module: 'scene',
25
+ version: VERSION,
26
+ extensions: ['usd', 'usda', 'usdz'],
27
+ mimeTypes: ['model/vnd.usd', 'model/vnd.usda', 'model/vnd.usdz+zip'],
28
+ text: true,
29
+ binary: true,
30
+ tests: ['#usda', 'PK'],
31
+ options: {
32
+ usd: {
33
+ compose: true,
34
+ loadReferences: true,
35
+ maxReferenceDepth: 12,
36
+ variantSelections: {}
37
+ }
38
+ },
39
+ preload
40
+ } as const satisfies Loader<USDStage, never, USDLoaderOptions>;
@@ -0,0 +1,26 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {LoaderWithParser} from '@loaders.gl/loader-utils';
6
+ import {parseUSD} from './lib/parse-usd';
7
+ import type {USDLoaderOptions, USDStage} from './lib/usd-types';
8
+ import {USDLoader as USDLoaderMetadata} from './usd-loader-types';
9
+
10
+ const {preload: _USDLoaderPreload, ...USDLoaderMetadataWithoutPreload} = USDLoaderMetadata;
11
+
12
+ /** Parser-bearing loader for OpenUSD ASCII and uncompressed USDZ scenes. */
13
+ export const USDLoaderWithParser = {
14
+ ...USDLoaderMetadataWithoutPreload,
15
+ parse: parseUSD,
16
+ parseText
17
+ } as const satisfies LoaderWithParser<USDStage, never, USDLoaderOptions>;
18
+
19
+ /** Encodes USDA source text before using the shared binary parser. */
20
+ async function parseText(
21
+ text: string,
22
+ options?: USDLoaderOptions,
23
+ context?: Parameters<typeof parseUSD>[2]
24
+ ): Promise<USDStage> {
25
+ return parseUSD(new TextEncoder().encode(text).buffer, options, context);
26
+ }