729 lines
20 KiB
JavaScript
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
|
||
|
}
|