UNPKG

svdsvglib

Version:

SVG Lib

1,652 lines (1,294 loc) 129 kB
'use strict'; var runningUnderNodeJS = false; var numericQuantity = null; if (typeof exports !== "undefined") { runningUnderNodeJS = true; console.log("Running under NodeJS"); } exports.test= function(args, success, failure) { executor(args,success,failure); }; exports.testParse=function(textFile ) { processTextStringToSVG(textFile); }; exports.svdSVG=function() { return new svdSVG(0); } exports.newFile=function() { return newFile(); } exports.svdSVGCommandList=function() { return new svdSVGCommandList(); } exports.svdSVGCreateCommand=function(commandIndex) { return new svdSVGCreateCommand(commandIndex); } exports.svdSVGCreateCommandData=function() { return new svdSVGCommandData(); ; } var DEBUG_LEVEL_0=0; var DEBUG_LEVEL_1=1; var DEBUG_LEVEL_2=2; var DEBUG_LEVEL_3=3; exports.svdSVGCommandEnum={firstPos:0, units: 0, depth: 1,moveTo: 2 , lineTo: 3, curvedLineTo: 4 , puzzleLineTo: 5, wavyLineTo:6, rect: 7, circle: 8, wavyRect: 9, boxLineTo: 10, arcTo: 11, text: 12, splineLineTo: 13, materialThickness: 14, cutType: 15, bitSize:16, ifCC:17, label:18, let:19, goto:20, then:21, image:22, closepath:23, include:24, keyLineTo: 25 , scarf:26, tolerance:27, warning:28, error:29, lastPos: 30}; // make sure last is set to +1 of last item exports.svdSVGSides={ top : 0x01 , right : 0x02 , bottom : 0x04 , left : 0x08 }; exports.svdSVGCutType={firstPos:0, fill : 0 , outlineOnPath : 1 , outlineOutside : 2 , outlineInside : 3 , lastPos: 4}; exports.svdSVGDirection={noDirection:0, xDirection:1,yDirection:2,zDirection:3}; exports.svdSVGCutTypeText=[ "FILL" , "OUTLINEONPATH" , "OUTLINEOUTSIDE" , "OUTLINEINSIDE" ]; exports.globalError= ""; exports.debugLevel= DEBUG_LEVEL_1; // Define a properties array that returns array of objects representing // the accepted properties for your application var properties = [ {id: "Offset File", type: "file-input"} ]; var listOfCommands=["UNITS","DEPTH OF CUT","MOVE TO","LINE TO","CURVED LINE TO", "PUZZLE LINE TO","WAVY LINE TO","RECT","CIRCLE","WAVY RECT", "BOX LINE TO","ARC TO","TEXT","SPLINE LINE TO","MATERIAL THICKNESS","CUT TYPE","BIT SIZE","IF","LABEL","LET","GOTO","THEN","IMAGE","CLOSEPATH","INCLUDE","KEY LINE TO","SCARF","TOLERANCE", "WARNING","ERROR"]; // make sure this and the enum are kept in sync var successFunction; var failureFunction; var commandEnum= exports.svdSVGCommandEnum; // // gets the file contents from the server after it has been uploaded // function loadFile(fileName) { //console.log(fileName); var xhr = new XMLHttpRequest(); // to get the file after it has uploaded xhr.open('GET', fileName, true); xhr.onload = function(e) { if (this.status == 200) { var str = this.responseText; processTextStringToSVG(str); } }; xhr.send(); } // Define an executor function that generates a valid SVG document string, // and passes it to the provided success callback, or invokes the failure // callback if unable to do so var executor = function(args, success, failure) { var params = args[0]; var selectedVolumes = args[1]; successFunction=success; failureFunction=failure; var fileName; if ( typeof params["Offset File"] != 'undefined' ) { fileName= params["Offset File"]; if (fileName.length > 0 ) { loadFile(fileName); } } }; // this is the main guts. It takes the command file loaded // in as a string, and calls the parse functions to process // it to an SVG function processTextStringToSVG(textFile) { //console.log(commandList); var svg = new svdSVG(); var commands=svg.parseCommandTextFile(textFile); // console.log("Length of command list "+commands.length()); if (exports.globalError.length > 0) { failureFunction(exports.globalError); } else { svg.convertCommandListToSVG(commands); if (exports.globalError.length > 0) { failureFunction(exports.globalError); } else { successFunction(svg.svgString()); } } } //return the control point necessary for a bezier to pass through // p2 given start p1 and end p3 // U is variable, but uses 0.5 as a default function findPOintOnBexierQuadratic(p1,p2,p3){ var rtnPoint=0; do { var u =0.5; p1=parseFloat(p1); p2=parseFloat(p2); p3=parseFloat(p3); // try and calculate where on the line between points the middle point is, in order to closer calculate u if (p3 != p2){ // u=Math.abs(p2-p1)/Math.abs(p3-p1); // u=Math.abs(p2-p1)/(Math.abs(p3-p2)+Math.abs(p2-p1)); } if (u == 0 ) { rtnPoint= p1; break; } if (u == 1 ) { rtnPoint= p3; break; } if ((u > 1 ) || ( u < 0 )) { u=0.5; break; } rtnPoint=(1/(2*(1-u)*u) * p2) - (((1-u)/(2*u)) * p1) - (u/(2*(1-u)) * p3); } while (0); console.log("u "+u+" p1 "+p1+" p2 "+p2+" p3 "+p3+" rtn "+rtnPoint); return rtnPoint; // https://github.com/mbostock/d3/blob/master/src/svg/line.js#L377 } function polarToCartesian(centerX, centerY, radius, angleInDegrees) { angleInDegrees-=90; angleInDegrees= angleInDegrees % 360; var angleInRadians = (angleInDegrees) * Math.PI / 180.0; return { x: centerX + (radius * Math.cos(angleInRadians)), y: centerY + (radius * Math.sin(angleInRadians)) }; } /*computes control points given knots K, this is the brain of the operation*/ function computeControlPoints(K) { p1=new Array(); p2=new Array(); n = K.length-1; /*rhs vector*/ a=new Array(); b=new Array(); c=new Array(); r=new Array(); /*left most segment*/ a[0]=0; b[0]=2; c[0]=1; r[0] = K[0]+2*K[1]; /*internal segments*/ for (i = 1; i < n - 1; i++) { a[i]=1; b[i]=4; c[i]=1; r[i] = 4 * K[i] + 2 * K[i+1]; } /*right segment*/ a[n-1]=2; b[n-1]=7; c[n-1]=0; r[n-1] = 8*K[n-1]+K[n]; /*solves Ax=b with the Thomas algorithm (from Wikipedia)*/ for (i = 1; i < n; i++) { m = a[i]/b[i-1]; b[i] = b[i] - m * c[i - 1]; r[i] = r[i] - m*r[i-1]; } p1[n-1] = r[n-1]/b[n-1]; for (i = n - 2; i >= 0; --i) p1[i] = (r[i] - c[i] * p1[i+1]) / b[i]; /*we have p1, now compute p2*/ for (i=0;i<n-1;i++) p2[i]=2*K[i+1]-p1[i+1]; p2[n-1]=0.5*(K[n]+p1[n-1]); return {p1:p1, p2:p2}; } function isNumber(n) { // the last char may be a % sign, so remove and retry is necessary if (n[n.length-1] =='%') { n=n.slice(0,-1); // remove the % for the test // return isNumber(n); } return !isNaN(parseFloat(n)) && isFinite(n); } // parses a string and find the x/y pair function findPointOnTextLine(command) { var data= new svdSVGCommandData(); var coords=command.split(","); // console.log("coords.length "+coords.length+" command "+command+" "+typeof coords); data.addData(coords); return data; } function buildErrorString(line){ exports.globalError=" Line "+(line+1)+"-"+exports.globalError; } // finds the index of the command within the command list function findCommandIndex(command) { var com=command.trim().toUpperCase(); // get rid of trailing and leading whitespace com=com.replace(/\s+/g, ' '); // this gets rid of double spaces // is this a label ? if (com[com.length-1] == ':') { // so this is a label ! // console.log("Found label "+com); return(commandEnum.label); } for (var loop=commandEnum.firstPos;loop < commandEnum.lastPos ; loop++){ // if (listOfCommands[loop] == com) var temp = com.substr(0,listOfCommands[loop].length) if (listOfCommands[loop] == temp) { // console.log(temp+" "+com); return(loop); } } return commandEnum.lastPos; } // candidate to go into it's own separate library /** * inbuilt variables * @author andy (12/17/2017) */ var UNITS= '$units'; var CUTTYPE= '$cuttype'; var BITSIZE= '$bitsize'; var BITSIZEVARIANCE= '$bitsizevariance'; var CLOSEPATH = '$closepath'; var DEPTHOFCUT = '$depthofcut'; var MINY="$miny"; var MINX="$minx"; var MAXY="$maxy"; var MAXX="$maxx"; var MATERIALTHICKNESS="$materialthickness"; var DOGBONES = '$dogbones'; var OFFSETX= "$svgoffsetx"; var OFFSETY= "$svgoffsety"; var TOLERANCE= "$tolerance"; var PERCENT="$percent"; var OLDSTYLECOMMAND="$oldstylecommand"; /** * Variables which would be used within a file * * @author andy (12/17/2017) */ var X="x"; var Y="y"; var MALE="male"; //male or frmale is just resolved to this one var var DELTAX="deltax"; var DELTAY="deltay"; var REPEAT="repeat"; var INTERSPACE="interspace"; var WIDTH="width"; var HEIGHT="height"; var NOSTEPS="nosteps"; var RADIUS="radius"; var RADIUSX="rx"; var RADIUSY="ry"; var ROTATE="rotate"; var TRANSFORM="transform"; var OFFSETX1="sizex1"; var OFFSETX2="sizex2"; var OFFSETX3="sizex3"; var OFFSETY1="sizey1"; var OFFSETY2="sizey2"; var FONTFAMILY="fontfamily"; var FONTTEXT="text"; var FONTSIZE="fontsize"; var STARTANGLE="startangle"; var ENDANGLE="endangle"; var SWEEP="sweep"; /** * contants which are to define a fixed item to a more sensible * constant * * @author andy (12/17/2017) */ var PERCENT_RAW="%"; var newFile = function() { var rtn=""; var varPrefix= ";Variable: "; console.log("in new file"); rtn += ";Describe the function of this file here12\n"; rtn += ";\n"; rtn += ";\n"; rtn += ";Date this file was created or modified, your choice\n"; rtn += ";"+Date().toString()+"\n"; rtn +="\n"; rtn +="\n"; rtn +=";Best to first define your units used in dimensions\n"; rtn +=";use either mm or in\n"; rtn +=";default is in\n"; rtn +=varPrefix+UNITS+"\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.units]+"\n"; rtn +="in\n"; rtn +="\n"; rtn +="\n"; rtn +=";include statements allow you to set a series of variables or common blocks\n"; rtn +=";to be used in your files\n"; rtn +=";Anything after the include statement will override what is in the include file\n"; rtn +=";This allows to to define a set of common blocks or variables, and then specialise in each file\n"; rtn +=";There is nothing special about the name dimensions.txt use anything you want ;-)\n"; rtn +="\n"; rtn +=";include dimensions.txt\n"; rtn +="\n"; rtn +=";This won't be automatically imported into Easel, you will need to set manually\n"; rtn +=varPrefix+BITSIZE+"\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.bitSize]+"\n"; rtn +="1/8 \n"; rtn +="\n"; rtn +=varPrefix+MATERIALTHICKNESS+"\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.materialThickness]+"\n"; rtn +="1/4 \n"; rtn +="\n"; rtn +=";Use % or actual DoC\n"; rtn +=";This won't be automatically imported into Easel, you will need to set manually\n"; rtn +=varPrefix+DEPTHOFCUT+"\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.depth]+"\n"; rtn +="100% \n"; rtn +="\n"; rtn +=varPrefix+CUTTYPE+"\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.cutType]+"\n"; rtn +=exports.svdSVGCutTypeText[exports.svdSVGCutType.fill]+"\n"; rtn +=";"+exports.svdSVGCutTypeText[exports.svdSVGCutType.outlineOnPath]+"\n"; rtn +=";"+exports.svdSVGCutTypeText[exports.svdSVGCutType.outlineOutside]+"\n"; rtn +=";"+exports.svdSVGCutTypeText[exports.svdSVGCutType.outlineInside]+"\n"; rtn +="\n"; rtn +="\n"; rtn +=";The following are examples of how to declare variables\n"; rtn +=";Although not enforced, it is good to keep to the convention that built in variables\n"; rtn +=";start with $, and that other variables do not. This can make it easier to debug.\n"; rtn +="let\n"; rtn +="widthOfPiece =4;\n"; rtn +="heightOfPiece =5;\n"; rtn +="x =9;\n"; rtn +="\n"; rtn +=";The following are examples of commands to use\n"; rtn +=";Most commands have two forms. \n"; rtn +=";Shorthand of numbers in an order e.g 0,0,10,20\n"; rtn +=";Longhand e.g x=0, y=0, width=10, height=20\n"; rtn +=";When using shorthand, other variables specific to that item can be added \n"; rtn +=";in longhand at the end, but nothing that would normally be used for that command in longhand\n"; rtn +="\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.moveTo]+"\n"; rtn +=X+"=0,"+Y+"=0\n"; rtn +=";0,0\n"; rtn +="\n"; rtn +="; A basic line from one location to another"; rtn +="\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.lineTo]+"\n"; rtn +=X+"=1,"+Y+"=2\n"; rtn +=";1,2\n"; rtn +="\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.splineLineTo]+"\n"; rtn +=X+"=1.5,"+Y+"=2\n"; rtn +=X+"=2,"+Y+"=2\n"; rtn +=";1.5,2\n"; rtn +=";2,2\n"; rtn +="\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.circle]+"\n"; rtn +=X+"=4,"+Y+"=4, "+RADIUS+"=0.5\n"; rtn +=";4,4, 0.5\n"; rtn +="\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.rect]+"\n"; rtn +=";rx, ry, rotate are optional. For shorthand, they are added in that order, longhand use whichever you want\n"; // rtn +="x=10,y=20,width=widthOfPiece,height=heightOfPiece, rotate=0,cuttype=outsideoutline\n"; rtn +="x=20,y=5,width=widthOfPiece,height=heightOfPiece, "+ROTATE+"=30,"+CUTTYPE+"=fill\n"; rtn +=";20,5,widthOfPiece,heightOfPiece \n"; rtn +="\n"; rtn +=listOfCommands[exports.svdSVGCommandEnum.text]+"\n"; rtn +="x=5,y=15,fontsize=10,fontfamily=Ariel, text = blah blah \n"; rtn +=";5,15,10,Arial, blah blah \n"; return(rtn); } /** * d */ function svdSVG() { this.defaultVars = function() { this.svg = ''; this.pathOpened = false; this.offsetX=0; // used if there are negative x points to move the centerline into the middle this.offsetY=0; // used if there are negative y points to move the centerline into the middle this.drawingWidth=0; this.drawingHeight=0; this.lastPoint= new svdSVGCommandData(); this.hasText=false; this.hasTextConverted=false; this.parser=require("mathjs"); this.globalParserScope={}; this.setVariable(CUTTYPE,exports.svdSVGCutType.fill,this.globalParserScope); this.setVariable(UNITS,'in',this.globalParserScope); this.setVariable(BITSIZE,'0.125',this.globalParserScope); this.setVariable(BITSIZEVARIANCE,'2',this.globalParserScope); this.setVariable(DEPTHOFCUT,0,this.globalParserScope); this.setVariable(CLOSEPATH,1 ,this.globalParserScope); this.setVariable(MATERIALTHICKNESS, 0.5 ,this.globalParserScope); this.setVariable(MAXX,-99999999999,this.globalParserScope); this.setVariable(MAXY,-99999999999,this.globalParserScope); this.setVariable(MINX, 99999999999,this.globalParserScope); this.setVariable(MINY, 99999999999,this.globalParserScope); this.setVariable(DOGBONES, 0 ,this.globalParserScope); this.setVariable(OFFSETX, 0 ,this.globalParserScope); this.setVariable(OFFSETY, 0 ,this.globalParserScope); this.setVariable(TOLERANCE, 0 ,this.globalParserScope); this.setVariable(PERCENT, 0.01 ,this.globalParserScope); } /** * Basic logging function. Has ability to have different logging * levels */ this.log=function(level,logStr) { if (this.stackTrace != undefined) { var trace = this.stackTrace.get(); // console.log(trace[1].getFunctionName()+":"+trace[1].getLineNumber()+" "+" " +logStr); if (level <= exports.debugLevel) { console.log(trace[1].getFunctionName()+":"+trace[1].getLineNumber()+" "+" " +logStr); } } } // creates the SVG header including the viewbox functions. // make sure the dimensions have been set before calling this this.addSVGDimensions = function() { // this.defaultVars(); var width = this.drawingWidth+this.offsetX; var height= this.drawingHeight+this.offsetY; var units = this.getVariable(UNITS,this.globalParserScope); this.svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<svg\n xmlns="http://www.w3.org/2000/svg" version="1.0"\n width="'+this.drawingWidth+units+'"\n height="' +this.drawingHeight+units+'"\n viewBox="'+this.offsetX+" "+this.offsetY+" "+width+' '+height+'"\n preserveAspectRatio="xMinYMin meet" \n>'+this.svg; } this.closeSVG = function() { this.closePath(this.globalParserScope); // in case it was never closed this.svg+='\n</svg>'; this.drawingWidth=parseFloat(Math.abs(this.parser.eval(MAXX, this.globalParserScope)-this.parser.eval(MINX, this.globalParserScope))); this.drawingHeight=parseFloat(Math.abs(this.parser.eval(MAXY, this.globalParserScope)-this.parser.eval(MINY, this.globalParserScope))); // this.offsetX=parseFloat(0-this.limits.minX); // this.offsetY=parseFloat(0-this.limits.minY); this.offsetX=parseFloat(this.parser.eval(MINX, this.globalParserScope)); this.offsetY=parseFloat(this.parser.eval(MINY, this.globalParserScope)); // console.log ("limits.maxX "+this.limits.maxX+" limits.minX "+this.limits.minX+" limits.maxY "+this.limits.maxY+" limits.minY "+this.limits.minY); this.addSVGDimensions(); // add in the dimensions etc if (this.hasText == true) { if (this.hasTextConverted == false) { // used text, but it hasn't been converted yet this.convertTextToPaths(); } } } this.setUnits=function(currentCommand,context) { // var units = this.getVariableUnique(UNITS,context); var units = context[UNITS]; context[UNITS]=""; // clear it out, so we know if it got set below do { // console.log(dim); // console.log(context); for (var loop2=0;loop2 < currentCommand.data.length;loop2++) { var loopContext=this.populateContext(currentCommand, context, loop2); if (exports.globalError.length > 0) break; } if (exports.globalError.length > 0) break; var newUnits=this.getVariable(UNITS,context); // clear it out, so we know if it got set below if (exports.globalError.length > 0) break; // console.log("newUnits "+newUnits+" oldunits "+units); if (newUnits === "") { // it wasn't set for (var loop2=0;loop2 < currentCommand.data.length;loop2++) { newUnits= currentCommand.data[loop2].data[0].trim(); } } newUnits=newUnits.toLowerCase(); if ((newUnits !="in") && (newUnits !="mm")) { exports.globalError="Dimensions should be in or mm"; break; } units=newUnits; // console.log("newUnits "+newUnits+" oldunits "+units); } while (0); // console.log("dimensions= "+units); this.setVariable(UNITS,units,context); // units always get set ! } // adds a svg open path statement to a string this.openPath=function() { if (this.pathOpened === false) { this.svg+='\n<path d="' this.pathOpened=true; // console.log("path opened"); } } this.closePath=function(context) { do { if (this.pathOpened === true) { var closePath = this.processNumber(CLOSEPATH,this.globalParserScope); if (exports.globalError.length > 0) break; if (closePath > 0) { this.addCommandToSVGString("Z"); } this.svg+='"'; this.addStyleToPath(context); this.closeTag(); this.pathOpened=false; } } while (0); } // returns a copy of the svg string this.closeTag=function() { this.svg+='\n'; this.svg+='/>'; this.svg+='\n'; } // returns a copy of the svg string this.svgString=function() { // console.log(this.globalParserScope); return this.svg; } // // given a command list of commands and points, // parses it and generates an svg string // this.convertCommandListToSVG =function(commandList) { var width = 20; var length = 20; do { if (runningUnderNodeJS == true) { this.stackTrace = require('stack-trace'); } this.commandList=commandList; this.commandListPos=0; this.executeCommandListIncludes(); if (exports.globalError.length > 0) { buildErrorString(this.commandListPos); break; } this.commandListPos=0; this.executeCommandList(); if (exports.globalError.length > 0) { buildErrorString(this.commandListPos); break; } this.closePath(this.globalParserScope); this.closeSVG(); console.log ("drawingWidth "+this.drawingWidth+" drawingHeight "+this.drawingHeight+" offsetX "+this.offsetX+" offsetY "+this.offsetY); if (runningUnderNodeJS == true) { // var Gcanvas = require('gcanvas'); //var driver = new Gcanvas.GcodeDriver({ // write: function(cmd) { // console.log(cmd); // } //}); //var ctx = new Gcanvas(driver); // ctx.depthOfCut=5; // ctx.depth=1; // ctx.circle(10,10,5); } } while (0); if (exports.globalError.length > 0) { return ""; } // console.log(this.svg); return(this.svg); } // process any includes. This is done prior this.executeCommandListIncludes =function() { do { if ( (typeof this.commandList) == 'undefined' ) { exports.globalError="ExecuteCommandList called with a zero length command list"; break; } if ( this.commandList.length() == 0) { exports.globalError="ExecuteCommandList called with a zero length command list"; break; } var includeFound=false; var includeDepth=0; for (this.commandListPos=0;this.commandListPos < this.commandList.length();this.commandListPos++) { var currentCommand=this.commandList.itemAt(this.commandListPos); //console.log("command "+currentCommand.pos+" number of points "+currentCommand.data.length); if (exports.globalError.length > 0) { // buildErrorString(this.commandListPos); break; } if (currentCommand.valid == false) { continue; } switch(currentCommand.command) { case commandEnum.include : { this.include(currentCommand); includeFound=true; this.commandListPos=-1; } break; } // console.log("exports.globalError.length "+exports.globalError.length); if (exports.globalError.length > 0) { return; } } } while (0); } this.executeCommandList =function() { do { if ( (typeof this.commandList) == 'undefined' ) { exports.globalError="ExecuteCommandList called with a zero length command list"; break; } if ( this.commandList.length() == 0) { exports.globalError="ExecuteCommandList called with a zero length command list"; break; } for (this.commandListPos=0;this.commandListPos < this.commandList.length();this.commandListPos++) { var currentCommand=this.commandList.itemAt(this.commandListPos); //console.log("command "+currentCommand.command+" number of points "+currentCommand.data.length); if (exports.globalError.length > 0) { break; } if (currentCommand.valid == false) { continue; } var context= this.deepCopy(this.globalParserScope); // console.log("loop "+currentCommand.command); // console.log(context); switch(currentCommand.command) { case commandEnum.materialThickness : { this.materialThicknessCalc(currentCommand,this.globalParserScope); } break; case commandEnum.cutType: { this.cutType(currentCommand,this.globalParserScope); } break; case commandEnum.include : { console.log("You should really fix this, second include"); } break; case commandEnum.depth : { this.depthOfCut(currentCommand,this.globalParserScope); } break; case commandEnum.units : { // console.log("You should really fix this, using MM"); this.setUnits(currentCommand,this.globalParserScope) ; } break; case commandEnum.moveTo : { this.moveTo(currentCommand,context); } break; case commandEnum.text : { this.text(currentCommand,context); } break; case commandEnum.boxLineTo : { this.boxLineTo(currentCommand,context); } break; case commandEnum.puzzleLineTo : { this.puzzleLineTo(currentCommand,context); } break; case commandEnum.keyLineTo : { this.keyLineTo(currentCommand,context); } break; case commandEnum.lineTo : { this.lineTo(currentCommand,context); } break; case commandEnum.rect : { this.rect(currentCommand,context); } break; case commandEnum.circle : { this.circle(currentCommand,context); } break; case commandEnum.splineLineTo : { this.splineLineTo(currentCommand,context); } break; case commandEnum.arcTo : { this.arcTo(currentCommand,context); } break; case commandEnum.curvedLineTo : { this.curvedLineTo(currentCommand,context); } break; case commandEnum.wavyRect : { this.wavyRect(currentCommand,context); } break; case commandEnum.wavyLineTo : { this.wavyLineTo(currentCommand,context); } break; case commandEnum.let : { this.let(currentCommand,this.globalParserScope); } break; case commandEnum.ifCC : { this.ifCC(currentCommand); } break; case commandEnum.bitSize : { this.bitsize(currentCommand,this.globalParserScope); } break; case commandEnum.image : { this.image(currentCommand,context); } break; case commandEnum.scarf : { this.scarf(currentCommand,context); } break; case commandEnum.label : { // does nothing } break; case commandEnum.error : { // does nothing } break; case commandEnum.warning : { // does nothing } break; // default: // { // console.log("Unknown tag "+currentCommand.pos); // exports.globalError="Unknown Tag"; // } // break; } if (exports.globalError.length > 0) { return; } } } while (0); } /** * Does a deep javascript copy. This may not be the best for * super deep or speed, but seems to work for what we need ! */ this.deepCopy=function(obj) { var str = JSON.stringify( obj ); // console.log(str); var val=JSON.parse(str); return(val); } /** * Creates and populates a new context based on the current * context and the args passed in */ this.populateContext=function(currentCommand,context,index) { var loopContext; do { loopContext = this.deepCopy(context); loopContext[OLDSTYLECOMMAND]=true; if (exports.globalError.length > 0) break; // console.log("currentCommand.data[index] "+currentCommand.data[index]); if ( currentCommand.data[index] === undefined) break; for (var loop3=0;loop3 < currentCommand.data[index].data.length;loop3++) { var tempData = currentCommand.data[index].data[loop3].toString(); // console.log("tempData = "+tempData+" "+ typeof tempData); var temp = tempData.search("="); if (temp > 0) { var tempStr = currentCommand.data[index].data[loop3].substr(0,temp).trim().toLowerCase(); var tempVal= currentCommand.data[index].data[loop3].substr(temp+1); // is a string value //figure out ones where we just take the text // console.log("populateContext "+tempData+" as tempStr="+tempStr+" tempVal=" +tempVal); if (tempStr === FONTFAMILY) { // this.parser.set(tempStr, "blah blah",loopContext); loopContext[FONTFAMILY]=tempVal; } else if (tempStr == FONTTEXT) { loopContext[FONTTEXT]=tempVal; } else if (tempStr == CUTTYPE) { loopContext[CUTTYPE]=tempVal.toUpperCase(); } else if (tempStr == DEPTHOFCUT) { // var tempProcessVal=this.processNumber(tempVal,loopContext); // process to a number if it can loopContext[DEPTHOFCUT]=tempVal; this.depthOfCut(currentCommand,loopContext); } else { // this.processNumber(currentCommand.data[index].data[loop3],loopContext); if (exports.globalError.length > 0) break; this.processNumber(currentCommand.data[index].data[loop3],loopContext); } loopContext[OLDSTYLECOMMAND]=false; } else { // this.processNumber(currentCommand.data[index].data[loop3],loopContext); if (exports.globalError.length > 0) break; // this.processNumber(currentCommand.data[index].data[loop3],loopContext); } // console.log(currentCommand.data[index].data[loop3]); } } while (0); return(loopContext); } this.processNumber=function(num,context) { var val=null; try { // num may be an expression, or just a number, we don't know // num = this.parser.unit(num); // num may be an expression, or just a number, we don't know var validMatch="%|C|D/i"; var matchPos = num.toString().match(validMatch); var setMatchPos=false; this.log(DEBUG_LEVEL_2, "num "+num+" match "+matchPos); // // check for an num after a match, just in case it's a word like "chicken" if (matchPos != null) { if (((num[1] >="1") && (num[1] <="9")) || (num[1] == "-")) { // best guess that this is a number // this.modifiers[loop]=value[0]; num=num.substring(1); } if (num[num.length-1] == matchPos) { if (matchPos == '%') { // replace % with "percent" variable // num=num.substring(0,num.length-1); // num+=" "; // num+=PERCENT; // this.log(DEBUG_LEVEL_1, "num "+num+" match "+matchPos); setMatchPos=true; } else { // this.modifiers[loop]=match; num=num.substring(0,num.length-1); } } } // console.log("num "+num); val=this.parser.eval(num, context); if (setMatchPos == true) { // context[num].modifiers=matchPos; } // console.log("2"); // val = parseFloat(val); } catch (err) { console.log("processNumber error for \""+num+"\" which is a "+this.parser.typeof(num)+" isNumeric "+this.parser.isNumeric(num)); console.log(context); console.log(err); exports.globalError=String(err); } return(val); } this.setVariable=function(variable,value,context) { var val=null; try { var num=variable+"="+value; // this.log(DEBUG_LEVEL_2, variable+" to "+value); // val=this.processNumber(num, context); // val=this.parser.set(, context); context[variable]=value; } catch (err) { console.log("setVariable error for \""+num+"\" which is a "+this.parser.typeof(num)); console.log(context); console.log(err); exports.globalError=String(err); // buildErrorString(this.commandListPos); } // val=parseFloat(val); // console.log("num "+num+" val "+val+" type "+typeof val); return(val); } this.getVariable=function(num,context) { var val = this.processNumber(num,context); return(val); } this.getVariableUnique=function(num,context) { var rtn; do { var rtnUD=this.getVariable(num,context); if (exports.globalError.length > 0) { return; } // rtn=this.deepCopy(rtnUD); rtn = Object.assign({}, rtnUD); // console.log("orig "+rtnUD+" deep "+rtn); } while (0); return(rtn); } this.roundNumber=function(num) { num=parseFloat(num); if (num != parseInt(num)) { num+=0.0005; num=num * 1000; num=Math.round(num); num/=1000; } return(num); } // adds a number to the SVG String. // truncates the length so as not to have silly numbers with many DP this.addNumberToSVGString=function(num) { num=this.roundNumber(num); if ((this.svg.length > 0) && (this.svg[this.svg.length-1] != ' ')) { this.svg+=" "; } this.svg+=num+" "; } // adds a X location to the SVG String. // truncates the length so as not to have silly numbers with many DP this.addXLocationToSVGString=function(num) { num+=this.offsetx; this.addNumberToSVGString(num); } // adds a Y location to the SVG String. // truncates the length so as not to have silly numbers with many DP this.addYLocationToSVGString=function(num) { num+=this.offsety; this.addNumberToSVGString(num); } this.addQuotedTagToSVGString=function(tag,num) { if ((this.svg.length > 0) && (this.svg[this.svg.length-1] != ' ')) { this.svg+=" "; } this.svg+='\n'; if (tag === ROTATE) { this.svg+=TRANSFORM; num=tag+"("+num+")"; } else { this.svg+=tag; } this.svg+='='; this.addQuotedNumberToSVGString(num); } this.addQuotedNumberToSVGString=function(num) { if (typeof(num) === 'number') { num=this.roundNumber(num); } // if ((this.svg.length > 0) && (this.svg[this.svg.length-1] != ' ')) { // this.svg+=" "; // } this.svg+='"'+num+'"'; } this.addCRToSVGString=function() { this.svg+="\n"; } // adds a command to the SVG String this.addCommandToSVGString=function(command) { if (this.svg.length > 0) { if (this.svg[this.svg.length-1] != ' ') { this.svg+=" "; } } this.addCRToSVGString(); this.svg+=command+" "; this.addCRToSVGString(); } // adds the style tag to the path and closes it this.addStyleToPath=function(context) { var depth = this.getVariable(DEPTHOFCUT,context); var cutTypeStyle = this.getVariable(CUTTYPE,context); // console.log("cut type "+exports.svdSVGCutType.fill+" "+cutTypeStyle+" depth "+depth); this.svg+='\n'; if (cutTypeStyle == exports.svdSVGCutType.fill) { this.svg+='style="fill:rgb('+depth+','+depth+','+depth+')" '; } else { var sw= "0.1"; var bs=this.getVariable(BITSIZE,context); if (bs > 0) { sw=bs; var closePath = this.getVariable(CLOSEPATH,context); if (closePath == 0) { // an open path we need to add the variance to make sure it gets cut var variance = this.getVariable(BITSIZEVARIANCE,context); if (variance > 0) { sw=sw + ((bs * variance) / 100); } } } this.svg+='fill="none" stroke="rgb('+depth+','+depth+','+depth +')" stroke-width="'+sw+'"'; } } // searchs the command list to find a label this.findLabel=function(label) { var rtn= -1; do { // console.log("length "+this.commandList.length()); for (var loop=0;loop < this.commandList.length();loop++) { var currentCommand=this.commandList.itemAt(loop); // console.log("pos "+currentCommand.command+" "+currentCommand ); switch(currentCommand.command) { case commandEnum.label : { // console.log(currentCommand.length+" loop "+loop); var temp = currentCommand.data[0].data[0]; // console.log("temp "+temp); if (temp === label) { rtn = loop; break; } } break; } } } while (0); return(rtn); } this.include=function(currentCommand) { do { if ( currentCommand.data.length != 1 ) { exports.globalError="Include Command must have one value"; return; } for (var loop=0;loop < currentCommand.data.length;loop++) { var include = currentCommand.data[loop].data[0]; this.log("adding include "+include); if (include.length > 0) { var fs = require('fs'); // loading/saving files var os = require('os'); // loading/saving files var path = require('path'); // path parsing functionality currentCommand.valid=false; // regardless of outcome, we don't process this include again try { var data=fs.readFileSync(include, 'utf8'); } catch (err) { exports.globalError =String(err); return; } if (exports.globalError.length > 0) { return; } // console.log("data "+data); var insertList= this.parseCommandTextFile(data); if (exports.globalError.length > 0) { // buildErrorString(this.commandListPos); return; } // console.log(insertList); // console.log(this.commandListPos+" "+insertList.length()); // now need to add the new command list to the existing command list. for (var loop2=0;loop2 < insertList.length();loop2++) { this.commandList.insert(this.commandListPos+1+loop2,insertList.itemAt(loop2)); // console.log(insertList.itemAt(loop2)); } } if (exports.globalError.length > 0) { // buildErrorString(this.commandListPos); return; } } } while (0); // console.log(this.commandList); } this.materialThicknessCalc=function(currentCommand,context) { do { if ( currentCommand.data.length != 1 ) { exports.globalError="Material Thickness Command must have one value"; break; } var newThickness = this.processNumber(currentCommand.data[0].data[0],context); if (exports.globalError.length > 0) break; this.setVariable(MATERIALTHICKNESS,newThickness,context); if (exports.globalError.length > 0) { // buildErrorString(this.commandListPos); break; } } while (0); } // this inserts an SVG image into the SVG at the reqired coords this.image=function(currentCommand,context) { do { if ( currentCommand.data.length == 0 ) { exports.globalError="Image Command must have at least one value"; break; } this.closePath(context); //close out any existing paths for (var loop2=0;loop2 < currentCommand.data.length;loop2++) { var x = this.processNumber(currentCommand.data[loop2].data[0]); if (exports.globalError.length > 0) break; var y = this.processNumber(currentCommand.data[loop2].data[1]); if (exports.globalError.length > 0) break; var convertFileName = currentCommand.data[loop2].data[2]; // read in the image sync var data=fs.readFileSync(convertFileName, 'utf8'); console.log(data); } } while (0); } // this inserts a Scarf joint into the SVG at the reqired coords this.scarf=function(currentCommand,context) { do { if ( currentCommand.data.length == 0 ) { exports.globalError="Image Command must have at least one value"; break; } this.closePath(context); //close out any existing paths for (var loop2=0;loop2 < currentCommand.data.length;loop2++) { var x = this.processNumber(currentCommand.data[loop2].data[0],context); if (exports.globalError.length > 0) break; var y = this.processNumber(currentCommand.data[loop2].data[1],context); if (exports.globalError.length > 0) break; var convertFileName = currentCommand.data[loop2].data[2]; // read in the image sync var data=fs.readFileSync(convertFileName, 'utf8'); console.log(data); } } while (0); } this.cutType=function(currentCommand,context) { do { if ( currentCommand.data.length < 1 ) { exports.globalError="Cut Type Command must have one value"; break; } var text = String(currentCommand.data[0].data[0]); text=text.trim().toUpperCase(); // get rid of trailing and leading whitespace text = text.replace(/\s/g, ''); var valFound=false; // console.log("cut type text "+text); for (var loop=exports.svdSVGCutType.firstPos;loop < exports.svdSVGCutType.lastPos ; loop++){ if (exports.svdSVGCutTypeText[loop] == text) { this.closePath(context); this.setVariable(CUTTYPE,loop,context); valFound=true; // console.log("cut type text "+this.cutTypeStyle); break; } } if (valFound == false) { exports.globalError="Cut Type "+currentCommand.data[0].data[0] +" not found"; break; } } while (0); } this.depthOfCut=function(currentCommand,context) { do { // if ( currentCommand.data.length != 1) // { // exports.globalError="Depth Command must have only one value "+currentCommand.data; // break; // } this.doDepthOfCut(currentCommand,context); } while (0); } this.doDepthOfCut=function(currentCommand,context) { do { // var newDepth = String(currentCommand.data[0].data[0]); console.log(context); var newDepth = this.getVariable(DEPTHOFCUT,context); if ( newDepth === undefined ) { exports.globalError="doDepthOfCut-depth is undefined"; break; } var modifiers = currentCommand.data[0].modifiers[0]; var materialThickness = this.getVariable(MATERIALTHICKNESS,context).value; this.log(DEBUG_LEVEL_1,"new depth "+newDepth+ " modifiers "+modifiers+" mat thickness "+materialThickness+" NewDepth "+newDepth+" error "+exports.globalError.length); if (exports.globalError.length > 0) { break; } // console.log(context); if (materialThickness > 0) { if (modifiers == '%') { // it will just fall out in this case using existing logic } else { newDepth *= 255; newDepth /= materialThickness; } } // console.log("11 new depth "+this.depth+ " modifiers "+modifiers+" mat thickness "+materialThickness+" new "+String(currentCommand.data[0].data[0])+" NewDepth "+newDepth); if (modifiers == '%') { newDepth=parseFloat(newDepth) * 255; newDepth/=100; } // depth is "depth of cut", which is inverse if (newDepth < 0 ) { newDepth = 0; } if (newDepth > 255 ) { newDepth = 255; } // console.log("12 new depth "+this.depth+ " modifiers "+modifiers+" mat thickness "+materialThickness+" new "+String(currentCommand.data[0].data[0])+" NewDepth "+newDepth); newDepth = 255-newDepth; var depth = this.getVariable(DEPTHOFCUT,context); if (exports.globalError.length > 0) { this.log(DEBUG_LEVEL_1,"new depth "+newDepth+ " modifiers "+modifiers+" mat thickness "+materialThickness+" NewDepth "+newDepth+" error "+exports.globalError.length); break; } if (depth != newDepth) { this.closePath(context); } newDepth=parseInt(newDepth); this.setVariable(DEPTHOFCUT,newDepth,context); // this.log(DEBUG_LEVEL_1,"new depth "+newDepth+ " modifiers "+modifiers+" mat thickness "+materialThickness+" NewDepth "+newDepth+" error "+exports.globalError.length); if (exports.globalError.length > 0) { // buildErrorString(this.commandListPos); break; } // console.log(this.globalParserScope); // console.log("1 new depth "+this.depth+ " modifiers "+modifiers+" mat thickness "+materialThickness+" new "+String(currentCommand.data[0].data[0])+" NewDepth "+newDepth); } while (0); } this.bitsize=function(currentCommand,context) { do { if ( currentCommand.data.length != 1 ) { exports.globalError="Bitsize Command must have one value"; break; } // console.log("bitsize"); var bs = this.processNumber(currentCommand.data[0].data[0],context); if (exports.globalError.length > 0) { break; } this.bitSize=bs; this.setVariable(BITSIZE,bs,context); if (exports.globalError.length > 0) { break; } // console.log(this.globalParserScope); } while (0); } /* Generates an arc 0- X center coordinate 1- Y center coordinate 2- Radius X 3- Radius Y 4- start Angle in Degrees 5- end Angle in Degrees 6-