gltfjsx-rbl 0.0.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/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # gltfjsx-rbl
2
+
3
+ Turns `.glb`/`.gltf` files into typed, declarative [react-babylon-lite](https://github.com/brianzinn/react-babylon-lite)
4
+ components — a [pmndrs/gltfjsx](https://github.com/pmndrs/gltfjsx) for Babylon Lite.
5
+
6
+ ```bash
7
+ npx gltfjsx-rbl public/models/Fox.glb --name FoxModel --url /models/Fox.glb --output src/models/fox-model.tsx
8
+ ```
9
+
10
+ ```tsx
11
+ <FoxModel scaling={[0.028, 0.028, 0.028]} onAnimations={(api) => api.crossFade('Survey', 'Run', 300)} />
12
+ ```
13
+
14
+ ## How it works
15
+
16
+ The CLI reads ONLY the node graph (names, hierarchy, mesh/skin/material assignments, clip
17
+ names) via `@gltf-transform/core` — no GPU, no `@babylonjs/lite` import (lite cannot load
18
+ glTF headlessly). The generated component references the loaded objects by name at runtime
19
+ through `useGltfGraph(url)` and mounts each mesh with `<Primitive object={nodes.x}>`; when
20
+ the file has animation clips it wires `useAnimations` and exposes the clip api via an
21
+ `onAnimations` callback prop. The output is yours to edit: delete nodes, hang physics/gizmo/
22
+ pointer-event children on them, override transforms and material fields.
23
+
24
+ ## Options
25
+
26
+ | Flag | Meaning |
27
+ | ----------------- | ------------------------------------------------------------ |
28
+ | `--name` (`-n`) | Component name (default derived from the file name) |
29
+ | `--url` (`-u`) | Runtime URL the app loads the asset from (default `/<file>`) |
30
+ | `--output` (`-o`) | Output path (default `<ComponentName>.tsx`) |
31
+ | `--shadows` | Emit `receiveShadows` on each mesh |
32
+
33
+ The exact command is echoed into the generated file's header so it is reproducible.
34
+
35
+ ## Notes
36
+
37
+ - On Windows, run from PowerShell — Git Bash rewrites `--url /models/x.glb` into a Windows
38
+ path (MSYS argument conversion).
39
+ - Node keys mirror `useGltfGraph`'s runtime indexing exactly; material keys are best-effort
40
+ (lite's loader may not carry every glTF material name through).
41
+ - Multi-primitive meshes are flagged with a comment — lite may split them into one mesh per
42
+ primitive at load; verify the runtime names.
43
+ - Clips live on the renderer's shared animation manager (the container is never scene-added),
44
+ which also makes remounting safe.
45
+ - **glTF cameras are noted, not used.** Babylon Lite's glTF loader does not parse cameras
46
+ (they never reach the runtime container), so a camera in the file is reported in the header
47
+ comment — supply your own (`<ArcRotateCamera>`).
48
+ - Nameless nodes aren't addressable at runtime (lite drops them from the graph), so a file
49
+ whose only mesh is nameless generates a component that renders nothing.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { basename, resolve } from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import { emitComponent } from './emit.js';
6
+ import { readGraphModel } from './graph.js';
7
+ const { values, positionals } = parseArgs({
8
+ allowPositionals: true,
9
+ options: {
10
+ output: { type: 'string', short: 'o' },
11
+ url: { type: 'string', short: 'u' },
12
+ name: { type: 'string', short: 'n' },
13
+ shadows: { type: 'boolean' },
14
+ },
15
+ });
16
+ const file = positionals[0];
17
+ if (file === undefined) {
18
+ console.error('usage: gltfjsx-rbl <file.glb|file.gltf> [--name Model] [--url /models/x.glb] [--output Model.tsx] [--shadows]');
19
+ process.exit(1);
20
+ }
21
+ const toComponentName = (fileName) => {
22
+ const stem = fileName.replace(/\.(glb|gltf)$/i, '');
23
+ const pascal = stem
24
+ .split(/[^A-Za-z0-9]+/)
25
+ .filter(Boolean)
26
+ .map((part) => part[0].toUpperCase() + part.slice(1))
27
+ .join('');
28
+ return /^[0-9]/.test(pascal) || pascal.length === 0 ? `Model${pascal}` : `${pascal}Model`;
29
+ };
30
+ const model = await readGraphModel(resolve(file));
31
+ const componentName = values.name ?? toComponentName(basename(file));
32
+ const code = await emitComponent(model, {
33
+ componentName,
34
+ url: values.url ?? `/${basename(file)}`,
35
+ version: process.env['npm_package_version'] ?? '',
36
+ // Echo the exact invocation so the generated file records how to reproduce it.
37
+ command: `npx gltfjsx-rbl ${process.argv.slice(2).join(' ')}`,
38
+ shadows: values.shadows === true,
39
+ });
40
+ const outPath = resolve(values.output ?? `${componentName}.tsx`);
41
+ await writeFile(outPath, code, 'utf8');
42
+ console.log(`wrote ${outPath} (${model.flat.length} nodes, ${model.materials.length} materials, ${model.animations.length} clips)`);
43
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACxC,gBAAgB,EAAE,IAAI;IACtB,OAAO,EAAE;QACP,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;QACtC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;QACnC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;QACpC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KAC7B;CACF,CAAC,CAAC;AAEH,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;AAC5B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;IACvB,OAAO,CAAC,KAAK,CACX,+GAA+G,CAChH,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,eAAe,GAAG,CAAC,QAAgB,EAAU,EAAE;IACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI;SAChB,KAAK,CAAC,eAAe,CAAC;SACtB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACrD,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC;AAC5F,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAClD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,IAAI,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AACrE,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE;IACtC,aAAa;IACb,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE;IACvC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,EAAE;IACjD,+EAA+E;IAC/E,OAAO,EAAE,mBAAmB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;IAC7D,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI;CACjC,CAAC,CAAC;AACH,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,GAAG,aAAa,MAAM,CAAC,CAAC;AACjE,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACvC,OAAO,CAAC,GAAG,CACT,SAAS,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,WAAW,KAAK,CAAC,SAAS,CAAC,MAAM,eAAe,KAAK,CAAC,UAAU,CAAC,MAAM,SAAS,CACvH,CAAC"}
package/dist/emit.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { GraphModel } from './graph.js';
2
+ export interface EmitOptions {
3
+ /** Generated component name, e.g. "FoxModel". */
4
+ componentName: string;
5
+ /** Runtime URL the app loads the asset from, e.g. "/models/Fox.glb". */
6
+ url: string;
7
+ /** Tool version stamped into the header. */
8
+ version?: string;
9
+ /** The exact CLI invocation, echoed into the header so the file is reproducible. */
10
+ command?: string;
11
+ /** Emit `receiveShadows` on each mesh (gltfjsx `--shadows`). */
12
+ shadows?: boolean;
13
+ }
14
+ /** Graph model → a typed react-babylon-lite component module (TSX source, prettier-formatted). */
15
+ export declare const emitComponent: (model: GraphModel, options: EmitOptions) => Promise<string>;
16
+ //# sourceMappingURL=emit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAWD,kGAAkG;AAClG,eAAO,MAAM,aAAa,GAAU,OAAO,UAAU,EAAE,SAAS,WAAW,KAAG,OAAO,CAAC,MAAM,CA6G3F,CAAC"}
package/dist/emit.js ADDED
@@ -0,0 +1,99 @@
1
+ import { format } from 'prettier';
2
+ const primitiveLine = (node, shadows) => {
3
+ const receiveShadows = shadows ? ' receiveShadows' : '';
4
+ const warning = node.primitiveCount > 1
5
+ ? ` {/* ${node.primitiveCount} primitives — lite may split this mesh; verify the runtime name */}`
6
+ : '';
7
+ return ` <Primitive object={nodes.${node.identifier}}${receiveShadows} />${warning}`;
8
+ };
9
+ /** Graph model → a typed react-babylon-lite component module (TSX source, prettier-formatted). */
10
+ export const emitComponent = async (model, options) => {
11
+ const { componentName, url, shadows = false } = options;
12
+ // Babylon Lite's glTF loader drops camera nodes entirely (they never reach the runtime
13
+ // AssetContainer), so cameras are NOTED but never emitted as runtime nodes — doing so would
14
+ // type `nodes.x` as present when it is actually undefined at runtime.
15
+ const runtime = model.flat.filter((node) => node.kind !== 'camera');
16
+ const named = runtime.filter((node) => node.identifier !== null);
17
+ const meshNodes = named.filter((node) => node.kind === 'mesh');
18
+ const plainNodes = named.filter((node) => node.kind === 'node');
19
+ const cameraCount = model.flat.filter((node) => node.kind === 'camera').length;
20
+ const nameless = runtime.length - named.length;
21
+ const hasAnimations = model.animations.length > 0;
22
+ const hasMaterials = model.materials.length > 0;
23
+ const nodesType = named.length > 0
24
+ ? `export interface ${componentName}Nodes {
25
+ ${named.map((node) => ` ${node.identifier}: ${node.kind === 'mesh' ? 'Mesh' : 'SceneNode'};`).join('\n')}
26
+ }`
27
+ : `export type ${componentName}Nodes = Record<string, never>;`;
28
+ // Import only the types actually referenced (verbatimModuleSyntax + no-unused-vars).
29
+ const liteTypes = [
30
+ meshNodes.length > 0 ? 'Mesh' : '',
31
+ hasMaterials ? 'PbrMaterialProps' : '',
32
+ plainNodes.length > 0 ? 'SceneNode' : '',
33
+ ].filter(Boolean);
34
+ const liteTypeImport = liteTypes.length > 0 ? `import type { ${liteTypes.join(', ')} } from '@babylonjs/lite';\n` : '';
35
+ const commandLine = options.command !== undefined ? `\n * Command: ${options.command}` : '';
36
+ const cameraNote = cameraCount > 0
37
+ ? `\n * Note: the source declares ${cameraCount} camera(s), but Babylon Lite's glTF loader
38
+ * does not parse cameras — supply your own (e.g. <ArcRotateCamera>).`
39
+ : '';
40
+ const header = `/*
41
+ * Auto-generated by gltfjsx-rbl ${options.version ?? ''} — edit freely, it's yours.${commandLine}
42
+ * Source: ${model.source} (${named.length} named nodes, ${model.materials.length} materials${hasAnimations ? `, clips: ${model.animations.join(', ')}` : ''})
43
+ * Node keys mirror useGltfGraph's runtime indexing; material keys are best-effort (lite's
44
+ * loader may not carry every glTF material name through).${nameless > 0 ? `\n * ${nameless} nameless node(s) are not addressable at runtime.` : ''}${cameraNote}
45
+ */`;
46
+ const animationImports = hasAnimations
47
+ ? `import { useAnimations } from 'react-babylon-lite/animation';
48
+ import type { AnimationsApi } from 'react-babylon-lite/animation';
49
+ import { useEffect } from 'react';
50
+ `
51
+ : '';
52
+ const materialsType = hasMaterials
53
+ ? `export interface ${componentName}Materials {
54
+ ${model.materials.map((material) => ` ${material.identifier}: PbrMaterialProps;`).join('\n')}
55
+ }`
56
+ : `export type ${componentName}Materials = Record<string, never>;`;
57
+ const propsType = hasAnimations
58
+ ? `export interface ${componentName}Props extends ComponentProps<typeof TransformNode> {
59
+ /** Clip api (play / crossFade / setSpeed / …). Clips: ${model.animations.join(', ')}. */
60
+ onAnimations?: (animations: AnimationsApi) => void;
61
+ }`
62
+ : `export type ${componentName}Props = ComponentProps<typeof TransformNode>;`;
63
+ const signature = hasAnimations
64
+ ? `{ onAnimations, ...props }: ${componentName}Props`
65
+ : `props: ${componentName}Props`;
66
+ const graphDestructure = hasAnimations ? '{ nodes, animationGroups }' : '{ nodes }';
67
+ const animationBody = hasAnimations
68
+ ? ` const animations = useAnimations(animationGroups);
69
+ useEffect(() => onAnimations?.(animations), [animations, onAnimations]);
70
+ `
71
+ : '';
72
+ const children = meshNodes.length > 0
73
+ ? meshNodes.map((node) => primitiveLine(node, shadows)).join('\n')
74
+ : ' {/* no mesh nodes in this file */}';
75
+ const code = `${header}
76
+ ${liteTypeImport}import type { ComponentProps } from 'react';
77
+ ${animationImports}import { useGltfGraph } from 'react-babylon-lite';
78
+ import { Primitive, TransformNode } from 'react-babylon-lite/components';
79
+
80
+ export const MODEL_URL = '${url}';
81
+
82
+ ${nodesType}
83
+
84
+ ${materialsType}
85
+
86
+ ${propsType}
87
+
88
+ export function ${componentName}(${signature}) {
89
+ const ${graphDestructure} = useGltfGraph<${componentName}Nodes, ${componentName}Materials>(MODEL_URL);
90
+ ${animationBody} return (
91
+ <TransformNode name="${componentName}" {...props}>
92
+ ${children}
93
+ </TransformNode>
94
+ );
95
+ }
96
+ `;
97
+ return format(code, { parser: 'typescript', singleQuote: true, printWidth: 110 });
98
+ };
99
+ //# sourceMappingURL=emit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.js","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export interface GraphNodeModel {
2
+ /** Unique JS identifier (null when the glTF node is nameless — unreachable at runtime). */
3
+ identifier: string | null;
4
+ name: string;
5
+ kind: 'mesh' | 'camera' | 'node';
6
+ skinned: boolean;
7
+ /** >1 means lite may split this into one Mesh per primitive — flagged in the output. */
8
+ primitiveCount: number;
9
+ /** Identifiers of the materials this node's mesh primitives use. */
10
+ materials: string[];
11
+ children: GraphNodeModel[];
12
+ }
13
+ export interface GraphMaterialModel {
14
+ identifier: string;
15
+ name: string;
16
+ }
17
+ export interface GraphModel {
18
+ source: string;
19
+ roots: GraphNodeModel[];
20
+ /** Depth-first flattening of roots — the emission and runtime-claim order. */
21
+ flat: GraphNodeModel[];
22
+ materials: GraphMaterialModel[];
23
+ /** Clip names in file order (lite auto-plays the first). */
24
+ animations: string[];
25
+ }
26
+ /**
27
+ * Reads ONLY the node graph — names, hierarchy, mesh/material/skin assignments, clip names —
28
+ * via @gltf-transform/core. No geometry decoding, no GPU, so this runs anywhere node does
29
+ * (lite itself cannot load glTF headlessly — it needs a WebGPU engine).
30
+ */
31
+ export declare const readGraphModel: (filePath: string) => Promise<GraphModel>;
32
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,cAAc,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,8EAA8E;IAC9E,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,4DAA4D;IAC5D,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,UAAU,CA0DzE,CAAC"}
package/dist/graph.js ADDED
@@ -0,0 +1,64 @@
1
+ import { NodeIO } from '@gltf-transform/core';
2
+ import { basename } from 'node:path';
3
+ import { claimName, sanitizeName } from './sanitize.js';
4
+ /**
5
+ * Reads ONLY the node graph — names, hierarchy, mesh/material/skin assignments, clip names —
6
+ * via @gltf-transform/core. No geometry decoding, no GPU, so this runs anywhere node does
7
+ * (lite itself cannot load glTF headlessly — it needs a WebGPU engine).
8
+ */
9
+ export const readGraphModel = async (filePath) => {
10
+ const document = await new NodeIO().read(filePath);
11
+ const root = document.getRoot();
12
+ const nodeNames = new Set();
13
+ const materialNames = new Set();
14
+ const materialIds = new Map();
15
+ const materials = [];
16
+ let materialIndex = 0;
17
+ const flat = [];
18
+ // Mirrors the runtime walk: materials claimed at first encounter during the node DFS,
19
+ // nameless ones falling back to their discovery index.
20
+ const claimMaterial = (material) => {
21
+ const existing = materialIds.get(material);
22
+ if (existing !== undefined)
23
+ return existing;
24
+ const rawName = material.getName();
25
+ const base = rawName.length > 0 ? rawName : `material_${materialIndex}`;
26
+ materialIndex += 1;
27
+ const identifier = claimName(sanitizeName(base), materialNames);
28
+ materialIds.set(material, identifier);
29
+ materials.push({ identifier, name: rawName });
30
+ return identifier;
31
+ };
32
+ const visit = (node) => {
33
+ const name = node.getName();
34
+ const mesh = node.getMesh();
35
+ const primitives = mesh?.listPrimitives() ?? [];
36
+ const model = {
37
+ identifier: name.length > 0 ? claimName(sanitizeName(name), nodeNames) : null,
38
+ name,
39
+ kind: mesh !== null ? 'mesh' : node.getCamera() !== null ? 'camera' : 'node',
40
+ skinned: node.getSkin() !== null,
41
+ primitiveCount: primitives.length,
42
+ materials: [],
43
+ children: [],
44
+ };
45
+ for (const primitive of primitives) {
46
+ const material = primitive.getMaterial();
47
+ if (material !== null) {
48
+ const id = claimMaterial(material);
49
+ if (!model.materials.includes(id))
50
+ model.materials.push(id);
51
+ }
52
+ }
53
+ flat.push(model);
54
+ model.children = node.listChildren().map(visit);
55
+ return model;
56
+ };
57
+ const scene = root.getDefaultScene() ?? root.listScenes()[0];
58
+ const roots = scene === undefined ? [] : scene.listChildren().map(visit);
59
+ const animations = root
60
+ .listAnimations()
61
+ .map((animation, index) => (animation.getName().length > 0 ? animation.getName() : `animation_${index}`));
62
+ return { source: basename(filePath), roots, flat, materials, animations };
63
+ };
64
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AA8BxD;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,QAAgB,EAAuB,EAAE;IAC5E,MAAM,QAAQ,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;IAEhC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAoB,CAAC;IAChD,MAAM,SAAS,GAAyB,EAAE,CAAC;IAC3C,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,MAAM,IAAI,GAAqB,EAAE,CAAC;IAElC,sFAAsF;IACtF,uDAAuD;IACvD,MAAM,aAAa,GAAG,CAAC,QAAkB,EAAU,EAAE;QACnD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC;QAC5C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,aAAa,EAAE,CAAC;QACxE,aAAa,IAAI,CAAC,CAAC;QACnB,MAAM,UAAU,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC;QAChE,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACtC,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9C,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,IAAU,EAAkB,EAAE;QAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;QAChD,MAAM,KAAK,GAAmB;YAC5B,UAAU,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;YAC7E,IAAI;YACJ,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM;YAC5E,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI;YAChC,cAAc,EAAE,UAAU,CAAC,MAAM;YACjC,SAAS,EAAE,EAAE;YACb,QAAQ,EAAE,EAAE;SACb,CAAC;QACF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;YACzC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,MAAM,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACnC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAAE,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjB,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAEzE,MAAM,UAAU,GAAG,IAAI;SACpB,cAAc,EAAE;SAChB,GAAG,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC,CAAC;IAE5G,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAC5E,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { readGraphModel } from './graph.js';
2
+ export type { GraphMaterialModel, GraphModel, GraphNodeModel } from './graph.js';
3
+ export { emitComponent } from './emit.js';
4
+ export type { EmitOptions } from './emit.js';
5
+ export { claimName, sanitizeName } from './sanitize.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { readGraphModel } from './graph.js';
2
+ export { emitComponent } from './emit.js';
3
+ export { claimName, sanitizeName } from './sanitize.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * glTF names → JS identifiers. MUST stay in sync with react-babylon-lite's sanitizeGraphName
3
+ * and its claim() suffixing — the generated component's keys are only valid because the
4
+ * runtime hook reproduces them from the same names in the same depth-first order.
5
+ */
6
+ export declare const sanitizeName: (name: string) => string;
7
+ export declare const claimName: (base: string, taken: Set<string>) => string;
8
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAG3C,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,OAAO,GAAG,CAAC,MAAM,CAAC,KAAG,MAM5D,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * glTF names → JS identifiers. MUST stay in sync with react-babylon-lite's sanitizeGraphName
3
+ * and its claim() suffixing — the generated component's keys are only valid because the
4
+ * runtime hook reproduces them from the same names in the same depth-first order.
5
+ */
6
+ export const sanitizeName = (name) => {
7
+ const cleaned = name.replace(/[^A-Za-z0-9_$]/g, '_');
8
+ return /^[0-9]/.test(cleaned) || cleaned.length === 0 ? `_${cleaned}` : cleaned;
9
+ };
10
+ export const claimName = (base, taken) => {
11
+ let name = base;
12
+ let suffix = 1;
13
+ while (taken.has(name))
14
+ name = `${base}_${suffix++}`;
15
+ taken.add(name);
16
+ return name;
17
+ };
18
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAClF,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAkB,EAAU,EAAE;IACpE,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,GAAG,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC;IACrD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "gltfjsx-rbl",
3
+ "version": "0.0.1",
4
+ "description": "Turns glTF/GLB files into typed, declarative react-babylon-lite components (a pmndrs/gltfjsx for Babylon Lite)",
5
+ "keywords": [
6
+ "gltfjsx",
7
+ "gltf",
8
+ "glb",
9
+ "react",
10
+ "babylon-lite",
11
+ "react-babylon-lite",
12
+ "codegen"
13
+ ],
14
+ "type": "module",
15
+ "bin": {
16
+ "gltfjsx-rbl": "./dist/cli.js"
17
+ },
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "author": "Brian Zinn <542756+brianzinn@users.noreply.github.com>",
31
+ "license": "Unlicense",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/brianzinn/react-babylon-lite.git",
35
+ "directory": "packages/gltfjsx"
36
+ },
37
+ "homepage": "https://github.com/brianzinn/react-babylon-lite#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/brianzinn/react-babylon-lite/issues"
40
+ },
41
+ "dependencies": {
42
+ "@gltf-transform/core": "^4.0.0",
43
+ "prettier": "^3.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.0.0",
47
+ "rimraf": "^6.1.3",
48
+ "typescript": "^6.0.3",
49
+ "vitest": "^4.1.10"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.build.json",
53
+ "clean": "rimraf dist",
54
+ "prebuild": "npm run clean",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run"
57
+ }
58
+ }