add mesh:tool 3mf object output
This commit is contained in:
parent
9da2368a7a
commit
f2f5081669
3 changed files with 138 additions and 4 deletions
130
src/load/3mf.js
130
src/load/3mf.js
|
|
@ -212,6 +212,133 @@ function extractItems(records) {
|
|||
return outItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export vertex array(s) to 3MF format
|
||||
* @param {Array} recs - array of {file: string, varr: Float32Array} records
|
||||
* @param {Object} options - encoding options
|
||||
* @param {String} [options.unit='millimeter'] - unit of measurement
|
||||
* @param {String} [options.title='Model'] - model title
|
||||
* @returns {Promise<Blob>} 3MF file as a Blob
|
||||
*/
|
||||
export async function encode(recs, options = {}) {
|
||||
const {
|
||||
unit = 'millimeter',
|
||||
title = 'Model'
|
||||
} = options;
|
||||
|
||||
// Build the 3D model XML
|
||||
let objectId = 1;
|
||||
let resources = [];
|
||||
let buildItems = [];
|
||||
|
||||
for (let rec of recs) {
|
||||
let { file, varr } = rec;
|
||||
let name = file || `object-${objectId}`;
|
||||
|
||||
// Extract unique vertices and build triangle indices
|
||||
let vertices = [];
|
||||
let triangles = [];
|
||||
let vertexMap = new Map(); // map "x,y,z" -> vertex index
|
||||
let vertexIndex = 0;
|
||||
|
||||
// Process triangles (every 9 floats = 3 vertices = 1 triangle)
|
||||
for (let i = 0; i < varr.length; i += 9) {
|
||||
let triIndices = [];
|
||||
|
||||
// Process 3 vertices per triangle
|
||||
for (let j = 0; j < 3; j++) {
|
||||
let vi = i + j * 3;
|
||||
let x = varr[vi];
|
||||
let y = varr[vi + 1];
|
||||
let z = varr[vi + 2];
|
||||
|
||||
// Create vertex key for deduplication
|
||||
let key = `${x},${y},${z}`;
|
||||
let idx = vertexMap.get(key);
|
||||
|
||||
if (idx === undefined) {
|
||||
idx = vertexIndex++;
|
||||
vertexMap.set(key, idx);
|
||||
vertices.push({ x, y, z });
|
||||
}
|
||||
|
||||
triIndices.push(idx);
|
||||
}
|
||||
|
||||
triangles.push(triIndices);
|
||||
}
|
||||
|
||||
// Generate object XML
|
||||
let objectXml = [` <object id="${objectId}" name="${name}" type="model">`];
|
||||
objectXml.push(' <mesh>');
|
||||
objectXml.push(' <vertices>');
|
||||
|
||||
for (let v of vertices) {
|
||||
objectXml.push(` <vertex x="${v.x}" y="${v.y}" z="${v.z}"/>`);
|
||||
}
|
||||
|
||||
objectXml.push(' </vertices>');
|
||||
objectXml.push(' <triangles>');
|
||||
|
||||
for (let tri of triangles) {
|
||||
objectXml.push(` <triangle v1="${tri[0]}" v2="${tri[1]}" v3="${tri[2]}"/>`);
|
||||
}
|
||||
|
||||
objectXml.push(' </triangles>');
|
||||
objectXml.push(' </mesh>');
|
||||
objectXml.push(' </object>');
|
||||
|
||||
resources.push(objectXml.join('\n'));
|
||||
buildItems.push(` <item objectid="${objectId}"/>`);
|
||||
|
||||
objectId++;
|
||||
}
|
||||
|
||||
// Construct the complete 3D model XML
|
||||
let modelXml = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
`<model unit="${unit}" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02">`,
|
||||
' <resources>',
|
||||
resources.join('\n'),
|
||||
' </resources>',
|
||||
' <build>',
|
||||
buildItems.join('\n'),
|
||||
' </build>',
|
||||
'</model>'
|
||||
].join('\n');
|
||||
|
||||
// Create [Content_Types].xml
|
||||
let contentTypes = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
|
||||
' <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
|
||||
' <Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>',
|
||||
'</Types>'
|
||||
].join('\n');
|
||||
|
||||
// Create _rels/.rels
|
||||
let rels = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
|
||||
' <Relationship Target="/3D/3dmodel.model" Id="rel0" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>',
|
||||
'</Relationships>'
|
||||
].join('\n');
|
||||
|
||||
// Create ZIP archive
|
||||
let zip = new JSZip();
|
||||
zip.file('[Content_Types].xml', contentTypes);
|
||||
zip.file('_rels/.rels', rels);
|
||||
zip.file('3D/3dmodel.model', modelXml);
|
||||
|
||||
// Generate the 3MF file as a blob
|
||||
return await zip.generateAsync({
|
||||
type: "uint8array",
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: { level: 6 },
|
||||
streamFiles: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} data binary file
|
||||
* @returns {Array} vertex face array
|
||||
|
|
@ -229,4 +356,5 @@ export function parseAsync(data) {
|
|||
}
|
||||
resolve(extractItems(models));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1008,9 +1008,10 @@ let file = {
|
|||
h.input({ id: "filename", value: "mesh_export" })
|
||||
]),
|
||||
h.div([
|
||||
h.button({ _: "download OBJ", onclick() { doit('obj') } }),
|
||||
h.button({ _: "download STL", onclick() { doit('stl') } }),
|
||||
h.button({ _: "download STEP", onclick() { doit('step') } }),
|
||||
h.button({ _: "3MF", onclick() { doit('3mf') } }),
|
||||
h.button({ _: "OBJ", onclick() { doit('obj') } }),
|
||||
h.button({ _: "STL", onclick() { doit('stl') } }),
|
||||
h.button({ _: "STEP", onclick() { doit('step') } }),
|
||||
])
|
||||
]) ]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { CSG } from '../geo/csg.js';
|
|||
import { util } from './util.js';
|
||||
import { tool as meshTool } from './tool.js';
|
||||
import { meshToSTEPWithFaces } from '../load/step.js';
|
||||
import { encode as tmfEncode } from '../load/3mf.js';
|
||||
import { encode as objEncode } from '../load/obj.js';
|
||||
import { encode as stlEncode } from '../load/stl.js';
|
||||
|
||||
|
|
@ -580,6 +581,10 @@ const file = {
|
|||
return objEncode(recs, header);
|
||||
case "stl":
|
||||
return stlEncode(recs, header);
|
||||
case "3mf":
|
||||
send.async();
|
||||
tmfEncode(recs, header).then(data => send.done(data));
|
||||
return;
|
||||
default:
|
||||
throw `invalid format "${format}"`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue