GLTF2Export Do Not Export Mesh In Some Case

Hi~
I have a babylon project A and a vue project B. I write a script to load a glb file then compress model then export glb file with GLTF2Export.

When B use npm link to refer A, script export glb file correctly.
When B use npm install to refer A, script only export Transform Node to glb file.

It trouble me for long time, how should I do to fix it?

This is export script
SceneCombineHelper.combine is in here, I think it may be not important.

import { LoadAssetContainerAsync, Mesh, VertexData } from "babylonjs";
import { GLTF2Export, GLTFData } from 'babylonjs-serializers';
import { CameraViewScene } from "./CameraViewScene";
import { SceneCombineHelper } from "@/ExtTools/SceneCombine/Combine/SceneCombineHelper";
import { ImageMimeType } from "babylonjs-gltf2interface";

export class CompressViewScene extends CameraViewScene {
    loadReadyStatus: { [key: string]: boolean; } = {};

    GetMimeType(fileName: string): string | undefined {
        if (fileName.endsWith(".glb")) {
            return "model/gltf-binary";
        } else if (fileName.endsWith(".bin")) {
            return "application/octet-stream";
        } else if (fileName.endsWith(".gltf")) {
            return "model/gltf+json";
        } else if (fileName.endsWith(".jpeg") || fileName.endsWith(".jpg")) {
            return ImageMimeType.JPEG;
        } else if (fileName.endsWith(".png")) {
            return ImageMimeType.PNG;
        } else if (fileName.endsWith(".webp")) {
            return ImageMimeType.WEBP;
        }

        return undefined;
    }
    load(url: string, extension: string, gltfDataCallback?: (data: GLTFData) => void) {
        return new Promise<string | null>((rs, rj) => {
            LoadAssetContainerAsync(url, this.scene, {
                pluginExtension: extension
            }).then((container) => {
                console.log("Model compress 加载完成");
                container.addAllToScene();
                console.log("Model compress 即将进行合并");
                const root = SceneCombineHelper.combine(container);
                console.log("Model compress 合并完成");
                try {
                    this.scene.onReadyObservable.add(() => {
                        console.log("Scene Ready");
                        const meshes = root.getChildMeshes(false);
                        console.log("Mesh Count:", meshes.length);
                        meshes.forEach(absMesh => {
                            const mesh = absMesh as Mesh;
                            if (mesh._isMesh) {
                                const VD = VertexData.ExtractFromMesh(mesh);
                                if (VD.positions) {
                                    console.log("Mesh Name:", mesh.name, VD.positions.length / 3);
                                }
                                else {
                                    console.log("No Position Mesh Name:", mesh.name);
                                }
                            }
                        })
                        const metaKeys = [...SceneCombineHelper.metaKey];
                        GLTF2Export.GLBAsync(this.scene, "exportFile", {
                            exportUnusedUVs: true,
                            removeNoopRootNodes: false,
                            shouldExportNode(node) {
                                if (node === root) return true;
                                let parent = node.parent;
                                while (parent) {
                                    if (parent === root) return true;
                                    parent = parent.parent;
                                }
                                return false;
                            },
                            metadataSelector(metadata) {
                                if (!metadata) return;
                                const newMeta = {} as any;
                                metaKeys.forEach(key => {
                                    if (metadata[key] !== undefined) {
                                        newMeta[key] = metadata[key];
                                    }
                                })
                                return newMeta;
                            },
                        }).then(async (data) => {
                            data.downloadFiles();
                        });
                    });
                } catch (error) {
                    rj(error);
                }
            });
        })
    }

}

ExportGLB.zip (195.8 KB) GLBFile

Rollup config

import typescript from "@rollup/plugin-typescript";
import { babel } from "@rollup/plugin-babel";
import { dts } from "rollup-plugin-dts";
import { resolve } from "path";
import { copyFileSync, existsSync, mkdirSync } from "fs";

// 自定义插件用于复制 WASM 文件
function copyWasmFiles() {
  return {
    name: 'copy-wasm-files',
    generateBundle() {
      const wasmDir = resolve('./dist/wasm');
      if (!existsSync(wasmDir)) {
        mkdirSync(wasmDir, { recursive: true });
      }
      
      // 复制 WASM 文件
      const files = ['web-ifc.wasm', 'web-ifc-mt.wasm', 'web-ifc-node.wasm'];
      files.forEach(file => {
        const src = resolve(`./node_modules/web-ifc/${file}`);
        const dest = resolve(`./dist/wasm/${file}`);
        if (existsSync(src)) {
          copyFileSync(src, dest);
          console.log(`Copied ${file} to dist/wasm/`);
        } else {
          console.warn(`Warning: ${file} not found in node_modules/web-ifc/`);
        }
      });
    }
  };
}

export default [
  {
    input: "./src/index.ts",
    output: [
      {
        file: "./dist/kiwi3d.mjs",
        format: "esm",
      },
      {
        file: "./dist/kiwi3d.cjs",
        format: "cjs",
        exports: "named",
      },
    ],
    plugins: [
      typescript({ tsconfig: "./tsconfig.json" }),
      babel({
        exclude: "node_modules/**",
        // 使用打包内联的 helper,避免对 @babel/runtime 的外部依赖
        babelHelpers: "bundled",
      }),
      copyWasmFiles()
    ],
  },
  {
    input: "./dist/types/index.d.ts",
    output: [{ file: "dist/kiwi3d.d.ts", format: "es" }],
    plugins: [
      dts(),
    ],
  },
];

Summon the God of GLTF2Export !!!

@alexchuber I guess that’s you :slight_smile:

the only diff between npm link and install is the version you are using are you sure you are relying on the exact same commit ?

Yes, I check lots of times. I read the code of serializers, I think packages\dev\serializers\src\glTF\2.0\Extensions\index.ts is failed to be imported when npm install. I move all file in packages\dev\serializers\src\glTF into my project, it exports glb file correctly when npm install finally.

This feels like a UMD bundling issue, but I’m also not a bundler expert, so this is all just a guess :slight_smile:


Both package A and package B are using the same UMD packages (babylonjs, babylonjs-serializers). When you use npm link , both Project A and Project B resolve to the same babylonjs instance. However, when you use npm install , Project A has its own copy of babylonjs bundled within it, and Project B has another copy. This creates two separate instances of BABYLON.

The issue might manifest in the glTF exporter because:

  1. It uses many instanceof checks and internal type tracking
  2. Objects created with one instance of Babylon aren’t recognized properly by another instance
  3. The glTF exporter likely fails to recognize meshes created by Project A’s Babylon instance as valid meshes

I found some discussion about this sort of problem here:

That’s a reason to not use a bundled dependencies in your libraries. I always explicitly specify

import pkg from './package.json'
// ....
external: [
  ...Object.keys(pkg.dependencies || {}),
  ...Object.keys(pkg.peerDependencies || {}),
]

for my cjs and es targets.

So maybe something to try is 1. declaring babylonjs packages as peerDependencies in the package.json, then 2. doing some magic with externals in the bundler config.

2 Likes