├── .gitignore ├── .npmignore ├── carriers.js ├── package.json ├── LICENSE ├── hello_world.js ├── sample.js ├── README.md └── knitout.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.k -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | knitout-api/ 2 | .DS_Store 3 | .gitmodules 4 | .gitignore 5 | .git 6 | -------------------------------------------------------------------------------- /carriers.js: -------------------------------------------------------------------------------- 1 | var knitout = require('./knitout') 2 | k = new knitout.Writer({carriers:["1", "2", "3", "4", "10"]}); 3 | k.addHeader('Machine','SWGXYZ'); 4 | k.addHeader('Gauge','15'); 5 | k.addHeader('Presser','On'); 6 | k.addHeader('X-Presser','On'); 7 | k.addHeader('X-Takedown','On'); 8 | 9 | k.knit('-', 'b3', "10"); 10 | k.knit('-', 'b3', " 10"); 11 | k.knit('+', 'f1', "2", "3"); 12 | k.knit('+', 'f1', ["1 ", "2"]); 13 | k.knit('-', 'b3', "1 3"); 14 | k.tuck('-', 'b3', "1, 4"); 15 | k.write("carriers.k"); 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knitout", 3 | "version": "1.0.2", 4 | "description": "Handy writer of knitout in javascript", 5 | "main": "knitout.js", 6 | "scripts": { 7 | "start": "node --", 8 | "test": "node hello_world.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/textiles-lab/knitout-frontend-js.git" 13 | }, 14 | "keywords": [ 15 | "knitout", 16 | "knitting" 17 | ], 18 | "author": "", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/textiles-lab/knitout-frontend-js/issues" 22 | }, 23 | "homepage": "https://github.com/textiles-lab/knitout-frontend-js#readme" 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hello_world.js: -------------------------------------------------------------------------------- 1 | // import the knitoutWriter code and instantiate it as an object 2 | var knitout = require('./knitout'); 3 | k = new knitout.Writer({carriers:["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]}); 4 | 5 | // add some headers relevant to this job 6 | k.addHeader('Machine','SWGXYZ'); 7 | k.addHeader('Gauge','15'); 8 | 9 | // swatch variables 10 | var height = 10; 11 | var width = 10; 12 | var carrier = "6"; 13 | k.fabricPresser("auto"); 14 | // bring in carrier using yarn inserting hook 15 | k.inhook(carrier); 16 | // tuck on alternate needles to cast on 17 | for (var s=width; s>0; s--) { 18 | if (s%2 == 0) { 19 | k.tuck("-", "f"+s, carrier); 20 | } 21 | else { 22 | k.miss("-", "f"+s, carrier); 23 | } 24 | } 25 | for (var s=1; s<=width; s++) { 26 | if (s%2 != 0) { 27 | k.tuck("+", "f"+s, carrier); 28 | } 29 | else { 30 | k.miss("+", "f"+s, carrier); 31 | } 32 | } 33 | 34 | // release the yarn inserting hook 35 | k.releasehook(carrier); 36 | 37 | // knit some rows back and forth 38 | for (var h=0; h0; s--) { 40 | k.knit("-", "f"+s, carrier); 41 | } 42 | for (var s=1; s<=width; s++) { 43 | k.knit("+", "f"+s, carrier); 44 | } 45 | } 46 | 47 | // bring the yarn out with the yarn inserting hook 48 | k.outhook(carrier); 49 | 50 | // write the knitout to a file called "out.k" 51 | k.write('out.k'); 52 | console.log('wrote out.k') 53 | 54 | -------------------------------------------------------------------------------- /sample.js: -------------------------------------------------------------------------------- 1 | var knitout = require('./knitout') 2 | k = new knitout.Writer({carriers:["B","A","2","C"]}); 3 | k.addHeader('Machine','SWGXYZ'); 4 | k.addHeader('Gauge','15'); 5 | k.addHeader('Presser','On'); 6 | k.addHeader('X-Presser','On'); 7 | k.addHeader('X-Takedown','On'); 8 | k.in('A','B','C'); 9 | k.inhook( 'C'); 10 | k.releasehook("A"); 11 | k.stitch(10,20); 12 | k.stitch(10.0,20); 13 | k.stitch(10.33, 20 ); 14 | k.knit('+', 'f10', ['A','2']); 15 | k.knit('+', 'f10', "A", 'B', 'C'); 16 | k.comment("the following is a badly named single string carrier:"); 17 | try { 18 | k.knit('+', 'f10', "[1,2]"); //invalid carrier name 19 | console.assert(false); 20 | } catch (e) { } 21 | k.knit('+', 'f1099'); 22 | try { 23 | k.knit('_', 'f1099'); //_ is not a direction 24 | console.assert(false); 25 | } catch (e) { } 26 | k.rack(0.5); 27 | try { 28 | k.xfer('fs10', 'fs10.5'); //10.5 is not a valid needle 29 | console.assert(false); 30 | } catch (e) { } 31 | k.drop('f10'); 32 | k.amiss('f20'); 33 | k.comment("transfers are supported in knitout as an opcode"); 34 | k.xfer('f20','b20'); 35 | k.split('+', 'b10', 'f20', 'A', 'B'); 36 | try { 37 | k.split('+', 'b10', 'c20', 'A', 'B'); //c20 isn't a valid bed 38 | console.assert(false); 39 | } catch (e) { } 40 | try { 41 | k.knit('-', {bed:'f', needle:10000}, '1'); //'1' not a carrier 42 | console.assert(false); 43 | } catch (e) { } 44 | try { 45 | k.knit('-', ['b', 40000], 20000); //not a valid carrier name 46 | console.assert(false); 47 | } catch (e) { } 48 | try { 49 | k.knit('-', ['b', 40000], 20000); //not a valid carrier name 50 | console.assert(false); 51 | } catch (e) { } 52 | try { 53 | k.miss('+', {bed:'b', needle:-100},4); //4 is not a string, so not a valid carrier name 54 | console.assert(false); 55 | } catch (e) { } 56 | k.out(['A','B']); 57 | k.outhook('A','B','C'); 58 | k.pause('\tsome comment about the pause'); 59 | k.comment('a \tcomment; \n another comment;'); 60 | k.comment(';another comment'); 61 | k.comment(';;;more comments'); 62 | k.write('out.k'); 63 | 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # knitout-frontend-js 2 | 3 | 4 | A javascript wrapper for all operations supported by knitout with basic error and type checking. 5 | 6 | Very simple example rectangle: 7 | ```javascript 8 | const knitout = require('knitout'); 9 | let k = new knitout.Writer({carriers:["A", "B", "C"]}); 10 | 11 | k.in("B"); 12 | 13 | for (let n = 10; n >= 0; n -= 2) k.tuck("-", "f" + n, "B"); 14 | 15 | for (let n = 1; n <= 9; n += 2) k.tuck("+", "f" + n, "B"); 16 | 17 | for (let r = 0; r < 10; ++r) { 18 | for (let n = 10; n >= 0; --n) k.knit("-", "f" + n, "B"); 19 | for (let n = 0; n <= 10; ++n) k.knit("+", "f" + n, "B"); 20 | } 21 | 22 | k.out("B"); 23 | 24 | k.write("out.k"); //write to file 25 | k.write(); //print to console 26 | ``` 27 | 28 | When you create a ```Writer``` you are *required* to supply a "carriers" list in the options. 29 | 30 | 31 | In the table below: 32 | 33 | * `carriers` specify the yarn-carriers in use. These are specified as a string or an Array of strings. Multiple yarn carriers can be used to make one stitch ("plating"). 34 | 35 | * `direction` specifies the direction in which the operation is performed. Legal values include **"+"** indicating increasing needle number direction or **"-"** indicating decreasing needle number direction. 36 | 37 | * `bed+needle` is an alpha-numeric value that specifies the bed and needle number. 38 | Legal values for `bed` are (**f**)ront, (**b**)ack, (**f**)ront(**s**)lider, (**b**)ack(**s**)lider. 39 | Needle is a Number value within the range supported by the machine. 40 | "fs10", for example, specifies front-bed slider location 10, "b20" specifies back-bed needle 20. 41 | The front-end also allows specifying bed needles as `{bed:"f", needle:5}`, `["f",5]`, or as `"f", 5`. 42 | 43 | All knitout opcodes are supported as a front-end function. Currently, the frontend supports: 44 | 45 | Function | Arguments | Example | Description 46 | --- | --- | --- | --- 47 | addHeader | `name`(String),`value`(String) | addHeader('Machine', 'SWGXYZ')| Add header information as name,value pairs. 48 | in | `carriers` | in("5") | Bring in yarn carriers 49 | inhook | `carriers` | inhook("B") | Bring in yarn carriers using the yarn inserting hook 50 | releasehook | `carriers` | releasehook("5") | Release the yarn inserting hook used to bring in the given yarn carriers 51 | out | `carriers` | out("6") | Take out yarn carrier 52 | outhook | `carriers` | outhook("5") | Take out yarn carrier with yarn inserting hook 53 | stitch | `before`(Number) `after`(Number) | stitch(25,40) | Before forming the loop, pull needle by `before` machine units, after forming the loop by `after` machine units. *Not well-supported by the back-end currently*. 54 | stitchNumber|`index`(Number) | stitchNumber(5) | Explicit function for using stitch number extension that reads stitch values at `index` from a table. See [extensions](https://textiles-lab.github.io/knitout/extensions.html) for details. 55 | fabricPresser|`mode`(String) | fabricPresser('auto') | Explicit function for using fabric presser extension. Valid modes include 'auto', 'on', 'off'. See [extensions](https://textiles-lab.github.io/knitout/extensions.html) for details. 56 | rack | `rack value`(Number) | rack(1) | Translate the back bed relative to the front bed by `rack value` needle units. Fractional values are legal and may be supported by the machine. 57 | tuck | `direction`,`bed+needle`,`carriers` | tuck("+","f10","B") | Tuck on `bed` at `needle` using `carriers` in `direction` direction. 58 | knit | `direction`,`bed+needle`,`carriers` | knit("+","f10","B") | Knit on `bed` at `needle` using `carriers` in `direction` direction. 59 | xfer | `from bed+needle`,`to bed+needle` | xfer("f10","b10") | Transfer loops from `from bed` at `needle` to `to bed` at `needle`. 60 | miss | `direction`,`bed+needle`,`carriers` | miss("+","f10","B") | Miss on `bed` at `needle` using `carriers` in `direction` direction i.e., perform carrier motion without knitting 61 | split| `direction`,`from bed+needle`,`to bed+needle`, `carriers` | split("+","f10", "b10", "B") | Pull a loop from `from bed+needle` and transfer old loops to `to bed+needle` in `direction` using `carriers`. 62 | drop| `bed+needle`| drop("f10") | Drop loops from `bed+needle`. 63 | amiss| `bed+needle`| amiss("f10") | Tuck operation at `bed+needle` without using yarn. 64 | pause| None | pause() | Pause machine when instruction is encountered 65 | comment| String | comment("This is a \n multi-line comment") | Insert comments into knitout file 66 | addRawOperation| String | addRawOperation("your knitout inst string") | Escape hatch to directly inject knitout code 67 | 68 | 69 | See [knitout specification](https://textiles-lab.github.io/knitout/knitout.html) for further details. 70 | 71 | See hello_world.js and sample.js for example usage. See carriers.js for additional carrier examples. 72 | -------------------------------------------------------------------------------- /knitout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let machine = null; //can set this as machine header value (if provided), and use it to warn about unsupported extensions 4 | // basic writer 5 | var Writer = function(opts){ 6 | //public data: 7 | this.carriers = {}; //names of all currently active carriers 8 | this.needles = {}; //names of all currently-holding-loops needles 9 | this.racking = 0; //current racking value 10 | 11 | //private data: 12 | this._carriers = []; //array of carrier names, front-to-back order 13 | this._operations = []; //array of operations, stored as strings 14 | this._headers = []; //array of headers. stored as strings 15 | 16 | 17 | //fill '_carriers' array from opts.carriers: 18 | if (typeof(opts) === 'undefined' || !('carriers' in opts)) { 19 | console.warn("WARNING: options object passed to knitout.Writer does not contain a 'carriers' member. Will assume a default carrier layout (a single carrier named \"A\")"); 20 | this._carriers = ["A"]; 21 | } else { 22 | if (!Array.isArray(opts.carriers)) throw new Error("opts.carriers should be an array of carrier names"); 23 | opts.carriers.forEach((name) => { 24 | if (!(typeof(name) === 'string' && name.indexOf(' ') === -1)) { 25 | throw new Error("Carrier names must be strings that do not contain the space character (' ')."); 26 | } 27 | }); 28 | this._carriers = opts.carriers.slice(); 29 | } 30 | //build a 'carriers' header from the '_carriers' list: 31 | this._headers.push(";;Carriers: " + this._carriers.join(" ")); 32 | }; 33 | 34 | // function that queues header information to header list 35 | Writer.prototype.addHeader = function (name, value) { 36 | if (name === undefined || value === undefined) { 37 | throw new Error("Writer.addHeader should be called with a name and a value"); 38 | } 39 | 40 | if (!(typeof(name) === 'string' && name.indexOf(': ') === -1)) { 41 | throw new Error("Header names must be strings that don't contain the sequence ': '"); 42 | } 43 | if (!(typeof(value) === 'string' && value.indexOf('\n') === -1)) { 44 | throw new Error("Header values must be strings that do not contain the LF character ('\\n')."); 45 | } 46 | 47 | //Check for valid headers: 48 | if (name === "Carriers") { 49 | throw new Error("Writer.addHeader can't set Carriers header (use the 'carriers' option when creating the writer instead)."); 50 | } else if (name === "Machine") { 51 | machine = value; 52 | //no restrictions on value 53 | } else if (name === "Gauge") { 54 | if ((typeof(value) !== 'string' && !/^[0-9 ]+$/.test(value))) throw new Error(`Value of 'Gauge' header must be a string representing a number. Provided value: '${value}' is not valid.`); 55 | } else if (name === "Position") { 56 | let supported_positions = ['Left', 'Center', 'Right', 'Keep']; 57 | if (!supported_positions.includes(value)) throw new Error(`'Position' header must have one of the following values: ${supported_positions.join(', ')}. Provided value: '${value}' is not valid.`); 58 | } else if (name.startsWith("Yarn-")) { 59 | //check for valid carrier name, warn otherwise 60 | let carrierName = name.substr(5); 61 | if (this._carriers.indexOf(carrierName) === -1) { 62 | console.warn("Warning: header '" + name + "' mentions a carrier that isn't in the carriers list."); 63 | } 64 | } else if (name.startsWith('X-')) { 65 | //all extension header values are okay! 66 | } else { 67 | console.warn("Warning: header name '" + name + "' not recognized; header will still be written."); 68 | } 69 | this._headers.push(';;' + name + ': ' + value); 70 | }; 71 | 72 | // escape hatch to dump your custom instruction to knitout 73 | // if you know what you are doing 74 | Writer.prototype.addRawOperation = function( operation ){ 75 | console.warn("Warning: operation added to list as-is, no error checking performed."); 76 | this._operations.push(operation); 77 | }; 78 | 79 | //helpers to extract parameters from argument arrays: 80 | // (these remove the extracted arguments from the start of the array and throw on errors) 81 | 82 | //shiftDirection interprets the first element of 'args' as a direction, and throws on error: 83 | // returns '+' or '-'. 84 | function shiftDirection(args) { 85 | console.assert(Array.isArray(args)); 86 | if (args.length === 0) { 87 | throw new Error("Direction missing."); 88 | } 89 | if (!(args[0] === '+' || args[0] === '-')) { 90 | throw new Error("Direction should be '+' or '-'."); 91 | } 92 | let dir = args.shift(); 93 | return dir; 94 | } 95 | 96 | //shiftBedNeedle interprets the first one or two arguments of 'args' as a bed+needle number, and throws on error: 97 | // returns a {bed:, needle:} object. 98 | 99 | const BedNeedleRegex = /^([fb]s?)(-?\d+)$/; 100 | const BedRegex = /^[fb]s?$/; 101 | function shiftBedNeedle(args) { 102 | console.assert(Array.isArray(args)); 103 | if (args.length === 0) { 104 | throw new Error("Needle missing."); 105 | } 106 | let bed, needle; 107 | //case: bed string, needle number: 108 | if (typeof(args[0]) === 'string' && BedRegex.test(args[0])) { 109 | if (args.length < 2 || !Number.isInteger(args[1])) { 110 | throw new Error("Expecting bed name to be followed by a needle number."); 111 | } 112 | bed = args.shift(); 113 | needle = args.shift(); 114 | //case: single string "f12", "b-2", "bs66": 115 | } else if (typeof(args[0]) === 'string') { 116 | let m = args[0].match(BedNeedleRegex); 117 | if (m === null) { 118 | throw new Error("String '" + args[0] + "' does not look like a compound bed+needle string."); 119 | } 120 | bed = m[1]; 121 | needle = parseInt(m[2]); 122 | args.shift(); 123 | //case: two-member array ["fs", 2] 124 | } else if (Array.isArray(args[0])) { 125 | if (!( args[0].length === 2 && typeof(args[0][0]) === 'string' && BedRegex.test(args[0][0]) && Number.isInteger(args[0][1]) )) { 126 | throw new Error("Bed+needle array should look like [\"f\", 12]."); 127 | } 128 | bed = args[0][0]; 129 | needle = args[0][1]; 130 | args.shift(); 131 | //case: object {bed:"fs", needle:5} 132 | } else if (typeof(args[0]) === 'object') { 133 | if (!( 'bed' in args[0] && typeof(args[0].bed) === 'string' && BedRegex.test(args[0].bed) )) { 134 | throw new Error("Bed+needle object should have a 'bed' member string naming the bed."); 135 | } 136 | if (!( 'needle' in args[0] && Number.isInteger(args[0].needle) )) { 137 | throw new Error("Bed+needle object should have a 'needle' member integer."); 138 | } 139 | bed = args[0].bed; 140 | needle = args[0].needle; 141 | args.shift(); 142 | } else { 143 | throw new Error("Expecting bed+needle as name+number (\"fs\", 6), string (\"b-2\"), array ([\"f\", 6]), or object ({bed:\"bs\", needle:12}). Got '" + JSON.stringify(args) + "'"); 144 | } 145 | return {bed:bed, needle:needle}; 146 | } 147 | 148 | //shiftCarrierSet interprets the remaining contents of 'args' as an array of carrier names, and throws on error: 149 | // returns an array of carrier names, e.g., ["C", "A"]. 150 | function shiftCarrierSet(args, carrierNames) { 151 | let carrierSet = []; 152 | //carrier set as array, e.g., knit(..., ["A", "B"]): 153 | if (args.length === 1 && Array.isArray(args[0])) { 154 | carrierSet = args.shift().slice(); 155 | } else { 156 | //carrier set as parameters, e.g., knit(..., "A", "B"); 157 | carrierSet = args.splice(0,args.length).slice(); 158 | } 159 | 160 | // slightly ugly handling of various ways of typeing "A B", "A, B" 161 | carrierSet.forEach(function(name, idx){ 162 | let space_split = name.split(" "); 163 | let first = true; 164 | space_split.forEach(function(s, sidx){ 165 | if(s == '') return; 166 | if(first){ 167 | carrierSet[idx] = s; 168 | first = false; 169 | } 170 | else carrierSet.push(s); 171 | }); 172 | }); 173 | 174 | carrierSet.forEach(function(name, idx){ 175 | let comma_split = name.split(","); 176 | let first = true; 177 | comma_split.forEach(function(s, sidx){ 178 | if(s =='') return; 179 | if(first) { 180 | carrierSet[idx] = s; 181 | first = false; 182 | } 183 | else carrierSet.push(s); 184 | }); 185 | 186 | }); 187 | 188 | 189 | carrierSet.forEach(function(name){ 190 | if (carrierNames.indexOf(name) === -1) { 191 | throw new Error("Invalid carrier name '" + name + "'"); 192 | } 193 | }); 194 | 195 | return carrierSet; 196 | } 197 | 198 | Writer.prototype.in = function(...args){ 199 | 200 | let cs = shiftCarrierSet(args, this._carriers); 201 | if (cs.length === 0) { 202 | throw new Error("It doesn't make sense to 'in' on an empty carrier set."); 203 | } 204 | cs.forEach(function(cn){ 205 | if (cn in this.carriers) { 206 | throw new Error("Carrier '" + cn + "' is already in."); 207 | } 208 | this.carriers[cn] = {hook:false}; 209 | }, this); 210 | 211 | this._operations.push('in ' + cs.join(' ')); 212 | 213 | }; 214 | 215 | Writer.prototype.inhook = function(...args){ 216 | 217 | let cs = shiftCarrierSet(args, this._carriers); 218 | if (cs.length === 0) { 219 | throw new Error("It doesn't make sense to 'inhook' on an empty carrier set."); 220 | } 221 | cs.forEach(function(cn){ 222 | if (cn in this.carriers) { 223 | throw new Error("Carrier '" + cn + "' is already in."); 224 | } 225 | this.carriers[cn] = {hook:true}; 226 | }, this); 227 | 228 | this._operations.push('inhook ' + cs.join(' ')); 229 | 230 | }; 231 | 232 | 233 | Writer.prototype.releasehook = function(...args){ 234 | 235 | let cs = shiftCarrierSet(args, this._carriers); 236 | if (cs.length === 0) { 237 | throw new Error("It doesn't make sense to 'releasehook' on an empty carrier set."); 238 | } 239 | cs.forEach(function(cn){ 240 | if (!(cn in this.carriers)) { 241 | throw new Error("Carrier '" + cn + "' isn't in."); 242 | } 243 | if (!this.carriers[cn].hook) { 244 | throw new Error("Carrier '" + cn + "' isn't in the hook."); 245 | } 246 | this.carriers[cn].hook = false; 247 | }, this); 248 | 249 | this._operations.push('releasehook ' + cs.join(' ')); 250 | 251 | }; 252 | 253 | Writer.prototype.out = function(...args){ 254 | 255 | let cs = shiftCarrierSet(args, this._carriers); 256 | if (cs.length === 0) { 257 | throw new Error("It doesn't make sense to 'out' on an empty carrier set."); 258 | } 259 | cs.forEach(function(cn){ 260 | if (!(cn in this.carriers)) { 261 | throw new Error("Carrier '" + cn + "' isn't in."); 262 | } 263 | delete this.carriers[cn]; 264 | }, this); 265 | 266 | this._operations.push('out ' + cs.join(' ')); 267 | 268 | }; 269 | 270 | Writer.prototype.outhook = function(...args){ 271 | 272 | let cs = shiftCarrierSet(args, this._carriers); 273 | if (cs.length === 0) { 274 | throw new Error("It doesn't make sense to 'outhook' on an empty carrier set."); 275 | } 276 | cs.forEach(function(cn){ 277 | if (!(cn in this.carriers)) { 278 | throw new Error("Carrier '" + cn + "' isn't in."); 279 | } 280 | delete this.carriers[cn]; 281 | }, this); 282 | 283 | this._operations.push('outhook ' + cs.join(' ')); 284 | }; 285 | 286 | function isFiniteNumber( n ) { 287 | if (typeof(n) === 'number' && Number.isFinite(n) && !Number.isNaN(n)) return true; 288 | return false; 289 | } 290 | 291 | Writer.prototype.stitch = function(before, after) { 292 | if (!(isFiniteNumber(before) && isFiniteNumber(after))) { 293 | throw new Error("Stitch L and T values must be finite numbers."); 294 | } 295 | 296 | this._operations.push('stitch ' + before.toString() + ' ' + after.toString()); 297 | }; 298 | 299 | //throw warning if ;;Machine: header is included & machine doesn't support extension 300 | function machineSupport(extension, supported) { 301 | if (!machine.toUpperCase().includes(supported)) console.warn(`Warning: ${extension} is not supported on ${machine}. Including it anyway.`); 302 | } 303 | 304 | // --- extensions --- 305 | Writer.prototype.stitchNumber = function (stitchNumber) { 306 | if (!(Number.isInteger(stitchNumber) && stitchNumber >= 0)) { 307 | throw new Error("Stitch numbers are non-negative integer values."); 308 | } 309 | 310 | this._operations.push('x-stitch-number ' + stitchNumber.toString()); 311 | }; 312 | 313 | Writer.prototype.fabricPresser = function (presserMode) { 314 | machineSupport('presser mode', 'SWG'); 315 | if(presserMode === 'auto'){ 316 | this._operations.push('x-presser-mode auto'); 317 | } 318 | else if(presserMode === 'on'){ 319 | this._operations.push('x-presser-mode on'); 320 | } 321 | else if(presserMode === 'off'){ 322 | this._operations.push('x-presser-mode off'); 323 | } 324 | else{ 325 | console.warn('Ignoring presser mode extension, unknown mode ' + presserMode + '. Valid modes: on, off, auto'); 326 | } 327 | } 328 | 329 | Writer.prototype.visColor = function (hex, carrier) { 330 | let warning = false; 331 | if (arguments.length !== 2) { 332 | warning = true; 333 | console.warn(`Ignoring vis color extension, since it is meant to take 2 parameters: 1) #hexColorCode and 2) carrierNumber.`); 334 | } 335 | if (hex.charAt(0) !== '#') { 336 | warning = true; 337 | console.warn(`Ignoring vis color extension, since the first arg is meant to be a hex code to assign the given carrier. Expected e.g. #FF0000`); 338 | } 339 | if (this._carriers.indexOf(carrier) === -1) { 340 | warning = true; 341 | console.warn(`Ignoring vis color extension, since the second arg is meant to be the carrier number to which you are assigning the color. ${carrier} is not listed in the 'Carriers' header.`); 342 | } 343 | if (!warning) this._operations.push(`x-vis-color ${hex} ${carrier}`); 344 | } 345 | 346 | Writer.prototype.speedNumber = function (value) { 347 | //TODO: check to make sure it's within the accepted range 348 | if (!(Number.isInteger(value) && value >= 0)) { 349 | console.warn(`Ignoring speed number extension, since provided value: ${value} is not a non-negative integer.`); 350 | } else this._operations.push(`x-speed-number ${value}`); 351 | }; 352 | 353 | Writer.prototype.rollerAdvance = function (value) { 354 | machineSupport('roller advance', 'KNITERATE'); 355 | //TODO: check to make sure it's within the accepted range 356 | if (!Number.isInteger(value)) { 357 | console.warn(`Ignoring roller advance extension, since provided value: ${value} is not an integer.`); 358 | } else this._operations.push(`x-speed-number ${value}`); 359 | let warning = false; 360 | if (!warning) this._operations.push(`x-roller-advance ${value}`); 361 | }; 362 | 363 | Writer.prototype.addRollerAdvance = function (value) { 364 | machineSupport('add roller advance', 'KNITERATE'); 365 | //TODO: check to make sure it's within the accepted range 366 | if (!Number.isInteger(value)) { 367 | console.warn(`Ignoring add roller advance extension, since provided value: ${value} is not an integer.`); 368 | } else this._operations.push(`x-add-roller-advance ${value}`); 369 | }; 370 | 371 | Writer.prototype.carrierSpacing = function (value) { 372 | machineSupport('carrier spacing', 'KNITERATE'); 373 | if (!(Number.isInteger(value) && value > 0)) { 374 | console.warn(`Ignoring carrier spacing extension, since provided value: ${value} is not a positive integer.`); 375 | } else this._operations.push(`x-carrier-spacing ${value}`); 376 | }; 377 | 378 | Writer.prototype.carrierStoppingDistance = function (value) { 379 | machineSupport('carrier stopping distance', 'KNITERATE'); 380 | if (!(Number.isInteger(value) && value > 0)) { 381 | console.warn(`Ignoring carrier stopping distance extension, since provided value: ${value} is not a positive integer.`); 382 | } else this._operations.push(`x-carrier-stopping-distance ${value}`); 383 | }; 384 | 385 | // --- operations ---// 386 | Writer.prototype.rack = function(rack) { 387 | 388 | if (!(isFiniteNumber(rack))) { 389 | throw new Error("Racking values must be finite numbers."); 390 | } 391 | 392 | this.racking = rack; 393 | 394 | this._operations.push('rack ' + rack.toString()); 395 | }; 396 | 397 | Writer.prototype.knit = function(...args) { 398 | let dir = shiftDirection(args); 399 | let bn = shiftBedNeedle(args); 400 | let cs = shiftCarrierSet(args, this._carriers); 401 | 402 | if (cs.length > 0) { 403 | this.needles[bn.bed + bn.needle.toString()] = true; 404 | } else { 405 | delete this.needles[bn.bed + bn.needle.toString()]; 406 | } 407 | 408 | this._operations.push('knit ' + dir + ' ' + bn.bed + bn.needle.toString() + ' ' + cs.join(' ')); 409 | }; 410 | 411 | Writer.prototype.tuck = function(...args) { 412 | let dir = shiftDirection(args); 413 | let bn = shiftBedNeedle(args); 414 | let cs = shiftCarrierSet(args, this._carriers); 415 | 416 | this.needles[bn.bed + bn.needle.toString()] = true; 417 | 418 | this._operations.push('tuck ' + dir + ' ' + bn.bed + bn.needle.toString() + ' ' + cs.join(' ')); 419 | }; 420 | 421 | Writer.prototype.split = function(...args) { 422 | let dir = shiftDirection(args); 423 | let from = shiftBedNeedle(args); 424 | let to = shiftBedNeedle(args); 425 | let cs = shiftCarrierSet(args, this._carriers); 426 | 427 | if ((from.bed + from.needle.toString()) in this.needles) { 428 | this.needles[to.bed + to.needle.toString()] = true; 429 | delete this.needles[from.bed + from.needle.toString()]; 430 | } 431 | if (cs.length > 0) { 432 | this.needles[from.bed + from.needle.toString()] = true; 433 | } 434 | 435 | this._operations.push('split ' + dir + ' ' + from.bed + from.needle.toString() + ' ' + to.bed + to.needle.toString() + ' ' + cs.join(' ')); 436 | }; 437 | 438 | Writer.prototype.miss = function(...args) { 439 | let dir = shiftDirection(args); 440 | let bn = shiftBedNeedle(args); 441 | let cs = shiftCarrierSet(args, this._carriers); 442 | 443 | if (cs.length === 0) { 444 | throw new Error("It doesn't make sense to miss with no carriers."); 445 | } 446 | 447 | this._operations.push('miss ' + dir + ' ' + bn.bed + bn.needle.toString() + ' ' + cs.join(' ')); 448 | }; 449 | 450 | 451 | // drop -> knit without yarn, but supported in knitout 452 | Writer.prototype.drop = function(...args) { 453 | let bn = shiftBedNeedle(args); 454 | 455 | if (args.length !== 0) { 456 | throw new Error("drop only takes a bed+needle"); 457 | } 458 | 459 | delete this.needles[bn.bed + bn.needle.toString()]; 460 | 461 | this._operations.push('drop ' + bn.bed + bn.needle.toString()); 462 | }; 463 | 464 | // amiss -> tuck without yarn, but supported in knitout 465 | Writer.prototype.amiss = function(...args) { 466 | let bn = shiftBedNeedle(args); 467 | 468 | if (args.length !== 0) { 469 | throw new Error("amiss only takes a bed+needle"); 470 | } 471 | 472 | this._operations.push('amiss ' + bn.bed + bn.needle.toString()); 473 | }; 474 | 475 | // xfer -> split without yarn, but supported in knitout 476 | Writer.prototype.xfer = function(...args) { 477 | 478 | let from = shiftBedNeedle(args); 479 | let to = shiftBedNeedle(args); 480 | 481 | if (args.length !== 0) { 482 | throw new Error("xfer only takes two bed+needles"); 483 | } 484 | 485 | if ((from.bed + from.needle.toString()) in this.needles) { 486 | this.needles[to.bed + to.needle.toString()] = true; 487 | delete this.needles[from.bed + from.needle.toString()]; 488 | } 489 | 490 | this._operations.push('xfer ' + from.bed + from.needle.toString() + ' ' + to.bed + to.needle.toString()); 491 | }; 492 | 493 | // add comments to knitout 494 | Writer.prototype.comment = function( str ){ 495 | 496 | let multi = str.split('\n'); 497 | multi.forEach(function(entry){ 498 | // cannot add header comments with comment 499 | while(entry.startsWith(';')){ 500 | console.warn('Warning: comment starts with ; use addHeader for adding header comments.'); 501 | entry = entry.substr(1, entry.length); 502 | } 503 | this._operations.push(';' + entry.toString()); 504 | }, this); 505 | }; 506 | 507 | Writer.prototype.pause = function(comment){ 508 | // deals with multi-line comments 509 | this.comment(comment); 510 | this._operations.push('pause'); 511 | }; 512 | 513 | Writer.prototype.write = function(filename){ 514 | let version = ';!knitout-2'; 515 | let content = version + '\n' + 516 | this._headers.join('\n') + '\n' + 517 | this._operations.join('\n'); 518 | if (typeof(filename) === 'undefined') { 519 | console.warn("filename not passed to Writer.write; writing to stdout."); 520 | console.log(content); 521 | } else { 522 | try{ 523 | let fs = require('fs'); 524 | fs.writeFileSync(filename, content + '\n'); //default is utf8 525 | } 526 | catch(e){ 527 | console.warn("Can't load 'fs'. Did not write file."); 528 | } 529 | } 530 | return content; 531 | }; 532 | 533 | // browser-compatibility 534 | if(typeof(module) !== 'undefined'){ 535 | module.exports.Writer = Writer; 536 | } 537 | 538 | 539 | --------------------------------------------------------------------------------