diff --git a/src/io/export_3mf.ts b/src/io/export_3mf.ts new file mode 100644 index 0000000..2c5806f --- /dev/null +++ b/src/io/export_3mf.ts @@ -0,0 +1,86 @@ +import UZIP from "uzip"; +import { IndexedPolyhedron } from "./common"; +import { v4 as uuidv4 } from "uuid"; +import chroma from "chroma-js"; + +function getColorMapping(colors: chroma.Color[], projectedColors: chroma.Color[]): number[] { + const projectedLabs = projectedColors.map(c => c.lab()); + + return colors.map((targetColor, i) => { + const targetLab = targetColor.lab(); + + let closestIndex = 0; + let minDelta = Infinity; + projectedLabs.forEach((projectedLab, index) => { + const deltaL = targetLab[0] - projectedLab[0]; + const deltaA = targetLab[1] - projectedLab[1]; + const deltaB = targetLab[2] - projectedLab[2]; + const d = Math.sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB); + if (d < minDelta) { + minDelta = d; + closestIndex = index; + } + }); + return closestIndex; + }); +} + +// Reverse-engineered from PrusaSlicer / BambuStudio's output. +const PAINT_COLOR_MAP = ['', '8', '0C', '1C', '2C', '3C', '4C', '5C', '6C', '7C', '8C', '9C', 'AC', 'BC', 'CC', 'DC']; + +export function exportOffTo3MF(data: IndexedPolyhedron, extruderColors?: chroma.Color[]): Blob { + const objectUuid = uuidv4(); + const buildUuid = uuidv4(); + const paintColorByColorIndex = extruderColors && + getColorMapping(data.colors.map(c => chroma.rgb(...c)), extruderColors).map(i => PAINT_COLOR_MAP[i]); + + const archive = { + // _rels/.rels + '[Content Types].xml': new TextEncoder().encode([ + '', + '', + '', + '', + ].join('\n')), + '3D/3dmodel.model': new TextEncoder().encode([ + '', + '', + '', + '', + '', + '', + '', + ...data.colors.map((color, i) => ``), + '', + ``, + '', + '', + ...data.vertices.map((vertex, i) => ``), + '', + '', + ...data.faces.map((face, i) => { + const { vertices, colorIndex } = face; + if (vertices.length != 3) throw new Error('Face must have 3 vertices'); + const attrs = vertices.map((v, i) => `v${i + 1}="${v}"`); + if (colorIndex > 0) { + attrs.push(`pid="2" p1="${colorIndex}"`); + } + const paintColor = paintColorByColorIndex && paintColorByColorIndex[colorIndex]; + if (paintColor) { + attrs.push(`paint_color="${paintColor}"`); + } + return ``; + }), + '', + '', + '', + '', + ``, + ``, + '', + '', + ].join('\n')), + }; + return new Blob([UZIP.encode(archive)], {type: 'model/3mf'}); +} +