Create export_3mf.ts
This commit is contained in:
parent
22fd82e3f2
commit
34f8bc5c33
1 changed files with 86 additions and 0 deletions
86
src/io/export_3mf.ts
Normal file
86
src/io/export_3mf.ts
Normal file
|
|
@ -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([
|
||||
'<?xml version="1.0" encoding="utf-8"?>',
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
|
||||
'<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>',
|
||||
'</Types>',
|
||||
].join('\n')),
|
||||
'3D/3dmodel.model': new TextEncoder().encode([
|
||||
'<?xml version="1.0" encoding="utf-8"?>',
|
||||
'<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06">',
|
||||
'<meta name="BambuStudio:3mfVersion" value="1"/>',
|
||||
'<meta name="slic3rpe:Version3mf" value="1"/>',
|
||||
'<meta name="slic3rpe:MmPaintingVersion" value="1"/>',
|
||||
'<resources>',
|
||||
'<basematerials id="2">',
|
||||
...data.colors.map((color, i) => `<base name="color_${i}" displaycolor="#${chroma.rgb(...color).hex()}"/>`),
|
||||
'</basematerials>',
|
||||
`<object id="1" name="OpenSCAD Model" type="model" p:UUID="${objectUuid}" pid="2" pindex="0">`,
|
||||
'<mesh>',
|
||||
'<vertices>',
|
||||
...data.vertices.map((vertex, i) => `<vertex x="${vertex.x}" y="${vertex.y}" z="${vertex.z}" />`),
|
||||
'</vertices>',
|
||||
'<triangles>',
|
||||
...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 `<triangle ${attrs.join(' ')} />`;
|
||||
}),
|
||||
'</triangles>',
|
||||
'</mesh>',
|
||||
'</object>',
|
||||
'</resources>',
|
||||
`<build p:UUID="${buildUuid}}">`,
|
||||
`<item objectid="1" p:UUID="${objectUuid}"/>`,
|
||||
'</build>',
|
||||
'</model>',
|
||||
].join('\n')),
|
||||
};
|
||||
return new Blob([UZIP.encode(archive)], {type: 'model/3mf'});
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue