From 5ac81d691fa5cfeb5bf23474eb0bd2f70c868dc9 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sat, 11 Apr 2026 14:52:20 +0100 Subject: [PATCH 1/2] Add 3MF export format support --- api/xr.js | 18 +++++++++++++++++ blocks/connect.js | 1 + tests/xr-export.test.js | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/api/xr.js b/api/xr.js index 45cb3d587..b45fb79ae 100644 --- a/api/xr.js +++ b/api/xr.js @@ -176,6 +176,24 @@ export const flockXR = { ); } else if (format === "OBJ") { flock.EXPORT.OBJExport.OBJ(mesh); + } else if (format === "3MF") { + try { + const serializer = new flock.EXPORT.ThreeMfSerializer(); + const data = await flock.EXPORT.ThreeMf.SerializeToMemoryAsync( + serializer, + ...meshList, + ); + if (data?.length) { + flock.download(`${mesh.name}.3mf`, data, "model/3mf"); + } else { + console.error( + "3MF export did not produce output data for mesh:", + mesh.name, + ); + } + } catch (error) { + console.error("3MF export failed:", error); + } } else if (format === "GLB") { const ghostMat = new flock.BABYLON.PBRMaterial( "_tmpExportWrapperGhost", diff --git a/blocks/connect.js b/blocks/connect.js index 83d7a77c1..a182b91d0 100644 --- a/blocks/connect.js +++ b/blocks/connect.js @@ -314,6 +314,7 @@ export function defineConnectBlocks() { ["STL", "STL"], ["OBJ", "OBJ"], ["GLB", "GLB"], + ["3MF", "3MF"], ], }, ], diff --git a/tests/xr-export.test.js b/tests/xr-export.test.js index 789e97284..89b1e9432 100644 --- a/tests/xr-export.test.js +++ b/tests/xr-export.test.js @@ -108,5 +108,50 @@ export function runXRExportTests(flock) { .filter((mesh) => shouldExportNode(mesh)); expect(exportedDescendants.length).to.be.greaterThan(0); }); + + it("exports 3MF using Babylon serializer and downloads file", async function () { + const treeId = flock.createObject({ + modelName: "tree.glb", + modelId: `tree.glb__3mf_export_${Date.now()}`, + position: { x: 1, y: 0, z: 0 }, + }); + const treeMesh = await waitForModel(flock, treeId); + + const originalSerializer = flock.EXPORT.ThreeMfSerializer; + const originalSerializeToMemoryAsync = flock.EXPORT.ThreeMf.SerializeToMemoryAsync; + const originalDownload = flock.download; + + let serializerInstance; + let serializeMeshes; + let downloadedFile = null; + + flock.EXPORT.ThreeMfSerializer = function MockThreeMfSerializer() { + serializerInstance = this; + }; + flock.EXPORT.ThreeMf.SerializeToMemoryAsync = async (serializer, ...meshes) => { + serializeMeshes = meshes; + expect(serializer).to.equal(serializerInstance); + return new Uint8Array([1, 2, 3]); + }; + flock.download = (filename, data, mimeType) => { + downloadedFile = { filename, data, mimeType }; + }; + + try { + await flock.exportMesh(treeId, "3MF"); + } finally { + flock.EXPORT.ThreeMfSerializer = originalSerializer; + flock.EXPORT.ThreeMf.SerializeToMemoryAsync = originalSerializeToMemoryAsync; + flock.download = originalDownload; + } + + expect(serializeMeshes).to.be.an("array"); + expect(serializeMeshes.length).to.be.greaterThan(0); + expect(serializeMeshes[0]).to.equal(treeMesh); + expect(downloadedFile).to.not.equal(null); + expect(downloadedFile.filename).to.equal(`${treeMesh.name}.3mf`); + expect(downloadedFile.mimeType).to.equal("model/3mf"); + expect(downloadedFile.data).to.be.instanceof(Uint8Array); + }); }); } From ed0d829f8131dece4755246919461f22abcb2462 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sat, 11 Apr 2026 15:00:50 +0100 Subject: [PATCH 2/2] Bundle 3MF exporter fflate dependency for PWA --- api/xr.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/xr.js b/api/xr.js index b45fb79ae..f9a647de5 100644 --- a/api/xr.js +++ b/api/xr.js @@ -1,4 +1,5 @@ import { translate } from "../main/translation.js"; +import * as fflate from "fflate"; let flock; @@ -178,6 +179,9 @@ export const flockXR = { flock.EXPORT.OBJExport.OBJ(mesh); } else if (format === "3MF") { try { + if (!globalThis.fflate) { + globalThis.fflate = fflate; + } const serializer = new flock.EXPORT.ThreeMfSerializer(); const data = await flock.EXPORT.ThreeMf.SerializeToMemoryAsync( serializer,