{"record":{"id":"92b1d4feb4d35820","repo":"BabylonJS/Babylon.js","slug":"to-call-addpart-addparts-each-source-mesh-mus","errorCode":null,"errorMessage":"To call addPart()/addParts(), each source mesh must be fully loaded","messagePattern":"To call addPart\\(\\)/addParts\\(\\), each source mesh must be fully loaded","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts","lineNumber":1128,"sourceCode":"     * Core implementation for adding one or more source parts as new\r\n     * parts. Writes directly into texture-sized CPU arrays, updates the retained merged source\r\n     * buffers, and uploads in one pass.\r\n     *\r\n     * @param others - Source meshes to append (must each be non-compound and fully loaded)\r\n     * @param disposeOthers - Dispose source meshes after appending\r\n     * @returns Proxy meshes and their assigned part indices\r\n     */\r\n    protected _addPartsInternal(others: IGaussianSplattingPartSource[], disposeOthers: boolean): { proxyMeshes: GaussianSplattingPartProxyMesh[]; assignedPartIndices: number[] } {\r\n        if (others.length === 0) {\r\n            return { proxyMeshes: [], assignedPartIndices: [] };\r\n        }\r\n\r\n        // Validate\r\n        for (const other of others) {\r\n            // Reserved-empty placeholders (reserveStreamingPart) intentionally carry no splat data —\r\n            // their atlas region is left zeroed (invisible) for a streaming engine to decode into later.\r\n            if (!other._splatsData && !other._isReservedEmpty) {\r\n                throw new Error(`To call addPart()/addParts(), each source mesh must be fully loaded`);\r\n            }\r\n            if (other.isCompound) {\r\n                throw new Error(`To call addPart()/addParts(), each source mesh must not be a compound`);\r\n            }\r\n        }\r\n\r\n        const splatCountA = this._vertexCount;\r\n        const totalOtherCount = others.reduce((s, o) => s + o._vertexCount, 0);\r\n        const totalCount = splatCountA + totalOtherCount;\r\n\r\n        const textureSize = this._getTextureSize(totalCount);\r\n        const textureLength = textureSize.x * textureSize.y;\r\n        const covBSItemSize = this._useRGBACovariants ? 4 : 2;\r\n\r\n        // Allocate destination arrays for the full new texture\r\n        const covA = new Uint16Array(textureLength * 4);\r\n        const covB = new Uint16Array(covBSItemSize * textureLength);\r\n        const colorArray = new Uint8Array(textureLength * 4);\r","sourceCodeStart":1110,"sourceCodeEnd":1146,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts#L1110-L1146","documentation":"The GaussianSplattingMesh addPart/addParts validation throws when a source mesh (`other`) has no `_splatsData` and is not a reserved-empty streaming placeholder — i.e. it hasn't finished loading/decoding its splat data. Compound meshes cannot be added; parts must be fully decoded first.","triggerScenarios":"Calling `mesh.addParts([other])` where `other` is still loading from a .splat/.ply/.ksplat URL (its async decode hasn't resolved, so `_splatsData` is undefined), or passing a mesh constructed but never awaited.","commonSituations":"Building a compound splat from several downloaded assets without awaiting each one; queuing addParts immediately after construction; a failed load leaving `_splatsData` null.","solutions":["Await each source mesh's load/decode promise (e.g. `await other.whenReadyAsync()` or the promise from GaussianSplattingMesh creation) before addParts.","Check `other._splatsData` (or a public loaded flag/`isReady`) for every source before merging.","If using streaming, mark intentional placeholders via `reserveStreamingPart` instead of passing unloaded meshes."],"exampleFix":"// before\nconst a = await GaussianSplattingMesh.Parse(...urlA...);\nconst b = new GaussianSplattingMesh(\"b\", urlB, scene); // not awaited\na.addParts([b]);\n\n// after\nconst a = await GaussianSplattingMesh.Parse(...urlA...);\nconst b = await new GaussianSplattingMesh(\"b\", urlB, scene).whenReadyAsync(); // ensure loaded\na.addParts([b]);","handlingStrategy":"validation","validationCode":"if (!other._splatsData && !other._isReservedEmpty) {\n    await other.whenReadyAsync(); // or the mesh's load promise\n}\nmesh.addParts([other]);","typeGuard":"function isSplatsSourceLoaded(mesh: GaussianSplattingMeshBase): boolean {\n    return !!mesh._splatsData || mesh._isReservedEmpty;\n}","tryCatchPattern":"try {\n    compound.addParts(sources);\n} catch (e) {\n    if (e instanceof Error && e.message.includes(\"fully loaded\")) {\n        await Promise.all(sources.map(s => s.whenReadyAsync()));\n        return compound.addParts(sources);\n    }\n    throw e;\n}","preventionTips":["Always await the loading promise of each source splat mesh before addPart/addParts","Track pending loads and queue merges until all sources resolve","Use reserveStreamingPart for intentional empty placeholders instead of unloaded meshes","Handle load failures so sources don't silently remain unloaded"],"tags":["gaussian-splatting","async-loading","state-validation","compound"],"backgroundTag":"source-not-loaded","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}