@dreamerbird/camera-studio 0.2.0 → 0.3.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/CameraScene.d.ts +1 -1
- package/dist/CameraScene.d.ts.map +1 -1
- package/dist/CameraScene.js +36 -6
- package/dist/PlacementManager.d.ts +15 -1
- package/dist/PlacementManager.d.ts.map +1 -1
- package/dist/PlacementManager.js +123 -14
- package/dist/agent/api.d.ts +5 -0
- package/dist/agent/api.d.ts.map +1 -1
- package/dist/agent/api.js +25 -0
- package/dist/agent/api.test.js +75 -1
- package/dist/agent/commands/assets.d.ts +5 -0
- package/dist/agent/commands/assets.d.ts.map +1 -1
- package/dist/agent/commands/assets.js +93 -21
- package/dist/agent/commands/assets.test.js +53 -0
- package/dist/agent/commands/materials.d.ts +18 -0
- package/dist/agent/commands/materials.d.ts.map +1 -0
- package/dist/agent/commands/materials.js +192 -0
- package/dist/agent/commands/materials.test.d.ts +2 -0
- package/dist/agent/commands/materials.test.d.ts.map +1 -0
- package/dist/agent/commands/materials.test.js +294 -0
- package/dist/agent/commands/modelRecovery.test.d.ts +2 -0
- package/dist/agent/commands/modelRecovery.test.d.ts.map +1 -0
- package/dist/agent/commands/modelRecovery.test.js +122 -0
- package/dist/agent/commands/models.d.ts +13 -0
- package/dist/agent/commands/models.d.ts.map +1 -1
- package/dist/agent/commands/models.js +98 -1
- package/dist/agent/commands/primitives.d.ts +27 -0
- package/dist/agent/commands/primitives.d.ts.map +1 -0
- package/dist/agent/commands/primitives.js +117 -0
- package/dist/agent/commands/primitives.test.d.ts +2 -0
- package/dist/agent/commands/primitives.test.d.ts.map +1 -0
- package/dist/agent/commands/primitives.test.js +135 -0
- package/dist/agent/expectedCommands.d.ts +1 -1
- package/dist/agent/expectedCommands.d.ts.map +1 -1
- package/dist/agent/expectedCommands.js +6 -0
- package/dist/agent/propAccess.d.ts +2 -0
- package/dist/agent/propAccess.d.ts.map +1 -1
- package/dist/agent/propAccess.js +21 -0
- package/dist/agent/registerCommands.d.ts.map +1 -1
- package/dist/agent/registerCommands.js +5 -1
- package/dist/agent/runtime/objectRuntime.d.ts +2 -0
- package/dist/agent/runtime/objectRuntime.d.ts.map +1 -1
- package/dist/agent/runtime/objectRuntime.js +15 -1
- package/dist/agent/runtime.d.ts +1 -0
- package/dist/agent/runtime.d.ts.map +1 -1
- package/dist/agent/runtime.js +2 -0
- package/dist/assetLibrary/dispose.d.ts +4 -2
- package/dist/assetLibrary/dispose.d.ts.map +1 -1
- package/dist/assetLibrary/dispose.js +7 -5
- package/dist/assetLibrary/gltfLoader.d.ts +2 -1
- package/dist/assetLibrary/gltfLoader.d.ts.map +1 -1
- package/dist/assetLibrary/gltfLoader.js +2 -2
- package/dist/assetLibrary/gltfResourceSet.d.ts +6 -0
- package/dist/assetLibrary/gltfResourceSet.d.ts.map +1 -0
- package/dist/assetLibrary/gltfResourceSet.js +27 -0
- package/dist/assetLibrary/gltfResourceSet.test.d.ts +2 -0
- package/dist/assetLibrary/gltfResourceSet.test.d.ts.map +1 -0
- package/dist/assetLibrary/gltfResourceSet.test.js +40 -0
- package/dist/assetLibrary/importModelFile.d.ts +5 -0
- package/dist/assetLibrary/importModelFile.d.ts.map +1 -1
- package/dist/assetLibrary/importModelFile.js +3 -16
- package/dist/assetLibrary/importModelFiles.d.ts +15 -0
- package/dist/assetLibrary/importModelFiles.d.ts.map +1 -0
- package/dist/assetLibrary/importModelFiles.js +58 -0
- package/dist/assetLibrary/importModelFiles.test.d.ts +2 -0
- package/dist/assetLibrary/importModelFiles.test.d.ts.map +1 -0
- package/dist/assetLibrary/importModelFiles.test.js +217 -0
- package/dist/assetLibrary/importPipeline.d.ts +15 -1
- package/dist/assetLibrary/importPipeline.d.ts.map +1 -1
- package/dist/assetLibrary/importPipeline.js +74 -38
- package/dist/assetLibrary/importPipeline.test.js +25 -0
- package/dist/assetLibrary/library.d.ts +1 -0
- package/dist/assetLibrary/library.d.ts.map +1 -1
- package/dist/assetLibrary/library.js +22 -1
- package/dist/assetLibrary/library.test.js +76 -0
- package/dist/bootstrap.d.ts +2 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +69 -7
- package/dist/bootstrap.test.js +111 -0
- package/dist/config.d.ts +10 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/hostFlows.d.ts +1 -0
- package/dist/hostFlows.d.ts.map +1 -1
- package/dist/hostFlows.js +9 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/materials/modelMaterials.d.ts +19 -0
- package/dist/materials/modelMaterials.d.ts.map +1 -0
- package/dist/materials/modelMaterials.js +253 -0
- package/dist/materials/modelMaterials.test.d.ts +2 -0
- package/dist/materials/modelMaterials.test.d.ts.map +1 -0
- package/dist/materials/modelMaterials.test.js +188 -0
- package/dist/materials/types.d.ts +20 -0
- package/dist/materials/types.d.ts.map +1 -0
- package/dist/materials/types.js +1 -0
- package/dist/materials/useModelMaterials.d.ts +10 -0
- package/dist/materials/useModelMaterials.d.ts.map +1 -0
- package/dist/materials/useModelMaterials.js +26 -0
- package/dist/materials/useModelMaterials.test.d.ts +2 -0
- package/dist/materials/useModelMaterials.test.d.ts.map +1 -0
- package/dist/materials/useModelMaterials.test.js +75 -0
- package/dist/poses.d.ts +38 -0
- package/dist/poses.d.ts.map +1 -0
- package/dist/poses.js +41 -0
- package/dist/poses.test.d.ts +2 -0
- package/dist/poses.test.d.ts.map +1 -0
- package/dist/poses.test.js +38 -0
- package/dist/primitives.d.ts +5 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +15 -0
- package/dist/primitives.test.d.ts +2 -0
- package/dist/primitives.test.d.ts.map +1 -0
- package/dist/primitives.test.js +27 -0
- package/dist/reset.realStudio.test.d.ts +2 -0
- package/dist/reset.realStudio.test.d.ts.map +1 -0
- package/dist/reset.realStudio.test.js +60 -0
- package/dist/studioRoot.d.ts +4 -0
- package/dist/studioRoot.d.ts.map +1 -0
- package/dist/studioRoot.js +36 -0
- package/dist/studioRoot.test.d.ts +2 -0
- package/dist/studioRoot.test.d.ts.map +1 -0
- package/dist/studioRoot.test.js +62 -0
- package/dist/useCameraStudio.d.ts.map +1 -1
- package/dist/useCameraStudio.js +3 -2
- package/package.json +9 -1
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* 宿主外部模型入库:File → 既有导入管线(planImport/executeImport)。
|
|
3
|
-
* 放置管线只认库条目(按 id 解析 blob),外部模型必须真实入库,
|
|
4
|
-
* 姿势/骨架匹配/持久化全部照常。library/deps 可注入(测试用)。
|
|
5
|
-
*/
|
|
6
|
-
import { planImport, executeImport, realImportDeps } from './importPipeline';
|
|
1
|
+
import { importModelFiles } from './importModelFiles';
|
|
7
2
|
import { getLibrary } from './library';
|
|
8
|
-
export async function importModelFile(file, library = getLibrary(), deps
|
|
9
|
-
const
|
|
10
|
-
const plan = await planImport([file], library, deps);
|
|
11
|
-
// 宿主场景无"相似条目"交互,一律保留为新条目
|
|
12
|
-
await executeImport(plan, plan.similar.map(() => 'keep'), library);
|
|
13
|
-
const models = await library.listEntries('model');
|
|
14
|
-
const created = models.find((m) => !before.has(m.id));
|
|
15
|
-
if (!created || created.kind !== 'model') {
|
|
16
|
-
throw new Error('[hostFlows] 模型导入失败:未找到新建条目');
|
|
17
|
-
}
|
|
3
|
+
export async function importModelFile(file, library = getLibrary(), deps) {
|
|
4
|
+
const [created] = await importModelFiles([file], library, deps);
|
|
18
5
|
return created;
|
|
19
6
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ImportDeps } from './importPipeline';
|
|
2
|
+
import { type Library } from './library';
|
|
3
|
+
import type { ModelEntry } from './types';
|
|
4
|
+
export type ImportFileFailure = {
|
|
5
|
+
file: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
export declare class ImportModelFilesError extends Error {
|
|
9
|
+
readonly entries: ModelEntry[];
|
|
10
|
+
readonly failedItems: ImportFileFailure[];
|
|
11
|
+
readonly cause: unknown;
|
|
12
|
+
constructor(entries: ModelEntry[], failedItems: ImportFileFailure[], cause: unknown);
|
|
13
|
+
}
|
|
14
|
+
export declare function importModelFiles(files: File[], library?: Library, deps?: ImportDeps): Promise<ModelEntry[]>;
|
|
15
|
+
//# sourceMappingURL=importModelFiles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importModelFiles.d.ts","sourceRoot":"","sources":["../../src/assetLibrary/importModelFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAa,KAAK,OAAO,EAAC,MAAM,WAAW,CAAA;AAClD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA;AAEvC,MAAM,MAAM,iBAAiB,GAAG;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAA;AAE/D,qBAAa,qBAAsB,SAAQ,KAAK;IAE5C,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE;IAC9B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE;IACzC,QAAQ,CAAC,KAAK,EAAE,OAAO;gBAFd,OAAO,EAAE,UAAU,EAAE,EACrB,WAAW,EAAE,iBAAiB,EAAE,EAChC,KAAK,EAAE,OAAO;CAS1B;AASD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,GAAE,OAAsB,EAC/B,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAkDvB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { executeImport, ImportItemWriteError, planImport, realImportDeps, } from './importPipeline';
|
|
2
|
+
import { getLibrary } from './library';
|
|
3
|
+
export class ImportModelFilesError extends Error {
|
|
4
|
+
constructor(entries, failedItems, cause) {
|
|
5
|
+
super(`模型导入部分失败: ${failedItems
|
|
6
|
+
.map((item) => `${item.file}: ${item.message}`)
|
|
7
|
+
.join('; ')}`);
|
|
8
|
+
this.entries = entries;
|
|
9
|
+
this.failedItems = failedItems;
|
|
10
|
+
this.cause = cause;
|
|
11
|
+
this.name = 'ImportModelFilesError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function failureOf(item) {
|
|
15
|
+
return {
|
|
16
|
+
file: item.sourceFile ?? item.name,
|
|
17
|
+
message: item.type === 'skip' ? item.reason : '写入失败',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export async function importModelFiles(files, library = getLibrary(), deps) {
|
|
21
|
+
const before = new Set((await library.listEntries('model')).map((e) => e.id));
|
|
22
|
+
const modelFiles = files.filter((file) => /\.(glb|gltf|fbx)$/i.test(file.name));
|
|
23
|
+
const plan = await planImport(modelFiles, library, deps ?? realImportDeps(files));
|
|
24
|
+
const failedItems = plan.items
|
|
25
|
+
.filter((item) => item.type === 'skip' && !item.duplicate)
|
|
26
|
+
.map(failureOf);
|
|
27
|
+
let executeError;
|
|
28
|
+
try {
|
|
29
|
+
await executeImport(plan, plan.similar.map(() => 'keep'), library);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
executeError = error;
|
|
33
|
+
if (error instanceof ImportItemWriteError) {
|
|
34
|
+
const cause = error.cause;
|
|
35
|
+
failedItems.push({
|
|
36
|
+
...failureOf(error.item),
|
|
37
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
failedItems.push({
|
|
42
|
+
file: 'unknown',
|
|
43
|
+
message: error instanceof Error ? error.message : String(error),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const entries = (await library.listEntries('model')).filter((entry) => entry.kind === 'model' && !before.has(entry.id));
|
|
48
|
+
if (executeError || failedItems.length > 0) {
|
|
49
|
+
const cause = executeError instanceof ImportItemWriteError
|
|
50
|
+
? executeError.cause
|
|
51
|
+
: executeError;
|
|
52
|
+
throw new ImportModelFilesError(entries, failedItems, cause);
|
|
53
|
+
}
|
|
54
|
+
if (entries.length === 0) {
|
|
55
|
+
throw new Error('[hostFlows] 模型导入失败:未找到新建条目');
|
|
56
|
+
}
|
|
57
|
+
return entries;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importModelFiles.test.d.ts","sourceRoot":"","sources":["../../src/assetLibrary/importModelFiles.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { BoxGeometry, Group, Mesh, MeshBasicMaterial } from 'three';
|
|
2
|
+
import { importModelFiles } from './importModelFiles';
|
|
3
|
+
import { Library } from './library';
|
|
4
|
+
const tinyPng = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
|
|
5
|
+
function triangleGltf() {
|
|
6
|
+
const document = {
|
|
7
|
+
asset: { version: '2.0' },
|
|
8
|
+
scene: 0,
|
|
9
|
+
scenes: [{ nodes: [0] }],
|
|
10
|
+
nodes: [{ mesh: 0 }],
|
|
11
|
+
meshes: [
|
|
12
|
+
{
|
|
13
|
+
primitives: [{ attributes: { POSITION: 0 }, material: 0, mode: 4 }],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
materials: [
|
|
17
|
+
{
|
|
18
|
+
pbrMetallicRoughness: {
|
|
19
|
+
baseColorTexture: { index: 0 },
|
|
20
|
+
metallicFactor: 0,
|
|
21
|
+
roughnessFactor: 1,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
textures: [{ source: 0 }],
|
|
26
|
+
images: [{ uri: 'textures/tiny.png' }],
|
|
27
|
+
buffers: [{ uri: 'geometry/triangle.bin', byteLength: 36 }],
|
|
28
|
+
bufferViews: [{ buffer: 0, byteOffset: 0, byteLength: 36 }],
|
|
29
|
+
accessors: [
|
|
30
|
+
{
|
|
31
|
+
bufferView: 0,
|
|
32
|
+
componentType: 5126,
|
|
33
|
+
count: 3,
|
|
34
|
+
type: 'VEC3',
|
|
35
|
+
min: [0, 0, 0],
|
|
36
|
+
max: [1, 1, 0],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
return new File([JSON.stringify(document)], 'triangle.gltf', {
|
|
41
|
+
type: 'model/gltf+json',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function triangleBin() {
|
|
45
|
+
const bytes = new ArrayBuffer(36);
|
|
46
|
+
const positions = new Float32Array(bytes);
|
|
47
|
+
positions.set([0, 0, 0, 1, 0, 0, 0, 1, 0]);
|
|
48
|
+
return new File([bytes], 'triangle.bin', {
|
|
49
|
+
type: 'application/octet-stream',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function tinyPngFile() {
|
|
53
|
+
return new File([tinyPng], 'tiny.png', { type: 'image/png' });
|
|
54
|
+
}
|
|
55
|
+
function memoryBackend() {
|
|
56
|
+
const entries = new Map();
|
|
57
|
+
const blobs = new Map();
|
|
58
|
+
const backend = {
|
|
59
|
+
id: 'mock',
|
|
60
|
+
listEntries: async (kind) => [...entries.values()].filter((entry) => !kind || entry.kind === kind),
|
|
61
|
+
getEntry: async (id) => entries.get(id),
|
|
62
|
+
putEntry: async (entry) => void entries.set(entry.id, entry),
|
|
63
|
+
deleteEntry: async (id) => void entries.delete(id),
|
|
64
|
+
putBlob: async (blob) => {
|
|
65
|
+
const id = `blob-${blobs.size}`;
|
|
66
|
+
blobs.set(id, blob);
|
|
67
|
+
return id;
|
|
68
|
+
},
|
|
69
|
+
getBlobUrl: async () => 'blob:stored',
|
|
70
|
+
deleteBlob: async (id) => void blobs.delete(id),
|
|
71
|
+
listBlobIds: async () => [...blobs.keys()],
|
|
72
|
+
getRigAliases: async () => [],
|
|
73
|
+
putRigAlias: async () => { },
|
|
74
|
+
};
|
|
75
|
+
return { backend, entries, blobs };
|
|
76
|
+
}
|
|
77
|
+
function modelDeps(names) {
|
|
78
|
+
return {
|
|
79
|
+
load: async (file) => {
|
|
80
|
+
if (!names.includes(file.name))
|
|
81
|
+
throw new Error('无法解析');
|
|
82
|
+
const root = new Group();
|
|
83
|
+
root.name = file.name;
|
|
84
|
+
root.add(new Mesh(new BoxGeometry(), new MeshBasicMaterial()));
|
|
85
|
+
return { root, animations: [] };
|
|
86
|
+
},
|
|
87
|
+
exportModel: async (root) => new TextEncoder().encode(root.name).buffer,
|
|
88
|
+
exportAnimation: async () => new TextEncoder().encode('anim').buffer,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function gltfJsonChunk(glb) {
|
|
92
|
+
const view = new DataView(glb);
|
|
93
|
+
const jsonLength = view.getUint32(12, true);
|
|
94
|
+
expect(view.getUint32(16, true)).toBe(0x4e4f534a);
|
|
95
|
+
return JSON.parse(new TextDecoder().decode(new Uint8Array(glb, 20, jsonLength)));
|
|
96
|
+
}
|
|
97
|
+
describe('importModelFiles', () => {
|
|
98
|
+
const originalFetch = globalThis.fetch;
|
|
99
|
+
const originalSelf = globalThis.self;
|
|
100
|
+
const originalCreateImageBitmap = globalThis.createImageBitmap;
|
|
101
|
+
const originalImageData = globalThis.ImageData;
|
|
102
|
+
const originalCreateObjectURL = URL.createObjectURL;
|
|
103
|
+
const originalRevokeObjectURL = URL.revokeObjectURL;
|
|
104
|
+
const originalCreateElement = document.createElement;
|
|
105
|
+
let objectUrls;
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
globalThis.self = globalThis;
|
|
108
|
+
let nextId = 0;
|
|
109
|
+
objectUrls = new Map();
|
|
110
|
+
URL.createObjectURL = jest.fn((blob) => {
|
|
111
|
+
const url = `blob:test-${++nextId}`;
|
|
112
|
+
objectUrls.set(url, blob);
|
|
113
|
+
return url;
|
|
114
|
+
});
|
|
115
|
+
URL.revokeObjectURL = jest.fn((url) => {
|
|
116
|
+
objectUrls.delete(url);
|
|
117
|
+
});
|
|
118
|
+
globalThis.fetch = jest.fn(async (input) => {
|
|
119
|
+
const url = typeof input === 'string' || input instanceof URL
|
|
120
|
+
? String(input)
|
|
121
|
+
: input.url;
|
|
122
|
+
const blob = objectUrls.get(url);
|
|
123
|
+
if (!blob)
|
|
124
|
+
throw new Error(`未登记的测试 URL: ${url}`);
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
status: 200,
|
|
128
|
+
statusText: 'OK',
|
|
129
|
+
url,
|
|
130
|
+
headers: { get: () => null },
|
|
131
|
+
arrayBuffer: () => blob.arrayBuffer(),
|
|
132
|
+
blob: async () => blob,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
globalThis.createImageBitmap = jest.fn(async () => ({
|
|
136
|
+
data: new Uint8ClampedArray([255, 255, 255, 255]),
|
|
137
|
+
width: 1,
|
|
138
|
+
height: 1,
|
|
139
|
+
close: jest.fn(),
|
|
140
|
+
}));
|
|
141
|
+
globalThis.ImageData = class {
|
|
142
|
+
constructor(data, width, height) {
|
|
143
|
+
this.data = data;
|
|
144
|
+
this.width = width;
|
|
145
|
+
this.height = height;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
document.createElement = jest.fn(() => ({
|
|
149
|
+
width: 0,
|
|
150
|
+
height: 0,
|
|
151
|
+
getContext: () => ({
|
|
152
|
+
translate: jest.fn(),
|
|
153
|
+
scale: jest.fn(),
|
|
154
|
+
drawImage: jest.fn(),
|
|
155
|
+
putImageData: jest.fn(),
|
|
156
|
+
}),
|
|
157
|
+
toBlob: (callback) => callback(new Blob([tinyPng], { type: 'image/png' })),
|
|
158
|
+
}));
|
|
159
|
+
});
|
|
160
|
+
afterEach(() => {
|
|
161
|
+
globalThis.fetch = originalFetch;
|
|
162
|
+
globalThis.self = originalSelf;
|
|
163
|
+
globalThis.createImageBitmap = originalCreateImageBitmap;
|
|
164
|
+
globalThis.ImageData = originalImageData;
|
|
165
|
+
URL.createObjectURL = originalCreateObjectURL;
|
|
166
|
+
URL.revokeObjectURL = originalRevokeObjectURL;
|
|
167
|
+
if (originalCreateElement)
|
|
168
|
+
document.createElement = originalCreateElement;
|
|
169
|
+
else
|
|
170
|
+
Reflect.deleteProperty(document, 'createElement');
|
|
171
|
+
});
|
|
172
|
+
it('把相对 buffer/image 一起装入一个自包含 GLB,并回收全部临时 URL', async () => {
|
|
173
|
+
const { backend, entries, blobs } = memoryBackend();
|
|
174
|
+
const library = new Library({ sources: [backend] });
|
|
175
|
+
const imported = await importModelFiles([triangleGltf(), triangleBin(), tinyPngFile()], library);
|
|
176
|
+
expect(imported).toHaveLength(1);
|
|
177
|
+
expect(imported[0]).toMatchObject({
|
|
178
|
+
kind: 'model',
|
|
179
|
+
name: 'triangle',
|
|
180
|
+
vertexCount: 3,
|
|
181
|
+
});
|
|
182
|
+
expect(entries.size).toBe(1);
|
|
183
|
+
const glb = await blobs.get(imported[0].blobId).arrayBuffer();
|
|
184
|
+
expect(new DataView(glb).getUint32(0, true)).toBe(0x46546c67);
|
|
185
|
+
const json = gltfJsonChunk(glb);
|
|
186
|
+
expect(json.buffers[0].uri).toBeUndefined();
|
|
187
|
+
expect(json.images[0].uri).toBeUndefined();
|
|
188
|
+
expect(json.images[0].bufferView).toEqual(expect.any(Number));
|
|
189
|
+
expect(URL.createObjectURL).toHaveBeenCalledTimes(2);
|
|
190
|
+
expect(URL.revokeObjectURL).toHaveBeenCalledTimes(2);
|
|
191
|
+
expect(objectUrls.size).toBe(0);
|
|
192
|
+
});
|
|
193
|
+
it('引用的 bin 缺失时不创建任何新条目,并回收已创建的 URL', async () => {
|
|
194
|
+
const { backend, entries } = memoryBackend();
|
|
195
|
+
const library = new Library({ sources: [backend] });
|
|
196
|
+
await expect(importModelFiles([triangleGltf(), tinyPngFile()], library)).rejects.toThrow();
|
|
197
|
+
expect(entries.size).toBe(0);
|
|
198
|
+
expect(objectUrls.size).toBe(0);
|
|
199
|
+
expect(URL.revokeObjectURL).toHaveBeenCalledWith(expect.stringMatching(/^blob:test-/));
|
|
200
|
+
});
|
|
201
|
+
it('多个模型根全部成功时按原数组契约返回全部新条目', async () => {
|
|
202
|
+
const { backend } = memoryBackend();
|
|
203
|
+
const library = new Library({ sources: [backend] });
|
|
204
|
+
const imported = await importModelFiles([new File(['a'], 'a.gltf'), new File(['b'], 'b.gltf')], library, modelDeps(['a.gltf', 'b.gltf']));
|
|
205
|
+
expect(imported.map((entry) => entry.name)).toEqual(['a', 'b']);
|
|
206
|
+
expect(await library.listEntries('model')).toHaveLength(2);
|
|
207
|
+
});
|
|
208
|
+
it('混合成功时保留成功条目并报告失败文件及原因', async () => {
|
|
209
|
+
const { backend } = memoryBackend();
|
|
210
|
+
const library = new Library({ sources: [backend] });
|
|
211
|
+
await expect(importModelFiles([new File(['a'], 'a.gltf'), new File(['x'], 'broken.gltf')], library, modelDeps(['a.gltf']))).rejects.toMatchObject({
|
|
212
|
+
entries: [expect.objectContaining({ kind: 'model', name: 'a' })],
|
|
213
|
+
failedItems: [{ file: 'broken.gltf', message: expect.any(String) }],
|
|
214
|
+
});
|
|
215
|
+
expect((await library.listEntries('model')).map((entry) => entry.name)).toEqual(['a']);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -12,10 +12,11 @@ export type ImportDeps = {
|
|
|
12
12
|
exportModel: (root: Object3D) => Promise<ArrayBuffer>;
|
|
13
13
|
exportAnimation: (root: Object3D, clip: AnimationClip) => Promise<ArrayBuffer>;
|
|
14
14
|
};
|
|
15
|
-
export declare const realImportDeps: () => ImportDeps;
|
|
15
|
+
export declare const realImportDeps: (files?: File[]) => ImportDeps;
|
|
16
16
|
export type ImportItem = {
|
|
17
17
|
type: 'model';
|
|
18
18
|
name: string;
|
|
19
|
+
sourceFile?: string;
|
|
19
20
|
rig: string;
|
|
20
21
|
contentHash: string;
|
|
21
22
|
boneCount: number;
|
|
@@ -24,6 +25,7 @@ export type ImportItem = {
|
|
|
24
25
|
} | {
|
|
25
26
|
type: 'animation';
|
|
26
27
|
name: string;
|
|
28
|
+
sourceFile?: string;
|
|
27
29
|
rig: string;
|
|
28
30
|
contentHash: string;
|
|
29
31
|
duration: number;
|
|
@@ -31,12 +33,15 @@ export type ImportItem = {
|
|
|
31
33
|
} | {
|
|
32
34
|
type: 'pose';
|
|
33
35
|
name: string;
|
|
36
|
+
sourceFile?: string;
|
|
34
37
|
rig: string;
|
|
35
38
|
bones: PoseEntry['bones'];
|
|
36
39
|
} | {
|
|
37
40
|
type: 'skip';
|
|
38
41
|
name: string;
|
|
42
|
+
sourceFile?: string;
|
|
39
43
|
reason: string;
|
|
44
|
+
duplicate?: boolean;
|
|
40
45
|
};
|
|
41
46
|
/** 哈希不同但同族同结构的模型,入库前要用户选 reuse/keep */
|
|
42
47
|
export type SimilarMatch = {
|
|
@@ -56,6 +61,15 @@ export declare function baseName(fileName: string): string;
|
|
|
56
61
|
* 第二层:哈希不同但同族 + 顶点/骨骼数相同 → 记入 similar 交给用户。
|
|
57
62
|
*/
|
|
58
63
|
export declare function planImport(files: File[], library: Pick<Library, 'listEntries'>, deps: ImportDeps, tr?: TFunction): Promise<ImportPlan>;
|
|
64
|
+
export declare class ImportItemWriteError extends Error {
|
|
65
|
+
readonly item: Exclude<ImportItem, {
|
|
66
|
+
type: 'skip';
|
|
67
|
+
}>;
|
|
68
|
+
readonly cause: unknown;
|
|
69
|
+
constructor(item: Exclude<ImportItem, {
|
|
70
|
+
type: 'skip';
|
|
71
|
+
}>, cause: unknown);
|
|
72
|
+
}
|
|
59
73
|
/** 落库:choices 与 plan.similar 一一对应,'reuse' 丢弃对应 model 项 */
|
|
60
74
|
export declare function executeImport(plan: ImportPlan, choices: Array<'reuse' | 'keep'>, library: Library): Promise<void>;
|
|
61
75
|
//# sourceMappingURL=importPipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"importPipeline.d.ts","sourceRoot":"","sources":["../../src/assetLibrary/importPipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AACzD,OAAO,EAAY,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"importPipeline.d.ts","sourceRoot":"","sources":["../../src/assetLibrary/importPipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { translate } from '@dreamerbird/studio/i18n';
|
|
2
2
|
import { classifyModel, validatePoseJson } from './classify';
|
|
3
3
|
import { createGLTFLoader } from './gltfLoader';
|
|
4
|
+
import { createResourceUrlModifier } from './gltfResourceSet';
|
|
4
5
|
import { exportAnimationGLB, exportModelGLB } from './exportGLB';
|
|
5
6
|
import { hashBytes } from './hash';
|
|
6
7
|
import { collectBoneNames, meshStats, rigIdOf } from './rig';
|
|
7
|
-
export const realImportDeps = () => ({
|
|
8
|
-
load: defaultLoad,
|
|
8
|
+
export const realImportDeps = (files) => ({
|
|
9
|
+
load: (file) => defaultLoad(file, files),
|
|
9
10
|
exportModel: exportModelGLB,
|
|
10
11
|
exportAnimation: exportAnimationGLB,
|
|
11
12
|
});
|
|
12
|
-
async function defaultLoad(file) {
|
|
13
|
+
async function defaultLoad(file, resourceFiles) {
|
|
13
14
|
const { FBXLoader } = await import('three-stdlib');
|
|
14
15
|
const { LoadingManager } = await import('three');
|
|
16
|
+
if (resourceFiles && /\.gltf$/i.test(file.name)) {
|
|
17
|
+
const resources = createResourceUrlModifier(resourceFiles);
|
|
18
|
+
try {
|
|
19
|
+
const gltf = await createGLTFLoader(resources.manager).parseAsync(await file.text(), '');
|
|
20
|
+
return { root: gltf.scene, animations: gltf.animations };
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
resources.dispose();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
15
26
|
const url = URL.createObjectURL(file);
|
|
16
27
|
try {
|
|
17
28
|
if (/\.glb$|\.gltf$/i.test(file.name)) {
|
|
@@ -81,6 +92,7 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
81
92
|
items.push({
|
|
82
93
|
type: 'skip',
|
|
83
94
|
name: file.name,
|
|
95
|
+
sourceFile: file.name,
|
|
84
96
|
reason: tr('pg.import.reason.badPoseJson'),
|
|
85
97
|
});
|
|
86
98
|
}
|
|
@@ -88,6 +100,7 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
88
100
|
items.push({
|
|
89
101
|
type: 'pose',
|
|
90
102
|
name: baseName(file.name),
|
|
103
|
+
sourceFile: file.name,
|
|
91
104
|
rig: parsed.rig,
|
|
92
105
|
bones: parsed.bones,
|
|
93
106
|
});
|
|
@@ -102,6 +115,7 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
102
115
|
items.push({
|
|
103
116
|
type: 'skip',
|
|
104
117
|
name: file.name,
|
|
118
|
+
sourceFile: file.name,
|
|
105
119
|
reason: tr('pg.import.reason.parseFailed'),
|
|
106
120
|
});
|
|
107
121
|
continue;
|
|
@@ -111,6 +125,7 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
111
125
|
items.push({
|
|
112
126
|
type: 'skip',
|
|
113
127
|
name: file.name,
|
|
128
|
+
sourceFile: file.name,
|
|
114
129
|
reason: tr('pg.import.reason.noContent'),
|
|
115
130
|
});
|
|
116
131
|
continue;
|
|
@@ -128,6 +143,7 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
128
143
|
items.push({
|
|
129
144
|
type: 'skip',
|
|
130
145
|
name: baseName(file.name),
|
|
146
|
+
sourceFile: file.name,
|
|
131
147
|
reason: tr('pg.import.reason.modelExportFailed'),
|
|
132
148
|
});
|
|
133
149
|
}
|
|
@@ -140,6 +156,7 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
140
156
|
const index = items.push({
|
|
141
157
|
type: 'model',
|
|
142
158
|
name: baseName(file.name),
|
|
159
|
+
sourceFile: file.name,
|
|
143
160
|
rig,
|
|
144
161
|
contentHash,
|
|
145
162
|
boneCount,
|
|
@@ -163,7 +180,9 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
163
180
|
items.push({
|
|
164
181
|
type: 'skip',
|
|
165
182
|
name: baseName(file.name),
|
|
183
|
+
sourceFile: file.name,
|
|
166
184
|
reason: tr('pg.import.reason.modelExists', { name: dup.name }),
|
|
185
|
+
duplicate: true,
|
|
167
186
|
});
|
|
168
187
|
}
|
|
169
188
|
// dup 命中:模型跳过,下面的动作照常提取
|
|
@@ -180,6 +199,7 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
180
199
|
items.push({
|
|
181
200
|
type: 'skip',
|
|
182
201
|
name: `${baseName(file.name)}/${clip.name}`,
|
|
202
|
+
sourceFile: file.name,
|
|
183
203
|
reason: tr('pg.import.reason.animationExportFailed'),
|
|
184
204
|
});
|
|
185
205
|
continue;
|
|
@@ -189,13 +209,16 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
189
209
|
items.push({
|
|
190
210
|
type: 'skip',
|
|
191
211
|
name: `${baseName(file.name)}/${clip.name}`,
|
|
212
|
+
sourceFile: file.name,
|
|
192
213
|
reason: tr('pg.import.reason.animationExists'),
|
|
214
|
+
duplicate: true,
|
|
193
215
|
});
|
|
194
216
|
continue;
|
|
195
217
|
}
|
|
196
218
|
items.push({
|
|
197
219
|
type: 'animation',
|
|
198
220
|
name: clip.name || baseName(file.name),
|
|
221
|
+
sourceFile: file.name,
|
|
199
222
|
rig,
|
|
200
223
|
contentHash,
|
|
201
224
|
duration: clip.duration,
|
|
@@ -205,6 +228,14 @@ export async function planImport(files, library, deps, tr = translate) {
|
|
|
205
228
|
}
|
|
206
229
|
return { items, similar, summary: summarize(items, tr) };
|
|
207
230
|
}
|
|
231
|
+
export class ImportItemWriteError extends Error {
|
|
232
|
+
constructor(item, cause) {
|
|
233
|
+
super(`导入项 ${item.name} 写入失败: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
234
|
+
this.item = item;
|
|
235
|
+
this.cause = cause;
|
|
236
|
+
this.name = 'ImportItemWriteError';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
208
239
|
/** 落库:choices 与 plan.similar 一一对应,'reuse' 丢弃对应 model 项 */
|
|
209
240
|
export async function executeImport(plan, choices, library) {
|
|
210
241
|
const drop = new Set(plan.similar.filter((_, i) => choices[i] === 'reuse').map((s) => s.index));
|
|
@@ -212,42 +243,47 @@ export async function executeImport(plan, choices, library) {
|
|
|
212
243
|
const item = plan.items[i];
|
|
213
244
|
if (item.type === 'skip')
|
|
214
245
|
continue;
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
246
|
+
try {
|
|
247
|
+
if (item.type === 'model') {
|
|
248
|
+
if (drop.has(i))
|
|
249
|
+
continue;
|
|
250
|
+
await library.putBlobWithEntry(new Blob([item.glb]), (blobId) => ({
|
|
251
|
+
id: newId(),
|
|
252
|
+
kind: 'model',
|
|
253
|
+
name: item.name,
|
|
254
|
+
rig: item.rig,
|
|
255
|
+
blobId,
|
|
256
|
+
contentHash: item.contentHash,
|
|
257
|
+
boneCount: item.boneCount,
|
|
258
|
+
vertexCount: item.vertexCount,
|
|
259
|
+
createdAt: Date.now(),
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
else if (item.type === 'animation') {
|
|
263
|
+
await library.putBlobWithEntry(new Blob([item.glb]), (blobId) => ({
|
|
264
|
+
id: newId(),
|
|
265
|
+
kind: 'animation',
|
|
266
|
+
name: item.name,
|
|
267
|
+
rig: item.rig,
|
|
268
|
+
blobId,
|
|
269
|
+
contentHash: item.contentHash,
|
|
270
|
+
duration: item.duration,
|
|
271
|
+
createdAt: Date.now(),
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
await library.putEntry({
|
|
276
|
+
id: newId(),
|
|
277
|
+
kind: 'pose',
|
|
278
|
+
name: item.name,
|
|
279
|
+
rig: item.rig,
|
|
280
|
+
bones: item.bones,
|
|
281
|
+
createdAt: Date.now(),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
241
284
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
id: newId(),
|
|
245
|
-
kind: 'pose',
|
|
246
|
-
name: item.name,
|
|
247
|
-
rig: item.rig,
|
|
248
|
-
bones: item.bones,
|
|
249
|
-
createdAt: Date.now(),
|
|
250
|
-
});
|
|
285
|
+
catch (error) {
|
|
286
|
+
throw new ImportItemWriteError(item, error);
|
|
251
287
|
}
|
|
252
288
|
}
|
|
253
289
|
}
|
|
@@ -185,4 +185,29 @@ describe('executeImport', () => {
|
|
|
185
185
|
await executeImport(plan, ['keep'], lib);
|
|
186
186
|
expect((await backend.listEntries('model')).length).toBe(1);
|
|
187
187
|
});
|
|
188
|
+
it('第二个模型写库失败时保留实际失败项与原始错误', async () => {
|
|
189
|
+
const backend = memoryBackend();
|
|
190
|
+
let writes = 0;
|
|
191
|
+
backend.putBlob = async () => {
|
|
192
|
+
writes++;
|
|
193
|
+
if (writes === 2)
|
|
194
|
+
throw new Error('disk full');
|
|
195
|
+
return `blob-${writes}`;
|
|
196
|
+
};
|
|
197
|
+
const lib = new Library({ sources: [backend] });
|
|
198
|
+
const deps = depsFor({
|
|
199
|
+
'a.fbx': { root: skinnedModel('Hips'), animations: [] },
|
|
200
|
+
'b.fbx': { root: skinnedModel('Spine'), animations: [] },
|
|
201
|
+
});
|
|
202
|
+
deps.exportModel = async (root) => {
|
|
203
|
+
const names = [];
|
|
204
|
+
root.traverse((object) => names.push(object.name));
|
|
205
|
+
return new TextEncoder().encode(names.join('|')).buffer;
|
|
206
|
+
};
|
|
207
|
+
const plan = await planImportZh([file('a.fbx'), file('b.fbx')], backend, deps);
|
|
208
|
+
await expect(executeImport(plan, [], lib)).rejects.toMatchObject({
|
|
209
|
+
item: { name: 'b', sourceFile: 'b.fbx' },
|
|
210
|
+
cause: new Error('disk full'),
|
|
211
|
+
});
|
|
212
|
+
});
|
|
188
213
|
});
|
|
@@ -84,6 +84,7 @@ export declare class Library {
|
|
|
84
84
|
listBlobIds(): Promise<string[]>;
|
|
85
85
|
/** 所有条目引用的 blob id 集合(模型 blobId + thumbnailId) */
|
|
86
86
|
private referencedBlobIds;
|
|
87
|
+
private referencedBlobIdsForRollback;
|
|
87
88
|
/** 删条目并清理它的 blob;blob 被其他条目共享时保留 */
|
|
88
89
|
deleteEntryDeep(id: string): Promise<void>;
|
|
89
90
|
/** 清理无条目引用的孤儿 blob,返回删除数量 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"library.d.ts","sourceRoot":"","sources":["../../src/assetLibrary/library.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,YAAY,EAAC,MAAM,UAAU,CAAA;AACvD,OAAO,KAAK,EAAC,aAAa,EAAE,qBAAqB,EAAC,MAAM,UAAU,CAAA;AAGlE,OAAO,KAAK,EAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAA;AAE9D,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAA;CACtC,CAAA;AAKD,mCAAmC;AACnC,eAAO,MAAM,cAAc,WAAW,CAAA;AAYtC;;;;;;;GAOG;AACH,qBAAa,OAAO;IAClB,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,CAAA;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IACzC,OAAO,CAAC,OAAO,CAAC,CAA6B;IAC7C,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,eAAe,CAA8B;IACrD,+CAA+C;IAC/C,OAAO,CAAC,cAAc,CAAmC;gBAE7C,IAAI,EAAE,WAAW;IAoB7B,4CAA4C;IAC5C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI;IAIpC;;;;OAIG;IACH,kBAAkB,IAAI,SAAS,YAAY,EAAE;IAI7C,kDAAkD;IAClD,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIrD,OAAO,CAAC,eAAe;IAWvB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAOrC,OAAO,CAAC,IAAI;IAKZ,gCAAgC;IAChC,OAAO,CAAC,OAAO;IASf;;;;;;;;;;OAUG;IACG,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA8D5D;;;;;;OAMG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAmB7D;;;OAGG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUjD,OAAO,CAAC,eAAe;IAOjB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB1D,kDAAkD;IAC5C,gBAAgB,CACpB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,YAAY,GAC1C,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"library.d.ts","sourceRoot":"","sources":["../../src/assetLibrary/library.ts"],"names":[],"mappings":"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"}
|