UNPKG

openjscad-csg

Version:
339 lines (298 loc) 13.2 kB
/* ## License Copyright (c) 2014 bebbi (elghatta@gmail.com) Copyright (c) 2013 Eduard Bespalov (edwbes@gmail.com) Copyright (c) 2012 Joost Nieuwenhuijse (joost@newhouse.nl) Copyright (c) 2011 Evan Wallace (http://evanw.github.com/csg.js/) Copyright (c) 2012 Alexandre Girard (https://github.com/alx) All code released under MIT license */ var csgjs = require('./csg.js'); var CSG = csgjs.CSG; var CAG = csgjs.CAG; //////////////////////////////////////////// // X3D Export //////////////////////////////////////////// CSG.prototype.toX3D = function() { // materialPolygonLists // key: a color string (e.g. "0 1 1" for yellow) // value: an array of strings specifying polygons of this color // (as space-separated indices into vertexCoords) var materialPolygonLists = {}, // list of coordinates (as "x y z" strings) vertexCoords = [], // map to look up the index in vertexCoords of a given vertex vertexTagToCoordIndexMap = {}; this.polygons.map(function(p) { var red = 0, green = 0, blue = 1; // default color is blue if (p.shared && p.shared.color) { red = p.shared.color[0]; green = p.shared.color[1]; blue = p.shared.color[2]; } var polygonVertexIndices = [], numvertices = p.vertices.length, vertex; for (var i = 0; i < numvertices; i++) { vertex = p.vertices[i]; if (!(vertex.getTag() in vertexTagToCoordIndexMap)) { vertexCoords.push(vertex.pos._x.toString() + " " + vertex.pos._y.toString() + " " + vertex.pos._z.toString() ); vertexTagToCoordIndexMap[vertex.getTag()] = vertexCoords.length - 1; } polygonVertexIndices.push(vertexTagToCoordIndexMap[vertex.getTag()]); } var polygonString = polygonVertexIndices.join(" "); var colorString = red.toString() + " " + green.toString() + " " + blue.toString(); if (!(colorString in materialPolygonLists)) { materialPolygonLists[colorString] = []; } // add this polygonString to the list of colorString-colored polygons materialPolygonLists[colorString].push(polygonString); }); // create output document var docType = document.implementation.createDocumentType("X3D", 'ISO//Web3D//DTD X3D 3.1//EN" "http://www.web3d.org/specifications/x3d-3.1.dtd', null); var exportDoc = document.implementation.createDocument(null, "X3D", docType); exportDoc.insertBefore( exportDoc.createProcessingInstruction('xml', 'version="1.0" encoding="UTF-8"'), exportDoc.doctype); var exportRoot = exportDoc.getElementsByTagName("X3D")[0]; exportRoot.setAttribute("profile", "Interchange"); exportRoot.setAttribute("version", "3.1"); exportRoot.setAttribute("xsd:noNamespaceSchemaLocation", "http://www.web3d.org/specifications/x3d-3.1.xsd"); exportRoot.setAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema-instance"); var exportScene = exportDoc.createElement("Scene"); exportRoot.appendChild(exportScene); /* For each color, create a shape made of an appropriately colored material which contains all polygons that are this color. The first shape will contain the definition of all vertices, (<Coordinate DEF="coords_mesh"/>), which will be referenced by subsequent shapes. */ var coordsMeshDefined = false; for (var colorString in materialPolygonLists) { var polygonList = materialPolygonLists[colorString]; var shape = exportDoc.createElement("Shape"); exportScene.appendChild(shape); var appearance = exportDoc.createElement("Appearance"); shape.appendChild(appearance); var material = exportDoc.createElement("Material"); appearance.appendChild(material); material.setAttribute("diffuseColor", colorString); material.setAttribute("ambientIntensity", "1.0"); var ifs = exportDoc.createElement("IndexedFaceSet"); shape.appendChild(ifs); ifs.setAttribute("solid", "true"); ifs.setAttribute("coordIndex", polygonList.join(" -1 ") + " -1"); var coordinate = exportDoc.createElement("Coordinate"); ifs.appendChild(coordinate); if (coordsMeshDefined) { coordinate.setAttribute("USE", "coords_mesh"); } else { coordinate.setAttribute("DEF", "coords_mesh"); coordinate.setAttribute("point", vertexCoords.join(" ")); coordsMeshDefined = true; } } var x3dstring = (new XMLSerializer()).serializeToString(exportDoc); return new Blob([x3dstring], { type: "model/x3d+xml" }); }; //////////////////////////////////////////// // STL Binary Export //////////////////////////////////////////// // see http://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL CSG.prototype.toStlBinary = function() { // first check if the host is little-endian: var buffer = new ArrayBuffer(4); var int32buffer = new Int32Array(buffer, 0, 1); var int8buffer = new Int8Array(buffer, 0, 4); int32buffer[0] = 0x11223344; if (int8buffer[0] != 0x44) { throw new Error("Binary STL output is currently only supported on little-endian (Intel) processors"); } var numtriangles = 0; this.polygons.map(function(p) { var numvertices = p.vertices.length; var thisnumtriangles = (numvertices >= 3) ? numvertices - 2 : 0; numtriangles += thisnumtriangles; }); var headerarray = new Uint8Array(80); for (var i = 0; i < 80; i++) { headerarray[i] = 65; } var ar1 = new Uint32Array(1); ar1[0] = numtriangles; // write the triangles to allTrianglesBuffer: var allTrianglesBuffer = new ArrayBuffer(50 * numtriangles); var allTrianglesBufferAsInt8 = new Int8Array(allTrianglesBuffer); // a tricky problem is that a Float32Array must be aligned at 4-byte boundaries (at least in certain browsers) // while each triangle takes 50 bytes. Therefore we write each triangle to a temporary buffer, and copy that // into allTrianglesBuffer: var triangleBuffer = new ArrayBuffer(50); var triangleBufferAsInt8 = new Int8Array(triangleBuffer); // each triangle consists of 12 floats: var triangleFloat32array = new Float32Array(triangleBuffer, 0, 12); // and one uint16: var triangleUint16array = new Uint16Array(triangleBuffer, 48, 1); var byteoffset = 0; this.polygons.map(function(p) { var numvertices = p.vertices.length; for (var i = 0; i < numvertices - 2; i++) { var normal = p.plane.normal; triangleFloat32array[0] = normal._x; triangleFloat32array[1] = normal._y; triangleFloat32array[2] = normal._z; var arindex = 3; for (var v = 0; v < 3; v++) { var vv = v + ((v > 0) ? i : 0); var vertexpos = p.vertices[vv].pos; triangleFloat32array[arindex++] = vertexpos._x; triangleFloat32array[arindex++] = vertexpos._y; triangleFloat32array[arindex++] = vertexpos._z; } triangleUint16array[0] = 0; // copy the triangle into allTrianglesBuffer: allTrianglesBufferAsInt8.set(triangleBufferAsInt8, byteoffset); byteoffset += 50; } }); return new Blob([headerarray.buffer, ar1.buffer, allTrianglesBuffer], { type: "application/sla" }); }; //////////////////////////////////////////// // STL String Export //////////////////////////////////////////// CSG.prototype.toStlString = function() { var result = "solid csg.js\n"; this.polygons.map(function(p) { result += p.toStlString(); }); result += "endsolid csg.js\n"; return result; }; CSG.Vector3D.prototype.toStlString = function() { return this._x + " " + this._y + " " + this._z; }; CSG.Vertex.prototype.toStlString = function() { return "vertex " + this.pos.toStlString() + "\n"; }; CSG.Polygon.prototype.toStlString = function() { var result = ""; if (this.vertices.length >= 3) // should be! { // STL requires triangular polygons. If our polygon has more vertices, create // multiple triangles: var firstVertexStl = this.vertices[0].toStlString(); for (var i = 0; i < this.vertices.length - 2; i++) { result += "facet normal " + this.plane.normal.toStlString() + "\nouter loop\n"; result += firstVertexStl; result += this.vertices[i + 1].toStlString(); result += this.vertices[i + 2].toStlString(); result += "endloop\nendfacet\n"; } } return result; }; //////////////////////////////////////////// // DXF Export //////////////////////////////////////////// CAG.PathsToDxf = function(paths) { var str = "999\nDXF generated by OpenJsCad\n"; str += " 0\nSECTION\n 2\nHEADER\n"; str += " 0\nENDSEC\n"; str += " 0\nSECTION\n 2\nTABLES\n"; str += " 0\nTABLE\n 2\nLTYPE\n 70\n1\n"; str += " 0\nLTYPE\n 2\nCONTINUOUS\n 3\nSolid Line\n 72\n65\n 73\n0\n 40\n0.0\n"; str += " 0\nENDTAB\n"; str += " 0\nTABLE\n 2\nLAYER\n 70\n1\n"; str += " 0\nLAYER\n 2\nOpenJsCad\n 62\n7\n 6\ncontinuous\n"; str += " 0\nENDTAB\n"; str += " 0\nTABLE\n 2\nSTYLE\n 70\n0\n 0\nENDTAB\n"; str += " 0\nTABLE\n 2\nVIEW\n 70\n0\n 0\nENDTAB\n"; str += " 0\nENDSEC\n"; str += " 0\nSECTION\n 2\nBLOCKS\n"; str += " 0\nENDSEC\n"; str += " 0\nSECTION\n 2\nENTITIES\n"; paths.map(function(path) { var numpoints_closed = path.points.length + (path.closed ? 1 : 0); str += " 0\nLWPOLYLINE\n 8\nOpenJsCad\n 90\n" + numpoints_closed + "\n 70\n" + (path.closed ? 1 : 0) + "\n"; for (var pointindex = 0; pointindex < numpoints_closed; pointindex++) { var pointindexwrapped = pointindex; if (pointindexwrapped >= path.points.length) pointindexwrapped -= path.points.length; var point = path.points[pointindexwrapped]; str += " 10\n" + point.x + "\n 20\n" + point.y + "\n 30\n0.0\n"; } }); str += " 0\nENDSEC\n 0\nEOF\n"; return new Blob([str], { type: "application/dxf" }); }; CAG.prototype.toDxf = function() { var paths = this.getOutlinePaths(); return CAG.PathsToDxf(paths); }; //////////////////////////////////////////// // AMF Export //////////////////////////////////////////// CSG.prototype.toAMFString = function(m) { var result = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<amf"+(m&&m.unit?" unit=\"+m.unit\"":"")+">\n"; for(var k in m) { result += "<metadata type=\""+k+"\">"+m[k]+"</metadata>\n"; } result += "<object id=\"0\">\n<mesh>\n<vertices>\n"; this.polygons.map(function(p) { // first we dump all vertices of all polygons for(var i=0; i<p.vertices.length; i++) { result += p.vertices[i].toAMFString(); } }); result += "</vertices>\n"; var n = 0; this.polygons.map(function(p) { // then we dump all polygons result += "<volume>\n"; if(p.vertices.length<3) return; var r = 1, g = 0.4, b = 1, a = 1, colorSet = false; if(p.shared && p.shared.color) { r = p.shared.color[0]; g = p.shared.color[1]; b = p.shared.color[2]; a = p.shared.color[3]; colorSet = true; } else if(p.color) { r = p.color[0]; g = p.color[1]; b = p.color[2]; if(p.color.length()>3) a = p.color[3]; colorSet = true; } result += "<color><r>"+r+"</r><g>"+g+"</g><b>"+b+"</b>"+(a!==undefined?"<a>"+a+"</a>":"")+"</color>"; for(var i=0; i<p.vertices.length-2; i++) { // making sure they are all triangles (triangular polygons) result += "<triangle>"; result += "<v1>" + (n) + "</v1>"; result += "<v2>" + (n+i+1) + "</v2>"; result += "<v3>" + (n+i+2) + "</v3>"; result += "</triangle>\n"; } n += p.vertices.length; result += "</volume>\n"; }); result += "</mesh>\n</object>\n"; result += "</amf>\n"; return result; }; CSG.Vector3D.prototype.toAMFString = function() { return "<x>" + this._x + "</x><y>" + this._y + "</y><z>" + this._z + "</z>"; }; CSG.Vertex.prototype.toAMFString = function() { return "<vertex><coordinates>" + this.pos.toAMFString() + "</coordinates></vertex>\n"; };