This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/index.js

729 lines
20 KiB
JavaScript

/*
## License
Copyright (c) 2016 Z3 Development https://github.com/z3dev
Copyright (c) 2013-2016 by Rene K. Mueller <spiritdude@gmail.com>
Copyright (c) 2016 by Z3D Development
All code released under MIT license
History:
2016/06/27: 0.5.1: rewrote using SAX XML parser, enhanced for multiple objects, materials, units by Z3Dev
2013/04/11: 0.018: added alpha support to AMF export
*/
// //////////////////////////////////////////
//
// AMF is a language for describing three-dimensional graphics in XML
// See http://www.astm.org/Standards/ISOASTM52915.htm
// See http://amf.wikispaces.com/
//
// //////////////////////////////////////////
const sax = require('sax')
const inchMM = (1 / 0.039370) // used for scaling AMF (inch) to CAG coordinates(MM)
// processing controls
sax.SAXParser.prototype.amfLast = null // last object found
sax.SAXParser.prototype.amfDefinition = 0 // definitions beinging created
// 0-AMF,1-object,2-material,3-texture,4-constellation,5-metadata
// high level elements / definitions
sax.SAXParser.prototype.amfObjects = [] // list of objects
sax.SAXParser.prototype.amfMaterials = [] // list of materials
sax.SAXParser.prototype.amfTextures = [] // list of textures
sax.SAXParser.prototype.amfConstels = [] // list of constellations
sax.SAXParser.prototype.amfMetadata = [] // list of metadata
sax.SAXParser.prototype.amfObj = null // amf in object form
function amfAmf (element) {
// default SVG with no viewport
var obj = {type: 'amf', unit: 'mm', scale: 1.0}
if ('UNIT' in element) { obj.unit = element.UNIT.toLowerCase() }
// set scaling
switch (obj.unit.toLowerCase()) {
case 'inch':
obj.scale = inchMM
break
case 'foot':
obj.scale = inchMM * 12.0
break
case 'meter':
obj.scale = 1000.0
break
case 'micron':
obj.scale = 0.001
break
case 'millimeter':
default:
break
}
obj.objects = []
return obj
}
sax.SAXParser.prototype.amfObject = function (element) {
var obj = {type: 'object', id: 'JSCAD' + (this.amfObjects.length)} // default ID
if ('ID' in element) { obj.id = element.ID }
obj.objects = []
return obj
}
function amfMesh (element) {
var obj = {type: 'mesh'}
obj.objects = []
return obj
}
// Note: TBD Vertices can have a color, which is used to interpolate a face color (from the 3 vertices)
function amfVertices (element) {
var obj = {type: 'vertices'}
obj.objects = []
return obj
}
function amfCoordinates (element) {
var obj = {type: 'coordinates'}
obj.objects = []
return obj
}
function amfNormal (element) {
var obj = {type: 'normal'}
obj.objects = []
return obj
}
function amfX (element) {
return {type: 'x', value: '0'}
}
function amfY (element) {
return {type: 'y', value: '0'}
}
function amfZ (element) {
return {type: 'z', value: '0'}
}
function amfVolume (element) {
var obj = {type: 'volume'}
if ('MATERIALID' in element) { obj.materialid = element.MATERIALID }
obj.objects = []
return obj
}
function amfTriangle (element) {
var obj = {type: 'triangle'}
obj.objects = []
return obj
}
function amfV1 (element) {
return {type: 'v1', value: '0'}
}
function amfV2 (element) {
return {type: 'v2', value: '0'}
}
function amfV3 (element) {
return {type: 'v3', value: '0'}
}
function amfVertex (element) {
var obj = {type: 'vertex'}
obj.objects = []
return obj
}
function amfEdge (element) {
var obj = {type: 'edge'}
obj.objects = []
return obj
}
function amfMetadata (element) {
var obj = {type: 'metadata'}
if ('TYPE' in element) { obj.mtype = element.TYPE }
if ('ID' in element) { obj.id = element.ID }
return obj
}
function amfMaterial (element) {
var obj = {type: 'material'}
if ('ID' in element) { obj.id = element.ID }
obj.objects = []
return obj
}
function amfColor (element) {
var obj = {type: 'color'}
obj.objects = []
return obj
}
function amfR (element) {
return {type: 'r', value: '1'}
}
function amfG (element) {
return {type: 'g', value: '1'}
}
function amfB (element) {
return {type: 'b', value: '1'}
}
function amfA (element) {
return {type: 'a', value: '1'}
}
function amfMap (element) {
var obj = {type: 'map'}
if ('GTEXID' in element) { obj.gtexid = element.GTEXID }
if ('BTEXID' in element) { obj.btexid = element.BTEXID }
if ('RTEXID' in element) { obj.rtexid = element.RTEXID }
obj.objects = []
return obj
}
function amfU1 (element) {
return {type: 'u1', value: '0'}
}
function amfU2 (element) {
return {type: 'u2', value: '0'}
}
function amfU3 (element) {
return {type: 'u3', value: '0'}
}
function createAmfParser (src, pxPmm) {
// create a parser for the XML
var parser = sax.parser(false, {trim: true, lowercase: false, position: true})
parser.onerror = function (e) {
console.log('error: line ' + e.line + ', column ' + e.column + ', bad character [' + e.c + ']')
}
parser.onopentag = function (node) {
// console.log('opentag: '+node.name+' at line '+this.line+' position '+this.column);
// for (x in node.attributes) {
// console.log(' '+x+'='+node.attributes[x]);
// }
// case 'VTEX1':
// case 'VTEX2':
// case 'VTEX3':
var obj = null
switch (node.name) {
// top level elements
case 'AMF':
obj = amfAmf(node.attributes)
break
case 'OBJECT':
obj = this.amfObject(node.attributes)
if (this.amfDefinition === 0) this.amfDefinition = 1 // OBJECT processing
break
case 'MESH':
obj = amfMesh(node.attributes)
break
case 'VERTICES':
obj = amfVertices(node.attributes)
break
case 'VERTEX':
obj = amfVertex(node.attributes)
break
case 'EDGE':
obj = amfEdge(node.attributes)
break
case 'VOLUME':
obj = amfVolume(node.attributes)
break
case 'MATERIAL':
obj = amfMaterial(node.attributes)
if (this.amfDefinition === 0) this.amfDefinition = 2 // MATERIAL processing
break
case 'COMPOSITE':
break
case 'TEXTURE':
if (this.amfDefinition === 0) this.amfDefinition = 3 // TEXTURE processing
break
case 'CONSTELLATION':
if (this.amfDefinition === 0) this.amfDefinition = 4 // CONSTELLATION processing
break
case 'METADATA':
obj = amfMetadata(node.attributes)
if (this.amfDefinition === 0) this.amfDefinition = 5 // METADATA processing
break
// coordinate elements
case 'COORDINATES':
obj = amfCoordinates(node.attributes)
break
case 'NORMAL':
obj = amfNormal(node.attributes)
break
case 'X':
case 'NX':
obj = amfX(node.attributes)
break
case 'Y':
case 'NY':
obj = amfY(node.attributes)
break
case 'Z':
case 'NZ':
obj = amfZ(node.attributes)
break
// triangle elements
case 'TRIANGLE':
obj = amfTriangle(node.attributes)
break
case 'V1':
case 'VTEX1':
obj = amfV1(node.attributes)
break
case 'V2':
case 'VTEX2':
obj = amfV2(node.attributes)
break
case 'V3':
case 'VTEX3':
obj = amfV3(node.attributes)
break
// color elements
case 'COLOR':
obj = amfColor(node.attributes)
break
case 'R':
obj = amfR(node.attributes)
break
case 'G':
obj = amfG(node.attributes)
break
case 'B':
obj = amfB(node.attributes)
break
case 'A':
obj = amfA(node.attributes)
break
// map elements
case 'MAP':
case 'TEXMAP':
obj = amfMap(node.attributes)
break
case 'U1':
case 'UTEX1':
case 'WTEX1':
obj = amfU1(node.attributes)
break
case 'U2':
case 'UTEX2':
case 'WTEX2':
obj = amfU2(node.attributes)
break
case 'U3':
case 'UTEX3':
case 'WTEX3':
obj = amfU3(node.attributes)
break
default:
// console.log('opentag: '+node.name+' at line '+this.line+' position '+this.column);
break
}
if (obj !== null) {
// console.log('definitinon '+this.amfDefinition);
switch (this.amfDefinition) {
case 0: // definition of AMF
if ('objects' in obj) {
// console.log('push object ['+obj.type+']');
this.amfObjects.push(obj)
}
break
case 1: // definition of OBJECT
if (this.amfObjects.length > 0) {
var group = this.amfObjects.pop()
// add the object to the active group if necessary
if ('objects' in group) {
// console.log('object '+group.type+' adding ['+obj.type+']');
// console.log(JSON.stringify(obj));
group.objects.push(obj)
}
this.amfObjects.push(group)
// and push this object as a group object if necessary
if ('objects' in obj) {
// console.log('object group ['+obj.type+']');
this.amfObjects.push(obj)
}
}
break
case 2: // definition of MATERIAL
if (obj.type === 'material') {
// console.log('push material ['+obj.type+']');
this.amfMaterials.push(obj)
} else {
if (this.amfMaterials.length > 0) {
let group = this.amfMaterials.pop()
// add the object to the active group if necessary
if ('objects' in group) {
// console.log('material '+group.type+' adding ['+obj.type+']');
// console.log(JSON.stringify(obj));
group.objects.push(obj)
}
this.amfMaterials.push(group)
// and push this object as a group object if necessary
if ('objects' in obj) {
// console.log('push material ['+obj.type+']');
this.amfMaterials.push(obj)
}
}
}
break
case 3: // definition of TEXTURE
break
case 4: // definition of CONSTELLATION
break
case 5: // definition of METADATA
break
default:
console.log('ERROR: invalid AMF definition')
break
}
this.amfLast = obj // retain this object in order to add values
}
}
parser.onclosetag = function (node) {
// console.log('onclosetag: '+this.amfDefinition);
switch (node) {
// list those which have objects
case 'AMF':
case 'OBJECT':
case 'MESH':
case 'VERTICES':
case 'VERTEX':
case 'EDGE':
case 'COORDINATES':
case 'NORMAL':
case 'VOLUME':
case 'TRIANGLE':
case 'MATERIAL':
case 'COLOR':
case 'MAP':
case 'TEXMAP':
break
case 'TEXTURE':
if (this.amfDefinition === 3) { this.amfDefinition = 0 } // resume processing
return
case 'CONSTELLATION':
if (this.amfDefinition === 4) { this.amfDefinition = 0 } // resume processing
return
case 'METADATA':
if (this.amfDefinition === 5) { this.amfDefinition = 0 } // resume processing
return
default:
// console.log('closetag: '+node);
return
}
var obj = null
switch (this.amfDefinition) {
case 0: // definition of AMF
case 1: // definition of OBJECT
if (this.amfObjects.length > 0) {
obj = this.amfObjects.pop()
// console.log('pop object ['+obj.type+']');
if (obj.type === 'object') {
this.amfDefinition = 0 // AMF processing
}
}
// check for completeness
if (this.amfObjects.length === 0) {
this.amfObj = obj
}
break
case 2: // definition of MATERIAL
if (this.amfMaterials.length > 0) {
obj = this.amfMaterials.pop()
// console.log('pop material ['+obj.type+']');
if (obj.type === 'material') {
this.amfMaterials.push(obj) // keep a list of materials
this.amfDefinition = 0 // AMF processing
}
}
break
case 3: // definition of TEXTURE
this.amfDefinition = 0 // AMF processing
break
case 4: // definition of CONSTELLATION
this.amfDefinition = 0 // AMF processing
break
case 5: // definition of METADATA
this.amfDefinition = 0 // AMF processing
break
default:
break
}
}
parser.ontext = function (value) {
if (value !== null) {
if (this.amfLast && this.amfDefinition !== 0) {
this.amfLast.value = value
// console.log(JSON.stringify(this.amfLast));
}
}
}
parser.onend = function () {
// console.log('AMF parsing completed');
}
// start the parser
parser.write(src).close()
return parser
}
//
// convert the internal repreentation into JSCAD code
//
function codify (amf, data) {
if (amf.type !== 'amf' || (!amf.objects)) throw new Error('AMF malformed')
let code = ''
// hack due to lack of this in array map()
var objects = amf.objects
var materials = data.amfMaterials
var lastmaterial = null
function findMaterial (id) {
if (lastmaterial && lastmaterial.id === id) return lastmaterial
for (let i = 0; i < materials.length; i++) {
if (materials[i].id && materials[i].id === id) {
lastmaterial = materials[i]
return lastmaterial
}
}
return null
}
function getValue (objects, type) {
for (let i = 0; i < objects.length; i++) {
if (objects[i].type === type) return objects[i].value
}
return null
}
function getColor (objects) {
for (let i = 0; i < objects.length; i++) {
var obj = objects[i]
if (obj.type === 'color') {
var r = parseFloat(getValue(obj.objects, 'r'))
var g = parseFloat(getValue(obj.objects, 'g'))
var b = parseFloat(getValue(obj.objects, 'b'))
var a = parseFloat(getValue(obj.objects, 'a'))
if (Number.isNaN(r)) r = 1.0 // AMF default color
if (Number.isNaN(g)) g = 1.0
if (Number.isNaN(b)) b = 1.0
if (Number.isNaN(a)) a = 1.0
return [r, g, b, a]
}
}
return null
}
function findColorByMaterial (id) {
var m = findMaterial(id)
if (m) {
return getColor(m.objects)
}
return null
}
// convert high level definitions
function createDefinition (obj, didx) {
// console.log(materials.length);
switch (obj.type) {
case 'object':
createObject(obj, didx)
break
case 'metadata':
break
case 'material':
break
default:
console.log('Warning: unknown definition: ' + obj.type)
break
}
}
// convert all objects to CSG based code
function createObject (obj, oidx) {
var vertices = [] // [x,y,z]
var faces = [] // [v1,v2,v3]
var colors = [] // [r,g,b,a]
function addCoord (coord, cidx) {
if (coord.type === 'coordinates') {
var x = parseFloat(getValue(coord.objects, 'x'))
var y = parseFloat(getValue(coord.objects, 'y'))
var z = parseFloat(getValue(coord.objects, 'z'))
// console.log('['+x+','+y+','+z+']');
vertices.push([x, y, z])
}
// normal is possible
}
function addVertex (vertex, vidx) {
// console.log(vertex.type);
if (vertex.type === 'vertex') {
vertex.objects.map(addCoord)
}
// edge is possible
}
function addTriangle (tri, tidx) {
if (tri.type === 'triangle') {
var v1 = parseInt(getValue(tri.objects, 'v1'))
var v2 = parseInt(getValue(tri.objects, 'v2'))
var v3 = parseInt(getValue(tri.objects, 'v3'))
// console.log('['+v1+','+v2+','+v3+']');
faces.push([v1, v2, v3]) // HINT: reverse order for polyhedron()
var c = getColor(tri.objects)
if (c) {
colors.push(c)
} else {
colors.push(tricolor)
}
}
}
var tricolor = null // for found colors
function addPart (part, pidx) {
// console.log(part.type);
switch (part.type) {
case 'vertices':
part.objects.map(addVertex, data)
break
case 'volume':
tricolor = getColor(part.objects)
if (part.materialid) {
// convert material to color
tricolor = findColorByMaterial(part.materialid)
}
part.objects.map(addTriangle, data)
break
default:
break
}
}
function addMesh (mesh, midx) {
// console.log(mesh.type);
if (mesh.type === 'mesh') {
mesh.objects.map(addPart, data)
}
}
if (obj.objects.length > 0) {
obj.objects.map(addMesh, data)
var fcount = faces.length
var vcount = vertices.length
code += '// Object ' + obj.id + '\n'
code += '// faces : ' + fcount + '\n'
code += '// vertices: ' + vcount + '\n'
code += 'function createObject' + obj.id + '() {\n'
code += ' var polys = [];\n'
// convert the results into function calls
for (var i = 0; i < fcount; i++) {
code += ' polys.push(\n'
code += ' PP([\n'
for (var j = 0; j < faces[i].length; j++) {
if (faces[i][j] < 0 || faces[i][j] >= vcount) {
// if (err.length === '') err += 'bad index for vertice (out of range)'
continue
}
if (j) code += ',\n'
code += ' VV(' + vertices[faces[i][j]] + ')'
}
code += '])'
if (colors[i]) {
var c = colors[i]
code += '.setColor([' + c[0] + ',' + c[1] + ',' + c[2] + ',' + c[3] + '])'
}
code += ');\n'
}
code += ' return CSG.fromPolygons(polys);\n'
code += '}\n'
}
}
// start everthing
code = '// Objects : ' + objects.length + '\n'
code += '// Materials: ' + materials.length + '\n'
code += '\n'
code += '// helper functions\n'
if (amf.scale !== 1.0) {
code += 'var SCALE = ' + amf.scale + '; // scaling units (' + amf.unit + ')\n'
code += 'var VV = function(x,y,z) { return new CSG.Vertex(new CSG.Vector3D(x*SCALE,y*SCALE,z*SCALE)); };\n'
} else {
code += 'var VV = function(x,y,z) { return new CSG.Vertex(new CSG.Vector3D(x,y,z)); };\n'
}
code += 'var PP = function(a) { return new CSG.Polygon(a); };\n'
code += '\n'
code += 'function main() {\n'
code += ' var csgs = [];\n'
for (let i = 0; i < objects.length; i++) {
var obj = objects[i]
if (obj.type === 'object') {
code += ' csgs.push(createObject' + obj.id + '());\n'
}
}
code += ' return union(csgs);\n'
code += '}\n'
code += '\n'
objects.map(createDefinition, data)
return code
}
//
// deserialize the given AMF source and return a JSCAD script
//
// fn (optional) original filename of AMF source
// options (optional) anonymous object with:
// pxPmm: pixels per milimeter for calcuations
// FIXME: add openjscad version in a cleaner manner ?
function deserialize (src, fn, options) {
fn = fn || 'amf'
const defaults = {version: '0.0.0'}
options = Object.assign({}, defaults, options)
const {version} = options
// parse the AMF source
const parser = createAmfParser(src)
// convert the internal objects to JSCAD code
var code = ''
code += '//\n'
code += '// producer: OpenJSCAD.org ' + version + ' AMF Importer\n'
code += '// date: ' + (new Date()) + '\n'
code += '// source: ' + fn + '\n'
code += '//\n'
if (parser.amfObj !== null) {
// console.log(JSON.stringify(parser.amfObj))
// console.log(JSON.stringify(parser.amfMaterials))
code += codify(parser.amfObj, parser)
} else {
console.log('Warning: AMF parsing failed')
}
return code
}
module.exports = {
deserialize
}