@vizij/render 0.1.0 → 0.1.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.
- package/dist/index.d.mts +39 -2
- package/dist/index.d.ts +39 -2
- package/dist/index.js +372 -41
- package/dist/index.mjs +371 -41
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -357,12 +357,40 @@ interface VizijBundleAnimationEntry {
|
|
|
357
357
|
[key: string]: unknown;
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
|
+
interface VizijSpeechConfig {
|
|
361
|
+
/** TTS voice name (e.g., "Ruth") */
|
|
362
|
+
voice?: string;
|
|
363
|
+
/** Speech mode */
|
|
364
|
+
mode?: "echo" | "conversation";
|
|
365
|
+
/** Agent name for LLM system prompt */
|
|
366
|
+
agentName?: string;
|
|
367
|
+
/** Custom system prompt (supports {{agent_name}} template) */
|
|
368
|
+
systemPrompt?: string;
|
|
369
|
+
/** Input path for avatar-speaking state (default: /speech/speaking) */
|
|
370
|
+
speakingInputPath?: string;
|
|
371
|
+
/** Input path for user-speaking state (default: /speech/user_speaking) */
|
|
372
|
+
userSpeakingInputPath?: string;
|
|
373
|
+
/** Input path for thinking state (default: /speech/thinking) */
|
|
374
|
+
thinkingInputPath?: string;
|
|
375
|
+
/** Pose group ID for viseme mapping */
|
|
376
|
+
visemeGroupId?: string;
|
|
377
|
+
/** Pose group ID for emotion mapping */
|
|
378
|
+
emotionGroupId?: string;
|
|
379
|
+
/** TTS API base URL */
|
|
380
|
+
apiBaseUrl?: string;
|
|
381
|
+
/** Auto-activate microphone when speech is ready (default: false) */
|
|
382
|
+
autoActivateMic?: boolean;
|
|
383
|
+
}
|
|
360
384
|
interface VizijBundleExtension {
|
|
361
385
|
version: VizijBundleVersion;
|
|
362
386
|
exportedAt?: string;
|
|
363
387
|
graphs?: VizijBundleGraphEntry[];
|
|
364
388
|
poses?: VizijBundlePoseSection | null;
|
|
365
389
|
animations?: VizijBundleAnimationEntry[];
|
|
390
|
+
/**
|
|
391
|
+
* Bundle-level metadata. May include `speechConfig: VizijSpeechConfig`
|
|
392
|
+
* for configuring the STT/LLM/TTS speech pipeline.
|
|
393
|
+
*/
|
|
366
394
|
metadata?: Record<string, unknown>;
|
|
367
395
|
}
|
|
368
396
|
|
|
@@ -571,6 +599,12 @@ declare function useVizijStoreGetter(): () => VizijData & VizijActions;
|
|
|
571
599
|
declare class EmptyModelError extends Error {
|
|
572
600
|
constructor(message: string);
|
|
573
601
|
}
|
|
602
|
+
type ParserJsonFallbackSource = {
|
|
603
|
+
url?: string;
|
|
604
|
+
blob?: Blob;
|
|
605
|
+
arrayBuffer?: ArrayBuffer;
|
|
606
|
+
};
|
|
607
|
+
declare function parseGlbJsonChunk(buffer: ArrayBuffer): unknown | undefined;
|
|
574
608
|
declare function loadGLTF(url: string, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
|
|
575
609
|
center: RawVector2;
|
|
576
610
|
size: RawVector2;
|
|
@@ -584,11 +618,12 @@ type LoadedVizijAsset = {
|
|
|
584
618
|
animatables: Record<string, AnimatableValue>;
|
|
585
619
|
bundle: VizijBundleExtension | null;
|
|
586
620
|
animations: VizijAnimationClipData[];
|
|
621
|
+
scene: Group$1;
|
|
587
622
|
};
|
|
588
623
|
declare function loadGLTFWithBundle(url: string, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
|
|
589
624
|
center: RawVector2;
|
|
590
625
|
size: RawVector2;
|
|
591
|
-
}): Promise<LoadedVizijAsset>;
|
|
626
|
+
}, parserJsonFallback?: ParserJsonFallbackSource): Promise<LoadedVizijAsset>;
|
|
592
627
|
declare function loadGLTFFromBlobWithBundle(blob: Blob, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
|
|
593
628
|
center: RawVector2;
|
|
594
629
|
size: RawVector2;
|
|
@@ -607,10 +642,12 @@ type ExportSceneOptions = {
|
|
|
607
642
|
bundle?: VizijBundleExtension | null;
|
|
608
643
|
animations?: AnimationClip[];
|
|
609
644
|
binary?: boolean;
|
|
645
|
+
onError?: (error: Error) => void;
|
|
646
|
+
onComplete?: () => void;
|
|
610
647
|
};
|
|
611
648
|
declare function exportScene(data: Group$1, fileNameOrOptions?: string | ExportSceneOptions): void;
|
|
612
649
|
|
|
613
650
|
declare function extractVizijBundle(object: Object3D, parserJson?: unknown): VizijBundleExtension | null;
|
|
614
651
|
declare function applyVizijBundle(object: Object3D, bundle: VizijBundleExtension | null): () => void;
|
|
615
652
|
|
|
616
|
-
export { type AnimatedFeature, type Ellipse, type EllipseFeature, EmptyModelError, type ExportSceneOptions, type Feature, type Group, type GroupFeature, InnerVizij, type InnerVizijProps, type LoadedVizijAsset, type Rectangle, type RectangleFeature, type RenderableBase, type RenderableFeature, type Selection, type Shape, type ShapeFeature, ShapeMaterial, type StaticFeature, type Stored, type StoredAnimatedFeature, type StoredEllipse, type StoredFeatures, type StoredGroup, type StoredRectangle, type StoredRenderable, type StoredShape, Vizij, type VizijActions, type VizijAnimationClipData, type VizijAnimationId, type VizijAnimationTrackData, type VizijBundleAnimationClip, type VizijBundleAnimationEntry, type VizijBundleAnimationKeyframe, type VizijBundleAnimationTrack, type VizijBundleExtension, type VizijBundleGraphEntry, type VizijBundleGraphKind, type VizijBundleGraphMetadata, type VizijBundlePoseSection, type VizijBundleVersion, VizijContext, type VizijData, type VizijGraphId, type VizijPoseDefinition, type VizijPoseId, type VizijPoseRigConfig, type VizijProps, VizijSlice, type VizijStore, type VizijStoreGetter, type VizijStoreSetter, type World, applyVizijBundle, createVizijStore, exportScene, extractVizijBundle, loadGLTF, loadGLTFFromBlob, loadGLTFFromBlobWithBundle, loadGLTFWithBundle, loadGltfFromBlob, useDefaultVizijStore, useFeatures, useVizijStore, useVizijStoreGetter, useVizijStoreSetter, useVizijStoreSubscription };
|
|
653
|
+
export { type AnimatedFeature, type Ellipse, type EllipseFeature, EmptyModelError, type ExportSceneOptions, type Feature, type Group, type GroupFeature, InnerVizij, type InnerVizijProps, type LoadedVizijAsset, type Rectangle, type RectangleFeature, type RenderableBase, type RenderableFeature, type Selection, type Shape, type ShapeFeature, ShapeMaterial, type StaticFeature, type Stored, type StoredAnimatedFeature, type StoredEllipse, type StoredFeatures, type StoredGroup, type StoredRectangle, type StoredRenderable, type StoredShape, Vizij, type VizijActions, type VizijAnimationClipData, type VizijAnimationId, type VizijAnimationTrackData, type VizijBundleAnimationClip, type VizijBundleAnimationEntry, type VizijBundleAnimationKeyframe, type VizijBundleAnimationTrack, type VizijBundleExtension, type VizijBundleGraphEntry, type VizijBundleGraphKind, type VizijBundleGraphMetadata, type VizijBundlePoseSection, type VizijBundleVersion, VizijContext, type VizijData, type VizijGraphId, type VizijPoseDefinition, type VizijPoseId, type VizijPoseRigConfig, type VizijProps, VizijSlice, type VizijSpeechConfig, type VizijStore, type VizijStoreGetter, type VizijStoreSetter, type World, applyVizijBundle, createVizijStore, exportScene, extractVizijBundle, loadGLTF, loadGLTFFromBlob, loadGLTFFromBlobWithBundle, loadGLTFWithBundle, loadGltfFromBlob, parseGlbJsonChunk, useDefaultVizijStore, useFeatures, useVizijStore, useVizijStoreGetter, useVizijStoreSetter, useVizijStoreSubscription };
|
package/dist/index.d.ts
CHANGED
|
@@ -357,12 +357,40 @@ interface VizijBundleAnimationEntry {
|
|
|
357
357
|
[key: string]: unknown;
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
|
+
interface VizijSpeechConfig {
|
|
361
|
+
/** TTS voice name (e.g., "Ruth") */
|
|
362
|
+
voice?: string;
|
|
363
|
+
/** Speech mode */
|
|
364
|
+
mode?: "echo" | "conversation";
|
|
365
|
+
/** Agent name for LLM system prompt */
|
|
366
|
+
agentName?: string;
|
|
367
|
+
/** Custom system prompt (supports {{agent_name}} template) */
|
|
368
|
+
systemPrompt?: string;
|
|
369
|
+
/** Input path for avatar-speaking state (default: /speech/speaking) */
|
|
370
|
+
speakingInputPath?: string;
|
|
371
|
+
/** Input path for user-speaking state (default: /speech/user_speaking) */
|
|
372
|
+
userSpeakingInputPath?: string;
|
|
373
|
+
/** Input path for thinking state (default: /speech/thinking) */
|
|
374
|
+
thinkingInputPath?: string;
|
|
375
|
+
/** Pose group ID for viseme mapping */
|
|
376
|
+
visemeGroupId?: string;
|
|
377
|
+
/** Pose group ID for emotion mapping */
|
|
378
|
+
emotionGroupId?: string;
|
|
379
|
+
/** TTS API base URL */
|
|
380
|
+
apiBaseUrl?: string;
|
|
381
|
+
/** Auto-activate microphone when speech is ready (default: false) */
|
|
382
|
+
autoActivateMic?: boolean;
|
|
383
|
+
}
|
|
360
384
|
interface VizijBundleExtension {
|
|
361
385
|
version: VizijBundleVersion;
|
|
362
386
|
exportedAt?: string;
|
|
363
387
|
graphs?: VizijBundleGraphEntry[];
|
|
364
388
|
poses?: VizijBundlePoseSection | null;
|
|
365
389
|
animations?: VizijBundleAnimationEntry[];
|
|
390
|
+
/**
|
|
391
|
+
* Bundle-level metadata. May include `speechConfig: VizijSpeechConfig`
|
|
392
|
+
* for configuring the STT/LLM/TTS speech pipeline.
|
|
393
|
+
*/
|
|
366
394
|
metadata?: Record<string, unknown>;
|
|
367
395
|
}
|
|
368
396
|
|
|
@@ -571,6 +599,12 @@ declare function useVizijStoreGetter(): () => VizijData & VizijActions;
|
|
|
571
599
|
declare class EmptyModelError extends Error {
|
|
572
600
|
constructor(message: string);
|
|
573
601
|
}
|
|
602
|
+
type ParserJsonFallbackSource = {
|
|
603
|
+
url?: string;
|
|
604
|
+
blob?: Blob;
|
|
605
|
+
arrayBuffer?: ArrayBuffer;
|
|
606
|
+
};
|
|
607
|
+
declare function parseGlbJsonChunk(buffer: ArrayBuffer): unknown | undefined;
|
|
574
608
|
declare function loadGLTF(url: string, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
|
|
575
609
|
center: RawVector2;
|
|
576
610
|
size: RawVector2;
|
|
@@ -584,11 +618,12 @@ type LoadedVizijAsset = {
|
|
|
584
618
|
animatables: Record<string, AnimatableValue>;
|
|
585
619
|
bundle: VizijBundleExtension | null;
|
|
586
620
|
animations: VizijAnimationClipData[];
|
|
621
|
+
scene: Group$1;
|
|
587
622
|
};
|
|
588
623
|
declare function loadGLTFWithBundle(url: string, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
|
|
589
624
|
center: RawVector2;
|
|
590
625
|
size: RawVector2;
|
|
591
|
-
}): Promise<LoadedVizijAsset>;
|
|
626
|
+
}, parserJsonFallback?: ParserJsonFallbackSource): Promise<LoadedVizijAsset>;
|
|
592
627
|
declare function loadGLTFFromBlobWithBundle(blob: Blob, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
|
|
593
628
|
center: RawVector2;
|
|
594
629
|
size: RawVector2;
|
|
@@ -607,10 +642,12 @@ type ExportSceneOptions = {
|
|
|
607
642
|
bundle?: VizijBundleExtension | null;
|
|
608
643
|
animations?: AnimationClip[];
|
|
609
644
|
binary?: boolean;
|
|
645
|
+
onError?: (error: Error) => void;
|
|
646
|
+
onComplete?: () => void;
|
|
610
647
|
};
|
|
611
648
|
declare function exportScene(data: Group$1, fileNameOrOptions?: string | ExportSceneOptions): void;
|
|
612
649
|
|
|
613
650
|
declare function extractVizijBundle(object: Object3D, parserJson?: unknown): VizijBundleExtension | null;
|
|
614
651
|
declare function applyVizijBundle(object: Object3D, bundle: VizijBundleExtension | null): () => void;
|
|
615
652
|
|
|
616
|
-
export { type AnimatedFeature, type Ellipse, type EllipseFeature, EmptyModelError, type ExportSceneOptions, type Feature, type Group, type GroupFeature, InnerVizij, type InnerVizijProps, type LoadedVizijAsset, type Rectangle, type RectangleFeature, type RenderableBase, type RenderableFeature, type Selection, type Shape, type ShapeFeature, ShapeMaterial, type StaticFeature, type Stored, type StoredAnimatedFeature, type StoredEllipse, type StoredFeatures, type StoredGroup, type StoredRectangle, type StoredRenderable, type StoredShape, Vizij, type VizijActions, type VizijAnimationClipData, type VizijAnimationId, type VizijAnimationTrackData, type VizijBundleAnimationClip, type VizijBundleAnimationEntry, type VizijBundleAnimationKeyframe, type VizijBundleAnimationTrack, type VizijBundleExtension, type VizijBundleGraphEntry, type VizijBundleGraphKind, type VizijBundleGraphMetadata, type VizijBundlePoseSection, type VizijBundleVersion, VizijContext, type VizijData, type VizijGraphId, type VizijPoseDefinition, type VizijPoseId, type VizijPoseRigConfig, type VizijProps, VizijSlice, type VizijStore, type VizijStoreGetter, type VizijStoreSetter, type World, applyVizijBundle, createVizijStore, exportScene, extractVizijBundle, loadGLTF, loadGLTFFromBlob, loadGLTFFromBlobWithBundle, loadGLTFWithBundle, loadGltfFromBlob, useDefaultVizijStore, useFeatures, useVizijStore, useVizijStoreGetter, useVizijStoreSetter, useVizijStoreSubscription };
|
|
653
|
+
export { type AnimatedFeature, type Ellipse, type EllipseFeature, EmptyModelError, type ExportSceneOptions, type Feature, type Group, type GroupFeature, InnerVizij, type InnerVizijProps, type LoadedVizijAsset, type Rectangle, type RectangleFeature, type RenderableBase, type RenderableFeature, type Selection, type Shape, type ShapeFeature, ShapeMaterial, type StaticFeature, type Stored, type StoredAnimatedFeature, type StoredEllipse, type StoredFeatures, type StoredGroup, type StoredRectangle, type StoredRenderable, type StoredShape, Vizij, type VizijActions, type VizijAnimationClipData, type VizijAnimationId, type VizijAnimationTrackData, type VizijBundleAnimationClip, type VizijBundleAnimationEntry, type VizijBundleAnimationKeyframe, type VizijBundleAnimationTrack, type VizijBundleExtension, type VizijBundleGraphEntry, type VizijBundleGraphKind, type VizijBundleGraphMetadata, type VizijBundlePoseSection, type VizijBundleVersion, VizijContext, type VizijData, type VizijGraphId, type VizijPoseDefinition, type VizijPoseId, type VizijPoseRigConfig, type VizijProps, VizijSlice, type VizijSpeechConfig, type VizijStore, type VizijStoreGetter, type VizijStoreSetter, type World, applyVizijBundle, createVizijStore, exportScene, extractVizijBundle, loadGLTF, loadGLTFFromBlob, loadGLTFFromBlobWithBundle, loadGLTFWithBundle, loadGltfFromBlob, parseGlbJsonChunk, useDefaultVizijStore, useFeatures, useVizijStore, useVizijStoreGetter, useVizijStoreSetter, useVizijStoreSubscription };
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ __export(index_exports, {
|
|
|
45
45
|
loadGLTFFromBlobWithBundle: () => loadGLTFFromBlobWithBundle,
|
|
46
46
|
loadGLTFWithBundle: () => loadGLTFWithBundle,
|
|
47
47
|
loadGltfFromBlob: () => loadGltfFromBlob,
|
|
48
|
+
parseGlbJsonChunk: () => parseGlbJsonChunk,
|
|
48
49
|
useDefaultVizijStore: () => useDefaultVizijStore,
|
|
49
50
|
useFeatures: () => useFeatures,
|
|
50
51
|
useVizijStore: () => useVizijStore,
|
|
@@ -1434,6 +1435,35 @@ function createAnimatable(value) {
|
|
|
1434
1435
|
}
|
|
1435
1436
|
createAnimatable({ type: "euler", name: "Rotation" });
|
|
1436
1437
|
|
|
1438
|
+
// src/functions/exportable-bodies.ts
|
|
1439
|
+
function asGroupEntries(world) {
|
|
1440
|
+
return Object.values(world).filter((entry) => entry.type === "group");
|
|
1441
|
+
}
|
|
1442
|
+
function selectExportableGroupEntries(world, filterIds) {
|
|
1443
|
+
const groupEntries = asGroupEntries(world);
|
|
1444
|
+
const filterSet = Array.isArray(filterIds) && filterIds.length > 0 ? new Set(filterIds) : null;
|
|
1445
|
+
if (filterSet) {
|
|
1446
|
+
return groupEntries.filter((entry) => filterSet.has(entry.id));
|
|
1447
|
+
}
|
|
1448
|
+
const rootBoundsGroups = groupEntries.filter(
|
|
1449
|
+
(entry) => Boolean(entry.rootBounds)
|
|
1450
|
+
);
|
|
1451
|
+
if (rootBoundsGroups.length > 0) {
|
|
1452
|
+
return rootBoundsGroups;
|
|
1453
|
+
}
|
|
1454
|
+
const explicitRootGroups = groupEntries.filter(
|
|
1455
|
+
(entry) => entry.root === true
|
|
1456
|
+
);
|
|
1457
|
+
if (explicitRootGroups.length > 0) {
|
|
1458
|
+
return explicitRootGroups;
|
|
1459
|
+
}
|
|
1460
|
+
const topLevelGroups = groupEntries.filter((entry) => !entry.parent);
|
|
1461
|
+
if (topLevelGroups.length > 0) {
|
|
1462
|
+
return topLevelGroups;
|
|
1463
|
+
}
|
|
1464
|
+
return groupEntries;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1437
1467
|
// src/store.ts
|
|
1438
1468
|
THREE3.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
1439
1469
|
(0, import_immer.enableMapSet)();
|
|
@@ -1509,23 +1539,17 @@ var VizijSlice = (set, get) => ({
|
|
|
1509
1539
|
},
|
|
1510
1540
|
getExportableBodies: (filterIds) => {
|
|
1511
1541
|
const worldData = get().world;
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
const firstNs = Object.keys(entry.refs)[0];
|
|
1524
|
-
const refGroup = entry.refs[firstNs].current;
|
|
1525
|
-
return refGroup;
|
|
1526
|
-
});
|
|
1527
|
-
return bodies;
|
|
1528
|
-
}
|
|
1542
|
+
const candidateGroups = selectExportableGroupEntries(
|
|
1543
|
+
worldData,
|
|
1544
|
+
filterIds
|
|
1545
|
+
);
|
|
1546
|
+
return candidateGroups.flatMap((entry) => {
|
|
1547
|
+
const refs = Object.values(
|
|
1548
|
+
entry.refs ?? {}
|
|
1549
|
+
);
|
|
1550
|
+
const resolved = refs.find((ref) => ref?.current)?.current ?? null;
|
|
1551
|
+
return resolved ? [resolved] : [];
|
|
1552
|
+
});
|
|
1529
1553
|
},
|
|
1530
1554
|
setGeometry: (id, geometry) => {
|
|
1531
1555
|
set(
|
|
@@ -2304,6 +2328,15 @@ function importMesh(mesh, namespaces, colorLookup) {
|
|
|
2304
2328
|
world = { ...world, ...newWorldItems };
|
|
2305
2329
|
animatables = { ...animatables, ...newAnimatables };
|
|
2306
2330
|
children.push(childId);
|
|
2331
|
+
} else if (shouldImportAsGroupChild(child)) {
|
|
2332
|
+
const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
|
|
2333
|
+
...colorLookup,
|
|
2334
|
+
...newColorLookup
|
|
2335
|
+
});
|
|
2336
|
+
newColorLookup = { ...newColorLookup, ...newMeshColors };
|
|
2337
|
+
world = { ...world, ...newWorldItems };
|
|
2338
|
+
animatables = { ...animatables, ...newAnimatables };
|
|
2339
|
+
children.push(childId);
|
|
2307
2340
|
}
|
|
2308
2341
|
});
|
|
2309
2342
|
const newShape = {
|
|
@@ -2347,6 +2380,24 @@ function getShapeMaterial(mesh, useEmissive) {
|
|
|
2347
2380
|
return "standard" /* Standard */;
|
|
2348
2381
|
}
|
|
2349
2382
|
}
|
|
2383
|
+
function shouldImportAsGroupChild(child) {
|
|
2384
|
+
if (!child.isObject3D) {
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
if (child.isMesh) {
|
|
2388
|
+
return false;
|
|
2389
|
+
}
|
|
2390
|
+
if (child.isCamera) {
|
|
2391
|
+
return false;
|
|
2392
|
+
}
|
|
2393
|
+
if (child.isLight) {
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
if (child.isBone) {
|
|
2397
|
+
return false;
|
|
2398
|
+
}
|
|
2399
|
+
return true;
|
|
2400
|
+
}
|
|
2350
2401
|
|
|
2351
2402
|
// src/functions/gltf-loading/import-group.ts
|
|
2352
2403
|
import_three4.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
@@ -2406,7 +2457,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
|
|
|
2406
2457
|
world = { ...world, ...newWorldItems };
|
|
2407
2458
|
animatables = { ...animatables, ...newAnimatables };
|
|
2408
2459
|
children.push(childId);
|
|
2409
|
-
} else if (child
|
|
2460
|
+
} else if (shouldImportAsGroupChild2(child)) {
|
|
2410
2461
|
const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
|
|
2411
2462
|
...colorLookup,
|
|
2412
2463
|
...newColorLookup
|
|
@@ -2435,6 +2486,24 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
|
|
|
2435
2486
|
world = { ...world, [newGroup.id]: newGroup };
|
|
2436
2487
|
return [world, animatables, newGroup.id, newColorLookup];
|
|
2437
2488
|
}
|
|
2489
|
+
function shouldImportAsGroupChild2(child) {
|
|
2490
|
+
if (!child.isObject3D) {
|
|
2491
|
+
return false;
|
|
2492
|
+
}
|
|
2493
|
+
if (child.isMesh) {
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
if (child.isCamera) {
|
|
2497
|
+
return false;
|
|
2498
|
+
}
|
|
2499
|
+
if (child.isLight) {
|
|
2500
|
+
return false;
|
|
2501
|
+
}
|
|
2502
|
+
if (child.isBone) {
|
|
2503
|
+
return false;
|
|
2504
|
+
}
|
|
2505
|
+
return true;
|
|
2506
|
+
}
|
|
2438
2507
|
|
|
2439
2508
|
// src/functions/gltf-loading/import-scene.ts
|
|
2440
2509
|
import_three5.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
@@ -2745,6 +2814,13 @@ function searchParserJsonForBundle(parserJson) {
|
|
|
2745
2814
|
if (!parserJson || typeof parserJson !== "object") {
|
|
2746
2815
|
return null;
|
|
2747
2816
|
}
|
|
2817
|
+
const rootExtensions = parserJson && typeof parserJson === "object" ? parserJson.extensions : null;
|
|
2818
|
+
if (rootExtensions && typeof rootExtensions === "object") {
|
|
2819
|
+
const match = readExtensionValue(rootExtensions);
|
|
2820
|
+
if (match) {
|
|
2821
|
+
return cloneBundle(match.value);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2748
2824
|
const nodes = Array.isArray(parserJson.nodes) ? parserJson.nodes : [];
|
|
2749
2825
|
for (const node of nodes) {
|
|
2750
2826
|
const extensions = node && typeof node === "object" ? node.extensions : null;
|
|
@@ -3180,23 +3256,94 @@ function extractVizijAnimations(parserJson, clips) {
|
|
|
3180
3256
|
|
|
3181
3257
|
// src/functions/load-gltf.ts
|
|
3182
3258
|
THREE5.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
3259
|
+
var GLB_MAGIC = 1179937895;
|
|
3260
|
+
var GLB_VERSION = 2;
|
|
3261
|
+
var GLB_HEADER_BYTES = 12;
|
|
3262
|
+
var GLB_CHUNK_HEADER_BYTES = 8;
|
|
3263
|
+
var GLB_JSON_CHUNK_TYPE = 1313821514;
|
|
3183
3264
|
var EmptyModelError = class extends Error {
|
|
3184
3265
|
constructor(message) {
|
|
3185
3266
|
super(message);
|
|
3186
3267
|
this.name = "EmptyModelError";
|
|
3187
3268
|
}
|
|
3188
3269
|
};
|
|
3270
|
+
function parseGlbJsonChunk(buffer) {
|
|
3271
|
+
if (buffer.byteLength < GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES) {
|
|
3272
|
+
return void 0;
|
|
3273
|
+
}
|
|
3274
|
+
const view = new DataView(buffer);
|
|
3275
|
+
const magic = view.getUint32(0, true);
|
|
3276
|
+
const version = view.getUint32(4, true);
|
|
3277
|
+
if (magic !== GLB_MAGIC || version !== GLB_VERSION) {
|
|
3278
|
+
return void 0;
|
|
3279
|
+
}
|
|
3280
|
+
const chunkLength = view.getUint32(GLB_HEADER_BYTES, true);
|
|
3281
|
+
const chunkType = view.getUint32(GLB_HEADER_BYTES + 4, true);
|
|
3282
|
+
if (chunkType !== GLB_JSON_CHUNK_TYPE) {
|
|
3283
|
+
return void 0;
|
|
3284
|
+
}
|
|
3285
|
+
const chunkStart = GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES;
|
|
3286
|
+
const chunkEnd = chunkStart + chunkLength;
|
|
3287
|
+
if (chunkEnd > buffer.byteLength) {
|
|
3288
|
+
return void 0;
|
|
3289
|
+
}
|
|
3290
|
+
try {
|
|
3291
|
+
const chunkBytes = new Uint8Array(buffer, chunkStart, chunkLength);
|
|
3292
|
+
const jsonText = new TextDecoder().decode(chunkBytes);
|
|
3293
|
+
return JSON.parse(jsonText);
|
|
3294
|
+
} catch {
|
|
3295
|
+
return void 0;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
async function resolveParserJson(parserJson, fallback) {
|
|
3299
|
+
if (parserJson && typeof parserJson === "object") {
|
|
3300
|
+
return parserJson;
|
|
3301
|
+
}
|
|
3302
|
+
if (fallback.arrayBuffer) {
|
|
3303
|
+
const fromArrayBuffer = parseGlbJsonChunk(fallback.arrayBuffer);
|
|
3304
|
+
if (fromArrayBuffer && typeof fromArrayBuffer === "object") {
|
|
3305
|
+
return fromArrayBuffer;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
if (fallback.blob) {
|
|
3309
|
+
try {
|
|
3310
|
+
const blobBuffer = typeof fallback.blob.arrayBuffer === "function" ? await fallback.blob.arrayBuffer() : await new Response(fallback.blob).arrayBuffer();
|
|
3311
|
+
const fromBlob = parseGlbJsonChunk(blobBuffer);
|
|
3312
|
+
if (fromBlob && typeof fromBlob === "object") {
|
|
3313
|
+
return fromBlob;
|
|
3314
|
+
}
|
|
3315
|
+
} catch {
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
if (fallback.url && typeof fetch === "function") {
|
|
3319
|
+
try {
|
|
3320
|
+
const response = await fetch(fallback.url);
|
|
3321
|
+
if (response.ok) {
|
|
3322
|
+
const binary = await response.arrayBuffer();
|
|
3323
|
+
const fromUrl = parseGlbJsonChunk(binary);
|
|
3324
|
+
if (fromUrl && typeof fromUrl === "object") {
|
|
3325
|
+
return fromUrl;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
} catch {
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
return parserJson;
|
|
3332
|
+
}
|
|
3189
3333
|
async function loadGLTF(url, namespaces, aggressiveImport = false, rootBounds) {
|
|
3190
3334
|
const modelLoader = new import_three_stdlib.GLTFLoader();
|
|
3191
3335
|
modelLoader.setDRACOLoader(new import_three_stdlib.DRACOLoader());
|
|
3192
3336
|
const modelData = await modelLoader.loadAsync(url);
|
|
3337
|
+
const parserJson = await resolveParserJson(modelData?.parser?.json, {
|
|
3338
|
+
url
|
|
3339
|
+
});
|
|
3193
3340
|
const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
|
|
3194
3341
|
const asset = parseScene(
|
|
3195
3342
|
modelData.scene,
|
|
3196
3343
|
actualizedNamespaces,
|
|
3197
3344
|
aggressiveImport,
|
|
3198
3345
|
rootBounds,
|
|
3199
|
-
|
|
3346
|
+
parserJson,
|
|
3200
3347
|
modelData.animations
|
|
3201
3348
|
);
|
|
3202
3349
|
return [asset.world, asset.animatables, asset.animations];
|
|
@@ -3210,7 +3357,8 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
|
|
|
3210
3357
|
objectUrl,
|
|
3211
3358
|
actualizedNamespaces,
|
|
3212
3359
|
aggressiveImport,
|
|
3213
|
-
rootBounds
|
|
3360
|
+
rootBounds,
|
|
3361
|
+
{ blob }
|
|
3214
3362
|
);
|
|
3215
3363
|
return [asset.world, asset.animatables, asset.animations];
|
|
3216
3364
|
} finally {
|
|
@@ -3224,14 +3372,18 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
|
|
|
3224
3372
|
loader.parse(
|
|
3225
3373
|
arrayBuffer,
|
|
3226
3374
|
"",
|
|
3227
|
-
(gltf) => {
|
|
3375
|
+
async (gltf) => {
|
|
3228
3376
|
try {
|
|
3377
|
+
const parserJson = await resolveParserJson(
|
|
3378
|
+
gltf?.parser?.json,
|
|
3379
|
+
{ arrayBuffer }
|
|
3380
|
+
);
|
|
3229
3381
|
const asset = parseScene(
|
|
3230
3382
|
gltf.scene,
|
|
3231
3383
|
actualizedNamespaces,
|
|
3232
3384
|
aggressiveImport,
|
|
3233
3385
|
rootBounds,
|
|
3234
|
-
|
|
3386
|
+
parserJson,
|
|
3235
3387
|
gltf.animations
|
|
3236
3388
|
);
|
|
3237
3389
|
resolve([asset.world, asset.animatables, asset.animations]);
|
|
@@ -3258,19 +3410,23 @@ function parseScene(scene, namespaces, aggressiveImport, rootBounds, parserJson,
|
|
|
3258
3410
|
);
|
|
3259
3411
|
const bundle = extractVizijBundle(scene, parserJson);
|
|
3260
3412
|
const animations = extractVizijAnimations(parserJson, clips);
|
|
3261
|
-
return { world, animatables, bundle, animations };
|
|
3413
|
+
return { world, animatables, bundle, animations, scene };
|
|
3262
3414
|
}
|
|
3263
|
-
async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds) {
|
|
3415
|
+
async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds, parserJsonFallback) {
|
|
3264
3416
|
const modelLoader = new import_three_stdlib.GLTFLoader();
|
|
3265
3417
|
modelLoader.setDRACOLoader(new import_three_stdlib.DRACOLoader());
|
|
3266
3418
|
const modelData = await modelLoader.loadAsync(url);
|
|
3419
|
+
const parserJson = await resolveParserJson(modelData?.parser?.json, {
|
|
3420
|
+
url,
|
|
3421
|
+
...parserJsonFallback
|
|
3422
|
+
});
|
|
3267
3423
|
const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
|
|
3268
3424
|
return parseScene(
|
|
3269
3425
|
modelData.scene,
|
|
3270
3426
|
actualizedNamespaces,
|
|
3271
3427
|
aggressiveImport,
|
|
3272
3428
|
rootBounds,
|
|
3273
|
-
|
|
3429
|
+
parserJson,
|
|
3274
3430
|
modelData.animations
|
|
3275
3431
|
);
|
|
3276
3432
|
}
|
|
@@ -3283,7 +3439,8 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
|
|
|
3283
3439
|
objectUrl,
|
|
3284
3440
|
actualizedNamespaces,
|
|
3285
3441
|
aggressiveImport,
|
|
3286
|
-
rootBounds
|
|
3442
|
+
rootBounds,
|
|
3443
|
+
{ blob }
|
|
3287
3444
|
);
|
|
3288
3445
|
} finally {
|
|
3289
3446
|
URL.revokeObjectURL(objectUrl);
|
|
@@ -3296,14 +3453,18 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
|
|
|
3296
3453
|
loader.parse(
|
|
3297
3454
|
arrayBuffer,
|
|
3298
3455
|
"",
|
|
3299
|
-
(gltf) => {
|
|
3456
|
+
async (gltf) => {
|
|
3300
3457
|
try {
|
|
3458
|
+
const parserJson = await resolveParserJson(
|
|
3459
|
+
gltf?.parser?.json,
|
|
3460
|
+
{ arrayBuffer }
|
|
3461
|
+
);
|
|
3301
3462
|
const asset = parseScene(
|
|
3302
3463
|
gltf.scene,
|
|
3303
3464
|
actualizedNamespaces,
|
|
3304
3465
|
aggressiveImport,
|
|
3305
3466
|
rootBounds,
|
|
3306
|
-
|
|
3467
|
+
parserJson,
|
|
3307
3468
|
gltf.animations
|
|
3308
3469
|
);
|
|
3309
3470
|
resolve(asset);
|
|
@@ -3355,6 +3516,160 @@ var loadGltfFromBlob = (blob, namespaces) => {
|
|
|
3355
3516
|
var import_three_stdlib3 = require("three-stdlib");
|
|
3356
3517
|
var THREE6 = __toESM(require("three"));
|
|
3357
3518
|
THREE6.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
3519
|
+
function normalizeExportError(error) {
|
|
3520
|
+
if (error instanceof Error) {
|
|
3521
|
+
return error;
|
|
3522
|
+
}
|
|
3523
|
+
if (typeof error === "string") {
|
|
3524
|
+
return new Error(error);
|
|
3525
|
+
}
|
|
3526
|
+
return new Error("Failed to export scene.");
|
|
3527
|
+
}
|
|
3528
|
+
function triggerBlobDownload(blob, filename) {
|
|
3529
|
+
const url = URL.createObjectURL(blob);
|
|
3530
|
+
const link = document.createElement("a");
|
|
3531
|
+
link.href = url;
|
|
3532
|
+
link.download = filename;
|
|
3533
|
+
link.style.display = "none";
|
|
3534
|
+
document.body.appendChild(link);
|
|
3535
|
+
link.click();
|
|
3536
|
+
document.body.removeChild(link);
|
|
3537
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
3538
|
+
}
|
|
3539
|
+
var GLB_MAGIC2 = 1179937895;
|
|
3540
|
+
var GLB_VERSION2 = 2;
|
|
3541
|
+
var GLB_JSON_CHUNK_TYPE2 = 1313821514;
|
|
3542
|
+
var GLB_HEADER_BYTES2 = 12;
|
|
3543
|
+
var GLB_CHUNK_HEADER_BYTES2 = 8;
|
|
3544
|
+
var GLB_JSON_PADDING_BYTE = 32;
|
|
3545
|
+
function isNearlyEqual(a, b, epsilon = 1e-6) {
|
|
3546
|
+
return Math.abs(a - b) <= epsilon;
|
|
3547
|
+
}
|
|
3548
|
+
function isIdentityTransformNode(node) {
|
|
3549
|
+
const translation = node.translation;
|
|
3550
|
+
if (Array.isArray(translation) && (translation.length !== 3 || !isNearlyEqual(Number(translation[0]), 0) || !isNearlyEqual(Number(translation[1]), 0) || !isNearlyEqual(Number(translation[2]), 0))) {
|
|
3551
|
+
return false;
|
|
3552
|
+
}
|
|
3553
|
+
const rotation = node.rotation;
|
|
3554
|
+
if (Array.isArray(rotation) && (rotation.length !== 4 || !isNearlyEqual(Number(rotation[0]), 0) || !isNearlyEqual(Number(rotation[1]), 0) || !isNearlyEqual(Number(rotation[2]), 0) || !isNearlyEqual(Number(rotation[3]), 1))) {
|
|
3555
|
+
return false;
|
|
3556
|
+
}
|
|
3557
|
+
const scale = node.scale;
|
|
3558
|
+
if (Array.isArray(scale) && (scale.length !== 3 || !isNearlyEqual(Number(scale[0]), 1) || !isNearlyEqual(Number(scale[1]), 1) || !isNearlyEqual(Number(scale[2]), 1))) {
|
|
3559
|
+
return false;
|
|
3560
|
+
}
|
|
3561
|
+
return true;
|
|
3562
|
+
}
|
|
3563
|
+
function isPassThroughWrapperNode(node) {
|
|
3564
|
+
if (!node || typeof node !== "object") {
|
|
3565
|
+
return false;
|
|
3566
|
+
}
|
|
3567
|
+
if (!Array.isArray(node.children) || node.children.length === 0) {
|
|
3568
|
+
return false;
|
|
3569
|
+
}
|
|
3570
|
+
const hasOnlyNumericChildren = node.children.every(
|
|
3571
|
+
(index) => Number.isInteger(index)
|
|
3572
|
+
);
|
|
3573
|
+
if (!hasOnlyNumericChildren) {
|
|
3574
|
+
return false;
|
|
3575
|
+
}
|
|
3576
|
+
if (node.mesh !== void 0 || node.camera !== void 0 || node.skin !== void 0) {
|
|
3577
|
+
return false;
|
|
3578
|
+
}
|
|
3579
|
+
if (node.extensions && typeof node.extensions === "object" && Object.keys(node.extensions).length > 0) {
|
|
3580
|
+
return false;
|
|
3581
|
+
}
|
|
3582
|
+
return isIdentityTransformNode(node);
|
|
3583
|
+
}
|
|
3584
|
+
function normalizeExportedSceneJson(json, fallbackSceneName) {
|
|
3585
|
+
if (!json || typeof json !== "object") {
|
|
3586
|
+
return false;
|
|
3587
|
+
}
|
|
3588
|
+
if (!Array.isArray(json.scenes) || !Array.isArray(json.nodes)) {
|
|
3589
|
+
return false;
|
|
3590
|
+
}
|
|
3591
|
+
const sceneIndexRaw = json.scene;
|
|
3592
|
+
const sceneIndex = typeof sceneIndexRaw === "number" && Number.isInteger(sceneIndexRaw) ? sceneIndexRaw : 0;
|
|
3593
|
+
const sceneDef = json.scenes[sceneIndex];
|
|
3594
|
+
if (!sceneDef || typeof sceneDef !== "object") {
|
|
3595
|
+
return false;
|
|
3596
|
+
}
|
|
3597
|
+
let changed = false;
|
|
3598
|
+
let wrapperNodeName;
|
|
3599
|
+
if (Array.isArray(sceneDef.nodes) && sceneDef.nodes.length === 1) {
|
|
3600
|
+
const wrapperIndex = sceneDef.nodes[0];
|
|
3601
|
+
if (Number.isInteger(wrapperIndex)) {
|
|
3602
|
+
const wrapperNode = json.nodes[wrapperIndex];
|
|
3603
|
+
if (isPassThroughWrapperNode(wrapperNode)) {
|
|
3604
|
+
sceneDef.nodes = [...wrapperNode.children];
|
|
3605
|
+
wrapperNodeName = typeof wrapperNode.name === "string" ? wrapperNode.name : void 0;
|
|
3606
|
+
changed = true;
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
const currentSceneName = typeof sceneDef.name === "string" ? sceneDef.name.trim() : "";
|
|
3611
|
+
if (currentSceneName === "AuxScene") {
|
|
3612
|
+
const nextSceneName = (wrapperNodeName?.trim() || fallbackSceneName?.trim() || "Scene").trim();
|
|
3613
|
+
if (nextSceneName.length > 0 && nextSceneName !== currentSceneName) {
|
|
3614
|
+
sceneDef.name = nextSceneName;
|
|
3615
|
+
changed = true;
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
return changed;
|
|
3619
|
+
}
|
|
3620
|
+
function sanitizeExportedGlb(buffer, fallbackSceneName) {
|
|
3621
|
+
if (buffer.byteLength < GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2) {
|
|
3622
|
+
return buffer;
|
|
3623
|
+
}
|
|
3624
|
+
const originalBytes = new Uint8Array(buffer);
|
|
3625
|
+
const view = new DataView(buffer);
|
|
3626
|
+
const magic = view.getUint32(0, true);
|
|
3627
|
+
const version = view.getUint32(4, true);
|
|
3628
|
+
if (magic !== GLB_MAGIC2 || version !== GLB_VERSION2) {
|
|
3629
|
+
return buffer;
|
|
3630
|
+
}
|
|
3631
|
+
const jsonChunkLength = view.getUint32(GLB_HEADER_BYTES2, true);
|
|
3632
|
+
const jsonChunkType = view.getUint32(GLB_HEADER_BYTES2 + 4, true);
|
|
3633
|
+
if (jsonChunkType !== GLB_JSON_CHUNK_TYPE2) {
|
|
3634
|
+
return buffer;
|
|
3635
|
+
}
|
|
3636
|
+
const jsonChunkStart = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2;
|
|
3637
|
+
const jsonChunkEnd = jsonChunkStart + jsonChunkLength;
|
|
3638
|
+
if (jsonChunkEnd > originalBytes.length) {
|
|
3639
|
+
return buffer;
|
|
3640
|
+
}
|
|
3641
|
+
let jsonPayload;
|
|
3642
|
+
try {
|
|
3643
|
+
const jsonText = new TextDecoder().decode(
|
|
3644
|
+
originalBytes.slice(jsonChunkStart, jsonChunkEnd)
|
|
3645
|
+
);
|
|
3646
|
+
jsonPayload = JSON.parse(jsonText);
|
|
3647
|
+
} catch {
|
|
3648
|
+
return buffer;
|
|
3649
|
+
}
|
|
3650
|
+
const changed = normalizeExportedSceneJson(jsonPayload, fallbackSceneName);
|
|
3651
|
+
if (!changed) {
|
|
3652
|
+
return buffer;
|
|
3653
|
+
}
|
|
3654
|
+
const encodedJson = new TextEncoder().encode(JSON.stringify(jsonPayload));
|
|
3655
|
+
const paddedJsonLength = encodedJson.length + 3 & ~3;
|
|
3656
|
+
const paddedJson = new Uint8Array(paddedJsonLength);
|
|
3657
|
+
paddedJson.fill(GLB_JSON_PADDING_BYTE);
|
|
3658
|
+
paddedJson.set(encodedJson);
|
|
3659
|
+
const remainingChunks = originalBytes.slice(jsonChunkEnd);
|
|
3660
|
+
const totalLength = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2 + paddedJsonLength + remainingChunks.length;
|
|
3661
|
+
const sanitized = new ArrayBuffer(totalLength);
|
|
3662
|
+
const sanitizedBytes = new Uint8Array(sanitized);
|
|
3663
|
+
const sanitizedView = new DataView(sanitized);
|
|
3664
|
+
sanitizedView.setUint32(0, GLB_MAGIC2, true);
|
|
3665
|
+
sanitizedView.setUint32(4, GLB_VERSION2, true);
|
|
3666
|
+
sanitizedView.setUint32(8, totalLength, true);
|
|
3667
|
+
sanitizedView.setUint32(GLB_HEADER_BYTES2, paddedJsonLength, true);
|
|
3668
|
+
sanitizedView.setUint32(GLB_HEADER_BYTES2 + 4, GLB_JSON_CHUNK_TYPE2, true);
|
|
3669
|
+
sanitizedBytes.set(paddedJson, jsonChunkStart);
|
|
3670
|
+
sanitizedBytes.set(remainingChunks, jsonChunkStart + paddedJsonLength);
|
|
3671
|
+
return sanitized;
|
|
3672
|
+
}
|
|
3358
3673
|
function exportScene(data, fileNameOrOptions = "scene.glb") {
|
|
3359
3674
|
const options = typeof fileNameOrOptions === "string" ? { fileName: fileNameOrOptions } : fileNameOrOptions ?? {};
|
|
3360
3675
|
const fileName = options.fileName ?? "scene.glb";
|
|
@@ -3369,7 +3684,15 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
|
|
|
3369
3684
|
}
|
|
3370
3685
|
}
|
|
3371
3686
|
}));
|
|
3372
|
-
const
|
|
3687
|
+
const sourceRoot = data;
|
|
3688
|
+
const exportRoot = sourceRoot instanceof THREE6.Scene ? sourceRoot : sourceRoot.clone(true);
|
|
3689
|
+
const exportTarget = exportRoot instanceof THREE6.Scene ? exportRoot : (() => {
|
|
3690
|
+
const scene = new THREE6.Scene();
|
|
3691
|
+
scene.name = data.name?.trim() || "Scene";
|
|
3692
|
+
scene.add(exportRoot);
|
|
3693
|
+
return scene;
|
|
3694
|
+
})();
|
|
3695
|
+
const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(exportRoot, options.bundle) : () => {
|
|
3373
3696
|
};
|
|
3374
3697
|
const binary = options.binary ?? true;
|
|
3375
3698
|
const exporterOptions = {
|
|
@@ -3383,33 +3706,40 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
|
|
|
3383
3706
|
}
|
|
3384
3707
|
try {
|
|
3385
3708
|
exporter.parse(
|
|
3386
|
-
|
|
3709
|
+
exportTarget,
|
|
3387
3710
|
(gltf) => {
|
|
3388
3711
|
detachBundle();
|
|
3389
3712
|
if (!(gltf instanceof ArrayBuffer)) {
|
|
3390
|
-
|
|
3713
|
+
const error = new Error("Failed to export scene.");
|
|
3714
|
+
options.onError?.(error);
|
|
3715
|
+
return;
|
|
3391
3716
|
}
|
|
3392
|
-
const
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
type: "application/octet-stream"
|
|
3396
|
-
})
|
|
3717
|
+
const sanitizedGltf = sanitizeExportedGlb(
|
|
3718
|
+
gltf,
|
|
3719
|
+
data.name?.trim() || void 0
|
|
3397
3720
|
);
|
|
3398
3721
|
const trimmed = fileName.trim();
|
|
3399
3722
|
const safeFileName = trimmed.length > 0 ? trimmed : "scene.glb";
|
|
3400
3723
|
const downloadName = safeFileName.toLowerCase().endsWith(".glb") ? safeFileName : `${safeFileName}.glb`;
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3724
|
+
triggerBlobDownload(
|
|
3725
|
+
new Blob([sanitizedGltf], {
|
|
3726
|
+
type: "application/octet-stream"
|
|
3727
|
+
}),
|
|
3728
|
+
downloadName
|
|
3729
|
+
);
|
|
3730
|
+
options.onComplete?.();
|
|
3404
3731
|
},
|
|
3405
|
-
() => {
|
|
3732
|
+
(error) => {
|
|
3406
3733
|
detachBundle();
|
|
3734
|
+
options.onError?.(normalizeExportError(error));
|
|
3407
3735
|
},
|
|
3408
3736
|
exporterOptions
|
|
3409
3737
|
);
|
|
3410
3738
|
} catch (error) {
|
|
3411
3739
|
detachBundle();
|
|
3412
|
-
|
|
3740
|
+
const normalizedError = normalizeExportError(error);
|
|
3741
|
+
options.onError?.(normalizedError);
|
|
3742
|
+
throw normalizedError;
|
|
3413
3743
|
}
|
|
3414
3744
|
}
|
|
3415
3745
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -3429,6 +3759,7 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
|
|
|
3429
3759
|
loadGLTFFromBlobWithBundle,
|
|
3430
3760
|
loadGLTFWithBundle,
|
|
3431
3761
|
loadGltfFromBlob,
|
|
3762
|
+
parseGlbJsonChunk,
|
|
3432
3763
|
useDefaultVizijStore,
|
|
3433
3764
|
useFeatures,
|
|
3434
3765
|
useVizijStore,
|
package/dist/index.mjs
CHANGED
|
@@ -1403,6 +1403,35 @@ function createAnimatable(value) {
|
|
|
1403
1403
|
}
|
|
1404
1404
|
createAnimatable({ type: "euler", name: "Rotation" });
|
|
1405
1405
|
|
|
1406
|
+
// src/functions/exportable-bodies.ts
|
|
1407
|
+
function asGroupEntries(world) {
|
|
1408
|
+
return Object.values(world).filter((entry) => entry.type === "group");
|
|
1409
|
+
}
|
|
1410
|
+
function selectExportableGroupEntries(world, filterIds) {
|
|
1411
|
+
const groupEntries = asGroupEntries(world);
|
|
1412
|
+
const filterSet = Array.isArray(filterIds) && filterIds.length > 0 ? new Set(filterIds) : null;
|
|
1413
|
+
if (filterSet) {
|
|
1414
|
+
return groupEntries.filter((entry) => filterSet.has(entry.id));
|
|
1415
|
+
}
|
|
1416
|
+
const rootBoundsGroups = groupEntries.filter(
|
|
1417
|
+
(entry) => Boolean(entry.rootBounds)
|
|
1418
|
+
);
|
|
1419
|
+
if (rootBoundsGroups.length > 0) {
|
|
1420
|
+
return rootBoundsGroups;
|
|
1421
|
+
}
|
|
1422
|
+
const explicitRootGroups = groupEntries.filter(
|
|
1423
|
+
(entry) => entry.root === true
|
|
1424
|
+
);
|
|
1425
|
+
if (explicitRootGroups.length > 0) {
|
|
1426
|
+
return explicitRootGroups;
|
|
1427
|
+
}
|
|
1428
|
+
const topLevelGroups = groupEntries.filter((entry) => !entry.parent);
|
|
1429
|
+
if (topLevelGroups.length > 0) {
|
|
1430
|
+
return topLevelGroups;
|
|
1431
|
+
}
|
|
1432
|
+
return groupEntries;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1406
1435
|
// src/store.ts
|
|
1407
1436
|
THREE3.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
1408
1437
|
enableMapSet();
|
|
@@ -1478,23 +1507,17 @@ var VizijSlice = (set, get) => ({
|
|
|
1478
1507
|
},
|
|
1479
1508
|
getExportableBodies: (filterIds) => {
|
|
1480
1509
|
const worldData = get().world;
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
const firstNs = Object.keys(entry.refs)[0];
|
|
1493
|
-
const refGroup = entry.refs[firstNs].current;
|
|
1494
|
-
return refGroup;
|
|
1495
|
-
});
|
|
1496
|
-
return bodies;
|
|
1497
|
-
}
|
|
1510
|
+
const candidateGroups = selectExportableGroupEntries(
|
|
1511
|
+
worldData,
|
|
1512
|
+
filterIds
|
|
1513
|
+
);
|
|
1514
|
+
return candidateGroups.flatMap((entry) => {
|
|
1515
|
+
const refs = Object.values(
|
|
1516
|
+
entry.refs ?? {}
|
|
1517
|
+
);
|
|
1518
|
+
const resolved = refs.find((ref) => ref?.current)?.current ?? null;
|
|
1519
|
+
return resolved ? [resolved] : [];
|
|
1520
|
+
});
|
|
1498
1521
|
},
|
|
1499
1522
|
setGeometry: (id, geometry) => {
|
|
1500
1523
|
set(
|
|
@@ -2281,6 +2304,15 @@ function importMesh(mesh, namespaces, colorLookup) {
|
|
|
2281
2304
|
world = { ...world, ...newWorldItems };
|
|
2282
2305
|
animatables = { ...animatables, ...newAnimatables };
|
|
2283
2306
|
children.push(childId);
|
|
2307
|
+
} else if (shouldImportAsGroupChild(child)) {
|
|
2308
|
+
const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
|
|
2309
|
+
...colorLookup,
|
|
2310
|
+
...newColorLookup
|
|
2311
|
+
});
|
|
2312
|
+
newColorLookup = { ...newColorLookup, ...newMeshColors };
|
|
2313
|
+
world = { ...world, ...newWorldItems };
|
|
2314
|
+
animatables = { ...animatables, ...newAnimatables };
|
|
2315
|
+
children.push(childId);
|
|
2284
2316
|
}
|
|
2285
2317
|
});
|
|
2286
2318
|
const newShape = {
|
|
@@ -2324,6 +2356,24 @@ function getShapeMaterial(mesh, useEmissive) {
|
|
|
2324
2356
|
return "standard" /* Standard */;
|
|
2325
2357
|
}
|
|
2326
2358
|
}
|
|
2359
|
+
function shouldImportAsGroupChild(child) {
|
|
2360
|
+
if (!child.isObject3D) {
|
|
2361
|
+
return false;
|
|
2362
|
+
}
|
|
2363
|
+
if (child.isMesh) {
|
|
2364
|
+
return false;
|
|
2365
|
+
}
|
|
2366
|
+
if (child.isCamera) {
|
|
2367
|
+
return false;
|
|
2368
|
+
}
|
|
2369
|
+
if (child.isLight) {
|
|
2370
|
+
return false;
|
|
2371
|
+
}
|
|
2372
|
+
if (child.isBone) {
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
return true;
|
|
2376
|
+
}
|
|
2327
2377
|
|
|
2328
2378
|
// src/functions/gltf-loading/import-group.ts
|
|
2329
2379
|
Object3D6.DEFAULT_UP.set(0, 0, 1);
|
|
@@ -2383,7 +2433,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
|
|
|
2383
2433
|
world = { ...world, ...newWorldItems };
|
|
2384
2434
|
animatables = { ...animatables, ...newAnimatables };
|
|
2385
2435
|
children.push(childId);
|
|
2386
|
-
} else if (child
|
|
2436
|
+
} else if (shouldImportAsGroupChild2(child)) {
|
|
2387
2437
|
const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
|
|
2388
2438
|
...colorLookup,
|
|
2389
2439
|
...newColorLookup
|
|
@@ -2412,6 +2462,24 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
|
|
|
2412
2462
|
world = { ...world, [newGroup.id]: newGroup };
|
|
2413
2463
|
return [world, animatables, newGroup.id, newColorLookup];
|
|
2414
2464
|
}
|
|
2465
|
+
function shouldImportAsGroupChild2(child) {
|
|
2466
|
+
if (!child.isObject3D) {
|
|
2467
|
+
return false;
|
|
2468
|
+
}
|
|
2469
|
+
if (child.isMesh) {
|
|
2470
|
+
return false;
|
|
2471
|
+
}
|
|
2472
|
+
if (child.isCamera) {
|
|
2473
|
+
return false;
|
|
2474
|
+
}
|
|
2475
|
+
if (child.isLight) {
|
|
2476
|
+
return false;
|
|
2477
|
+
}
|
|
2478
|
+
if (child.isBone) {
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
return true;
|
|
2482
|
+
}
|
|
2415
2483
|
|
|
2416
2484
|
// src/functions/gltf-loading/import-scene.ts
|
|
2417
2485
|
Object3D7.DEFAULT_UP.set(0, 0, 1);
|
|
@@ -2722,6 +2790,13 @@ function searchParserJsonForBundle(parserJson) {
|
|
|
2722
2790
|
if (!parserJson || typeof parserJson !== "object") {
|
|
2723
2791
|
return null;
|
|
2724
2792
|
}
|
|
2793
|
+
const rootExtensions = parserJson && typeof parserJson === "object" ? parserJson.extensions : null;
|
|
2794
|
+
if (rootExtensions && typeof rootExtensions === "object") {
|
|
2795
|
+
const match = readExtensionValue(rootExtensions);
|
|
2796
|
+
if (match) {
|
|
2797
|
+
return cloneBundle(match.value);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2725
2800
|
const nodes = Array.isArray(parserJson.nodes) ? parserJson.nodes : [];
|
|
2726
2801
|
for (const node of nodes) {
|
|
2727
2802
|
const extensions = node && typeof node === "object" ? node.extensions : null;
|
|
@@ -3157,23 +3232,94 @@ function extractVizijAnimations(parserJson, clips) {
|
|
|
3157
3232
|
|
|
3158
3233
|
// src/functions/load-gltf.ts
|
|
3159
3234
|
THREE5.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
3235
|
+
var GLB_MAGIC = 1179937895;
|
|
3236
|
+
var GLB_VERSION = 2;
|
|
3237
|
+
var GLB_HEADER_BYTES = 12;
|
|
3238
|
+
var GLB_CHUNK_HEADER_BYTES = 8;
|
|
3239
|
+
var GLB_JSON_CHUNK_TYPE = 1313821514;
|
|
3160
3240
|
var EmptyModelError = class extends Error {
|
|
3161
3241
|
constructor(message) {
|
|
3162
3242
|
super(message);
|
|
3163
3243
|
this.name = "EmptyModelError";
|
|
3164
3244
|
}
|
|
3165
3245
|
};
|
|
3246
|
+
function parseGlbJsonChunk(buffer) {
|
|
3247
|
+
if (buffer.byteLength < GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES) {
|
|
3248
|
+
return void 0;
|
|
3249
|
+
}
|
|
3250
|
+
const view = new DataView(buffer);
|
|
3251
|
+
const magic = view.getUint32(0, true);
|
|
3252
|
+
const version = view.getUint32(4, true);
|
|
3253
|
+
if (magic !== GLB_MAGIC || version !== GLB_VERSION) {
|
|
3254
|
+
return void 0;
|
|
3255
|
+
}
|
|
3256
|
+
const chunkLength = view.getUint32(GLB_HEADER_BYTES, true);
|
|
3257
|
+
const chunkType = view.getUint32(GLB_HEADER_BYTES + 4, true);
|
|
3258
|
+
if (chunkType !== GLB_JSON_CHUNK_TYPE) {
|
|
3259
|
+
return void 0;
|
|
3260
|
+
}
|
|
3261
|
+
const chunkStart = GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES;
|
|
3262
|
+
const chunkEnd = chunkStart + chunkLength;
|
|
3263
|
+
if (chunkEnd > buffer.byteLength) {
|
|
3264
|
+
return void 0;
|
|
3265
|
+
}
|
|
3266
|
+
try {
|
|
3267
|
+
const chunkBytes = new Uint8Array(buffer, chunkStart, chunkLength);
|
|
3268
|
+
const jsonText = new TextDecoder().decode(chunkBytes);
|
|
3269
|
+
return JSON.parse(jsonText);
|
|
3270
|
+
} catch {
|
|
3271
|
+
return void 0;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
async function resolveParserJson(parserJson, fallback) {
|
|
3275
|
+
if (parserJson && typeof parserJson === "object") {
|
|
3276
|
+
return parserJson;
|
|
3277
|
+
}
|
|
3278
|
+
if (fallback.arrayBuffer) {
|
|
3279
|
+
const fromArrayBuffer = parseGlbJsonChunk(fallback.arrayBuffer);
|
|
3280
|
+
if (fromArrayBuffer && typeof fromArrayBuffer === "object") {
|
|
3281
|
+
return fromArrayBuffer;
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
if (fallback.blob) {
|
|
3285
|
+
try {
|
|
3286
|
+
const blobBuffer = typeof fallback.blob.arrayBuffer === "function" ? await fallback.blob.arrayBuffer() : await new Response(fallback.blob).arrayBuffer();
|
|
3287
|
+
const fromBlob = parseGlbJsonChunk(blobBuffer);
|
|
3288
|
+
if (fromBlob && typeof fromBlob === "object") {
|
|
3289
|
+
return fromBlob;
|
|
3290
|
+
}
|
|
3291
|
+
} catch {
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
if (fallback.url && typeof fetch === "function") {
|
|
3295
|
+
try {
|
|
3296
|
+
const response = await fetch(fallback.url);
|
|
3297
|
+
if (response.ok) {
|
|
3298
|
+
const binary = await response.arrayBuffer();
|
|
3299
|
+
const fromUrl = parseGlbJsonChunk(binary);
|
|
3300
|
+
if (fromUrl && typeof fromUrl === "object") {
|
|
3301
|
+
return fromUrl;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
} catch {
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
return parserJson;
|
|
3308
|
+
}
|
|
3166
3309
|
async function loadGLTF(url, namespaces, aggressiveImport = false, rootBounds) {
|
|
3167
3310
|
const modelLoader = new GLTFLoader();
|
|
3168
3311
|
modelLoader.setDRACOLoader(new DRACOLoader());
|
|
3169
3312
|
const modelData = await modelLoader.loadAsync(url);
|
|
3313
|
+
const parserJson = await resolveParserJson(modelData?.parser?.json, {
|
|
3314
|
+
url
|
|
3315
|
+
});
|
|
3170
3316
|
const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
|
|
3171
3317
|
const asset = parseScene(
|
|
3172
3318
|
modelData.scene,
|
|
3173
3319
|
actualizedNamespaces,
|
|
3174
3320
|
aggressiveImport,
|
|
3175
3321
|
rootBounds,
|
|
3176
|
-
|
|
3322
|
+
parserJson,
|
|
3177
3323
|
modelData.animations
|
|
3178
3324
|
);
|
|
3179
3325
|
return [asset.world, asset.animatables, asset.animations];
|
|
@@ -3187,7 +3333,8 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
|
|
|
3187
3333
|
objectUrl,
|
|
3188
3334
|
actualizedNamespaces,
|
|
3189
3335
|
aggressiveImport,
|
|
3190
|
-
rootBounds
|
|
3336
|
+
rootBounds,
|
|
3337
|
+
{ blob }
|
|
3191
3338
|
);
|
|
3192
3339
|
return [asset.world, asset.animatables, asset.animations];
|
|
3193
3340
|
} finally {
|
|
@@ -3201,14 +3348,18 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
|
|
|
3201
3348
|
loader.parse(
|
|
3202
3349
|
arrayBuffer,
|
|
3203
3350
|
"",
|
|
3204
|
-
(gltf) => {
|
|
3351
|
+
async (gltf) => {
|
|
3205
3352
|
try {
|
|
3353
|
+
const parserJson = await resolveParserJson(
|
|
3354
|
+
gltf?.parser?.json,
|
|
3355
|
+
{ arrayBuffer }
|
|
3356
|
+
);
|
|
3206
3357
|
const asset = parseScene(
|
|
3207
3358
|
gltf.scene,
|
|
3208
3359
|
actualizedNamespaces,
|
|
3209
3360
|
aggressiveImport,
|
|
3210
3361
|
rootBounds,
|
|
3211
|
-
|
|
3362
|
+
parserJson,
|
|
3212
3363
|
gltf.animations
|
|
3213
3364
|
);
|
|
3214
3365
|
resolve([asset.world, asset.animatables, asset.animations]);
|
|
@@ -3235,19 +3386,23 @@ function parseScene(scene, namespaces, aggressiveImport, rootBounds, parserJson,
|
|
|
3235
3386
|
);
|
|
3236
3387
|
const bundle = extractVizijBundle(scene, parserJson);
|
|
3237
3388
|
const animations = extractVizijAnimations(parserJson, clips);
|
|
3238
|
-
return { world, animatables, bundle, animations };
|
|
3389
|
+
return { world, animatables, bundle, animations, scene };
|
|
3239
3390
|
}
|
|
3240
|
-
async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds) {
|
|
3391
|
+
async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds, parserJsonFallback) {
|
|
3241
3392
|
const modelLoader = new GLTFLoader();
|
|
3242
3393
|
modelLoader.setDRACOLoader(new DRACOLoader());
|
|
3243
3394
|
const modelData = await modelLoader.loadAsync(url);
|
|
3395
|
+
const parserJson = await resolveParserJson(modelData?.parser?.json, {
|
|
3396
|
+
url,
|
|
3397
|
+
...parserJsonFallback
|
|
3398
|
+
});
|
|
3244
3399
|
const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
|
|
3245
3400
|
return parseScene(
|
|
3246
3401
|
modelData.scene,
|
|
3247
3402
|
actualizedNamespaces,
|
|
3248
3403
|
aggressiveImport,
|
|
3249
3404
|
rootBounds,
|
|
3250
|
-
|
|
3405
|
+
parserJson,
|
|
3251
3406
|
modelData.animations
|
|
3252
3407
|
);
|
|
3253
3408
|
}
|
|
@@ -3260,7 +3415,8 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
|
|
|
3260
3415
|
objectUrl,
|
|
3261
3416
|
actualizedNamespaces,
|
|
3262
3417
|
aggressiveImport,
|
|
3263
|
-
rootBounds
|
|
3418
|
+
rootBounds,
|
|
3419
|
+
{ blob }
|
|
3264
3420
|
);
|
|
3265
3421
|
} finally {
|
|
3266
3422
|
URL.revokeObjectURL(objectUrl);
|
|
@@ -3273,14 +3429,18 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
|
|
|
3273
3429
|
loader.parse(
|
|
3274
3430
|
arrayBuffer,
|
|
3275
3431
|
"",
|
|
3276
|
-
(gltf) => {
|
|
3432
|
+
async (gltf) => {
|
|
3277
3433
|
try {
|
|
3434
|
+
const parserJson = await resolveParserJson(
|
|
3435
|
+
gltf?.parser?.json,
|
|
3436
|
+
{ arrayBuffer }
|
|
3437
|
+
);
|
|
3278
3438
|
const asset = parseScene(
|
|
3279
3439
|
gltf.scene,
|
|
3280
3440
|
actualizedNamespaces,
|
|
3281
3441
|
aggressiveImport,
|
|
3282
3442
|
rootBounds,
|
|
3283
|
-
|
|
3443
|
+
parserJson,
|
|
3284
3444
|
gltf.animations
|
|
3285
3445
|
);
|
|
3286
3446
|
resolve(asset);
|
|
@@ -3332,6 +3492,160 @@ var loadGltfFromBlob = (blob, namespaces) => {
|
|
|
3332
3492
|
import { GLTFExporter } from "three-stdlib";
|
|
3333
3493
|
import * as THREE6 from "three";
|
|
3334
3494
|
THREE6.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
3495
|
+
function normalizeExportError(error) {
|
|
3496
|
+
if (error instanceof Error) {
|
|
3497
|
+
return error;
|
|
3498
|
+
}
|
|
3499
|
+
if (typeof error === "string") {
|
|
3500
|
+
return new Error(error);
|
|
3501
|
+
}
|
|
3502
|
+
return new Error("Failed to export scene.");
|
|
3503
|
+
}
|
|
3504
|
+
function triggerBlobDownload(blob, filename) {
|
|
3505
|
+
const url = URL.createObjectURL(blob);
|
|
3506
|
+
const link = document.createElement("a");
|
|
3507
|
+
link.href = url;
|
|
3508
|
+
link.download = filename;
|
|
3509
|
+
link.style.display = "none";
|
|
3510
|
+
document.body.appendChild(link);
|
|
3511
|
+
link.click();
|
|
3512
|
+
document.body.removeChild(link);
|
|
3513
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
3514
|
+
}
|
|
3515
|
+
var GLB_MAGIC2 = 1179937895;
|
|
3516
|
+
var GLB_VERSION2 = 2;
|
|
3517
|
+
var GLB_JSON_CHUNK_TYPE2 = 1313821514;
|
|
3518
|
+
var GLB_HEADER_BYTES2 = 12;
|
|
3519
|
+
var GLB_CHUNK_HEADER_BYTES2 = 8;
|
|
3520
|
+
var GLB_JSON_PADDING_BYTE = 32;
|
|
3521
|
+
function isNearlyEqual(a, b, epsilon = 1e-6) {
|
|
3522
|
+
return Math.abs(a - b) <= epsilon;
|
|
3523
|
+
}
|
|
3524
|
+
function isIdentityTransformNode(node) {
|
|
3525
|
+
const translation = node.translation;
|
|
3526
|
+
if (Array.isArray(translation) && (translation.length !== 3 || !isNearlyEqual(Number(translation[0]), 0) || !isNearlyEqual(Number(translation[1]), 0) || !isNearlyEqual(Number(translation[2]), 0))) {
|
|
3527
|
+
return false;
|
|
3528
|
+
}
|
|
3529
|
+
const rotation = node.rotation;
|
|
3530
|
+
if (Array.isArray(rotation) && (rotation.length !== 4 || !isNearlyEqual(Number(rotation[0]), 0) || !isNearlyEqual(Number(rotation[1]), 0) || !isNearlyEqual(Number(rotation[2]), 0) || !isNearlyEqual(Number(rotation[3]), 1))) {
|
|
3531
|
+
return false;
|
|
3532
|
+
}
|
|
3533
|
+
const scale = node.scale;
|
|
3534
|
+
if (Array.isArray(scale) && (scale.length !== 3 || !isNearlyEqual(Number(scale[0]), 1) || !isNearlyEqual(Number(scale[1]), 1) || !isNearlyEqual(Number(scale[2]), 1))) {
|
|
3535
|
+
return false;
|
|
3536
|
+
}
|
|
3537
|
+
return true;
|
|
3538
|
+
}
|
|
3539
|
+
function isPassThroughWrapperNode(node) {
|
|
3540
|
+
if (!node || typeof node !== "object") {
|
|
3541
|
+
return false;
|
|
3542
|
+
}
|
|
3543
|
+
if (!Array.isArray(node.children) || node.children.length === 0) {
|
|
3544
|
+
return false;
|
|
3545
|
+
}
|
|
3546
|
+
const hasOnlyNumericChildren = node.children.every(
|
|
3547
|
+
(index) => Number.isInteger(index)
|
|
3548
|
+
);
|
|
3549
|
+
if (!hasOnlyNumericChildren) {
|
|
3550
|
+
return false;
|
|
3551
|
+
}
|
|
3552
|
+
if (node.mesh !== void 0 || node.camera !== void 0 || node.skin !== void 0) {
|
|
3553
|
+
return false;
|
|
3554
|
+
}
|
|
3555
|
+
if (node.extensions && typeof node.extensions === "object" && Object.keys(node.extensions).length > 0) {
|
|
3556
|
+
return false;
|
|
3557
|
+
}
|
|
3558
|
+
return isIdentityTransformNode(node);
|
|
3559
|
+
}
|
|
3560
|
+
function normalizeExportedSceneJson(json, fallbackSceneName) {
|
|
3561
|
+
if (!json || typeof json !== "object") {
|
|
3562
|
+
return false;
|
|
3563
|
+
}
|
|
3564
|
+
if (!Array.isArray(json.scenes) || !Array.isArray(json.nodes)) {
|
|
3565
|
+
return false;
|
|
3566
|
+
}
|
|
3567
|
+
const sceneIndexRaw = json.scene;
|
|
3568
|
+
const sceneIndex = typeof sceneIndexRaw === "number" && Number.isInteger(sceneIndexRaw) ? sceneIndexRaw : 0;
|
|
3569
|
+
const sceneDef = json.scenes[sceneIndex];
|
|
3570
|
+
if (!sceneDef || typeof sceneDef !== "object") {
|
|
3571
|
+
return false;
|
|
3572
|
+
}
|
|
3573
|
+
let changed = false;
|
|
3574
|
+
let wrapperNodeName;
|
|
3575
|
+
if (Array.isArray(sceneDef.nodes) && sceneDef.nodes.length === 1) {
|
|
3576
|
+
const wrapperIndex = sceneDef.nodes[0];
|
|
3577
|
+
if (Number.isInteger(wrapperIndex)) {
|
|
3578
|
+
const wrapperNode = json.nodes[wrapperIndex];
|
|
3579
|
+
if (isPassThroughWrapperNode(wrapperNode)) {
|
|
3580
|
+
sceneDef.nodes = [...wrapperNode.children];
|
|
3581
|
+
wrapperNodeName = typeof wrapperNode.name === "string" ? wrapperNode.name : void 0;
|
|
3582
|
+
changed = true;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
const currentSceneName = typeof sceneDef.name === "string" ? sceneDef.name.trim() : "";
|
|
3587
|
+
if (currentSceneName === "AuxScene") {
|
|
3588
|
+
const nextSceneName = (wrapperNodeName?.trim() || fallbackSceneName?.trim() || "Scene").trim();
|
|
3589
|
+
if (nextSceneName.length > 0 && nextSceneName !== currentSceneName) {
|
|
3590
|
+
sceneDef.name = nextSceneName;
|
|
3591
|
+
changed = true;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return changed;
|
|
3595
|
+
}
|
|
3596
|
+
function sanitizeExportedGlb(buffer, fallbackSceneName) {
|
|
3597
|
+
if (buffer.byteLength < GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2) {
|
|
3598
|
+
return buffer;
|
|
3599
|
+
}
|
|
3600
|
+
const originalBytes = new Uint8Array(buffer);
|
|
3601
|
+
const view = new DataView(buffer);
|
|
3602
|
+
const magic = view.getUint32(0, true);
|
|
3603
|
+
const version = view.getUint32(4, true);
|
|
3604
|
+
if (magic !== GLB_MAGIC2 || version !== GLB_VERSION2) {
|
|
3605
|
+
return buffer;
|
|
3606
|
+
}
|
|
3607
|
+
const jsonChunkLength = view.getUint32(GLB_HEADER_BYTES2, true);
|
|
3608
|
+
const jsonChunkType = view.getUint32(GLB_HEADER_BYTES2 + 4, true);
|
|
3609
|
+
if (jsonChunkType !== GLB_JSON_CHUNK_TYPE2) {
|
|
3610
|
+
return buffer;
|
|
3611
|
+
}
|
|
3612
|
+
const jsonChunkStart = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2;
|
|
3613
|
+
const jsonChunkEnd = jsonChunkStart + jsonChunkLength;
|
|
3614
|
+
if (jsonChunkEnd > originalBytes.length) {
|
|
3615
|
+
return buffer;
|
|
3616
|
+
}
|
|
3617
|
+
let jsonPayload;
|
|
3618
|
+
try {
|
|
3619
|
+
const jsonText = new TextDecoder().decode(
|
|
3620
|
+
originalBytes.slice(jsonChunkStart, jsonChunkEnd)
|
|
3621
|
+
);
|
|
3622
|
+
jsonPayload = JSON.parse(jsonText);
|
|
3623
|
+
} catch {
|
|
3624
|
+
return buffer;
|
|
3625
|
+
}
|
|
3626
|
+
const changed = normalizeExportedSceneJson(jsonPayload, fallbackSceneName);
|
|
3627
|
+
if (!changed) {
|
|
3628
|
+
return buffer;
|
|
3629
|
+
}
|
|
3630
|
+
const encodedJson = new TextEncoder().encode(JSON.stringify(jsonPayload));
|
|
3631
|
+
const paddedJsonLength = encodedJson.length + 3 & ~3;
|
|
3632
|
+
const paddedJson = new Uint8Array(paddedJsonLength);
|
|
3633
|
+
paddedJson.fill(GLB_JSON_PADDING_BYTE);
|
|
3634
|
+
paddedJson.set(encodedJson);
|
|
3635
|
+
const remainingChunks = originalBytes.slice(jsonChunkEnd);
|
|
3636
|
+
const totalLength = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2 + paddedJsonLength + remainingChunks.length;
|
|
3637
|
+
const sanitized = new ArrayBuffer(totalLength);
|
|
3638
|
+
const sanitizedBytes = new Uint8Array(sanitized);
|
|
3639
|
+
const sanitizedView = new DataView(sanitized);
|
|
3640
|
+
sanitizedView.setUint32(0, GLB_MAGIC2, true);
|
|
3641
|
+
sanitizedView.setUint32(4, GLB_VERSION2, true);
|
|
3642
|
+
sanitizedView.setUint32(8, totalLength, true);
|
|
3643
|
+
sanitizedView.setUint32(GLB_HEADER_BYTES2, paddedJsonLength, true);
|
|
3644
|
+
sanitizedView.setUint32(GLB_HEADER_BYTES2 + 4, GLB_JSON_CHUNK_TYPE2, true);
|
|
3645
|
+
sanitizedBytes.set(paddedJson, jsonChunkStart);
|
|
3646
|
+
sanitizedBytes.set(remainingChunks, jsonChunkStart + paddedJsonLength);
|
|
3647
|
+
return sanitized;
|
|
3648
|
+
}
|
|
3335
3649
|
function exportScene(data, fileNameOrOptions = "scene.glb") {
|
|
3336
3650
|
const options = typeof fileNameOrOptions === "string" ? { fileName: fileNameOrOptions } : fileNameOrOptions ?? {};
|
|
3337
3651
|
const fileName = options.fileName ?? "scene.glb";
|
|
@@ -3346,7 +3660,15 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
|
|
|
3346
3660
|
}
|
|
3347
3661
|
}
|
|
3348
3662
|
}));
|
|
3349
|
-
const
|
|
3663
|
+
const sourceRoot = data;
|
|
3664
|
+
const exportRoot = sourceRoot instanceof THREE6.Scene ? sourceRoot : sourceRoot.clone(true);
|
|
3665
|
+
const exportTarget = exportRoot instanceof THREE6.Scene ? exportRoot : (() => {
|
|
3666
|
+
const scene = new THREE6.Scene();
|
|
3667
|
+
scene.name = data.name?.trim() || "Scene";
|
|
3668
|
+
scene.add(exportRoot);
|
|
3669
|
+
return scene;
|
|
3670
|
+
})();
|
|
3671
|
+
const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(exportRoot, options.bundle) : () => {
|
|
3350
3672
|
};
|
|
3351
3673
|
const binary = options.binary ?? true;
|
|
3352
3674
|
const exporterOptions = {
|
|
@@ -3360,33 +3682,40 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
|
|
|
3360
3682
|
}
|
|
3361
3683
|
try {
|
|
3362
3684
|
exporter.parse(
|
|
3363
|
-
|
|
3685
|
+
exportTarget,
|
|
3364
3686
|
(gltf) => {
|
|
3365
3687
|
detachBundle();
|
|
3366
3688
|
if (!(gltf instanceof ArrayBuffer)) {
|
|
3367
|
-
|
|
3689
|
+
const error = new Error("Failed to export scene.");
|
|
3690
|
+
options.onError?.(error);
|
|
3691
|
+
return;
|
|
3368
3692
|
}
|
|
3369
|
-
const
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
type: "application/octet-stream"
|
|
3373
|
-
})
|
|
3693
|
+
const sanitizedGltf = sanitizeExportedGlb(
|
|
3694
|
+
gltf,
|
|
3695
|
+
data.name?.trim() || void 0
|
|
3374
3696
|
);
|
|
3375
3697
|
const trimmed = fileName.trim();
|
|
3376
3698
|
const safeFileName = trimmed.length > 0 ? trimmed : "scene.glb";
|
|
3377
3699
|
const downloadName = safeFileName.toLowerCase().endsWith(".glb") ? safeFileName : `${safeFileName}.glb`;
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3700
|
+
triggerBlobDownload(
|
|
3701
|
+
new Blob([sanitizedGltf], {
|
|
3702
|
+
type: "application/octet-stream"
|
|
3703
|
+
}),
|
|
3704
|
+
downloadName
|
|
3705
|
+
);
|
|
3706
|
+
options.onComplete?.();
|
|
3381
3707
|
},
|
|
3382
|
-
() => {
|
|
3708
|
+
(error) => {
|
|
3383
3709
|
detachBundle();
|
|
3710
|
+
options.onError?.(normalizeExportError(error));
|
|
3384
3711
|
},
|
|
3385
3712
|
exporterOptions
|
|
3386
3713
|
);
|
|
3387
3714
|
} catch (error) {
|
|
3388
3715
|
detachBundle();
|
|
3389
|
-
|
|
3716
|
+
const normalizedError = normalizeExportError(error);
|
|
3717
|
+
options.onError?.(normalizedError);
|
|
3718
|
+
throw normalizedError;
|
|
3390
3719
|
}
|
|
3391
3720
|
}
|
|
3392
3721
|
export {
|
|
@@ -3405,6 +3734,7 @@ export {
|
|
|
3405
3734
|
loadGLTFFromBlobWithBundle,
|
|
3406
3735
|
loadGLTFWithBundle,
|
|
3407
3736
|
loadGltfFromBlob,
|
|
3737
|
+
parseGlbJsonChunk,
|
|
3408
3738
|
useDefaultVizijStore,
|
|
3409
3739
|
useFeatures,
|
|
3410
3740
|
useVizijStore,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizij/render",
|
|
3
3
|
"description": "Higher-level visualization and interaction components for robot and ai faces.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"lint:fix": "pnpm --filter \"$npm_package_name\" exec eslint --ext .js,.jsx,.ts,.tsx --fix -- .",
|
|
77
77
|
"prettier:check": "prettier --check .",
|
|
78
78
|
"prettier:write": "prettier --write .",
|
|
79
|
-
"test": "node --
|
|
79
|
+
"test": "node --import ./tests/register-ts-loader.mjs --test tests/*.node-test.mjs",
|
|
80
80
|
"clean": "rm -rf dist .turbo coverage tsconfig.tsbuildinfo",
|
|
81
81
|
"reset": "rm -rf node_modules",
|
|
82
82
|
"reset:hard": "pnpm run reset && rm -f pnpm-lock.yaml package-lock.json yarn.lock",
|