@forgeax/engine-fbx 0.0.0-dev.8d955ade1c79
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 +202 -0
- package/README.md +188 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/blendshape-import.integration.test.d.ts +2 -0
- package/dist/__tests__/blendshape-import.integration.test.d.ts.map +1 -0
- package/dist/__tests__/blendshape-import.unit.test.d.ts +2 -0
- package/dist/__tests__/blendshape-import.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fbx-importer.test.d.ts +2 -0
- package/dist/__tests__/fbx-importer.test.d.ts.map +1 -0
- package/dist/__tests__/fbx-local-artifacts.test.d.ts +2 -0
- package/dist/__tests__/fbx-local-artifacts.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-material-slots.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-material-slots.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parse-mesh-multi-uv.test.d.ts +2 -0
- package/dist/__tests__/parse-mesh-multi-uv.test.d.ts.map +1 -0
- package/dist/__tests__/pick-e2e.integration.test.d.ts +2 -0
- package/dist/__tests__/pick-e2e.integration.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-texture-path.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-texture-path.unit.test.d.ts.map +1 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/fbx-importer.d.ts +17 -0
- package/dist/fbx-importer.d.ts.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1499 -0
- package/dist/index.mjs.map +1 -0
- package/dist/parse-animation-clip.d.ts +35 -0
- package/dist/parse-animation-clip.d.ts.map +1 -0
- package/dist/parse-material.d.ts +24 -0
- package/dist/parse-material.d.ts.map +1 -0
- package/dist/parse-mesh.d.ts +21 -0
- package/dist/parse-mesh.d.ts.map +1 -0
- package/dist/parse-scene.d.ts +28 -0
- package/dist/parse-scene.d.ts.map +1 -0
- package/dist/parse-skeleton.d.ts +15 -0
- package/dist/parse-skeleton.d.ts.map +1 -0
- package/dist/parse-skin.d.ts +20 -0
- package/dist/parse-skin.d.ts.map +1 -0
- package/dist/parse-texture.d.ts +11 -0
- package/dist/parse-texture.d.ts.map +1 -0
- package/dist/resolve-texture-path.d.ts +25 -0
- package/dist/resolve-texture-path.d.ts.map +1 -0
- package/dist/to-asset-pack.d.ts +31 -0
- package/dist/to-asset-pack.d.ts.map +1 -0
- package/package.json +74 -0
- package/pkg/fbx-wasm.mjs +2 -0
- package/pkg/fbx-wasm.wasm +0 -0
- package/scripts/build-wasm.mjs +85 -0
- package/scripts/content-key.mjs +88 -0
- package/scripts/ensure-wasm.mjs +28 -0
- package/scripts/fetch-ufbx.mjs +42 -0
- package/scripts/fetch-wasm.mjs +91 -0
- package/scripts/parity-diff.mjs +127 -0
- package/scripts/test-wasm.mjs +91 -0
- package/src/__tests__/blendshape-import.integration.test.ts +90 -0
- package/src/__tests__/blendshape-import.unit.test.ts +83 -0
- package/src/__tests__/fbx-importer.test.ts +36 -0
- package/src/__tests__/fbx-local-artifacts.test.ts +39 -0
- package/src/__tests__/index.test.ts +168 -0
- package/src/__tests__/mesh-material-slots.unit.test.ts +125 -0
- package/src/__tests__/parse-mesh-multi-uv.test.ts +187 -0
- package/src/__tests__/pick-e2e.integration.test.ts +247 -0
- package/src/__tests__/resolve-texture-path.unit.test.ts +50 -0
- package/src/errors.ts +112 -0
- package/src/fbx-importer.ts +146 -0
- package/src/index.ts +215 -0
- package/src/native/bridge.c +942 -0
- package/src/parse-animation-clip.ts +271 -0
- package/src/parse-material.ts +92 -0
- package/src/parse-mesh.ts +223 -0
- package/src/parse-scene.ts +75 -0
- package/src/parse-skeleton.ts +46 -0
- package/src/parse-skin.ts +71 -0
- package/src/parse-texture.ts +22 -0
- package/src/resolve-texture-path.ts +152 -0
- package/src/to-asset-pack.ts +742 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// fbx-importer.ts — TS wrapper around the ufbx WASM parser.
|
|
2
|
+
|
|
3
|
+
import type { ImportContext, Importer, ImportResult } from '@forgeax/engine-types';
|
|
4
|
+
import { fbxErr } from './errors.js';
|
|
5
|
+
import { initFbxWasm, parseFbx } from './index.js';
|
|
6
|
+
import {
|
|
7
|
+
type FbxRawAnimDoc,
|
|
8
|
+
parseAnimationClips,
|
|
9
|
+
resolveAnimationTargetIds,
|
|
10
|
+
} from './parse-animation-clip.js';
|
|
11
|
+
import { type FbxRawMaterial, parseMaterial } from './parse-material.js';
|
|
12
|
+
import { type FbxRawDocument, type FbxRawMesh, parseMesh } from './parse-mesh.js';
|
|
13
|
+
import { type FbxRawNodes, parseScene } from './parse-scene.js';
|
|
14
|
+
import { type FbxRawSkeletonDoc, parseSkeleton } from './parse-skeleton.js';
|
|
15
|
+
import { type FbxRawSkinDoc, parseSkin } from './parse-skin.js';
|
|
16
|
+
import { parseTextures } from './parse-texture.js';
|
|
17
|
+
import { toAssetPack } from './to-asset-pack.js';
|
|
18
|
+
|
|
19
|
+
export interface FbxSourceKeyOutput {
|
|
20
|
+
readonly kind: string;
|
|
21
|
+
readonly name?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type FbxSourceKeyResult =
|
|
25
|
+
| { readonly ok: true; readonly keys: readonly string[] }
|
|
26
|
+
| {
|
|
27
|
+
readonly ok: false;
|
|
28
|
+
readonly code: 'missing-source-key' | 'duplicate-source-key' | 'ambiguous-source-key';
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Derive FBX output identity from producer semantics, never from sourceIndex. */
|
|
32
|
+
export function sourceKeyForFbxOutput(output: FbxSourceKeyOutput): string | undefined {
|
|
33
|
+
const kind = output.kind.trim();
|
|
34
|
+
if (kind.length === 0) return undefined;
|
|
35
|
+
const name = output.name?.trim();
|
|
36
|
+
return name === undefined || name.length === 0 ? `fbx:${kind}` : `fbx:${kind}:${name}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function deriveFbxSourceKeys(outputs: readonly FbxSourceKeyOutput[]): FbxSourceKeyResult {
|
|
40
|
+
const keys = outputs.map(sourceKeyForFbxOutput);
|
|
41
|
+
if (keys.some((key) => key === undefined)) return { ok: false, code: 'missing-source-key' };
|
|
42
|
+
const seen = new Set<string>();
|
|
43
|
+
for (const [index, key] of keys.entries()) {
|
|
44
|
+
if (key === undefined) continue;
|
|
45
|
+
if (seen.has(key)) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
code: outputs[index]?.name === undefined ? 'ambiguous-source-key' : 'duplicate-source-key',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
seen.add(key);
|
|
52
|
+
}
|
|
53
|
+
return { ok: true, keys: keys as string[] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const fbxImporter: Importer = {
|
|
57
|
+
key: 'fbx',
|
|
58
|
+
|
|
59
|
+
async import(ctx: ImportContext): Promise<ImportResult> {
|
|
60
|
+
// ufbx WASM path: read raw FBX bytes via the import context (browser +
|
|
61
|
+
// Node both resolve through readSource), then parse in-memory. No native
|
|
62
|
+
// addon / SDK build step (the WASM module self-loads its .wasm).
|
|
63
|
+
const read = await ctx.readSource();
|
|
64
|
+
if (!read.ok) {
|
|
65
|
+
const wrapper = new Error(`fbx-source-unreadable: ${ctx.source}`);
|
|
66
|
+
(wrapper as { cause?: unknown }).cause = read.error;
|
|
67
|
+
throw wrapper;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await initFbxWasm();
|
|
71
|
+
const jsonStr = parseFbx(read.value);
|
|
72
|
+
const doc = JSON.parse(jsonStr) as FbxRawDocument &
|
|
73
|
+
FbxRawSkeletonDoc &
|
|
74
|
+
FbxRawSkinDoc &
|
|
75
|
+
FbxRawAnimDoc;
|
|
76
|
+
|
|
77
|
+
// NURBS / patch fail-fast: the bridge emits an error envelope for
|
|
78
|
+
// unsupported surface types (charter P3 explicit failure).
|
|
79
|
+
const maybeError = doc as unknown as {
|
|
80
|
+
error?: { code: string; meshType: string; meshName: string };
|
|
81
|
+
};
|
|
82
|
+
if (maybeError.error?.code === 'fbx-mesh-type-unsupported') {
|
|
83
|
+
const e = fbxErr('fbx-mesh-type-unsupported', {
|
|
84
|
+
meshType: maybeError.error.meshType as 'nurbs' | 'patch',
|
|
85
|
+
meshName: maybeError.error.meshName,
|
|
86
|
+
});
|
|
87
|
+
// The Importer interface returns Promise<ImportedAsset[]> (not Result),
|
|
88
|
+
// so structural FbxError cannot flow through the import-runner's typed
|
|
89
|
+
// error return. Throw a plain Error with the code in the message so
|
|
90
|
+
// AI users can grep import-internal-error.detail.reason for the
|
|
91
|
+
// 'fbx-mesh-type-unsupported' substring; the structural error rides on
|
|
92
|
+
// `.cause`.
|
|
93
|
+
const wrapper = new Error(`${e.code}: ${e.expected}`);
|
|
94
|
+
(wrapper as { cause?: unknown }).cause = e;
|
|
95
|
+
throw wrapper;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rawMeshes: readonly FbxRawMesh[] = doc.meshes ?? [];
|
|
99
|
+
const meshes = rawMeshes.map((raw, i) => parseMesh(raw, i));
|
|
100
|
+
|
|
101
|
+
const scene = parseScene(doc as unknown as FbxRawNodes);
|
|
102
|
+
|
|
103
|
+
const texturesModule = doc as unknown as { textures?: readonly unknown[] };
|
|
104
|
+
const textures = parseTextures({ textures: texturesModule.textures as never });
|
|
105
|
+
|
|
106
|
+
const materialDocs =
|
|
107
|
+
(doc as unknown as { materials?: readonly FbxRawMaterial[] }).materials ?? [];
|
|
108
|
+
const materials =
|
|
109
|
+
materialDocs.length > 0
|
|
110
|
+
? materialDocs.map((raw, i) => parseMaterial(raw, i))
|
|
111
|
+
: [parseMaterial({ kind: 'fallback' }, 0)];
|
|
112
|
+
|
|
113
|
+
const skeleton = parseSkeleton(doc);
|
|
114
|
+
const skin = parseSkin(doc);
|
|
115
|
+
const animationTargets = resolveAnimationTargetIds(
|
|
116
|
+
(doc as unknown as FbxRawNodes).nodes ?? [],
|
|
117
|
+
doc.clips ?? [],
|
|
118
|
+
);
|
|
119
|
+
if (!animationTargets.ok) {
|
|
120
|
+
const wrapper = new Error(
|
|
121
|
+
`${animationTargets.error.code}: ${animationTargets.error.expected}`,
|
|
122
|
+
);
|
|
123
|
+
(wrapper as { cause?: unknown }).cause = animationTargets.error;
|
|
124
|
+
throw wrapper;
|
|
125
|
+
}
|
|
126
|
+
const animationClips = parseAnimationClips(doc);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
value: {
|
|
131
|
+
assets: toAssetPack({
|
|
132
|
+
meshes,
|
|
133
|
+
scene,
|
|
134
|
+
materials,
|
|
135
|
+
textures,
|
|
136
|
+
skeleton,
|
|
137
|
+
skin,
|
|
138
|
+
animationClips,
|
|
139
|
+
subAssets: ctx.subAssets,
|
|
140
|
+
...(ctx.sourceOverrides === undefined ? {} : { sourceOverrides: ctx.sourceOverrides }),
|
|
141
|
+
}),
|
|
142
|
+
sourceDependencies: [],
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @forgeax/engine-fbx — barrel entry point.
|
|
3
|
+
*
|
|
4
|
+
* FBX importer via ufbx compiled to WebAssembly. This file carries the WASM
|
|
5
|
+
* runtime (initFbxWasm / parseFbx) plus the barrel re-exports of the
|
|
6
|
+
* parse-*.ts bridge layer + `fbxImporter` (single-entry indexability, charter
|
|
7
|
+
* F1). Both browser and Node resolve through the same self-loading WASM glue.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { fbxImporter } from '@forgeax/engine-fbx'; // build-time importer
|
|
11
|
+
* // or the low-level parse API:
|
|
12
|
+
* import { initFbxWasm, parseFbx } from '@forgeax/engine-fbx';
|
|
13
|
+
* await initFbxWasm(); // load .wasm once
|
|
14
|
+
* const json = parseFbx(fbxBytes); // Uint8Array -> JSON string
|
|
15
|
+
* const pod = JSON.parse(json); // engine FBX POD schema
|
|
16
|
+
*
|
|
17
|
+
* WASM asset resolution (mirrors @forgeax/engine-wgpu-wasm):
|
|
18
|
+
* - Browser / Vite: new URL() resolves to a fetch-able asset URL.
|
|
19
|
+
* - Node runtime: the emcc glue is built with ENVIRONMENT=web,node, so it
|
|
20
|
+
* self-loads the .wasm via fs from the locateFile URL — no manual
|
|
21
|
+
* fs.readFile + wasmBinary hand-off in this layer (Derive, Don't Duplicate).
|
|
22
|
+
* - vitest: runs under Node; same self-load path.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// The emcc glue (pkg/fbx-wasm.mjs) is a gitignored build artifact — present only
|
|
26
|
+
// after build:wasm / fetch-wasm. It is imported LAZILY inside _loadWasm (not at
|
|
27
|
+
// module top level) so that merely importing @forgeax/engine-fbx — e.g. the
|
|
28
|
+
// studio editor-core barrel re-exporting cookFbxMeta, or the hermetic
|
|
29
|
+
// barrel-export-contract test (bun test, no wasm build) — does NOT require the
|
|
30
|
+
// built artifact. The glue is only needed once a caller invokes initFbxWasm().
|
|
31
|
+
type CreateFbxModule = (opts?: Record<string, unknown>) => Promise<FbxWasmModule>;
|
|
32
|
+
|
|
33
|
+
/* ── Types ─────────────────────────────────────────────────────────── */
|
|
34
|
+
|
|
35
|
+
interface FbxWasmModule {
|
|
36
|
+
/** @internal emcc-exported C entry: parse FBX bytes at ptr into internal result buffer. */
|
|
37
|
+
_parseFbxWasm(ptr: number, size: number): void;
|
|
38
|
+
/** @internal emcc-exported C entry: pointer to the result JSON string. */
|
|
39
|
+
_getResultPtr(): number;
|
|
40
|
+
/** @internal emcc-exported C entry: byte length of the result JSON string. */
|
|
41
|
+
_getResultLen(): number;
|
|
42
|
+
/** @internal emcc-exported C entry: free the result buffer. */
|
|
43
|
+
_freeResult(): void;
|
|
44
|
+
/** @internal emscripten runtime: allocate `size` bytes in the wasm heap. */
|
|
45
|
+
_malloc(size: number): number;
|
|
46
|
+
/** @internal emscripten runtime: free a wasm-heap pointer. */
|
|
47
|
+
_free(ptr: number): void;
|
|
48
|
+
HEAPU8: Uint8Array;
|
|
49
|
+
UTF8ToString(ptr: number, maxLen?: number): string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let wasmModule: FbxWasmModule | null = null;
|
|
53
|
+
let initPromise: Promise<FbxWasmModule> | null = null;
|
|
54
|
+
|
|
55
|
+
/* ── Internal loader ───────────────────────────────────────────────── */
|
|
56
|
+
|
|
57
|
+
async function _loadWasm(overrideUrl?: string): Promise<FbxWasmModule> {
|
|
58
|
+
// Lazy-load the emcc glue only now that a caller actually wants the wasm.
|
|
59
|
+
// @vite-ignore keeps the bundler from eagerly resolving the (possibly unbuilt)
|
|
60
|
+
// artifact at import-graph time; it resolves at call time in every scenario
|
|
61
|
+
// (browser/Vite, Node, vitest) exactly as the old top-level import did.
|
|
62
|
+
const glueId = '../pkg/fbx-wasm.mjs';
|
|
63
|
+
const createModule = ((await import(/* @vite-ignore */ glueId)) as { default: CreateFbxModule })
|
|
64
|
+
.default;
|
|
65
|
+
|
|
66
|
+
// The emcc glue (ENVIRONMENT=web,node) self-loads the .wasm from this URL:
|
|
67
|
+
// via fetch() in the browser, via fs in Node. We only tell it where to look.
|
|
68
|
+
const wasmAssetUrl = overrideUrl
|
|
69
|
+
? new URL(overrideUrl, import.meta.url)
|
|
70
|
+
: new URL('../pkg/fbx-wasm.wasm', import.meta.url);
|
|
71
|
+
|
|
72
|
+
const opts: Record<string, unknown> = {
|
|
73
|
+
locateFile: () => wasmAssetUrl.href,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
return await createModule(opts);
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`@forgeax/engine-fbx: failed to load WASM from ${wasmAssetUrl.href}. ` +
|
|
81
|
+
'pkg/fbx-wasm.wasm may be missing. Self-help: ' +
|
|
82
|
+
'(1) fetch a prebuilt artifact via `pnpm -F @forgeax/engine-fbx fetch-wasm`, or ' +
|
|
83
|
+
'(2) compile locally with emcc via `pnpm -F @forgeax/engine-fbx build:wasm`.',
|
|
84
|
+
{ cause },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* ── Public API ────────────────────────────────────────────────────── */
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Initialize the WASM module. Must be called once before `parseFbx`.
|
|
93
|
+
* Safe to call multiple times (idempotent). Null-resets on failure so
|
|
94
|
+
* transient errors (e.g. fetch jitter) are retryable.
|
|
95
|
+
*
|
|
96
|
+
* @param wasmUrl — optional override URL for the .wasm file
|
|
97
|
+
*/
|
|
98
|
+
export async function initFbxWasm(wasmUrl?: string): Promise<void> {
|
|
99
|
+
if (wasmModule) return;
|
|
100
|
+
|
|
101
|
+
if (!initPromise) {
|
|
102
|
+
initPromise = _loadWasm(wasmUrl).catch((e: unknown) => {
|
|
103
|
+
initPromise = null;
|
|
104
|
+
throw e;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
wasmModule = await initPromise;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Parse an FBX file in-memory and return the JSON POD string.
|
|
113
|
+
*
|
|
114
|
+
* The returned JSON follows the engine FBX POD schema, containing:
|
|
115
|
+
* meshes, nodes, materials, skeletons, skins, clips.
|
|
116
|
+
*
|
|
117
|
+
* @param fbxBytes — raw FBX file bytes (binary or ASCII)
|
|
118
|
+
* @returns JSON string matching the engine's FBX POD schema
|
|
119
|
+
* @throws if WASM module is not initialized or parse fails
|
|
120
|
+
*/
|
|
121
|
+
export function parseFbx(fbxBytes: Uint8Array): string {
|
|
122
|
+
if (!wasmModule) {
|
|
123
|
+
throw new Error('@forgeax/engine-fbx: WASM not initialized. Call initFbxWasm() first.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const mod = wasmModule;
|
|
127
|
+
const size = fbxBytes.byteLength;
|
|
128
|
+
|
|
129
|
+
const ptr = mod._malloc(size);
|
|
130
|
+
if (!ptr) throw new Error('fbx-wasm: malloc failed for input buffer');
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
mod.HEAPU8.set(fbxBytes, ptr);
|
|
134
|
+
mod._parseFbxWasm(ptr, size);
|
|
135
|
+
} finally {
|
|
136
|
+
mod._free(ptr);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const resultPtr = mod._getResultPtr();
|
|
140
|
+
const resultLen = mod._getResultLen();
|
|
141
|
+
|
|
142
|
+
if (!resultPtr || !resultLen) {
|
|
143
|
+
mod._freeResult();
|
|
144
|
+
throw new Error('fbx-wasm: parseFbxWasm returned empty result');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Decode via a fresh sliced copy, NOT mod.UTF8ToString. Under
|
|
148
|
+
// ALLOW_MEMORY_GROWTH=1 the emcc glue backs HEAPU8 with a *resizable*
|
|
149
|
+
// ArrayBuffer (wasmMemory.toResizableBuffer()); glue's UTF8ArrayToString then
|
|
150
|
+
// calls TextDecoder.decode(HEAPU8.subarray(...)), which modern Chromium
|
|
151
|
+
// rejects ("The provided ArrayBuffer value must not be resizable"). HEAPU8
|
|
152
|
+
// .slice() returns a copy backed by a plain, non-resizable ArrayBuffer, so the
|
|
153
|
+
// decode is accepted in every environment (browser/Vite, Node, vitest).
|
|
154
|
+
const bytes = mod.HEAPU8.slice(resultPtr, resultPtr + resultLen);
|
|
155
|
+
mod._freeResult();
|
|
156
|
+
const json = new TextDecoder().decode(bytes);
|
|
157
|
+
|
|
158
|
+
const firstChars = json.substring(0, 30);
|
|
159
|
+
if (firstChars.includes('"error"')) {
|
|
160
|
+
const parsed = JSON.parse(json);
|
|
161
|
+
if (parsed.error) {
|
|
162
|
+
throw new Error(`fbx-wasm: ${parsed.error.message || 'parse failed'}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return json;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Convenience: parse FBX bytes and return the parsed POD object.
|
|
171
|
+
*/
|
|
172
|
+
export function parseFbxToObject(fbxBytes: Uint8Array): Record<string, unknown> {
|
|
173
|
+
return JSON.parse(parseFbx(fbxBytes));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if the WASM module is ready.
|
|
178
|
+
*/
|
|
179
|
+
export function isFbxWasmReady(): boolean {
|
|
180
|
+
return wasmModule !== null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ── Bridge-layer barrel (parse-*.ts + importer + errors) ──────────── */
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
FBX_ERROR_HINTS,
|
|
187
|
+
type FbxError,
|
|
188
|
+
type FbxErrorCode,
|
|
189
|
+
type FbxErrorDetail,
|
|
190
|
+
fbxErr,
|
|
191
|
+
} from './errors.js';
|
|
192
|
+
export {
|
|
193
|
+
deriveFbxSourceKeys,
|
|
194
|
+
fbxImporter,
|
|
195
|
+
sourceKeyForFbxOutput,
|
|
196
|
+
} from './fbx-importer.js';
|
|
197
|
+
export {
|
|
198
|
+
type FbxRawAnimDoc,
|
|
199
|
+
type FbxRawClip,
|
|
200
|
+
parseAnimationClips,
|
|
201
|
+
} from './parse-animation-clip.js';
|
|
202
|
+
export { type FbxRawMaterial, parseMaterial } from './parse-material.js';
|
|
203
|
+
export { type FbxRawDocument, type FbxRawMesh, parseMesh } from './parse-mesh.js';
|
|
204
|
+
export { type FbxRawNode, type FbxRawNodes, parseScene } from './parse-scene.js';
|
|
205
|
+
export { type FbxRawSkeletonDoc, parseSkeleton } from './parse-skeleton.js';
|
|
206
|
+
export { type FbxRawSkinDoc, parseSkin } from './parse-skin.js';
|
|
207
|
+
export { type FbxRawTexture, type FbxRawTextures, parseTextures } from './parse-texture.js';
|
|
208
|
+
export {
|
|
209
|
+
type FbxTextureCandidate,
|
|
210
|
+
type FbxTexturePathRequest,
|
|
211
|
+
type FbxTextureResolution,
|
|
212
|
+
type FbxTextureResolutionStrategy,
|
|
213
|
+
resolveFbxTexturePath,
|
|
214
|
+
} from './resolve-texture-path.js';
|
|
215
|
+
export { toAssetPack } from './to-asset-pack.js';
|