-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Davor Hrg <[email protected]>
- Loading branch information
Showing
19 changed files
with
476 additions
and
227 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// @ts-nocheck | ||
|
||
// if not in browser | ||
import { to3dmodel, to3mfZipContentSimple } from './index.js' | ||
|
||
function multiply(arr, mult, func){ | ||
let out = new func(arr.length * mult) | ||
for(let i=0; i<mult; i++){ | ||
out.set(arr, i * arr.length) | ||
} | ||
return out | ||
} | ||
|
||
let verticesData = [ | ||
-1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, | ||
-1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, | ||
1, 1, -1, 1, 1, | ||
] | ||
|
||
let indicesData = [ | ||
0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, | ||
20, 22, 23, | ||
] | ||
|
||
function oneRun(name,vertices, indices){ | ||
|
||
let start = performance.now() | ||
const zipContents = to3dmodel({ | ||
meshes: [{ vertices, indices, id: 1 }], | ||
header: { application: 'jscad.app', title: 'jscad model' } | ||
}) | ||
console.log(name) | ||
console.log('count', vertices.length+indices.length, 'vertices', vertices.length, 'indices', indices.length) | ||
console.log(Math.round(performance.now() - start), 'len',zipContents.length) | ||
console.log('') | ||
} | ||
|
||
let vertices = multiply(verticesData, 99,Float32Array) | ||
let indices = multiply(indicesData, 99,Uint32Array) | ||
oneRun('warmup',vertices, indices) | ||
vertices = multiply(verticesData, 9999,Float32Array) | ||
indices = multiply(indicesData, 9999,Uint32Array) | ||
oneRun('test1',vertices, indices) | ||
oneRun('test2',vertices, indices) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,123 +1,165 @@ | ||
// this implementation exports to 3mf by filling array of strings and doing join | ||
// at the encoding tests for large files have shown significant speedup related | ||
// to using string concatenation | ||
import { makeItem } from './src/makeItem.js' | ||
import { pushHeader } from './src/pushHeader.js' | ||
import { pushObjectWithComponents } from './src/pushObjectComponent.js' | ||
import { pushObjectWithMesh } from './src/pushObjectMesh.js' | ||
// This import should be | ||
// import {XMLBuilder} from 'fast-xml-parser' | ||
// but then tree shaking is not working correctly. Therefore this hack is required. | ||
import XMLBuilder from 'fast-xml-parser/src/xmlbuilder/json2xml.js' | ||
|
||
import { genItem } from './src/makeItem.js' | ||
import { genModel } from './src/pushHeader.js' | ||
import { genObjectWithComponents } from './src/pushObjectComponent.js' | ||
import { genObjectWithMesh } from './src/pushObjectMesh.js' | ||
import { fileForContentTypes } from './src/staticFiles.js' | ||
|
||
export * from './src/staticFiles.js' | ||
|
||
/** | ||
* @typedef Mesh3MF | ||
* @prop {string} id | ||
* @prop {number} id | ||
* @prop {Float32Array} vertices | ||
* @prop {Uint32Array} indices | ||
* @prop {string} [name] | ||
* | ||
* @typedef Mesh3MFSimpleExt | ||
* @prop {mat4} transform | ||
* @prop {import('./src/defMatrix.js').mat4} [transform] | ||
* | ||
* @typedef {Mesh3MF & Mesh3MFSimpleExt} Mesh3MFSimple | ||
* | ||
* @typedef Component3MF | ||
* @prop {string} id | ||
* @prop {number} id | ||
* @prop {string} name | ||
* @prop {Array<Child3MF>} children | ||
* | ||
* @typedef Child3MF | ||
* @prop {string} objectID | ||
* @prop {mat4} transform | ||
* @prop {number} objectID | ||
* @prop {import('./src/defMatrix.js').mat4} [transform] | ||
* | ||
* @typedef To3MF | ||
* @prop {Array<Mesh3MF>} meshes - manually declare meshes | ||
* @prop {Array<Component3MF>} [components] - components can combine items | ||
* @prop {Array<Child3MF>} items - output items, each pointing to component or mesh with objectID | ||
* @prop {number} precision | ||
* @prop {import('./src/pushHeader.js').Header} header | ||
* @prop {number} [precision] | ||
* @prop {import('./src/pushHeader.js').Header} [header] | ||
*/ | ||
|
||
/** | ||
* @param {To3MF} options | ||
* @returns string | ||
* @returns {string} | ||
*/ | ||
export function to3dmodel({ meshes = [], components = [], items = [], precision = 17, header }) { | ||
// items to be placed on the scene (build section of 3mf) | ||
const out = [] | ||
/** @type {import('./xml-schema-3mf.js').Xml3mf} */ | ||
const data = { | ||
'?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' }, | ||
model: genModel( | ||
header ?? {}, | ||
[ | ||
//Mesh objects | ||
...meshes.map(({ id, vertices, indices, name }) => ({ | ||
object: genObjectWithMesh(id, vertices, indices, precision, name) | ||
})), | ||
//Component objects | ||
...components.map(({ id, children, name }) => ({ | ||
object: genObjectWithComponents(id, children, name) | ||
})), | ||
], | ||
{ item: items.map(v => genItem(v.objectID, v.transform)) }, | ||
) | ||
} | ||
|
||
// <model> tag is opened here | ||
pushHeader(out, header) | ||
const builder = new XMLBuilder({ | ||
ignoreAttributes: false, | ||
format: true, | ||
suppressEmptyNode: true | ||
}) | ||
|
||
// #region resources | ||
out.push(' <resources>\n') | ||
return builder.build(data) | ||
} | ||
|
||
if (items.length == 0) { | ||
console.error('3MF empty build! Include items or simple.') | ||
} | ||
/** | ||
* Simple export provided meshes that have transform attached to them and autocreate items and pass to to3dmodel. | ||
* @param {Array<Mesh3MFSimple>} meshes | ||
* @param {import('./src/pushHeader.js').Header} [header] | ||
* @param {number} [precision] | ||
* @returns {string} | ||
*/ | ||
export function to3dmodelSimple(meshes, header, precision) { | ||
/** @type {Child3MF[]} */ | ||
const items = meshes.map(({ id, transform }) => ({ objectID: id, transform })) | ||
|
||
meshes.forEach(({ id, vertices, indices, name }) => pushObjectWithMesh(out, id, vertices, indices, precision, name)) | ||
return to3dmodel({ meshes, items, header, precision }) | ||
} | ||
|
||
components.forEach(({ id, children, name }) => { | ||
pushObjectWithComponents(out, id, children, name) | ||
}) | ||
/** | ||
* @typedef {[string,Uint8Array,boolean]} ZipContent FileName, FileContent, CanBeCompressed | ||
*/ | ||
|
||
out.push(' </resources>\n') | ||
// #endregion | ||
/** | ||
* @param {To3MF} options | ||
* @param {Uint8Array | undefined} [thumbnailPng] | ||
* @returns {ZipContent[]} | ||
*/ | ||
export function to3mfZipContent(options, thumbnailPng) { | ||
/** @type {ZipContent[]} */ | ||
const result = [] | ||
const utf8Encoder = new TextEncoder() | ||
|
||
out.push(`<build>\n`) | ||
items.forEach(({ objectID, transform }) => { | ||
out.push(makeItem(objectID, transform)) | ||
}) | ||
out.push('</build>\n') | ||
result.push([fileForContentTypes.name, utf8Encoder.encode(fileForContentTypes.content), true]) | ||
|
||
out.push('</model>\n') // model tag was opened in the pushHeader() | ||
const fileForRelThumbnail = new FileForRelThumbnail() | ||
fileForRelThumbnail.add3dModel('3D/3dmodel.model') | ||
|
||
return out.join('') | ||
if (thumbnailPng !== undefined) { | ||
result.push(['Metadata/thumbnail.png', thumbnailPng, false]) | ||
fileForRelThumbnail.addThumbnail('Metadata/thumbnail.png') | ||
} | ||
|
||
result.push(['3D/3dmodel.model', utf8Encoder.encode(to3dmodel(options)), true]) | ||
result.push([fileForRelThumbnail.name, utf8Encoder.encode(fileForRelThumbnail.content), true]) | ||
|
||
return result | ||
} | ||
|
||
/** Simple export provided meshes that have transform attached to them and autocreate items and pass to to3dmodel. | ||
* | ||
* @param {Array<Mesh3MFSimple>} meshes | ||
* @param {import('./src/pushHeader.js').Header} header | ||
* @param {number} precision | ||
* @returns string | ||
/** | ||
* @param {{meshes:Array<Mesh3MFSimple>,header?:import('./src/pushHeader.js').Header,precision?:number}} options | ||
* @param {Uint8Array | undefined} [thumbnailPng] | ||
* @returns {ZipContent[]} | ||
*/ | ||
export function to3dmodelSimple(meshes, header, precision) { | ||
const items = [] | ||
meshes.forEach(({ id, transform }) => { | ||
items.push({ objectID: id, transform }) | ||
}) | ||
return to3dmodel({ meshes, items, header, precision }) | ||
export function to3mfZipContentSimple({ meshes, header, precision }, thumbnailPng) { | ||
const items = meshes.map(({ id, transform }) => ({ objectID: id, transform })) | ||
|
||
return to3mfZipContent({ meshes, items, header, precision }, thumbnailPng) | ||
} | ||
|
||
/** File that describes file relationships inside a 3mf */ | ||
export class FileForRelThumbnail { | ||
constructor() { | ||
this.idSeq = 0 | ||
this.lines = [ | ||
`<?xml version="1.0" encoding="UTF-8"?>`, | ||
`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">`, | ||
] | ||
} | ||
get name() { | ||
return '_rels/.rels' | ||
} | ||
/** | ||
* | ||
* @param {*} target file path | ||
* @param {*} xmlType xml schema url | ||
idSeq = 0 | ||
name = '_rels/.rels' | ||
|
||
/** @type {import('./xml-schema-3mf.js').Xml3mfRelationFile} */ | ||
data = { | ||
'?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' }, | ||
Relationships: { | ||
'@_xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships', | ||
Relationship: [] | ||
}, | ||
} | ||
|
||
/** | ||
* @param {string} target file path | ||
* @param {string} xmlType xml schema url | ||
*/ | ||
addRel(target, xmlType) { | ||
this.lines.push(` <Relationship Target="${target}" Id="rel-${++this.idSeq}" Type="${xmlType}" />`) | ||
} | ||
add3dModel(path) { | ||
this.addRel(path, 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel') | ||
} | ||
addThumbnail(path) { | ||
this.addRel(path, 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail') | ||
this.data.Relationships.Relationship.push( | ||
{ '@_Target': target, "@_Id": `rel-${++this.idSeq}`, '@_Type': xmlType } | ||
) | ||
} | ||
|
||
/** @param {string} path */ | ||
add3dModel = (path) => this.addRel(path, 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel') | ||
|
||
/** @param {string} path */ | ||
addThumbnail = (path) => this.addRel(path, 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail') | ||
|
||
get content() { | ||
return this.lines.join('\n') + `\n</Relationships>` | ||
const builder = new XMLBuilder({ ignoreAttributes: false, format: true, suppressEmptyNode: true }) | ||
return builder.build(this.data) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
//This is required for the import tree shaking hack | ||
declare module 'fast-xml-parser/src/xmlbuilder/json2xml.js' { | ||
import { XMLBuilder } from 'fast-xml-parser' | ||
export default XMLBuilder | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
import { defMatrix } from './defMatrix.js' | ||
import { matrix2str } from './matrix2str.js' | ||
|
||
/** | ||
* | ||
* @param {string} id - must start with 1, can not be zero by spec | ||
* @param {mat4} matrix | ||
* @returns | ||
* @param {number} id - must start with 1, can not be zero by spec | ||
* @param {import('./defMatrix.js').mat4} [matrix] | ||
* @returns {import('../xml-schema-3mf.js').Xml3mfItem} | ||
*/ | ||
export const makeItem = (id = 1, matrix = defMatrix) => | ||
` <item objectid="${id}" transform="${matrix2str(matrix)}" />\n` | ||
export const genItem = (id, matrix) => ({ | ||
'@_objectid': id, | ||
'@_transform': matrix !== undefined ? matrix2str(matrix) : undefined, | ||
}) |
Oops, something went wrong.