@dreamerbird/camera-studio 0.1.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/LICENSE +866 -0
- package/assets/models/Y Bot.glb +0 -0
- package/dist/CameraScene.d.ts +1 -1
- package/dist/CameraScene.d.ts.map +1 -1
- package/dist/CameraScene.js +100 -9
- package/dist/PlacementManager.d.ts +15 -1
- package/dist/PlacementManager.d.ts.map +1 -1
- package/dist/PlacementManager.js +151 -15
- 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/exportGLB.d.ts +25 -0
- package/dist/assetLibrary/exportGLB.d.ts.map +1 -1
- package/dist/assetLibrary/exportGLB.js +43 -1
- package/dist/assetLibrary/exportGLB.test.js +56 -2
- 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/assetLibrary/seedAssetsManifest.js +1 -1
- 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/firstScreenGate.d.ts +28 -0
- package/dist/firstScreenGate.d.ts.map +1 -0
- package/dist/firstScreenGate.js +67 -0
- package/dist/firstScreenGate.test.d.ts +2 -0
- package/dist/firstScreenGate.test.d.ts.map +1 -0
- package/dist/firstScreenGate.test.js +83 -0
- 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 +18 -10
|
@@ -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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"}
|
|
@@ -238,7 +238,16 @@ export class Library {
|
|
|
238
238
|
await this.enqueue(async () => {
|
|
239
239
|
const writable = this.requireWritable();
|
|
240
240
|
const blobId = await writable.putBlob(data);
|
|
241
|
-
|
|
241
|
+
try {
|
|
242
|
+
await writable.putEntry(makeEntry(blobId));
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
const referenced = await this.referencedBlobIdsForRollback().catch(() => undefined);
|
|
246
|
+
if (referenced && !referenced.has(blobId)) {
|
|
247
|
+
await writable.deleteBlob(blobId).catch(() => { });
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
242
251
|
this.emit();
|
|
243
252
|
});
|
|
244
253
|
}
|
|
@@ -265,6 +274,18 @@ export class Library {
|
|
|
265
274
|
}
|
|
266
275
|
return ids;
|
|
267
276
|
}
|
|
277
|
+
async referencedBlobIdsForRollback() {
|
|
278
|
+
const catalogs = await Promise.all(this.sources.map((source) => source.listEntries()));
|
|
279
|
+
const ids = new Set();
|
|
280
|
+
for (const entry of catalogs.flat()) {
|
|
281
|
+
if ('blobId' in entry)
|
|
282
|
+
ids.add(entry.blobId);
|
|
283
|
+
if ('thumbnailId' in entry && entry.thumbnailId) {
|
|
284
|
+
ids.add(entry.thumbnailId);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return ids;
|
|
288
|
+
}
|
|
268
289
|
/** 删条目并清理它的 blob;blob 被其他条目共享时保留 */
|
|
269
290
|
async deleteEntryDeep(id) {
|
|
270
291
|
await this.enqueue(async () => {
|
|
@@ -400,6 +400,82 @@ describe('骨架族别名并集读(终审 M-4)', () => {
|
|
|
400
400
|
});
|
|
401
401
|
});
|
|
402
402
|
describe('blob 生命周期', () => {
|
|
403
|
+
test('putBlobWithEntry rolls back the upload when entry creation fails', async () => {
|
|
404
|
+
const backend = memoryBackend();
|
|
405
|
+
const failure = new Error('entry write failed');
|
|
406
|
+
backend.putEntry = async () => {
|
|
407
|
+
throw failure;
|
|
408
|
+
};
|
|
409
|
+
backend.deleteBlob = jest.fn(async () => { });
|
|
410
|
+
const lib = new Library({ sources: [backend] });
|
|
411
|
+
await expect(lib.putBlobWithEntry(new Blob(['model']), model)).rejects.toBe(failure);
|
|
412
|
+
expect(backend.deleteBlob).toHaveBeenCalledWith('blob-x');
|
|
413
|
+
});
|
|
414
|
+
test('putBlobWithEntry preserves a blob referenced by an existing entry after a later failure', async () => {
|
|
415
|
+
const backend = new IndexedDBBackend();
|
|
416
|
+
const lib = new Library({ sources: [backend] });
|
|
417
|
+
const bytes = new Blob(['shared-model-bytes']);
|
|
418
|
+
let sharedBlobId = '';
|
|
419
|
+
await lib.putBlobWithEntry(bytes, (blobId) => {
|
|
420
|
+
sharedBlobId = blobId;
|
|
421
|
+
return { ...model('shared-original'), blobId, contentHash: blobId };
|
|
422
|
+
});
|
|
423
|
+
const failure = new Error('second entry write failed');
|
|
424
|
+
backend.putEntry = async () => {
|
|
425
|
+
throw failure;
|
|
426
|
+
};
|
|
427
|
+
await expect(lib.putBlobWithEntry(bytes, (blobId) => ({
|
|
428
|
+
...model('shared-second'),
|
|
429
|
+
blobId,
|
|
430
|
+
contentHash: blobId,
|
|
431
|
+
}))).rejects.toBe(failure);
|
|
432
|
+
expect(await lib.listBlobIds()).toContain(sharedBlobId);
|
|
433
|
+
expect(await lib.getEntry('shared-original')).toMatchObject({
|
|
434
|
+
blobId: sharedBlobId,
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
test('putBlobWithEntry preserves shared bytes and the original error when any source cannot be enumerated', async () => {
|
|
438
|
+
const backend = new IndexedDBBackend();
|
|
439
|
+
const sourceFailure = new Error('catalog source unavailable');
|
|
440
|
+
let sourceEntries = [];
|
|
441
|
+
let sourceUnavailable = false;
|
|
442
|
+
const unavailableSource = {
|
|
443
|
+
id: 'unavailable-source',
|
|
444
|
+
listEntries: async () => {
|
|
445
|
+
if (sourceUnavailable)
|
|
446
|
+
throw sourceFailure;
|
|
447
|
+
return sourceEntries;
|
|
448
|
+
},
|
|
449
|
+
getEntry: async (id) => sourceEntries.find((entry) => entry.id === id),
|
|
450
|
+
getBlobUrl: async () => {
|
|
451
|
+
throw sourceFailure;
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
const lib = new Library({ sources: [backend, unavailableSource] });
|
|
455
|
+
const bytes = new Blob(['shared-partial-catalog-bytes']);
|
|
456
|
+
let sharedBlobId = '';
|
|
457
|
+
await lib.putBlobWithEntry(bytes, (blobId) => {
|
|
458
|
+
sharedBlobId = blobId;
|
|
459
|
+
return { ...model('shared-partial-original'), blobId, contentHash: blobId };
|
|
460
|
+
});
|
|
461
|
+
sourceEntries = [(await backend.getEntry('shared-partial-original'))];
|
|
462
|
+
await backend.deleteEntry('shared-partial-original');
|
|
463
|
+
sourceUnavailable = true;
|
|
464
|
+
const failure = new Error('partial catalog entry write failed');
|
|
465
|
+
backend.putEntry = async () => {
|
|
466
|
+
throw failure;
|
|
467
|
+
};
|
|
468
|
+
await expect(lib.putBlobWithEntry(bytes, (blobId) => ({
|
|
469
|
+
...model('shared-partial-second'),
|
|
470
|
+
blobId,
|
|
471
|
+
contentHash: blobId,
|
|
472
|
+
}))).rejects.toBe(failure);
|
|
473
|
+
await expect(backend.getBlobUrl(sharedBlobId)).resolves.toBe('blob:mock-url');
|
|
474
|
+
expect(await unavailableSource.getEntry('shared-partial-original')).toMatchObject({
|
|
475
|
+
blobId: sharedBlobId,
|
|
476
|
+
});
|
|
477
|
+
await backend.deleteBlob(sharedBlobId);
|
|
478
|
+
});
|
|
403
479
|
test('deleteEntryDeep: 缩略图被两个模型共享时,删一个不删 blob', async () => {
|
|
404
480
|
const lib = new Library({ sources: [new IndexedDBBackend()] });
|
|
405
481
|
const thumbId = await lib.putBlob(new Blob(['thumb']));
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface BootCtx {
|
|
|
7
7
|
library: Library;
|
|
8
8
|
unregisterEditors: () => void;
|
|
9
9
|
/** 节流通道手动 flush:dispose 时调用,尾部变更不丢 */
|
|
10
|
-
flushState: () => void
|
|
10
|
+
flushState: () => Promise<void>;
|
|
11
11
|
/**
|
|
12
12
|
* 状态通道入口:生产由 project 历史指针订阅喂入,测试可直接调。
|
|
13
13
|
* 尾部节流(缺省 1000ms)后转给 config.state.onStateChange。
|
|
@@ -34,4 +34,5 @@ export declare function isBootedWithDifferentConfig(config: CameraStudioConfig):
|
|
|
34
34
|
export declare function getBootConfig(): CameraStudioConfig | undefined;
|
|
35
35
|
export declare function initCameraStudio(config: CameraStudioConfig): Promise<BootCtx>;
|
|
36
36
|
export declare function disposeCameraStudio(ctx?: BootCtx): Promise<void>;
|
|
37
|
+
export declare function resetCameraStudio(): Promise<void>;
|
|
37
38
|
//# sourceMappingURL=bootstrap.d.ts.map
|
package/dist/bootstrap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAwBA,OAAO,qBAAqB,CAAA;AAS5B,OAAO,KAAK,EAAC,gBAAgB,EAAE,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAKlE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,wBAAwB,CAAA;AAqBnD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,aAAa,CAAA;AAOhD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,iBAAiB,EAAE,MAAM,IAAI,CAAA;IAC7B,sCAAsC;IACtC,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B;;;OAGG;IACH,iBAAiB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C,UAAU,EAAE,gBAAgB,CAAA;IAC5B,uDAAuD;IACvD,GAAG,EAAE,eAAe,CAAA;CACrB;AAaD;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AACD,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAuBD,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,GACzB,gBAAgB,CASlB;AA0CD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAIT;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,IAAI,kBAAkB,GAAG,SAAS,CAE9D;AAoRD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CA6B7E;AAED,wBAAsB,mBAAmB,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAMtE;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4CjD"}
|
package/dist/bootstrap.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 生命周期分层(R14):
|
|
7
7
|
* - boot 级(一次性,initPromise 保留):Library 单例、IDB 连接、词典、
|
|
8
|
-
* theatre.init、project 解析、节流机械。重挂幂等复用,dispose
|
|
8
|
+
* theatre.init、project 解析、节流机械。重挂幂等复用,dispose 不拆;
|
|
9
|
+
* 显式场景切换可在全部消费者卸载后经 resetCameraStudio 清空重 boot。
|
|
9
10
|
* - 会话级(每次挂载 acquire,dispose 拆解):3 个自定义属性编辑器注册、
|
|
10
11
|
* state 通道订阅、studio.ui 显隐。picker 注册表与宿主句柄由各自模块
|
|
11
12
|
* 自管(CameraScene 组件挂载时接线),这里不碰。
|
|
@@ -18,6 +19,8 @@ import theatre, { getProject, getStudio, getStudioSync, onChange, } from '@dream
|
|
|
18
19
|
import '@dreamerbird/studio';
|
|
19
20
|
import { initState } from '@dreamerbird/core/initState';
|
|
20
21
|
import { privateAPI } from '@dreamerbird/core/privateAPIs';
|
|
22
|
+
import projectsSingleton from '@dreamerbird/core/projects/projectsSingleton';
|
|
23
|
+
import getStudioInternal from '@dreamerbird/studio/getStudio';
|
|
21
24
|
import { registerCustomPropEditor, unregisterCustomPropEditor, } from '@dreamerbird/studio/propEditors/customPropEditors';
|
|
22
25
|
import { cameraErrorFromLibrary, cameraErrorFromUnknown } from './errors';
|
|
23
26
|
import { registerCameraMessages } from './i18n';
|
|
@@ -28,9 +31,11 @@ import { StaticCatalogSource } from './assetLibrary/staticCatalogSource';
|
|
|
28
31
|
import { isWritableSource } from './assetLibrary/source';
|
|
29
32
|
import { cleanupLegacySeeds } from './assetLibrary/legacySeedCleanup';
|
|
30
33
|
import { setUiConfig } from './uiConfig';
|
|
34
|
+
import { mountStudioRootWhenReady } from './studioRoot';
|
|
31
35
|
import PosePropEditor from './poseMode/PosePropEditor';
|
|
32
36
|
import { GesturePropEditorLeft, GesturePropEditorRight, } from './gesturePropsEditor';
|
|
33
37
|
import { createAgentRuntime } from './agent/runtime';
|
|
38
|
+
import { clearSceneSheetObjects } from './agent/runtime/objectRuntime';
|
|
34
39
|
import { activateCameraStudioApi, disposeCameraStudioApi, getActiveAgentRegistry, getCameraStudioApi, } from './agent/api';
|
|
35
40
|
import { createAgentBridgeClient } from './agent/bridgeClient';
|
|
36
41
|
/** 内置 IDB 缺省库名(与现状一字不差,硬约束:老用户浏览器里的库零迁移) */
|
|
@@ -40,6 +45,7 @@ let initPromise;
|
|
|
40
45
|
let bootConfig;
|
|
41
46
|
/** 漂移告警只发一次(同页多次重挂不同 config 不刷屏) */
|
|
42
47
|
let warnedConfigDrift = false;
|
|
48
|
+
let resetPromise;
|
|
43
49
|
/** 会话拆解函数;undefined = 当前无活跃会话(acquire 幂等守卫) */
|
|
44
50
|
let sessionTeardown;
|
|
45
51
|
/** 消费者计数由 hook 管(acquire/release 成对);dispose 据此做末位守卫 */
|
|
@@ -65,7 +71,12 @@ function resolveProject(config) {
|
|
|
65
71
|
}
|
|
66
72
|
const name = config.project ?? 'Space';
|
|
67
73
|
const initialState = config.state?.initialState;
|
|
68
|
-
return getProject(name,
|
|
74
|
+
return getProject(name, {
|
|
75
|
+
...(initialState === undefined ? {} : { state: initialState }),
|
|
76
|
+
...(config.ui?.projectName === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: { name: config.ui.projectName }),
|
|
79
|
+
});
|
|
69
80
|
}
|
|
70
81
|
/** sceneProps 同步解析(hook 首帧就要返回;getProject 渲染期调用安全,App 既有先例) */
|
|
71
82
|
export function resolveSceneProps(config) {
|
|
@@ -73,6 +84,9 @@ export function resolveSceneProps(config) {
|
|
|
73
84
|
project: resolveProject(config),
|
|
74
85
|
onFlows: config.flows?.onFlows,
|
|
75
86
|
onError: config.onError,
|
|
87
|
+
...(config.scene?.includeDemoModel === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: { includeDemoModel: config.scene.includeDemoModel }),
|
|
76
90
|
};
|
|
77
91
|
}
|
|
78
92
|
/** 参与漂移比对的顶层配置键(flows 回调引用每次渲染都新,不比) */
|
|
@@ -185,6 +199,13 @@ async function boot(config) {
|
|
|
185
199
|
if (config.ui?.hideLeftStrip) {
|
|
186
200
|
withStudioUi((ui) => ui.setLeftStripHidden(true));
|
|
187
201
|
}
|
|
202
|
+
if (config.ui?.panels) {
|
|
203
|
+
const panels = config.ui.panels;
|
|
204
|
+
withStudioUi((ui) => ui.setPanelVisibility(panels));
|
|
205
|
+
}
|
|
206
|
+
withStudioUi(() => {
|
|
207
|
+
void mountStudioRootWhenReady(config.ui?.portalContainer);
|
|
208
|
+
});
|
|
188
209
|
// 1. 数据源:可写源(若有) + 内置目录,聚合进 Library 单例
|
|
189
210
|
const { sources, idbName } = resolveSources(config);
|
|
190
211
|
const catalog = new StaticCatalogSource({
|
|
@@ -238,7 +259,7 @@ async function boot(config) {
|
|
|
238
259
|
let pendingContent;
|
|
239
260
|
let hasPending = false;
|
|
240
261
|
let timer;
|
|
241
|
-
const flushState = () => {
|
|
262
|
+
const flushState = async () => {
|
|
242
263
|
if (timer !== undefined) {
|
|
243
264
|
clearTimeout(timer);
|
|
244
265
|
timer = undefined;
|
|
@@ -248,7 +269,7 @@ async function boot(config) {
|
|
|
248
269
|
hasPending = false;
|
|
249
270
|
const content = pendingContent;
|
|
250
271
|
pendingContent = undefined;
|
|
251
|
-
config.state?.onStateChange?.(content);
|
|
272
|
+
await config.state?.onStateChange?.(content);
|
|
252
273
|
};
|
|
253
274
|
const notifyStateChange = (content) => {
|
|
254
275
|
if (!config.state?.onStateChange)
|
|
@@ -277,7 +298,7 @@ async function boot(config) {
|
|
|
277
298
|
}
|
|
278
299
|
/** studio.ui 显隐操作:studio 未挂载(宿主以 studio:false init 等)时静默降级 */
|
|
279
300
|
function withStudioUi(fn) {
|
|
280
|
-
Promise.resolve()
|
|
301
|
+
return Promise.resolve()
|
|
281
302
|
.then(() => getStudio())
|
|
282
303
|
.then(async (studio) => {
|
|
283
304
|
// 必须等 studio 初始化收尾(持久化/同步状态恢复落库)再读写显隐:
|
|
@@ -352,11 +373,13 @@ function acquireSession(ctx) {
|
|
|
352
373
|
ctx.unregisterEditors();
|
|
353
374
|
unsubState?.();
|
|
354
375
|
// 先断订阅再 flush:尾部变更带给宿主,不丢
|
|
355
|
-
ctx.flushState();
|
|
356
|
-
withStudioUi((ui) => ui.hide());
|
|
376
|
+
await ctx.flushState();
|
|
377
|
+
await withStudioUi((ui) => ui.hide());
|
|
357
378
|
};
|
|
358
379
|
}
|
|
359
380
|
export function initCameraStudio(config) {
|
|
381
|
+
if (resetPromise)
|
|
382
|
+
return resetPromise.then(() => initCameraStudio(config));
|
|
360
383
|
if (!initPromise) {
|
|
361
384
|
bootConfig = config;
|
|
362
385
|
const p = boot(config);
|
|
@@ -392,3 +415,42 @@ export async function disposeCameraStudio(ctx) {
|
|
|
392
415
|
return;
|
|
393
416
|
await sessionTeardown?.();
|
|
394
417
|
}
|
|
418
|
+
export function resetCameraStudio() {
|
|
419
|
+
if (resetPromise)
|
|
420
|
+
return resetPromise;
|
|
421
|
+
const previousInit = initPromise;
|
|
422
|
+
resetPromise = (async () => {
|
|
423
|
+
if (activeConsumers > 0) {
|
|
424
|
+
throw new Error('resetCameraStudio requires all camera-studio consumers to be unmounted');
|
|
425
|
+
}
|
|
426
|
+
const previousCtx = previousInit
|
|
427
|
+
? await previousInit.catch(() => undefined)
|
|
428
|
+
: undefined;
|
|
429
|
+
if (activeConsumers > 0) {
|
|
430
|
+
throw new Error('resetCameraStudio requires all camera-studio consumers to be unmounted');
|
|
431
|
+
}
|
|
432
|
+
await sessionTeardown?.();
|
|
433
|
+
clearSceneSheetObjects();
|
|
434
|
+
if (previousCtx) {
|
|
435
|
+
const projectId = previousCtx.sceneProps.project.address.projectId;
|
|
436
|
+
projectsSingleton.remove(projectId);
|
|
437
|
+
const studio = getStudioInternal();
|
|
438
|
+
if (studio) {
|
|
439
|
+
studio.transaction(({ stateEditors }) => {
|
|
440
|
+
stateEditors.deleteProject({ projectId });
|
|
441
|
+
}, false);
|
|
442
|
+
studio.ephemeralAtom.reduceByPointer((p) => p.coreByProject, (projects) => {
|
|
443
|
+
const next = { ...projects };
|
|
444
|
+
delete next[projectId];
|
|
445
|
+
return next;
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
initPromise = undefined;
|
|
450
|
+
bootConfig = undefined;
|
|
451
|
+
warnedConfigDrift = false;
|
|
452
|
+
})().finally(() => {
|
|
453
|
+
resetPromise = undefined;
|
|
454
|
+
});
|
|
455
|
+
return resetPromise;
|
|
456
|
+
}
|