├── test ├── cli │ ├── bad_input │ │ ├── bad.yaml │ │ ├── error │ │ └── command │ ├── missing_input │ │ ├── command │ │ └── error │ ├── minimal │ │ ├── command │ │ ├── reference │ │ │ ├── points │ │ │ │ ├── units.yaml │ │ │ │ ├── demo.svg │ │ │ │ ├── points.yaml │ │ │ │ ├── demo.dxf │ │ │ │ └── demo.yaml │ │ │ └── source │ │ │ │ ├── raw.txt │ │ │ │ └── canonical.yaml │ │ └── log │ ├── medium │ │ ├── command │ │ ├── log │ │ └── reference │ │ │ ├── outlines │ │ │ └── export.dxf │ │ │ └── pcbs │ │ │ └── export.kicad_pcb │ ├── nonexistent_input │ │ ├── command │ │ └── error │ └── big │ │ ├── command │ │ ├── reference │ │ ├── points │ │ │ ├── units.yaml │ │ │ ├── demo.svg │ │ │ ├── points.yaml │ │ │ ├── demo.dxf │ │ │ └── demo.yaml │ │ ├── outlines │ │ │ ├── _export.svg │ │ │ ├── export.svg │ │ │ ├── _export.dxf │ │ │ ├── export.dxf │ │ │ ├── _export.yaml │ │ │ └── export.yaml │ │ ├── source │ │ │ ├── raw.txt │ │ │ └── canonical.yaml │ │ ├── cases │ │ │ ├── export.jscad │ │ │ ├── export.stl │ │ │ ├── _export.stl │ │ │ └── _export.jscad │ │ └── pcbs │ │ │ ├── _export.kicad_pcb │ │ │ └── export.kicad_pcb │ │ └── log ├── points │ ├── default.yaml │ ├── units___units.json │ ├── basic_2x2.yaml │ ├── units.yaml │ ├── adjustments.yaml │ ├── overrides.yaml │ ├── default___points.json │ ├── basic_2x2___points.json │ └── adjustments___points.json ├── fixtures │ ├── minimal.yaml │ ├── medium.yaml │ ├── big.yaml │ └── atreus_kle.json ├── helpers │ ├── register.js │ ├── point.js │ └── mock_footprints.js ├── pcbs │ ├── references.yaml │ ├── mock_footprints.yaml │ └── references___pcbs.json ├── cases │ ├── 001_cube.yaml │ └── 001_cube___cases_cube_stl.stl ├── outlines │ ├── fillet.yaml │ ├── basic.yaml │ ├── circles.yaml │ ├── rectangles.yaml │ ├── polygons.yaml │ ├── gluing.yaml │ ├── affect_mirror.yaml │ ├── gluing___outlines_optout_dxf.dxf │ ├── circles___outlines_outline_dxf.dxf │ ├── fillet___outlines_fillet_dxf.dxf │ ├── polygons___outlines_outline_dxf.dxf │ ├── basic___outlines_outline_dxf.dxf │ ├── rectangles___outlines_outline_dxf.dxf │ └── affect_mirror___outlines_test_dxf.dxf └── unit │ ├── operation.js │ ├── units.js │ ├── prepare.js │ ├── assert.js │ ├── anchor.js │ ├── point.js │ ├── interface.js │ └── utils.js ├── .npmrc ├── src ├── footprints │ ├── bat.js │ ├── via.js │ ├── index.js │ ├── jumper.js │ ├── oled.js │ ├── alps.js │ ├── omron.js │ ├── b3u1000p.js │ ├── pad.js │ ├── jstph.js │ ├── button.js │ ├── diode.js │ ├── rgb.js │ ├── slider.js │ ├── trrs.js │ ├── pcm12.js │ ├── choc.js │ ├── mx.js │ ├── scrollwheel.js │ ├── rotary.js │ └── chocmini.js ├── units.js ├── operation.js ├── point.js ├── utils.js ├── io.js ├── assert.js ├── kle.js ├── anchor.js ├── cli.js ├── ergogen.js └── prepare.js ├── readme.md ├── rollup.config.js ├── license.md ├── package.json ├── .gitignore ├── input ├── config-1april.yaml ├── config-flipper.yaml ├── config-flame.yaml └── config-card.yaml └── roadmap.md /test/cli/bad_input/bad.yaml: -------------------------------------------------------------------------------- 1 | 'bad input' -------------------------------------------------------------------------------- /test/cli/missing_input/command: -------------------------------------------------------------------------------- 1 | node src/cli.js 2 | -------------------------------------------------------------------------------- /test/points/default.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: {} -------------------------------------------------------------------------------- /test/cli/minimal/command: -------------------------------------------------------------------------------- 1 | node src/cli.js test/fixtures/minimal.yaml -------------------------------------------------------------------------------- /test/cli/bad_input/error: -------------------------------------------------------------------------------- 1 | Error: Input doesn't resolve into an object! 2 | -------------------------------------------------------------------------------- /test/cli/medium/command: -------------------------------------------------------------------------------- 1 | node src/cli.js test/fixtures/medium.yaml 2 | -------------------------------------------------------------------------------- /test/cli/missing_input/error: -------------------------------------------------------------------------------- 1 | Usage: ergogen [options] 2 | -------------------------------------------------------------------------------- /test/cli/nonexistent_input/command: -------------------------------------------------------------------------------- 1 | node src/cli.js nonexistent.yaml 2 | -------------------------------------------------------------------------------- /test/cli/bad_input/command: -------------------------------------------------------------------------------- 1 | node src/cli.js test/cli/bad_input/bad.yaml 2 | -------------------------------------------------------------------------------- /test/cli/big/command: -------------------------------------------------------------------------------- 1 | node src/cli.js test/fixtures/big.yaml --debug --clean 2 | -------------------------------------------------------------------------------- /test/fixtures/minimal.yaml: -------------------------------------------------------------------------------- 1 | points.zones.matrix: 2 | columns.col: {} 3 | rows.row: {} -------------------------------------------------------------------------------- /test/cli/minimal/reference/points/units.yaml: -------------------------------------------------------------------------------- 1 | U: 19.05 2 | u: 19 3 | cx: 18 4 | cy: 17 5 | -------------------------------------------------------------------------------- /test/cli/big/reference/points/units.yaml: -------------------------------------------------------------------------------- 1 | U: 19.05 2 | u: 19 3 | cx: 18 4 | cy: 17 5 | a: 47 6 | -------------------------------------------------------------------------------- /test/cli/minimal/reference/source/raw.txt: -------------------------------------------------------------------------------- 1 | points.zones.matrix: 2 | columns.col: {} 3 | rows.row: {} -------------------------------------------------------------------------------- /test/points/units___units.json: -------------------------------------------------------------------------------- 1 | { 2 | "U": 19.05, 3 | "u": 19, 4 | "cx": 18, 5 | "cy": 17, 6 | "a": 10, 7 | "b": 15 8 | } 9 | -------------------------------------------------------------------------------- /test/cli/nonexistent_input/error: -------------------------------------------------------------------------------- 1 | Could not read config file "nonexistent.yaml": Error: ENOENT: no such file or directory, open 'nonexistent.yaml' 2 | -------------------------------------------------------------------------------- /test/points/basic_2x2.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | left: 6 | right: 7 | rows: 8 | bottom: 9 | top: -------------------------------------------------------------------------------- /test/helpers/register.js: -------------------------------------------------------------------------------- 1 | global.chai = require('chai') 2 | global.chai.use(require('chai-as-promised')) 3 | global.expect = global.chai.expect 4 | global.should = global.chai.should() -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # Suppress deprecation warnings from the old JSCAD libs 2 | # And either way, end users don't need to see warnings 3 | # Can be deleted locally for development purposes... 4 | loglevel=error -------------------------------------------------------------------------------- /test/cli/minimal/reference/source/canonical.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | col: {} 6 | rows: 7 | row: {} 8 | -------------------------------------------------------------------------------- /test/points/units.yaml: -------------------------------------------------------------------------------- 1 | units: 2 | a: cy - 7 3 | variables: 4 | b: a * 1.5 5 | points: 6 | zones: 7 | matrix: 8 | columns: 9 | col: 10 | rows: 11 | row: -------------------------------------------------------------------------------- /test/fixtures/medium.yaml: -------------------------------------------------------------------------------- 1 | points.zones.matrix: 2 | columns.col: {} 3 | outlines.exports: 4 | export: 5 | - type: 'keys' 6 | side: 'left' 7 | size: 18 8 | pcbs: 9 | export: {} 10 | -------------------------------------------------------------------------------- /test/pcbs/references.yaml: -------------------------------------------------------------------------------- 1 | points.zones.matrix: 2 | columns.one: 3 | rows.only: 4 | pcbs: 5 | shown: 6 | references: true 7 | footprints: 8 | - type: references_test 9 | hidden: 10 | footprints: 11 | - type: references_test -------------------------------------------------------------------------------- /test/helpers/point.js: -------------------------------------------------------------------------------- 1 | exports.check = (point, expected=[]) => { 2 | point.x.should.equal(expected[0] || 0) 3 | point.y.should.equal(expected[1] || 0) 4 | point.r.should.equal(expected[2] || 0) 5 | point.meta.should.deep.equal(expected[3] || {}) 6 | } -------------------------------------------------------------------------------- /test/cli/medium/log: -------------------------------------------------------------------------------- 1 | Ergogen CLI 2 | 3 | Interpreting format: YAML 4 | Preprocessing input... 5 | Calculating variables... 6 | Parsing points... 7 | Generating outlines... 8 | Extruding cases... 9 | Scaffolding PCBs... 10 | Writing output to disk... 11 | Done. 12 | 13 | -------------------------------------------------------------------------------- /test/points/adjustments.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | left: 6 | right: 7 | stagger: 5 8 | spread: 25 9 | rotate: 5 10 | origin: [-9, -9] 11 | rows: 12 | bottom: 13 | top: -------------------------------------------------------------------------------- /test/cli/big/log: -------------------------------------------------------------------------------- 1 | Ergogen CLI (Debug Mode) 2 | 3 | Interpreting format: YAML 4 | Preprocessing input... 5 | Calculating variables... 6 | Parsing points... 7 | Generating outlines... 8 | Extruding cases... 9 | Scaffolding PCBs... 10 | Cleaning output folder... 11 | Writing output to disk... 12 | Done. 13 | 14 | -------------------------------------------------------------------------------- /test/cli/minimal/log: -------------------------------------------------------------------------------- 1 | Ergogen CLI 2 | 3 | Interpreting format: YAML 4 | Preprocessing input... 5 | Calculating variables... 6 | Parsing points... 7 | Generating outlines... 8 | Extruding cases... 9 | Scaffolding PCBs... 10 | Output would be empty, rerunning in debug mode... 11 | Writing output to disk... 12 | Done. 13 | 14 | -------------------------------------------------------------------------------- /test/points/overrides.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | left: 6 | middle: 7 | rows: 8 | top: 9 | right: 10 | stagger: u 11 | row_overrides: 12 | home: 13 | top: 14 | rows: 15 | bottom: 16 | home: -------------------------------------------------------------------------------- /test/cases/001_cube.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | arst: 4 | columns: 5 | c1: 6 | rows: 7 | r1: 8 | outlines: 9 | exports: 10 | square: 11 | - type: rectangle 12 | size: [5, 5] 13 | cases: 14 | cube: 15 | - name: square 16 | extrude: 5 -------------------------------------------------------------------------------- /test/cli/big/reference/points/demo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/cli/big/reference/outlines/_export.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/cli/big/reference/outlines/export.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/cli/minimal/reference/points/demo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/outlines/fillet.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | key: 3 | padding: cy 4 | bind: 0.1 5 | zones: 6 | matrix: 7 | columns: 8 | one: 9 | two: 10 | rows: 11 | bottom: 12 | top: 13 | outlines: 14 | exports: 15 | base: 16 | - type: keys 17 | side: left 18 | size: cy 19 | fillet: 20 | - type: outline 21 | name: base 22 | fillet: 2 -------------------------------------------------------------------------------- /test/fixtures/big.yaml: -------------------------------------------------------------------------------- 1 | units: 2 | a: 28 + u 3 | points.zones.matrix: 4 | columns.col: {} 5 | rows.row: {} 6 | outlines.exports: 7 | export: 8 | - type: 'keys' 9 | side: 'left' 10 | size: 18 11 | _export: 12 | - type: 'keys' 13 | side: 'left' 14 | size: 18 15 | cases: 16 | export: 17 | - name: 'export' 18 | extrude: 1 19 | _export: 20 | - name: 'export' 21 | extrude: 1 22 | pcbs: 23 | export: {} 24 | _export: {} 25 | -------------------------------------------------------------------------------- /test/cli/big/reference/source/raw.txt: -------------------------------------------------------------------------------- 1 | units: 2 | a: 28 + u 3 | points.zones.matrix: 4 | columns.col: {} 5 | rows.row: {} 6 | outlines.exports: 7 | export: 8 | - type: 'keys' 9 | side: 'left' 10 | size: 18 11 | _export: 12 | - type: 'keys' 13 | side: 'left' 14 | size: 18 15 | cases: 16 | export: 17 | - name: 'export' 18 | extrude: 1 19 | _export: 20 | - name: 'export' 21 | extrude: 1 22 | pcbs: 23 | export: {} 24 | _export: {} 25 | -------------------------------------------------------------------------------- /test/outlines/basic.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | left.key.bind: [,10,,] 6 | right.key.bind: [,,,10] 7 | rows: 8 | bottom.key.bind: [10,,,] 9 | top.key.bind: [,,10,] 10 | key: 11 | bind: [0, 0, 0, 0] 12 | outlines: 13 | exports: 14 | outline: 15 | main: 16 | type: keys 17 | side: left 18 | size: 20 19 | min: 20 | type: keys 21 | side: left 22 | bound: false 23 | size: 14 24 | operation: subtract -------------------------------------------------------------------------------- /src/footprints/bat.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | pos: 'pos', 4 | neg: 'neg' 5 | }, 6 | params: { 7 | class: 'PAD' // for Button 8 | }, 9 | body: p => ` 10 | 11 | (module lib:bat (layer F.Cu) (tstamp 5BF2CC94) 12 | ${p.at /* parametric position */} 13 | (pad 1 thru_hole circle (at 0 -0.75 0) (size 1 1) (drill 0.7) (layers *.Cu *.SilkS *.Mask) ${p.net.pos.str}) 14 | (pad 2 thru_hole circle (at 0 0.75 0) (size 1 1) (drill 0.7) (layers *.Cu *.SilkS *.Mask) ${p.net.neg.str}) 15 | ) 16 | 17 | ` 18 | } 19 | -------------------------------------------------------------------------------- /src/units.js: -------------------------------------------------------------------------------- 1 | const a = require('./assert') 2 | const prep = require('./prepare') 3 | 4 | const default_units = { 5 | U: 19.05, 6 | u: 19, 7 | cx: 18, 8 | cy: 17 9 | } 10 | 11 | exports.parse = (config = {}) => { 12 | const raw_units = prep.extend( 13 | default_units, 14 | a.sane(config.units || {}, 'units', 'object')(), 15 | a.sane(config.variables || {}, 'variables', 'object')() 16 | ) 17 | const units = {} 18 | for (const [key, val] of Object.entries(raw_units)) { 19 | units[key] = a.mathnum(val)(units) 20 | } 21 | return units 22 | } -------------------------------------------------------------------------------- /test/outlines/circles.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: {} 4 | mirror: 20 5 | outlines: 6 | exports: 7 | outline: 8 | main: 9 | type: keys 10 | side: both 11 | size: 20 12 | bound: false 13 | middle_circle: 14 | type: circle 15 | anchor: 16 | ref: 17 | - matrix_default_default 18 | - mirror_matrix_default_default 19 | radius: 15 20 | outside_circles: 21 | type: circle 22 | anchor: 23 | ref: matrix_default_default 24 | shift: [-10, 10] 25 | radius: 5 26 | mirror: true -------------------------------------------------------------------------------- /test/cli/big/reference/points/points.yaml: -------------------------------------------------------------------------------- 1 | matrix_col_row: 2 | x: 0 3 | 'y': 0 4 | r: 0 5 | meta: 6 | shift: 7 | - 0 8 | - 0 9 | rotate: 0 10 | padding: 19 11 | width: 1 12 | height: 1 13 | skip: false 14 | asym: both 15 | name: matrix_col_row 16 | colrow: col_row 17 | col: 18 | stagger: 0 19 | spread: 0 20 | rotate: 0 21 | origin: 22 | - 0 23 | - 0 24 | rows: {} 25 | key: {} 26 | name: col 27 | row: row 28 | -------------------------------------------------------------------------------- /test/cli/minimal/reference/points/points.yaml: -------------------------------------------------------------------------------- 1 | matrix_col_row: 2 | x: 0 3 | 'y': 0 4 | r: 0 5 | meta: 6 | shift: 7 | - 0 8 | - 0 9 | rotate: 0 10 | padding: 19 11 | width: 1 12 | height: 1 13 | skip: false 14 | asym: both 15 | name: matrix_col_row 16 | colrow: col_row 17 | col: 18 | stagger: 0 19 | spread: 0 20 | rotate: 0 21 | origin: 22 | - 0 23 | - 0 24 | rows: {} 25 | key: {} 26 | name: col 27 | row: row 28 | -------------------------------------------------------------------------------- /test/outlines/rectangles.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: {} 4 | mirror: 20 5 | outlines: 6 | exports: 7 | outline: 8 | main: 9 | type: keys 10 | side: both 11 | size: 20 12 | bound: false 13 | middle_rect: 14 | type: rectangle 15 | anchor: 16 | ref: 17 | - matrix_default_default 18 | - mirror_matrix_default_default 19 | shift: [-sx/2, 0] 20 | size: [20, 40] 21 | outside_rects: 22 | type: rectangle 23 | anchor: 24 | ref: matrix_default_default 25 | shift: [-15, 5] 26 | size: 10 27 | mirror: true -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Ergogen 2 | 3 | Ergogen is a keyboard generator that aims to provide a common configuration format to describe **ergonomic** 2D layouts, and generate automatic plates, cases, and (un-routed) PCBs for them. 4 | The project grew out of (and is an integral part of) the [Absolem keyboard](https://zealot.hu/absolem), and shares its [Discord server](https://discord.gg/nbKcAZB) as well. 5 | 6 | For usage and config information, please refer to the [docs](https://docs.ergogen.xyz). 7 | 8 | 9 | ## Contributions 10 | 11 | Feature ideas, documentation improvements, examples, tests, or pull requests welcome! 12 | Get in touch [on Discord](https://discord.gg/nbKcAZB), and we can definitely find something you can help with, if you'd like to. 13 | -------------------------------------------------------------------------------- /src/operation.js: -------------------------------------------------------------------------------- 1 | const op_prefix = exports.op_prefix = str => { 2 | const suffix = str.slice(1) 3 | if (str.startsWith('+')) return {name: suffix, operation: 'add'} 4 | if (str.startsWith('-')) return {name: suffix, operation: 'subtract'} 5 | if (str.startsWith('~')) return {name: suffix, operation: 'intersect'} 6 | if (str.startsWith('^')) return {name: suffix, operation: 'stack'} 7 | return {name: str, operation: 'add'} 8 | } 9 | 10 | exports.operation = (str, choices={}, order=Object.keys(choices)) => { 11 | let res = op_prefix(str) 12 | for (const key of order) { 13 | if (choices[key].includes(res.name)) { 14 | res.type = key 15 | break 16 | } 17 | } 18 | return res 19 | } -------------------------------------------------------------------------------- /src/footprints/via.js: -------------------------------------------------------------------------------- 1 | // Via 2 | // Nets 3 | // net: the net this via should be connected to 4 | 5 | module.exports = { 6 | nets: { 7 | net: undefined 8 | }, 9 | body: p => ` 10 | (module VIA-0.6mm (layer F.Cu) (tedit 591DBFB0) 11 | ${p.at /* parametric position */} 12 | ${'' /* footprint reference */} 13 | (fp_text reference REF** (at 0 1.4) (layer F.SilkS) hide (effects (font (size 1 1) (thickness 0.15)))) 14 | (fp_text value VIA-0.6mm (at 0 -1.4) (layer F.Fab) hide (effects (font (size 1 1) (thickness 0.15)))) 15 | 16 | ${'' /* via */} 17 | (pad 1 thru_hole circle (at 0 0) (size 0.6 0.6) (drill 0.3) (layers *.Cu) (zone_connect 2) ${p.net.net.str}) 18 | ) 19 | ` 20 | } -------------------------------------------------------------------------------- /test/cli/big/reference/source/canonical.yaml: -------------------------------------------------------------------------------- 1 | units: 2 | a: 28 + u 3 | points: 4 | zones: 5 | matrix: 6 | columns: 7 | col: {} 8 | rows: 9 | row: {} 10 | outlines: 11 | exports: 12 | export: 13 | - 14 | type: keys 15 | side: left 16 | size: 18 17 | _export: 18 | - 19 | type: keys 20 | side: left 21 | size: 18 22 | cases: 23 | export: 24 | - 25 | name: export 26 | extrude: 1 27 | _export: 28 | - 29 | name: export 30 | extrude: 1 31 | pcbs: 32 | export: {} 33 | _export: {} 34 | -------------------------------------------------------------------------------- /test/outlines/polygons.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: {} 4 | mirror: 20 5 | outlines: 6 | exports: 7 | outline: 8 | main: 9 | type: keys 10 | side: both 11 | size: 20 12 | bound: false 13 | middle_poly: 14 | type: polygon 15 | points: 16 | - ref: 17 | - matrix_default_default 18 | - mirror_matrix_default_default 19 | shift: [0, 20] 20 | - shift: [20, -40] 21 | - shift: [-40, 0] 22 | outside_polys: 23 | type: polygon 24 | points: 25 | - ref: matrix_default_default 26 | shift: [-10, 15] 27 | - shift: [5, -10] 28 | - shift: [-10, 0] 29 | mirror: true -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' 2 | import json from '@rollup/plugin-json' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | 5 | export default { 6 | input: 'src/ergogen.js', 7 | external: ['makerjs', 'js-yaml', 'mathjs', 'kle-serial', '@jscad/openjscad', 'semver'], 8 | output: { 9 | name: 'ergogen', 10 | file: 'dist/ergogen.js', 11 | format: 'umd', 12 | banner: `/*!\n * Ergogen v${pkg.version}\n * https://zealot.hu/ergogen\n */\n`, 13 | globals: { 14 | 'makerjs': 'makerjs', 15 | 'js-yaml': 'jsyaml', 16 | 'mathjs': 'math', 17 | 'kle-serial': 'kle', 18 | '@jscad/openjscad': 'myjscad', 19 | 'semver': 'semver' 20 | } 21 | }, 22 | plugins: [ 23 | json(), 24 | commonjs() 25 | ] 26 | } -------------------------------------------------------------------------------- /test/pcbs/mock_footprints.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | one: 6 | rows: 7 | only: 8 | outlines: 9 | exports: 10 | edge: 11 | - type: keys 12 | side: left 13 | size: [u, u] 14 | pcbs: 15 | main: 16 | outlines: 17 | edge: 18 | outline: edge 19 | footprints: 20 | trace: 21 | type: trace_test 22 | anchor: 23 | shift: [1, 1] 24 | rotate: 30 25 | zone: 26 | type: zone_test 27 | anchor: 28 | shift: [1, 1] 29 | rotate: 30 30 | dyn: 31 | type: dynamic_net_test 32 | anc: 33 | type: anchor_test 34 | anchors: 35 | end: 36 | ref: matrix_one_only 37 | shift: [10, 10] -------------------------------------------------------------------------------- /test/cli/big/reference/points/demo.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -9 53 | 20 54 | 9 55 | 11 56 | 9 57 | 21 58 | 9 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 9 65 | 20 66 | 9 67 | 11 68 | 9 69 | 21 70 | -9 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 9 77 | 20 78 | -9 79 | 11 80 | -9 81 | 21 82 | -9 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -9 89 | 20 90 | -9 91 | 11 92 | -9 93 | 21 94 | 9 95 | 0 96 | ENDSEC 97 | 0 98 | EOF -------------------------------------------------------------------------------- /test/cli/big/reference/outlines/_export.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -9 53 | 20 54 | -9 55 | 11 56 | 9 57 | 21 58 | -9 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 9 65 | 20 66 | -9 67 | 11 68 | 9 69 | 21 70 | 9 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 9 77 | 20 78 | 9 79 | 11 80 | -9 81 | 21 82 | 9 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -9 89 | 20 90 | 9 91 | 11 92 | -9 93 | 21 94 | -9 95 | 0 96 | ENDSEC 97 | 0 98 | EOF -------------------------------------------------------------------------------- /test/cli/big/reference/outlines/export.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -9 53 | 20 54 | -9 55 | 11 56 | 9 57 | 21 58 | -9 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 9 65 | 20 66 | -9 67 | 11 68 | 9 69 | 21 70 | 9 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 9 77 | 20 78 | 9 79 | 11 80 | -9 81 | 21 82 | 9 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -9 89 | 20 90 | 9 91 | 11 92 | -9 93 | 21 94 | -9 95 | 0 96 | ENDSEC 97 | 0 98 | EOF -------------------------------------------------------------------------------- /test/cli/medium/reference/outlines/export.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -9 53 | 20 54 | -9 55 | 11 56 | 9 57 | 21 58 | -9 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 9 65 | 20 66 | -9 67 | 11 68 | 9 69 | 21 70 | 9 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 9 77 | 20 78 | 9 79 | 11 80 | -9 81 | 21 82 | 9 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -9 89 | 20 90 | 9 91 | 11 92 | -9 93 | 21 94 | -9 95 | 0 96 | ENDSEC 97 | 0 98 | EOF -------------------------------------------------------------------------------- /test/cli/minimal/reference/points/demo.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -9 53 | 20 54 | 9 55 | 11 56 | 9 57 | 21 58 | 9 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 9 65 | 20 66 | 9 67 | 11 68 | 9 69 | 21 70 | -9 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 9 77 | 20 78 | -9 79 | 11 80 | -9 81 | 21 82 | -9 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -9 89 | 20 90 | -9 91 | 11 92 | -9 93 | 21 94 | 9 95 | 0 96 | ENDSEC 97 | 0 98 | EOF -------------------------------------------------------------------------------- /src/footprints/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | alps: require('./alps'), 3 | button: require('./button'), 4 | choc: require('./choc'), 5 | chocmini: require('./chocmini'), 6 | diode: require('./diode'), 7 | jstph: require('./jstph'), 8 | jumper: require('./jumper'), 9 | mx: require('./mx'), 10 | oled: require('./oled'), 11 | omron: require('./omron'), 12 | pad: require('./pad'), 13 | promicro: require('./promicro'), 14 | promicro_pretty: require('./promicro_pretty'), 15 | rgb: require('./rgb'), 16 | rotary: require('./rotary'), 17 | scrollwheel: require('./scrollwheel'), 18 | slider: require('./slider'), 19 | trrs: require('./trrs'), 20 | via: require('./via'), 21 | pcm12: require('./pcm12'), 22 | bat: require('./bat'), 23 | b3u1000p: require('./b3u1000p'), 24 | } 25 | -------------------------------------------------------------------------------- /test/points/default___points.json: -------------------------------------------------------------------------------- 1 | { 2 | "matrix_default_default": { 3 | "x": 0, 4 | "y": 0, 5 | "r": 0, 6 | "meta": { 7 | "shift": [ 8 | 0, 9 | 0 10 | ], 11 | "rotate": 0, 12 | "padding": 19, 13 | "width": 1, 14 | "height": 1, 15 | "skip": false, 16 | "asym": "both", 17 | "name": "matrix_default_default", 18 | "colrow": "default_default", 19 | "col": { 20 | "stagger": 0, 21 | "spread": 0, 22 | "rotate": 0, 23 | "origin": [ 24 | 0, 25 | 0 26 | ], 27 | "rows": {}, 28 | "key": {}, 29 | "name": "default" 30 | }, 31 | "row": "default" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/footprints/jumper.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | from: undefined, 4 | to: undefined 5 | }, 6 | params: { 7 | class: 'J', 8 | side: 'F' 9 | }, 10 | body: p => ` 11 | (module lib:Jumper (layer F.Cu) (tedit 5E1ADAC2) 12 | ${p.at /* parametric position */} 13 | 14 | ${'' /* footprint reference */} 15 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 16 | (fp_text value Jumper (at 0 -7.3) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15)))) 17 | 18 | ${'' /* pins */} 19 | (pad 1 smd rect (at -0.50038 0 ${p.rot}) (size 0.635 1.143) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) 20 | (clearance 0.1905) ${p.net.from.str}) 21 | (pad 2 smd rect (at 0.50038 0 ${p.rot}) (size 0.635 1.143) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) 22 | (clearance 0.1905) ${p.net.to.str})) 23 | ` 24 | } -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bán Dénes 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 | -------------------------------------------------------------------------------- /test/outlines/gluing.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | left.key.bind: [,10,,] 6 | right.key.bind: [,,,10] 7 | rows: 8 | bottom.key.bind: [10,,,] 9 | top.key.bind: [,,10,] 10 | key: 11 | bind: [0, 0, 0, 0] 12 | rotate: -20 13 | mirror: 14 | ref: matrix_right_top 15 | distance: 30 16 | outlines: 17 | glue: 18 | default: 19 | top: 20 | left: 21 | ref: matrix_right_top 22 | shift: [,sy / 2] 23 | right: 24 | ref: mirror_matrix_right_top 25 | shift: [,sy / 2] 26 | bottom: 27 | left: 28 | ref: matrix_right_bottom 29 | shift: [,sy / -2] 30 | right: 31 | ref: mirror_matrix_right_bottom 32 | shift: [,sy / -2] 33 | exports: 34 | outline: 35 | main: 36 | type: keys 37 | side: both 38 | size: 20 39 | min: 40 | type: keys 41 | side: both 42 | bound: false 43 | size: 14 44 | operation: subtract 45 | optout: 46 | main: 47 | type: keys 48 | side: both 49 | size: 20 50 | glue: false -------------------------------------------------------------------------------- /test/unit/operation.js: -------------------------------------------------------------------------------- 1 | const o = require('../../src/operation') 2 | 3 | describe('Operation', function() { 4 | 5 | it('op_prefix', function() { 6 | o.op_prefix('arst').should.deep.equal({name: 'arst', operation: 'add'}) 7 | o.op_prefix('+arst').should.deep.equal({name: 'arst', operation: 'add'}) 8 | o.op_prefix('-arst').should.deep.equal({name: 'arst', operation: 'subtract'}) 9 | o.op_prefix('~arst').should.deep.equal({name: 'arst', operation: 'intersect'}) 10 | o.op_prefix('^arst').should.deep.equal({name: 'arst', operation: 'stack'}) 11 | }) 12 | 13 | it('operation', function() { 14 | // without choices, it's the same as op_prefix 15 | o.operation('arst').should.deep.equal({name: 'arst', operation: 'add'}) 16 | // with choices, it propagates type where it found the name 17 | o.operation('arst', {bad: [], good: ['arst']}).should.deep.equal({name: 'arst', operation: 'add', type: 'good'}) 18 | // it also respects order when overridden 19 | o.operation('arst', {first: ['arst'], second: ['arst']}, ['second', 'first']).should.deep.equal({name: 'arst', operation: 'add', type: 'second'}) 20 | }) 21 | 22 | }) -------------------------------------------------------------------------------- /src/footprints/oled.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | SDA: undefined, 4 | SCL: undefined, 5 | VCC: 'VCC', 6 | GND: 'GND' 7 | }, 8 | params: { 9 | class: 'OLED', 10 | side: 'F' 11 | }, 12 | body: p => ` 13 | (module lib:OLED_headers (layer F.Cu) (tedit 5E1ADAC2) 14 | ${p.at /* parametric position */} 15 | 16 | ${'' /* footprint reference */} 17 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 18 | (fp_text value OLED (at 0 -7.3) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15)))) 19 | 20 | ${'' /* pins */} 21 | (pad 4 thru_hole oval (at 1.6 2.18 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask) 22 | ${p.net.SDA.str}) 23 | (pad 3 thru_hole oval (at 1.6 4.72 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask) 24 | ${p.net.SCL.str}) 25 | (pad 2 thru_hole oval (at 1.6 7.26 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask) 26 | ${p.net.VCC.str}) 27 | (pad 1 thru_hole rect (at 1.6 9.8 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask) 28 | ${p.net.GND.str}) 29 | ) 30 | ` 31 | } -------------------------------------------------------------------------------- /test/outlines/affect_mirror.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | zones: 3 | matrix: 4 | columns: 5 | only: 6 | rows: 7 | bottom: 8 | top: 9 | mirror: 10 | ref: matrix_only_top 11 | distance: 30 12 | outlines: 13 | exports: 14 | test: 15 | keys: 16 | type: keys 17 | side: both 18 | size: 14 19 | bound: false 20 | rb: 21 | type: rectangle 22 | anchor: 23 | - ref: mirror_matrix_only_bottom 24 | # we do NOT specify `affect: xyr` here 25 | - shift: [-3,-3] 26 | orient: 30 27 | size: [6,6] 28 | operation: stack 29 | rt: 30 | type: rectangle 31 | anchor: 32 | - ref: mirror_matrix_only_top 33 | affect: xyr 34 | - shift: [-3,-3] 35 | orient: 30 36 | size: [6,6] 37 | operation: stack 38 | lb: 39 | type: rectangle 40 | anchor: 41 | - ref: matrix_only_bottom 42 | # again, no `affect: xyr` 43 | - shift: [-3,-3] 44 | orient: 30 45 | size: [6,6] 46 | operation: stack 47 | lt: 48 | type: rectangle 49 | anchor: 50 | - ref: matrix_only_top 51 | affect: xyr 52 | - shift: [-3,-3] 53 | orient: 30 54 | size: [6,6] 55 | operation: stack -------------------------------------------------------------------------------- /test/cli/big/reference/points/demo.yaml: -------------------------------------------------------------------------------- 1 | models: 2 | export: 3 | models: 4 | matrix_col_row: 5 | paths: 6 | top: 7 | type: line 8 | origin: 9 | - -9 10 | - 9 11 | end: 12 | - 9 13 | - 9 14 | right: 15 | type: line 16 | origin: 17 | - 9 18 | - 9 19 | end: 20 | - 9 21 | - -9 22 | bottom: 23 | type: line 24 | origin: 25 | - 9 26 | - -9 27 | end: 28 | - -9 29 | - -9 30 | left: 31 | type: line 32 | origin: 33 | - -9 34 | - -9 35 | end: 36 | - -9 37 | - 9 38 | origin: 39 | - 0 40 | - 0 41 | origin: 42 | - 0 43 | - 0 44 | units: mm 45 | origin: 46 | - 0 47 | - 0 48 | -------------------------------------------------------------------------------- /test/cli/minimal/reference/points/demo.yaml: -------------------------------------------------------------------------------- 1 | models: 2 | export: 3 | models: 4 | matrix_col_row: 5 | paths: 6 | top: 7 | type: line 8 | origin: 9 | - -9 10 | - 9 11 | end: 12 | - 9 13 | - 9 14 | right: 15 | type: line 16 | origin: 17 | - 9 18 | - 9 19 | end: 20 | - 9 21 | - -9 22 | bottom: 23 | type: line 24 | origin: 25 | - 9 26 | - -9 27 | end: 28 | - -9 29 | - -9 30 | left: 31 | type: line 32 | origin: 33 | - -9 34 | - -9 35 | end: 36 | - -9 37 | - 9 38 | origin: 39 | - 0 40 | - 0 41 | origin: 42 | - 0 43 | - 0 44 | units: mm 45 | origin: 46 | - 0 47 | - 0 48 | -------------------------------------------------------------------------------- /test/cases/001_cube___cases_cube_stl.stl: -------------------------------------------------------------------------------- 1 | solid csg.js 2 | facet normal 0 0 -1 3 | outer loop 4 | vertex 0 5 0 5 | vertex 5 5 0 6 | vertex 5 0 0 7 | endloop 8 | endfacet 9 | facet normal 0 0 -1 10 | outer loop 11 | vertex 0 5 0 12 | vertex 5 0 0 13 | vertex 0 0 0 14 | endloop 15 | endfacet 16 | facet normal 0 0 1 17 | outer loop 18 | vertex 0 5 5 19 | vertex 0 0 5 20 | vertex 5 0 5 21 | endloop 22 | endfacet 23 | facet normal 0 0 1 24 | outer loop 25 | vertex 0 5 5 26 | vertex 5 0 5 27 | vertex 5 5 5 28 | endloop 29 | endfacet 30 | facet normal 0 -1 0 31 | outer loop 32 | vertex 5 0 0 33 | vertex 5 0 5 34 | vertex 0 0 5 35 | endloop 36 | endfacet 37 | facet normal 0 -1 0 38 | outer loop 39 | vertex 5 0 0 40 | vertex 0 0 5 41 | vertex 0 0 0 42 | endloop 43 | endfacet 44 | facet normal 1 0 0 45 | outer loop 46 | vertex 5 5 0 47 | vertex 5 5 5 48 | vertex 5 0 5 49 | endloop 50 | endfacet 51 | facet normal 1 0 0 52 | outer loop 53 | vertex 5 5 0 54 | vertex 5 0 5 55 | vertex 5 0 0 56 | endloop 57 | endfacet 58 | facet normal 0 1 0 59 | outer loop 60 | vertex 5 5 5 61 | vertex 5 5 0 62 | vertex 0 5 0 63 | endloop 64 | endfacet 65 | facet normal 0 1 0 66 | outer loop 67 | vertex 5 5 5 68 | vertex 0 5 0 69 | vertex 0 5 5 70 | endloop 71 | endfacet 72 | facet normal -1 0 0 73 | outer loop 74 | vertex 0 5 5 75 | vertex 0 5 0 76 | vertex 0 0 0 77 | endloop 78 | endfacet 79 | facet normal -1 0 0 80 | outer loop 81 | vertex 0 5 5 82 | vertex 0 0 0 83 | vertex 0 0 5 84 | endloop 85 | endfacet 86 | endsolid csg.js 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ergogen", 3 | "version": "3.1.2", 4 | "description": "Ergonomic keyboard layout generator", 5 | "author": "Bán Dénes ", 6 | "license": "MIT", 7 | "homepage": "https://ergogen.xyz", 8 | "repository": "github:ergogen/ergogen", 9 | "bugs": "https://github.com/ergogen/ergogen/issues", 10 | "main": "./src/ergogen.js", 11 | "bin": "./src/cli.js", 12 | "scripts": { 13 | "build": "rollup -c", 14 | "test": "mocha -r test/helpers/register test/index.js", 15 | "coverage": "nyc --reporter=html --reporter=text npm test" 16 | }, 17 | "dependencies": { 18 | "@jscad/openjscad": "github:ergogen/oldjscad", 19 | "fs-extra": "^10.0.0", 20 | "js-yaml": "^3.14.0", 21 | "kle-serial": "github:ergogen/kle-serial#ergogen", 22 | "makerjs": "github:ergogen/maker.js#ergogen", 23 | "mathjs": "^10.0.0", 24 | "semver": "^7.3.5", 25 | "yargs": "^17.3.0" 26 | }, 27 | "devDependencies": { 28 | "@rollup/plugin-commonjs": "^21.0.1", 29 | "@rollup/plugin-json": "^4.1.0", 30 | "chai": "^4.3.4", 31 | "chai-as-promised": "^7.1.1", 32 | "dir-compare": "^3.3.0", 33 | "glob": "^7.2.0", 34 | "mocha": "^9.1.3", 35 | "nyc": "^15.1.0", 36 | "rollup": "^2.61.1" 37 | }, 38 | "nyc": { 39 | "all": true, 40 | "include": [ 41 | "src/**/*.js" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/cli/big/reference/cases/export.jscad: -------------------------------------------------------------------------------- 1 | function export_outline_fn(){ 2 | return new CSG.Path2D([[-9,-9],[9,-9]]).appendPoint([9,9]).appendPoint([-9,9]).appendPoint([-9,-9]).close().innerToCAG() 3 | .extrude({ offset: [0, 0, 1] }); 4 | } 5 | 6 | 7 | 8 | 9 | function export_case_fn() { 10 | 11 | 12 | // creating part 0 of case export 13 | let export__part_0 = export_outline_fn(); 14 | 15 | // make sure that rotations are relative 16 | let export__part_0_bounds = export__part_0.getBounds(); 17 | let export__part_0_x = export__part_0_bounds[0].x + (export__part_0_bounds[1].x - export__part_0_bounds[0].x) / 2 18 | let export__part_0_y = export__part_0_bounds[0].y + (export__part_0_bounds[1].y - export__part_0_bounds[0].y) / 2 19 | export__part_0 = translate([-export__part_0_x, -export__part_0_y, 0], export__part_0); 20 | export__part_0 = rotate([0,0,0], export__part_0); 21 | export__part_0 = translate([export__part_0_x, export__part_0_y, 0], export__part_0); 22 | 23 | export__part_0 = translate([0,0,0], export__part_0); 24 | let result = export__part_0; 25 | 26 | 27 | return result; 28 | } 29 | 30 | 31 | 32 | function main() { 33 | return export_case_fn(); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /test/cli/big/reference/cases/export.stl: -------------------------------------------------------------------------------- 1 | solid csg.js 2 | facet normal 0 0 -1 3 | outer loop 4 | vertex -9 9 0 5 | vertex 9 9 0 6 | vertex 9 -9 0 7 | endloop 8 | endfacet 9 | facet normal 0 0 -1 10 | outer loop 11 | vertex -9 9 0 12 | vertex 9 -9 0 13 | vertex -9 -9 0 14 | endloop 15 | endfacet 16 | facet normal 0 0 1 17 | outer loop 18 | vertex -9 9 1 19 | vertex -9 -9 1 20 | vertex 9 -9 1 21 | endloop 22 | endfacet 23 | facet normal 0 0 1 24 | outer loop 25 | vertex -9 9 1 26 | vertex 9 -9 1 27 | vertex 9 9 1 28 | endloop 29 | endfacet 30 | facet normal 0 -1 0 31 | outer loop 32 | vertex 9 -9 0 33 | vertex 9 -9 1 34 | vertex -9 -9 1 35 | endloop 36 | endfacet 37 | facet normal 0 -1 0 38 | outer loop 39 | vertex 9 -9 0 40 | vertex -9 -9 1 41 | vertex -9 -9 0 42 | endloop 43 | endfacet 44 | facet normal 1 0 0 45 | outer loop 46 | vertex 9 9 0 47 | vertex 9 9 1 48 | vertex 9 -9 1 49 | endloop 50 | endfacet 51 | facet normal 1 0 0 52 | outer loop 53 | vertex 9 9 0 54 | vertex 9 -9 1 55 | vertex 9 -9 0 56 | endloop 57 | endfacet 58 | facet normal 0 1 0 59 | outer loop 60 | vertex 9 9 1 61 | vertex 9 9 0 62 | vertex -9 9 0 63 | endloop 64 | endfacet 65 | facet normal 0 1 0 66 | outer loop 67 | vertex 9 9 1 68 | vertex -9 9 0 69 | vertex -9 9 1 70 | endloop 71 | endfacet 72 | facet normal -1 0 0 73 | outer loop 74 | vertex -9 9 1 75 | vertex -9 9 0 76 | vertex -9 -9 0 77 | endloop 78 | endfacet 79 | facet normal -1 0 0 80 | outer loop 81 | vertex -9 9 1 82 | vertex -9 -9 0 83 | vertex -9 -9 1 84 | endloop 85 | endfacet 86 | endsolid csg.js 87 | -------------------------------------------------------------------------------- /test/unit/units.js: -------------------------------------------------------------------------------- 1 | const u = require('../../src/units') 2 | 3 | describe('Units', function() { 4 | 5 | it('defaults', function() { 6 | // check that an empty config has the default units (and nothing more) 7 | const def = u.parse({}) 8 | Object.keys(def).length.should.equal(4) 9 | def.U.should.equal(19.05) 10 | def.u.should.equal(19) 11 | def.cx.should.equal(18) 12 | def.cy.should.equal(17) 13 | }) 14 | 15 | it('units', function() { 16 | // check that units can contain formulas, and reference each other 17 | const res = u.parse({ 18 | units: { 19 | a: 'cx / 2', 20 | b: 'a + 1' 21 | } 22 | }) 23 | Object.keys(res).length.should.equal(6) 24 | res.a.should.equal(9) 25 | res.b.should.equal(10) 26 | // also check that order matters, which it should 27 | u.parse.bind(this, { 28 | units: { 29 | a: 'b + 1', 30 | b: 'cx / 2' 31 | } 32 | }).should.throw() 33 | }) 34 | 35 | it('variables', function() { 36 | // check that variables work, and can override units 37 | const res = u.parse({ 38 | units: { 39 | a: 'cx / 2', 40 | }, 41 | variables: { 42 | a: 'U + 1' 43 | } 44 | }) 45 | Object.keys(res).length.should.equal(5) 46 | res.a.should.equal(20.05) 47 | }) 48 | 49 | }) -------------------------------------------------------------------------------- /test/cli/big/reference/cases/_export.stl: -------------------------------------------------------------------------------- 1 | solid csg.js 2 | facet normal 0 0 -1 3 | outer loop 4 | vertex -9 9 0 5 | vertex 9 9 0 6 | vertex 9 -9 0 7 | endloop 8 | endfacet 9 | facet normal 0 0 -1 10 | outer loop 11 | vertex -9 9 0 12 | vertex 9 -9 0 13 | vertex -9 -9 0 14 | endloop 15 | endfacet 16 | facet normal 0 0 1 17 | outer loop 18 | vertex -9 9 1 19 | vertex -9 -9 1 20 | vertex 9 -9 1 21 | endloop 22 | endfacet 23 | facet normal 0 0 1 24 | outer loop 25 | vertex -9 9 1 26 | vertex 9 -9 1 27 | vertex 9 9 1 28 | endloop 29 | endfacet 30 | facet normal 0 -1 0 31 | outer loop 32 | vertex 9 -9 0 33 | vertex 9 -9 1 34 | vertex -9 -9 1 35 | endloop 36 | endfacet 37 | facet normal 0 -1 0 38 | outer loop 39 | vertex 9 -9 0 40 | vertex -9 -9 1 41 | vertex -9 -9 0 42 | endloop 43 | endfacet 44 | facet normal 1 0 0 45 | outer loop 46 | vertex 9 9 0 47 | vertex 9 9 1 48 | vertex 9 -9 1 49 | endloop 50 | endfacet 51 | facet normal 1 0 0 52 | outer loop 53 | vertex 9 9 0 54 | vertex 9 -9 1 55 | vertex 9 -9 0 56 | endloop 57 | endfacet 58 | facet normal 0 1 0 59 | outer loop 60 | vertex 9 9 1 61 | vertex 9 9 0 62 | vertex -9 9 0 63 | endloop 64 | endfacet 65 | facet normal 0 1 0 66 | outer loop 67 | vertex 9 9 1 68 | vertex -9 9 0 69 | vertex -9 9 1 70 | endloop 71 | endfacet 72 | facet normal -1 0 0 73 | outer loop 74 | vertex -9 9 1 75 | vertex -9 9 0 76 | vertex -9 -9 0 77 | endloop 78 | endfacet 79 | facet normal -1 0 0 80 | outer loop 81 | vertex -9 9 1 82 | vertex -9 -9 0 83 | vertex -9 -9 1 84 | endloop 85 | endfacet 86 | endsolid csg.js 87 | -------------------------------------------------------------------------------- /test/cli/big/reference/cases/_export.jscad: -------------------------------------------------------------------------------- 1 | function export_outline_fn(){ 2 | return new CSG.Path2D([[-9,-9],[9,-9]]).appendPoint([9,9]).appendPoint([-9,9]).appendPoint([-9,-9]).close().innerToCAG() 3 | .extrude({ offset: [0, 0, 1] }); 4 | } 5 | 6 | 7 | 8 | 9 | function _export_case_fn() { 10 | 11 | 12 | // creating part 0 of case _export 13 | let _export__part_0 = export_outline_fn(); 14 | 15 | // make sure that rotations are relative 16 | let _export__part_0_bounds = _export__part_0.getBounds(); 17 | let _export__part_0_x = _export__part_0_bounds[0].x + (_export__part_0_bounds[1].x - _export__part_0_bounds[0].x) / 2 18 | let _export__part_0_y = _export__part_0_bounds[0].y + (_export__part_0_bounds[1].y - _export__part_0_bounds[0].y) / 2 19 | _export__part_0 = translate([-_export__part_0_x, -_export__part_0_y, 0], _export__part_0); 20 | _export__part_0 = rotate([0,0,0], _export__part_0); 21 | _export__part_0 = translate([_export__part_0_x, _export__part_0_y, 0], _export__part_0); 22 | 23 | _export__part_0 = translate([0,0,0], _export__part_0); 24 | let result = _export__part_0; 25 | 26 | 27 | return result; 28 | } 29 | 30 | 31 | 32 | function main() { 33 | return _export_case_fn(); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/footprints/alps.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | from: undefined, 4 | to: undefined 5 | }, 6 | params: { 7 | class: 'S' 8 | }, 9 | body: p => ` 10 | 11 | (module ALPS (layer F.Cu) (tedit 5CF31DEF) 12 | 13 | ${p.at /* parametric position */} 14 | 15 | ${'' /* footprint reference */} 16 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 17 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 18 | 19 | ${''/* corner marks */} 20 | (fp_line (start -7 -6) (end -7 -7) (layer Dwgs.User) (width 0.15)) 21 | (fp_line (start -7 7) (end -6 7) (layer Dwgs.User) (width 0.15)) 22 | (fp_line (start -6 -7) (end -7 -7) (layer Dwgs.User) (width 0.15)) 23 | (fp_line (start -7 7) (end -7 6) (layer Dwgs.User) (width 0.15)) 24 | (fp_line (start 7 6) (end 7 7) (layer Dwgs.User) (width 0.15)) 25 | (fp_line (start 7 -7) (end 6 -7) (layer Dwgs.User) (width 0.15)) 26 | (fp_line (start 6 7) (end 7 7) (layer Dwgs.User) (width 0.15)) 27 | (fp_line (start 7 -7) (end 7 -6) (layer Dwgs.User) (width 0.15)) 28 | 29 | ${''/* pins */} 30 | (pad 1 thru_hole circle (at 2.5 -4.5) (size 2.25 2.25) (drill 1.47) (layers *.Cu *.Mask) ${p.net.from.str}) 31 | (pad 2 thru_hole circle (at -2.5 -4) (size 2.25 2.25) (drill 1.47) (layers *.Cu *.Mask) ${p.net.to.str}) 32 | ) 33 | 34 | ` 35 | } -------------------------------------------------------------------------------- /src/point.js: -------------------------------------------------------------------------------- 1 | const m = require('makerjs') 2 | const u = require('./utils') 3 | 4 | module.exports = class Point { 5 | constructor(x=0, y=0, r=0, meta={}) { 6 | if (Array.isArray(x)) { 7 | this.x = x[0] 8 | this.y = x[1] 9 | this.r = 0 10 | this.meta = {} 11 | } else { 12 | this.x = x 13 | this.y = y 14 | this.r = r 15 | this.meta = meta 16 | } 17 | } 18 | 19 | get p() { 20 | return [this.x, this.y] 21 | } 22 | 23 | set p(val) { 24 | [this.x, this.y] = val 25 | } 26 | 27 | shift(s, relative=true) { 28 | if (relative) { 29 | s = m.point.rotate(s, this.r) 30 | } 31 | this.x += s[0] 32 | this.y += s[1] 33 | return this 34 | } 35 | 36 | rotate(angle, origin=[0, 0]) { 37 | this.p = m.point.rotate(this.p, angle, origin) 38 | this.r += angle 39 | return this 40 | } 41 | 42 | mirror(x) { 43 | this.x = 2 * x - this.x 44 | this.r = -this.r 45 | return this 46 | } 47 | 48 | clone() { 49 | return new Point( 50 | this.x, 51 | this.y, 52 | this.r, 53 | u.deepcopy(this.meta) 54 | ) 55 | } 56 | 57 | position(model) { 58 | return m.model.moveRelative(m.model.rotate(model, this.r), this.p) 59 | } 60 | 61 | rect(size=14) { 62 | let rect = u.rect(size, size, [-size/2, -size/2], this.meta.mirrored) 63 | return this.position(rect) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/outlines/gluing___outlines_optout_dxf.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -12.8171276 53 | 20 54 | -5.9767248 55 | 11 56 | 23.8308846 57 | 21 58 | -19.3155103 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | -12.8171276 65 | 20 66 | -5.9767248 67 | 11 68 | 0.5216579 69 | 21 70 | 30.6712874 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 0.5216579 77 | 20 78 | 30.6712874 79 | 11 80 | 37.1696701 81 | 21 82 | 17.3325019 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | 23.8308846 89 | 20 90 | -19.3155103 91 | 11 92 | 37.1696701 93 | 21 94 | 17.3325019 95 | 0 96 | LINE 97 | 8 98 | 0 99 | 10 100 | 54.8742004 101 | 20 102 | -19.3155103 103 | 11 104 | 91.5222126 105 | 21 106 | -5.9767248 107 | 0 108 | LINE 109 | 8 110 | 0 111 | 10 112 | 78.1834271 113 | 20 114 | 30.6712874 115 | 11 116 | 91.5222126 117 | 21 118 | -5.9767248 119 | 0 120 | LINE 121 | 8 122 | 0 123 | 10 124 | 41.5354149 125 | 20 126 | 17.3325019 127 | 11 128 | 78.1834271 129 | 21 130 | 30.6712874 131 | 0 132 | LINE 133 | 8 134 | 0 135 | 10 136 | 41.5354149 137 | 20 138 | 17.3325019 139 | 11 140 | 54.8742004 141 | 21 142 | -19.3155103 143 | 0 144 | ENDSEC 145 | 0 146 | EOF -------------------------------------------------------------------------------- /test/outlines/circles___outlines_outline_dxf.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -10 53 | 20 54 | -10 55 | 11 56 | 8.8196601 57 | 21 58 | -10 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 8.8196601 65 | 20 66 | 10 67 | 11 68 | -5 69 | 21 70 | 10 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | -10 77 | 20 78 | 5 79 | 11 80 | -10 81 | 21 82 | -10 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | 31.1803399 89 | 20 90 | -10 91 | 11 92 | 50 93 | 21 94 | -10 95 | 0 96 | LINE 97 | 8 98 | 0 99 | 10 100 | 50 101 | 20 102 | -10 103 | 11 104 | 50 105 | 21 106 | 5 107 | 0 108 | LINE 109 | 8 110 | 0 111 | 10 112 | 45 113 | 20 114 | 10 115 | 11 116 | 31.1803399 117 | 21 118 | 10 119 | 0 120 | ARC 121 | 8 122 | 0 123 | 10 124 | 20 125 | 20 126 | 0 127 | 40 128 | 15 129 | 50 130 | 221.8103149 131 | 51 132 | 318.1896851 133 | 0 134 | ARC 135 | 8 136 | 0 137 | 10 138 | 20 139 | 20 140 | 0 141 | 40 142 | 15 143 | 50 144 | 401.8103149 145 | 51 146 | 498.1896851 147 | 0 148 | ARC 149 | 8 150 | 0 151 | 10 152 | -10 153 | 20 154 | 10 155 | 40 156 | 5 157 | 50 158 | 0 159 | 51 160 | 270 161 | 0 162 | ARC 163 | 8 164 | 0 165 | 10 166 | 50 167 | 20 168 | 10 169 | 40 170 | 5 171 | 50 172 | 270 173 | 51 174 | 540 175 | 0 176 | ENDSEC 177 | 0 178 | EOF -------------------------------------------------------------------------------- /src/footprints/omron.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | from: undefined, 4 | to: undefined 5 | }, 6 | params: { 7 | class: 'S' 8 | }, 9 | body: p => ` 10 | 11 | (module OMRON_B3F-4055 (layer F.Cu) (tstamp 5BF2CC94) 12 | 13 | ${p.at /* parametric position */} 14 | ${'' /* footprint reference */} 15 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 16 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 17 | 18 | ${'' /* stabilizers */} 19 | (pad "" np_thru_hole circle (at 0 -4.5) (size 1.8 1.8) (drill 1.8) (layers *.Cu *.Mask)) 20 | (pad "" np_thru_hole circle (at 0 4.5) (size 1.8 1.8) (drill 1.8) (layers *.Cu *.Mask)) 21 | 22 | ${'' /* switch marks */} 23 | (fp_line (start -6 -6) (end 6 -6) (layer Dwgs.User) (width 0.15)) 24 | (fp_line (start 6 -6) (end 6 6) (layer Dwgs.User) (width 0.15)) 25 | (fp_line (start 6 6) (end -6 6) (layer Dwgs.User) (width 0.15)) 26 | (fp_line (start -6 6) (end -6 -6) (layer Dwgs.User) (width 0.15)) 27 | 28 | ${'' /* pins */} 29 | (pad 1 np_thru_hole circle (at 6.25 -2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.from.str}) 30 | (pad 2 np_thru_hole circle (at -6.25 -2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.from.str}) 31 | (pad 3 np_thru_hole circle (at 6.25 2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.to.str}) 32 | (pad 4 np_thru_hole circle (at -6.25 2.5 ) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.to.str}) 33 | ) 34 | 35 | ` 36 | } -------------------------------------------------------------------------------- /src/footprints/b3u1000p.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | r1: 'r1', 4 | r2: 'r2' 5 | }, 6 | params: { 7 | class: 'S', 8 | reverse: true 9 | }, 10 | body: p => { 11 | const standard = ` 12 | (module Button_Switch_SMD:SW_SPST_B3U-1000P (layer F.Cu) (tedit 5A02FC95) 13 | ${p.at /* parametric position */} 14 | (descr "Ultra-small-sized Tactile Switch with High Contact Reliability, Top-actuated Model, without Ground Terminal, without Boss") 15 | (tags "Tactile Switch") 16 | (attr smd) 17 | (fp_circle (center 0 0) (end 0.75 0) (layer F.Fab) (width 0.1)) 18 | (fp_line (start -1.5 1.25) (end -1.5 -1.25) (layer F.Fab) (width 0.1)) 19 | (fp_line (start 1.5 1.25) (end -1.5 1.25) (layer F.Fab) (width 0.1)) 20 | (fp_line (start 1.5 -1.25) (end 1.5 1.25) (layer F.Fab) (width 0.1)) 21 | (fp_line (start -1.5 -1.25) (end 1.5 -1.25) (layer F.Fab) (width 0.1)) 22 | (fp_line (start -2.4 -1.65) (end -2.4 1.65) (layer F.CrtYd) (width 0.05)) 23 | (fp_line (start 2.4 -1.65) (end -2.4 -1.65) (layer F.CrtYd) (width 0.05)) 24 | (fp_line (start 2.4 1.65) (end 2.4 -1.65) (layer F.CrtYd) (width 0.05)) 25 | (fp_line (start -2.4 1.65) (end 2.4 1.65) (layer F.CrtYd) (width 0.05)) 26 | (fp_text user %R (at 0 -2.5) (layer F.Fab) 27 | (effects (font (size 1 1) (thickness 0.15))) 28 | ) 29 | 30 | ` 31 | function pins(def_neg, def_pos, def_side) { 32 | return ` 33 | ${''/* pins */} 34 | (pad 1 smd rect (at ${def_neg}1.7 0) (size 0.9 1.7) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.r1.str}) 35 | (pad 2 smd rect (at ${def_pos}1.7 0) (size 0.9 1.7) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.r2.str}) 36 | ` 37 | } 38 | if(p.param.reverse) { 39 | return ` 40 | ${standard} 41 | ${pins('-', '', 'B')} 42 | ${pins('', '-', 'F')}) 43 | ` 44 | } else { 45 | return ` 46 | ${standard} 47 | ${pins('-', '', 'B')}) 48 | ` 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/footprints/pad.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | net: undefined 4 | }, 5 | params: { 6 | class: 'PAD', 7 | width: 1, 8 | height: 1, 9 | front: true, 10 | back: true, 11 | text: '', 12 | align: 'left', 13 | mirrored: '=mirrored' 14 | }, 15 | body: p => { 16 | 17 | const layout = (toggle, side) => { 18 | if (!toggle) return '' 19 | let x = 0, y = 0 20 | const mirror = side == 'B' ? '(justify mirror)' : '' 21 | const plus = (p.param.text.length + 1) * 0.5 22 | let align = p.param.align 23 | if (p.param.mirrored === true) { 24 | if (align == 'left') align = 'right' 25 | else if (align == 'right') align = 'left' 26 | } 27 | if (align == 'left') x -= p.param.width / 2 + plus 28 | if (align == 'right') x += p.param.width / 2 + plus 29 | if (align == 'up') y += p.param.height / 2 + plus 30 | if (align == 'down') y -= p.param.height / 2 + plus 31 | const text = `(fp_text user ${p.param.text} (at ${x} ${y} ${p.rot}) (layer ${side}.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15)) ${mirror}))` 32 | return `(pad 1 smd rect (at 0 0 ${p.rot}) (size ${p.param.width} ${p.param.height}) (layers ${side}.Cu ${side}.Paste ${side}.Mask) ${p.net.net.str})\n${text}` 33 | } 34 | 35 | return ` 36 | 37 | (module SMDPad (layer F.Cu) (tedit 5B24D78E) 38 | 39 | ${p.at /* parametric position */} 40 | 41 | ${'' /* footprint reference */} 42 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 43 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 44 | 45 | ${''/* SMD pads */} 46 | ${layout(p.param.front, 'F')} 47 | ${layout(p.param.back, 'B')} 48 | 49 | ) 50 | 51 | ` 52 | } 53 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const m = require('makerjs') 2 | 3 | exports.deepcopy = value => { 4 | if (value === undefined) return undefined 5 | return JSON.parse(JSON.stringify(value)) 6 | } 7 | 8 | const deep = exports.deep = (obj, key, val) => { 9 | const levels = key.split('.') 10 | const last = levels.pop() 11 | let step = obj 12 | for (const level of levels) { 13 | step[level] = step[level] || {} 14 | step = step[level] 15 | } 16 | if (val === undefined) return step[last] 17 | step[last] = val 18 | return obj 19 | } 20 | 21 | const eq = exports.eq = (a=[], b=[]) => { 22 | return a[0] === b[0] && a[1] === b[1] 23 | } 24 | 25 | const line = exports.line = (a, b) => { 26 | return new m.paths.Line(a, b) 27 | } 28 | 29 | exports.circle = (p, r) => { 30 | return {paths: {circle: new m.paths.Circle(p, r)}} 31 | } 32 | 33 | exports.rect = (w, h, o=[0, 0]) => { 34 | const res = { 35 | top: line([0, h], [w, h]), 36 | right: line([w, h], [w, 0]), 37 | bottom: line([w, 0], [0, 0]), 38 | left: line([0, 0], [0, h]) 39 | } 40 | return m.model.move({paths: res}, o) 41 | } 42 | 43 | exports.poly = (arr) => { 44 | let counter = 0 45 | let prev = arr[arr.length - 1] 46 | const res = { 47 | paths: {} 48 | } 49 | for (const p of arr) { 50 | if (eq(prev, p)) continue 51 | res.paths['p' + (++counter)] = line(prev, p) 52 | prev = p 53 | } 54 | return res 55 | } 56 | 57 | const farPoint = [1234.1234, 2143.56789] 58 | 59 | exports.union = (a, b) => { 60 | return m.model.combine(a, b, false, true, false, true, { 61 | farPoint 62 | }) 63 | } 64 | 65 | exports.subtract = (a, b) => { 66 | return m.model.combine(a, b, false, true, true, false, { 67 | farPoint 68 | }) 69 | } 70 | 71 | exports.intersect = (a, b) => { 72 | return m.model.combine(a, b, true, false, true, false, { 73 | farPoint 74 | }) 75 | } 76 | 77 | exports.stack = (a, b) => { 78 | return { 79 | models: { 80 | a, b 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/footprints/jstph.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | pos: undefined, 4 | neg: undefined 5 | }, 6 | params: { 7 | class: 'JST', 8 | side: 'F' 9 | }, 10 | body: p => ` 11 | 12 | (module JST_PH_S2B-PH-K_02x2.00mm_Angled (layer F.Cu) (tedit 58D3FE32) 13 | 14 | (descr "JST PH series connector, S2B-PH-K, side entry type, through hole, Datasheet: http://www.jst-mfg.com/product/pdf/eng/ePH.pdf") 15 | (tags "connector jst ph") 16 | 17 | ${p.at /* parametric position */} 18 | 19 | ${'' /* footprint reference */} 20 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 21 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 22 | 23 | (fp_line (start -2.25 0.25) (end -2.25 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 24 | (fp_line (start -2.25 -1.35) (end -2.95 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 25 | (fp_line (start -2.95 -1.35) (end -2.95 6.25) (layer ${p.param.side}.SilkS) (width 0.15)) 26 | (fp_line (start -2.95 6.25) (end 2.95 6.25) (layer ${p.param.side}.SilkS) (width 0.15)) 27 | (fp_line (start 2.95 6.25) (end 2.95 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 28 | (fp_line (start 2.95 -1.35) (end 2.25 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 29 | (fp_line (start 2.25 -1.35) (end 2.25 0.25) (layer ${p.param.side}.SilkS) (width 0.15)) 30 | (fp_line (start 2.25 0.25) (end -2.25 0.25) (layer ${p.param.side}.SilkS) (width 0.15)) 31 | 32 | (fp_line (start -1 1.5) (end -1 2.0) (layer ${p.param.side}.SilkS) (width 0.15)) 33 | (fp_line (start -1.25 1.75) (end -0.75 1.75) (layer ${p.param.side}.SilkS) (width 0.15)) 34 | 35 | (pad 1 thru_hole rect (at -1 0 ${p.rot}) (size 1.2 1.7) (drill 0.75) (layers *.Cu *.Mask) ${p.net.pos.str}) 36 | (pad 2 thru_hole oval (at 1 0 ${p.rot}) (size 1.2 1.7) (drill 0.75) (layers *.Cu *.Mask) ${p.net.neg.str}) 37 | 38 | ) 39 | 40 | ` 41 | } -------------------------------------------------------------------------------- /test/cli/big/reference/outlines/_export.yaml: -------------------------------------------------------------------------------- 1 | models: 2 | export: 3 | models: 4 | a: 5 | models: {} 6 | origin: 7 | - 0 8 | - 0 9 | b: 10 | models: 11 | a: 12 | models: {} 13 | origin: 14 | - 0 15 | - 0 16 | b: 17 | paths: 18 | ShapeLine1: 19 | type: line 20 | origin: 21 | - -9 22 | - -9 23 | end: 24 | - 9 25 | - -9 26 | ShapeLine2: 27 | type: line 28 | origin: 29 | - 9 30 | - -9 31 | end: 32 | - 9 33 | - 9 34 | ShapeLine3: 35 | type: line 36 | origin: 37 | - 9 38 | - 9 39 | end: 40 | - -9 41 | - 9 42 | ShapeLine4: 43 | type: line 44 | origin: 45 | - -9 46 | - 9 47 | end: 48 | - -9 49 | - -9 50 | origin: 51 | - 0 52 | - 0 53 | origin: 54 | - 0 55 | - 0 56 | origin: 57 | - 0 58 | - 0 59 | units: mm 60 | origin: 61 | - 0 62 | - 0 63 | -------------------------------------------------------------------------------- /test/cli/big/reference/outlines/export.yaml: -------------------------------------------------------------------------------- 1 | models: 2 | export: 3 | models: 4 | a: 5 | models: {} 6 | origin: 7 | - 0 8 | - 0 9 | b: 10 | models: 11 | a: 12 | models: {} 13 | origin: 14 | - 0 15 | - 0 16 | b: 17 | paths: 18 | ShapeLine1: 19 | type: line 20 | origin: 21 | - -9 22 | - -9 23 | end: 24 | - 9 25 | - -9 26 | ShapeLine2: 27 | type: line 28 | origin: 29 | - 9 30 | - -9 31 | end: 32 | - 9 33 | - 9 34 | ShapeLine3: 35 | type: line 36 | origin: 37 | - 9 38 | - 9 39 | end: 40 | - -9 41 | - 9 42 | ShapeLine4: 43 | type: line 44 | origin: 45 | - -9 46 | - 9 47 | end: 48 | - -9 49 | - -9 50 | origin: 51 | - 0 52 | - 0 53 | origin: 54 | - 0 55 | - 0 56 | origin: 57 | - 0 58 | - 0 59 | units: mm 60 | origin: 61 | - 0 62 | - 0 63 | -------------------------------------------------------------------------------- /src/footprints/button.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | from: undefined, 4 | to: undefined 5 | }, 6 | params: { 7 | class: 'B', // for Button 8 | side: 'F' 9 | }, 10 | body: p => ` 11 | 12 | (module E73:SW_TACT_ALPS_SKQGABE010 (layer F.Cu) (tstamp 5BF2CC94) 13 | 14 | (descr "Low-profile SMD Tactile Switch, https://www.e-switch.com/product-catalog/tact/product-lines/tl3342-series-low-profile-smt-tact-switch") 15 | (tags "SPST Tactile Switch") 16 | 17 | ${p.at /* parametric position */} 18 | ${'' /* footprint reference */} 19 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 20 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 21 | 22 | ${'' /* outline */} 23 | (fp_line (start 2.75 1.25) (end 1.25 2.75) (layer ${p.param.side}.SilkS) (width 0.15)) 24 | (fp_line (start 2.75 -1.25) (end 1.25 -2.75) (layer ${p.param.side}.SilkS) (width 0.15)) 25 | (fp_line (start 2.75 -1.25) (end 2.75 1.25) (layer ${p.param.side}.SilkS) (width 0.15)) 26 | (fp_line (start -1.25 2.75) (end 1.25 2.75) (layer ${p.param.side}.SilkS) (width 0.15)) 27 | (fp_line (start -1.25 -2.75) (end 1.25 -2.75) (layer ${p.param.side}.SilkS) (width 0.15)) 28 | (fp_line (start -2.75 1.25) (end -1.25 2.75) (layer ${p.param.side}.SilkS) (width 0.15)) 29 | (fp_line (start -2.75 -1.25) (end -1.25 -2.75) (layer ${p.param.side}.SilkS) (width 0.15)) 30 | (fp_line (start -2.75 -1.25) (end -2.75 1.25) (layer ${p.param.side}.SilkS) (width 0.15)) 31 | 32 | ${'' /* pins */} 33 | (pad 1 smd rect (at -3.1 -1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.from.str}) 34 | (pad 1 smd rect (at 3.1 -1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.from.str}) 35 | (pad 2 smd rect (at -3.1 1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.to.str}) 36 | (pad 2 smd rect (at 3.1 1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.to.str}) 37 | ) 38 | 39 | ` 40 | } 41 | -------------------------------------------------------------------------------- /test/outlines/fillet___outlines_fillet_dxf.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | 8.6 53 | 20 54 | -6.6 55 | 11 56 | 8.6 57 | 21 58 | 23.6 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | -6.6 65 | 20 66 | -8.6 67 | 11 68 | 6.6 69 | 21 70 | -8.6 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | -8.6 77 | 20 78 | -6.6 79 | 11 80 | -8.6 81 | 21 82 | 23.6 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -6.6 89 | 20 90 | 25.6 91 | 11 92 | 6.6 93 | 21 94 | 25.6 95 | 0 96 | LINE 97 | 8 98 | 0 99 | 10 100 | 27.6 101 | 20 102 | -6.6 103 | 11 104 | 27.6 105 | 21 106 | 23.6 107 | 0 108 | LINE 109 | 8 110 | 0 111 | 10 112 | 12.4 113 | 20 114 | -8.6 115 | 11 116 | 25.6 117 | 21 118 | -8.6 119 | 0 120 | LINE 121 | 8 122 | 0 123 | 10 124 | 10.4 125 | 20 126 | -6.6 127 | 11 128 | 10.4 129 | 21 130 | 23.6 131 | 0 132 | LINE 133 | 8 134 | 0 135 | 10 136 | 12.4 137 | 20 138 | 25.6 139 | 11 140 | 25.6 141 | 21 142 | 25.6 143 | 0 144 | ARC 145 | 8 146 | 0 147 | 10 148 | 6.6 149 | 20 150 | 23.6 151 | 40 152 | 2 153 | 50 154 | 0 155 | 51 156 | 90 157 | 0 158 | ARC 159 | 8 160 | 0 161 | 10 162 | -6.6 163 | 20 164 | 23.6 165 | 40 166 | 2 167 | 50 168 | 90 169 | 51 170 | 180 171 | 0 172 | ARC 173 | 8 174 | 0 175 | 10 176 | -6.6 177 | 20 178 | -6.6 179 | 40 180 | 2 181 | 50 182 | 180 183 | 51 184 | 270 185 | 0 186 | ARC 187 | 8 188 | 0 189 | 10 190 | 6.6 191 | 20 192 | -6.6 193 | 40 194 | 2 195 | 50 196 | 270 197 | 51 198 | 0 199 | 0 200 | ARC 201 | 8 202 | 0 203 | 10 204 | 25.6 205 | 20 206 | 23.6 207 | 40 208 | 2 209 | 50 210 | 0 211 | 51 212 | 90 213 | 0 214 | ARC 215 | 8 216 | 0 217 | 10 218 | 12.4 219 | 20 220 | 23.6 221 | 40 222 | 2 223 | 50 224 | 90 225 | 51 226 | 180 227 | 0 228 | ARC 229 | 8 230 | 0 231 | 10 232 | 12.4 233 | 20 234 | -6.6 235 | 40 236 | 2 237 | 50 238 | 180 239 | 51 240 | 270 241 | 0 242 | ARC 243 | 8 244 | 0 245 | 10 246 | 25.6 247 | 20 248 | -6.6 249 | 40 250 | 2 251 | 50 252 | 270 253 | 51 254 | 0 255 | 0 256 | ENDSEC 257 | 0 258 | EOF -------------------------------------------------------------------------------- /src/io.js: -------------------------------------------------------------------------------- 1 | const yaml = require('js-yaml') 2 | const makerjs = require('makerjs') 3 | const jscad = require('@jscad/openjscad') 4 | 5 | const u = require('./utils') 6 | const a = require('./assert') 7 | const kle = require('./kle') 8 | 9 | exports.interpret = (raw, logger) => { 10 | let config = raw 11 | let format = 'OBJ' 12 | if (a.type(raw)() == 'string') { 13 | try { 14 | config = yaml.safeLoad(raw) 15 | format = 'YAML' 16 | } catch (yamlex) { 17 | try { 18 | config = new Function(raw)() 19 | a.assert( 20 | a.type(config)() == 'object', 21 | 'Input JS Code doesn\'t resolve into an object!' 22 | ) 23 | format = 'JS' 24 | } catch (codeex) { 25 | logger('YAML exception:', yamlex) 26 | logger('Code exception:', codeex) 27 | throw new Error('Input is not valid YAML, JSON, or JS Code!') 28 | } 29 | } 30 | } 31 | 32 | try { 33 | // assume it's KLE and try to convert it 34 | config = kle.convert(config, logger) 35 | format = 'KLE' 36 | } catch (kleex) { 37 | // nope... nevermind 38 | } 39 | 40 | if (a.type(config)() != 'object') { 41 | throw new Error('Input doesn\'t resolve into an object!') 42 | } 43 | 44 | if (!Object.keys(config).length) { 45 | throw new Error('Input appears to be empty!') 46 | } 47 | 48 | return [config, format] 49 | } 50 | 51 | exports.twodee = (model, debug) => { 52 | const assembly = makerjs.model.originate({ 53 | models: { 54 | export: u.deepcopy(model) 55 | }, 56 | units: 'mm' 57 | }) 58 | 59 | const result = { 60 | dxf: makerjs.exporter.toDXF(assembly), 61 | } 62 | if (debug) { 63 | result.yaml = assembly 64 | result.svg = makerjs.exporter.toSVG(assembly) 65 | } 66 | return result 67 | } 68 | 69 | exports.threedee = async (script, debug) => { 70 | const compiled = await new Promise((resolve, reject) => { 71 | jscad.compile(script, {}).then(compiled => { 72 | resolve(compiled) 73 | }) 74 | }) 75 | const result = { 76 | stl: jscad.generateOutput('stla', compiled).asBuffer().toString() 77 | } 78 | if (debug) { 79 | result.jscad = script 80 | } 81 | return result 82 | } 83 | -------------------------------------------------------------------------------- /test/outlines/polygons___outlines_outline_dxf.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -10 53 | 20 54 | -10 55 | 11 56 | 5 57 | 21 58 | -10 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 10 65 | 20 66 | 0 67 | 11 68 | 10 69 | 21 70 | 10 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 10 77 | 20 78 | 10 79 | 11 80 | -7.5 81 | 21 82 | 10 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -10 89 | 20 90 | 5 91 | 11 92 | -10 93 | 21 94 | -10 95 | 0 96 | LINE 97 | 8 98 | 0 99 | 10 100 | 35 101 | 20 102 | -10 103 | 11 104 | 50 105 | 21 106 | -10 107 | 0 108 | LINE 109 | 8 110 | 0 111 | 10 112 | 50 113 | 20 114 | -10 115 | 11 116 | 50 117 | 21 118 | 5 119 | 0 120 | LINE 121 | 8 122 | 0 123 | 10 124 | 47.5 125 | 20 126 | 10 127 | 11 128 | 30 129 | 21 130 | 10 131 | 0 132 | LINE 133 | 8 134 | 0 135 | 10 136 | 30 137 | 20 138 | 10 139 | 11 140 | 30 141 | 21 142 | 0 143 | 0 144 | LINE 145 | 8 146 | 0 147 | 10 148 | 0 149 | 20 150 | -20 151 | 11 152 | 5 153 | 21 154 | -10 155 | 0 156 | LINE 157 | 8 158 | 0 159 | 10 160 | 10 161 | 20 162 | 0 163 | 11 164 | 20 165 | 21 166 | 20 167 | 0 168 | LINE 169 | 8 170 | 0 171 | 10 172 | 20 173 | 20 174 | 20 175 | 11 176 | 30 177 | 21 178 | 0 179 | 0 180 | LINE 181 | 8 182 | 0 183 | 10 184 | 35 185 | 20 186 | -10 187 | 11 188 | 40 189 | 21 190 | -20 191 | 0 192 | LINE 193 | 8 194 | 0 195 | 10 196 | 40 197 | 20 198 | -20 199 | 11 200 | 0 201 | 21 202 | -20 203 | 0 204 | LINE 205 | 8 206 | 0 207 | 10 208 | -15 209 | 20 210 | 5 211 | 11 212 | -10 213 | 21 214 | 15 215 | 0 216 | LINE 217 | 8 218 | 0 219 | 10 220 | -10 221 | 20 222 | 15 223 | 11 224 | -7.5 225 | 21 226 | 10 227 | 0 228 | LINE 229 | 8 230 | 0 231 | 10 232 | -10 233 | 20 234 | 5 235 | 11 236 | -15 237 | 21 238 | 5 239 | 0 240 | LINE 241 | 8 242 | 0 243 | 10 244 | 55 245 | 20 246 | 5 247 | 11 248 | 50 249 | 21 250 | 15 251 | 0 252 | LINE 253 | 8 254 | 0 255 | 10 256 | 50 257 | 20 258 | 15 259 | 11 260 | 47.5 261 | 21 262 | 10 263 | 0 264 | LINE 265 | 8 266 | 0 267 | 10 268 | 50 269 | 20 270 | 5 271 | 11 272 | 55 273 | 21 274 | 5 275 | 0 276 | ENDSEC 277 | 0 278 | EOF -------------------------------------------------------------------------------- /test/outlines/basic___outlines_outline_dxf.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -10 53 | 20 54 | -10 55 | 11 56 | 29 57 | 21 58 | -10 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | -10 65 | 20 66 | -10 67 | 11 68 | -10 69 | 21 70 | 29 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | -10 77 | 20 78 | 29 79 | 11 80 | 29 81 | 21 82 | 29 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | 29 89 | 20 90 | -10 91 | 11 92 | 29 93 | 21 94 | 29 95 | 0 96 | LINE 97 | 8 98 | 0 99 | 10 100 | -7 101 | 20 102 | -7 103 | 11 104 | 7 105 | 21 106 | -7 107 | 0 108 | LINE 109 | 8 110 | 0 111 | 10 112 | 7 113 | 20 114 | -7 115 | 11 116 | 7 117 | 21 118 | 7 119 | 0 120 | LINE 121 | 8 122 | 0 123 | 10 124 | 7 125 | 20 126 | 7 127 | 11 128 | -7 129 | 21 130 | 7 131 | 0 132 | LINE 133 | 8 134 | 0 135 | 10 136 | -7 137 | 20 138 | 7 139 | 11 140 | -7 141 | 21 142 | -7 143 | 0 144 | LINE 145 | 8 146 | 0 147 | 10 148 | -7 149 | 20 150 | 12 151 | 11 152 | 7 153 | 21 154 | 12 155 | 0 156 | LINE 157 | 8 158 | 0 159 | 10 160 | 7 161 | 20 162 | 12 163 | 11 164 | 7 165 | 21 166 | 26 167 | 0 168 | LINE 169 | 8 170 | 0 171 | 10 172 | 7 173 | 20 174 | 26 175 | 11 176 | -7 177 | 21 178 | 26 179 | 0 180 | LINE 181 | 8 182 | 0 183 | 10 184 | -7 185 | 20 186 | 26 187 | 11 188 | -7 189 | 21 190 | 12 191 | 0 192 | LINE 193 | 8 194 | 0 195 | 10 196 | 12 197 | 20 198 | -7 199 | 11 200 | 26 201 | 21 202 | -7 203 | 0 204 | LINE 205 | 8 206 | 0 207 | 10 208 | 26 209 | 20 210 | -7 211 | 11 212 | 26 213 | 21 214 | 7 215 | 0 216 | LINE 217 | 8 218 | 0 219 | 10 220 | 26 221 | 20 222 | 7 223 | 11 224 | 12 225 | 21 226 | 7 227 | 0 228 | LINE 229 | 8 230 | 0 231 | 10 232 | 12 233 | 20 234 | 7 235 | 11 236 | 12 237 | 21 238 | -7 239 | 0 240 | LINE 241 | 8 242 | 0 243 | 10 244 | 12 245 | 20 246 | 12 247 | 11 248 | 26 249 | 21 250 | 12 251 | 0 252 | LINE 253 | 8 254 | 0 255 | 10 256 | 26 257 | 20 258 | 12 259 | 11 260 | 26 261 | 21 262 | 26 263 | 0 264 | LINE 265 | 8 266 | 0 267 | 10 268 | 26 269 | 20 270 | 26 271 | 11 272 | 12 273 | 21 274 | 26 275 | 0 276 | LINE 277 | 8 278 | 0 279 | 10 280 | 12 281 | 20 282 | 26 283 | 11 284 | 12 285 | 21 286 | 12 287 | 0 288 | ENDSEC 289 | 0 290 | EOF -------------------------------------------------------------------------------- /src/footprints/diode.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | from: undefined, 4 | to: undefined 5 | }, 6 | params: { 7 | class: 'D' 8 | }, 9 | body: p => ` 10 | 11 | (module ComboDiode (layer F.Cu) (tedit 5B24D78E) 12 | 13 | 14 | ${p.at /* parametric position */} 15 | 16 | ${'' /* footprint reference */} 17 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 18 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 19 | 20 | ${''/* diode symbols */} 21 | (fp_line (start 0.25 0) (end 0.75 0) (layer F.SilkS) (width 0.1)) 22 | (fp_line (start 0.25 0.4) (end -0.35 0) (layer F.SilkS) (width 0.1)) 23 | (fp_line (start 0.25 -0.4) (end 0.25 0.4) (layer F.SilkS) (width 0.1)) 24 | (fp_line (start -0.35 0) (end 0.25 -0.4) (layer F.SilkS) (width 0.1)) 25 | (fp_line (start -0.35 0) (end -0.35 0.55) (layer F.SilkS) (width 0.1)) 26 | (fp_line (start -0.35 0) (end -0.35 -0.55) (layer F.SilkS) (width 0.1)) 27 | (fp_line (start -0.75 0) (end -0.35 0) (layer F.SilkS) (width 0.1)) 28 | (fp_line (start 0.25 0) (end 0.75 0) (layer B.SilkS) (width 0.1)) 29 | (fp_line (start 0.25 0.4) (end -0.35 0) (layer B.SilkS) (width 0.1)) 30 | (fp_line (start 0.25 -0.4) (end 0.25 0.4) (layer B.SilkS) (width 0.1)) 31 | (fp_line (start -0.35 0) (end 0.25 -0.4) (layer B.SilkS) (width 0.1)) 32 | (fp_line (start -0.35 0) (end -0.35 0.55) (layer B.SilkS) (width 0.1)) 33 | (fp_line (start -0.35 0) (end -0.35 -0.55) (layer B.SilkS) (width 0.1)) 34 | (fp_line (start -0.75 0) (end -0.35 0) (layer B.SilkS) (width 0.1)) 35 | 36 | ${''/* SMD pads on both sides */} 37 | (pad 1 smd rect (at -1.65 0 ${p.rot}) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ${p.net.to.str}) 38 | (pad 2 smd rect (at 1.65 0 ${p.rot}) (size 0.9 1.2) (layers B.Cu B.Paste B.Mask) ${p.net.from.str}) 39 | (pad 1 smd rect (at -1.65 0 ${p.rot}) (size 0.9 1.2) (layers B.Cu B.Paste B.Mask) ${p.net.to.str}) 40 | (pad 2 smd rect (at 1.65 0 ${p.rot}) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ${p.net.from.str}) 41 | 42 | ${''/* THT terminals */} 43 | (pad 1 thru_hole circle (at 3.81 0 ${p.rot}) (size 1.905 1.905) (drill 0.9906) (layers *.Cu *.Mask) ${p.net.from.str}) 44 | (pad 2 thru_hole rect (at -3.81 0 ${p.rot}) (size 1.778 1.778) (drill 0.9906) (layers *.Cu *.Mask) ${p.net.to.str}) 45 | ) 46 | 47 | ` 48 | } -------------------------------------------------------------------------------- /test/outlines/rectangles___outlines_outline_dxf.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -10 53 | 20 54 | -10 55 | 11 56 | 10 57 | 21 58 | -10 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 10 65 | 20 66 | -10 67 | 11 68 | 10 69 | 21 70 | 0 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 10 77 | 20 78 | 10 79 | 11 80 | -5 81 | 21 82 | 10 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -10 89 | 20 90 | 5 91 | 11 92 | -10 93 | 21 94 | -10 95 | 0 96 | LINE 97 | 8 98 | 0 99 | 10 100 | 30 101 | 20 102 | -10 103 | 11 104 | 50 105 | 21 106 | -10 107 | 0 108 | LINE 109 | 8 110 | 0 111 | 10 112 | 50 113 | 20 114 | -10 115 | 11 116 | 50 117 | 21 118 | 5 119 | 0 120 | LINE 121 | 8 122 | 0 123 | 10 124 | 45 125 | 20 126 | 10 127 | 11 128 | 30 129 | 21 130 | 10 131 | 0 132 | LINE 133 | 8 134 | 0 135 | 10 136 | 30 137 | 20 138 | 0 139 | 11 140 | 30 141 | 21 142 | -10 143 | 0 144 | LINE 145 | 8 146 | 0 147 | 10 148 | 10 149 | 20 150 | 0 151 | 11 152 | 30 153 | 21 154 | 0 155 | 0 156 | LINE 157 | 8 158 | 0 159 | 10 160 | 30 161 | 20 162 | 10 163 | 11 164 | 30 165 | 21 166 | 40 167 | 0 168 | LINE 169 | 8 170 | 0 171 | 10 172 | 30 173 | 20 174 | 40 175 | 11 176 | 10 177 | 21 178 | 40 179 | 0 180 | LINE 181 | 8 182 | 0 183 | 10 184 | 10 185 | 20 186 | 40 187 | 11 188 | 10 189 | 21 190 | 10 191 | 0 192 | LINE 193 | 8 194 | 0 195 | 10 196 | -15 197 | 20 198 | 5 199 | 11 200 | -10 201 | 21 202 | 5 203 | 0 204 | LINE 205 | 8 206 | 0 207 | 10 208 | -5 209 | 20 210 | 10 211 | 11 212 | -5 213 | 21 214 | 15 215 | 0 216 | LINE 217 | 8 218 | 0 219 | 10 220 | -5 221 | 20 222 | 15 223 | 11 224 | -15 225 | 21 226 | 15 227 | 0 228 | LINE 229 | 8 230 | 0 231 | 10 232 | -15 233 | 20 234 | 15 235 | 11 236 | -15 237 | 21 238 | 5 239 | 0 240 | LINE 241 | 8 242 | 0 243 | 10 244 | 50 245 | 20 246 | 5 247 | 11 248 | 55 249 | 21 250 | 5 251 | 0 252 | LINE 253 | 8 254 | 0 255 | 10 256 | 55 257 | 20 258 | 5 259 | 11 260 | 55 261 | 21 262 | 15 263 | 0 264 | LINE 265 | 8 266 | 0 267 | 10 268 | 55 269 | 20 270 | 15 271 | 11 272 | 45 273 | 21 274 | 15 275 | 0 276 | LINE 277 | 8 278 | 0 279 | 10 280 | 45 281 | 20 282 | 15 283 | 11 284 | 45 285 | 21 286 | 10 287 | 0 288 | ENDSEC 289 | 0 290 | EOF -------------------------------------------------------------------------------- /src/assert.js: -------------------------------------------------------------------------------- 1 | const m = require('makerjs') 2 | const u = require('./utils') 3 | const Point = require('./point') 4 | const mathjs = require('mathjs') 5 | 6 | const mathnum = exports.mathnum = raw => units => { 7 | return mathjs.evaluate(`${raw}`, units || {}) 8 | } 9 | 10 | const assert = exports.assert = (exp, msg) => { 11 | if (!exp) { 12 | throw new Error(msg) 13 | } 14 | } 15 | 16 | const type = exports.type = val => units => { 17 | if (Array.isArray(val)) return 'array' 18 | if (val === null) return 'null' 19 | try { 20 | const num = mathnum(val)(units) 21 | if (typeof num === 'number') return 'number' 22 | } catch (err) {} 23 | return typeof val 24 | } 25 | 26 | const sane = exports.sane = (val, name, _type) => units => { 27 | assert(type(val)(units) == _type, `Field "${name}" should be of type ${_type}!`) 28 | if (_type == 'number') return mathnum(val)(units) 29 | return val 30 | } 31 | 32 | const unexpected = exports.unexpected = (obj, name, expected) => { 33 | const sane_obj = sane(obj, name, 'object')() 34 | for (const key of Object.keys(sane_obj)) { 35 | assert(expected.includes(key), `Unexpected key "${key}" within field "${name}"!`) 36 | } 37 | } 38 | 39 | const _in = exports.in = (raw, name, arr) => { 40 | assert(arr.includes(raw), `Field "${name}" should be one of [${arr.join(', ')}]!`) 41 | return raw 42 | } 43 | 44 | const arr = exports.arr = (raw, name, length, _type, _default) => units => { 45 | assert(type(raw)(units) == 'array', `Field "${name}" should be an array!`) 46 | assert(length == 0 || raw.length == length, `Field "${name}" should be an array of length ${length}!`) 47 | raw = raw.map(val => val || _default) 48 | raw.map(val => assert(type(val)(units) == _type, `Field "${name}" should contain ${_type}s!`)) 49 | if (_type == 'number') { 50 | raw = raw.map(val => mathnum(val)(units)) 51 | } 52 | return raw 53 | } 54 | 55 | const numarr = exports.numarr = (raw, name, length) => units => arr(raw, name, length, 'number', 0)(units) 56 | const strarr = exports.strarr = (raw, name) => arr(raw, name, 0, 'string', '')() 57 | 58 | const xy = exports.xy = (raw, name) => units => numarr(raw, name, 2)(units) 59 | 60 | const wh = exports.wh = (raw, name) => units => { 61 | if (!Array.isArray(raw)) raw = [raw, raw] 62 | return xy(raw, name)(units) 63 | } 64 | 65 | exports.trbl = (raw, name) => units => { 66 | if (!Array.isArray(raw)) raw = [raw, raw, raw, raw] 67 | if (raw.length == 2) raw = [raw[1], raw[0], raw[1], raw[0]] 68 | return numarr(raw, name, 4, 'number', 0)(units) 69 | } 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | # Project specific 119 | output 120 | temp* 121 | .DS_Store 122 | *.swp 123 | -------------------------------------------------------------------------------- /src/kle.js: -------------------------------------------------------------------------------- 1 | const u = require('./utils') 2 | const kle = require('kle-serial') 3 | const yaml = require('js-yaml') 4 | 5 | exports.convert = (config, logger) => { 6 | const keyboard = kle.Serial.deserialize(config) 7 | const result = {points: {zones: {}}, pcbs: {main: {}}} 8 | 9 | // if the keyboard notes are valid YAML/JSON, they get added to each key as metadata 10 | let meta 11 | try { 12 | meta = yaml.load(keyboard.meta.notes) 13 | } catch (ex) { 14 | // notes were not valid YAML/JSON, oh well... 15 | } 16 | meta = meta || {} 17 | 18 | let index = 1 19 | for (const key of keyboard.keys) { 20 | const id = `key${index++}` 21 | const colid = `${id}col` 22 | const rowid = `${id}row` 23 | // we try to look at the first non-empty label 24 | const label = key.labels.filter(e => !!e)[0] || '' 25 | 26 | // PCB nets can be specified through key labels 27 | let row_net = id 28 | let col_net = 'GND' 29 | if (label.match(/^\d+_\d+$/)) { 30 | const parts = label.split('_') 31 | row_net = `row_${parts[0]}` 32 | col_net = `col_${parts[1]}` 33 | } 34 | 35 | // need to account for keycap sizes, as KLE anchors 36 | // at the corners, while we consider the centers 37 | const x = key.x + (key.width - 1) / 2 38 | const y = key.y + (key.height - 1) / 2 39 | 40 | // KLE deals in absolute rotation origins so we calculate 41 | // a relative difference as an origin for the column rotation 42 | // again, considering corner vs. center with the extra half width/height 43 | const diff_x = key.rotation_x - (key.x + key.width / 2) 44 | const diff_y = key.rotation_y - (key.y + key.height / 2) 45 | 46 | // anchoring the per-key zone to the KLE-computed coords 47 | const converted = { 48 | anchor: { 49 | shift: [`${x} u`, `${-y} u`], 50 | }, 51 | columns: {} 52 | } 53 | 54 | // adding a column-level rotation with origin 55 | converted.columns[colid] = { 56 | rotate: -key.rotation_angle, 57 | origin: [`${diff_x} u`, `${-diff_y} u`], 58 | rows: {} 59 | } 60 | 61 | // passing along metadata to each key 62 | converted.columns[colid].rows[rowid] = u.deepcopy(meta) 63 | converted.columns[colid].rows[rowid].width = key.width 64 | converted.columns[colid].rows[rowid].height = key.height 65 | converted.columns[colid].rows[rowid].label = label 66 | converted.columns[colid].rows[rowid].column_net = col_net 67 | converted.columns[colid].rows[rowid].row_net = row_net 68 | 69 | result.points.zones[id] = converted 70 | } 71 | 72 | return result 73 | } -------------------------------------------------------------------------------- /src/footprints/rgb.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | din: undefined, 4 | dout: undefined, 5 | VCC: 'VCC', 6 | GND: 'GND' 7 | }, 8 | params: { 9 | class: 'LED', 10 | side: 'F' 11 | }, 12 | body: p => ` 13 | 14 | (module WS2812B (layer F.Cu) (tedit 53BEE615) 15 | 16 | ${p.at /* parametric position */} 17 | 18 | ${'' /* footprint reference */} 19 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 20 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 21 | 22 | (fp_line (start -1.75 -1.75) (end -1.75 1.75) (layer ${p.param.side}.SilkS) (width 0.15)) 23 | (fp_line (start -1.75 1.75) (end 1.75 1.75) (layer ${p.param.side}.SilkS) (width 0.15)) 24 | (fp_line (start 1.75 1.75) (end 1.75 -1.75) (layer ${p.param.side}.SilkS) (width 0.15)) 25 | (fp_line (start 1.75 -1.75) (end -1.75 -1.75) (layer ${p.param.side}.SilkS) (width 0.15)) 26 | 27 | (fp_line (start -2.5 -2.5) (end -2.5 2.5) (layer ${p.param.side}.SilkS) (width 0.15)) 28 | (fp_line (start -2.5 2.5) (end 2.5 2.5) (layer ${p.param.side}.SilkS) (width 0.15)) 29 | (fp_line (start 2.5 2.5) (end 2.5 -2.5) (layer ${p.param.side}.SilkS) (width 0.15)) 30 | (fp_line (start 2.5 -2.5) (end -2.5 -2.5) (layer ${p.param.side}.SilkS) (width 0.15)) 31 | 32 | (fp_poly (pts (xy 4 2.2) (xy 4 0.375) (xy 5 1.2875)) (layer ${p.param.side}.SilkS) (width 0.1)) 33 | 34 | (pad 1 smd rect (at -2.2 -0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.VCC.str}) 35 | (pad 2 smd rect (at -2.2 0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.dout.str}) 36 | (pad 3 smd rect (at 2.2 0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.GND.str}) 37 | (pad 4 smd rect (at 2.2 -0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.din.str}) 38 | 39 | (pad 11 smd rect (at -2.5 -1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.VCC.str}) 40 | (pad 22 smd rect (at -2.5 1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.dout.str}) 41 | (pad 33 smd rect (at 2.5 1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.GND.str}) 42 | (pad 44 smd rect (at 2.5 -1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.din.str}) 43 | 44 | ) 45 | 46 | ` 47 | } -------------------------------------------------------------------------------- /test/cli/big/reference/pcbs/_export.kicad_pcb: -------------------------------------------------------------------------------- 1 | 2 | 3 | (kicad_pcb (version 20171130) (host pcbnew 5.1.6) 4 | 5 | (page A3) 6 | (title_block 7 | (title _export) 8 | (rev v1.0.0) 9 | (company Unknown) 10 | ) 11 | 12 | (general 13 | (thickness 1.6) 14 | ) 15 | 16 | (layers 17 | (0 F.Cu signal) 18 | (31 B.Cu signal) 19 | (32 B.Adhes user) 20 | (33 F.Adhes user) 21 | (34 B.Paste user) 22 | (35 F.Paste user) 23 | (36 B.SilkS user) 24 | (37 F.SilkS user) 25 | (38 B.Mask user) 26 | (39 F.Mask user) 27 | (40 Dwgs.User user) 28 | (41 Cmts.User user) 29 | (42 Eco1.User user) 30 | (43 Eco2.User user) 31 | (44 Edge.Cuts user) 32 | (45 Margin user) 33 | (46 B.CrtYd user) 34 | (47 F.CrtYd user) 35 | (48 B.Fab user) 36 | (49 F.Fab user) 37 | ) 38 | 39 | (setup 40 | (last_trace_width 0.25) 41 | (trace_clearance 0.2) 42 | (zone_clearance 0.508) 43 | (zone_45_only no) 44 | (trace_min 0.2) 45 | (via_size 0.8) 46 | (via_drill 0.4) 47 | (via_min_size 0.4) 48 | (via_min_drill 0.3) 49 | (uvia_size 0.3) 50 | (uvia_drill 0.1) 51 | (uvias_allowed no) 52 | (uvia_min_size 0.2) 53 | (uvia_min_drill 0.1) 54 | (edge_width 0.05) 55 | (segment_width 0.2) 56 | (pcb_text_width 0.3) 57 | (pcb_text_size 1.5 1.5) 58 | (mod_edge_width 0.12) 59 | (mod_text_size 1 1) 60 | (mod_text_width 0.15) 61 | (pad_size 1.524 1.524) 62 | (pad_drill 0.762) 63 | (pad_to_mask_clearance 0.05) 64 | (aux_axis_origin 0 0) 65 | (visible_elements FFFFFF7F) 66 | (pcbplotparams 67 | (layerselection 0x010fc_ffffffff) 68 | (usegerberextensions false) 69 | (usegerberattributes true) 70 | (usegerberadvancedattributes true) 71 | (creategerberjobfile true) 72 | (excludeedgelayer true) 73 | (linewidth 0.100000) 74 | (plotframeref false) 75 | (viasonmask false) 76 | (mode 1) 77 | (useauxorigin false) 78 | (hpglpennumber 1) 79 | (hpglpenspeed 20) 80 | (hpglpendiameter 15.000000) 81 | (psnegative false) 82 | (psa4output false) 83 | (plotreference true) 84 | (plotvalue true) 85 | (plotinvisibletext false) 86 | (padsonsilk false) 87 | (subtractmaskfromsilk false) 88 | (outputformat 1) 89 | (mirror false) 90 | (drillshape 1) 91 | (scaleselection 1) 92 | (outputdirectory "")) 93 | ) 94 | 95 | (net 0 "") 96 | 97 | (net_class Default "This is the default net class." 98 | (clearance 0.2) 99 | (trace_width 0.25) 100 | (via_dia 0.8) 101 | (via_drill 0.4) 102 | (uvia_dia 0.3) 103 | (uvia_drill 0.1) 104 | (add_net "") 105 | ) 106 | 107 | 108 | 109 | 110 | ) 111 | 112 | -------------------------------------------------------------------------------- /test/cli/big/reference/pcbs/export.kicad_pcb: -------------------------------------------------------------------------------- 1 | 2 | 3 | (kicad_pcb (version 20171130) (host pcbnew 5.1.6) 4 | 5 | (page A3) 6 | (title_block 7 | (title export) 8 | (rev v1.0.0) 9 | (company Unknown) 10 | ) 11 | 12 | (general 13 | (thickness 1.6) 14 | ) 15 | 16 | (layers 17 | (0 F.Cu signal) 18 | (31 B.Cu signal) 19 | (32 B.Adhes user) 20 | (33 F.Adhes user) 21 | (34 B.Paste user) 22 | (35 F.Paste user) 23 | (36 B.SilkS user) 24 | (37 F.SilkS user) 25 | (38 B.Mask user) 26 | (39 F.Mask user) 27 | (40 Dwgs.User user) 28 | (41 Cmts.User user) 29 | (42 Eco1.User user) 30 | (43 Eco2.User user) 31 | (44 Edge.Cuts user) 32 | (45 Margin user) 33 | (46 B.CrtYd user) 34 | (47 F.CrtYd user) 35 | (48 B.Fab user) 36 | (49 F.Fab user) 37 | ) 38 | 39 | (setup 40 | (last_trace_width 0.25) 41 | (trace_clearance 0.2) 42 | (zone_clearance 0.508) 43 | (zone_45_only no) 44 | (trace_min 0.2) 45 | (via_size 0.8) 46 | (via_drill 0.4) 47 | (via_min_size 0.4) 48 | (via_min_drill 0.3) 49 | (uvia_size 0.3) 50 | (uvia_drill 0.1) 51 | (uvias_allowed no) 52 | (uvia_min_size 0.2) 53 | (uvia_min_drill 0.1) 54 | (edge_width 0.05) 55 | (segment_width 0.2) 56 | (pcb_text_width 0.3) 57 | (pcb_text_size 1.5 1.5) 58 | (mod_edge_width 0.12) 59 | (mod_text_size 1 1) 60 | (mod_text_width 0.15) 61 | (pad_size 1.524 1.524) 62 | (pad_drill 0.762) 63 | (pad_to_mask_clearance 0.05) 64 | (aux_axis_origin 0 0) 65 | (visible_elements FFFFFF7F) 66 | (pcbplotparams 67 | (layerselection 0x010fc_ffffffff) 68 | (usegerberextensions false) 69 | (usegerberattributes true) 70 | (usegerberadvancedattributes true) 71 | (creategerberjobfile true) 72 | (excludeedgelayer true) 73 | (linewidth 0.100000) 74 | (plotframeref false) 75 | (viasonmask false) 76 | (mode 1) 77 | (useauxorigin false) 78 | (hpglpennumber 1) 79 | (hpglpenspeed 20) 80 | (hpglpendiameter 15.000000) 81 | (psnegative false) 82 | (psa4output false) 83 | (plotreference true) 84 | (plotvalue true) 85 | (plotinvisibletext false) 86 | (padsonsilk false) 87 | (subtractmaskfromsilk false) 88 | (outputformat 1) 89 | (mirror false) 90 | (drillshape 1) 91 | (scaleselection 1) 92 | (outputdirectory "")) 93 | ) 94 | 95 | (net 0 "") 96 | 97 | (net_class Default "This is the default net class." 98 | (clearance 0.2) 99 | (trace_width 0.25) 100 | (via_dia 0.8) 101 | (via_drill 0.4) 102 | (uvia_dia 0.3) 103 | (uvia_drill 0.1) 104 | (add_net "") 105 | ) 106 | 107 | 108 | 109 | 110 | ) 111 | 112 | -------------------------------------------------------------------------------- /test/cli/medium/reference/pcbs/export.kicad_pcb: -------------------------------------------------------------------------------- 1 | 2 | 3 | (kicad_pcb (version 20171130) (host pcbnew 5.1.6) 4 | 5 | (page A3) 6 | (title_block 7 | (title export) 8 | (rev v1.0.0) 9 | (company Unknown) 10 | ) 11 | 12 | (general 13 | (thickness 1.6) 14 | ) 15 | 16 | (layers 17 | (0 F.Cu signal) 18 | (31 B.Cu signal) 19 | (32 B.Adhes user) 20 | (33 F.Adhes user) 21 | (34 B.Paste user) 22 | (35 F.Paste user) 23 | (36 B.SilkS user) 24 | (37 F.SilkS user) 25 | (38 B.Mask user) 26 | (39 F.Mask user) 27 | (40 Dwgs.User user) 28 | (41 Cmts.User user) 29 | (42 Eco1.User user) 30 | (43 Eco2.User user) 31 | (44 Edge.Cuts user) 32 | (45 Margin user) 33 | (46 B.CrtYd user) 34 | (47 F.CrtYd user) 35 | (48 B.Fab user) 36 | (49 F.Fab user) 37 | ) 38 | 39 | (setup 40 | (last_trace_width 0.25) 41 | (trace_clearance 0.2) 42 | (zone_clearance 0.508) 43 | (zone_45_only no) 44 | (trace_min 0.2) 45 | (via_size 0.8) 46 | (via_drill 0.4) 47 | (via_min_size 0.4) 48 | (via_min_drill 0.3) 49 | (uvia_size 0.3) 50 | (uvia_drill 0.1) 51 | (uvias_allowed no) 52 | (uvia_min_size 0.2) 53 | (uvia_min_drill 0.1) 54 | (edge_width 0.05) 55 | (segment_width 0.2) 56 | (pcb_text_width 0.3) 57 | (pcb_text_size 1.5 1.5) 58 | (mod_edge_width 0.12) 59 | (mod_text_size 1 1) 60 | (mod_text_width 0.15) 61 | (pad_size 1.524 1.524) 62 | (pad_drill 0.762) 63 | (pad_to_mask_clearance 0.05) 64 | (aux_axis_origin 0 0) 65 | (visible_elements FFFFFF7F) 66 | (pcbplotparams 67 | (layerselection 0x010fc_ffffffff) 68 | (usegerberextensions false) 69 | (usegerberattributes true) 70 | (usegerberadvancedattributes true) 71 | (creategerberjobfile true) 72 | (excludeedgelayer true) 73 | (linewidth 0.100000) 74 | (plotframeref false) 75 | (viasonmask false) 76 | (mode 1) 77 | (useauxorigin false) 78 | (hpglpennumber 1) 79 | (hpglpenspeed 20) 80 | (hpglpendiameter 15.000000) 81 | (psnegative false) 82 | (psa4output false) 83 | (plotreference true) 84 | (plotvalue true) 85 | (plotinvisibletext false) 86 | (padsonsilk false) 87 | (subtractmaskfromsilk false) 88 | (outputformat 1) 89 | (mirror false) 90 | (drillshape 1) 91 | (scaleselection 1) 92 | (outputdirectory "")) 93 | ) 94 | 95 | (net 0 "") 96 | 97 | (net_class Default "This is the default net class." 98 | (clearance 0.2) 99 | (trace_width 0.25) 100 | (via_dia 0.8) 101 | (via_drill 0.4) 102 | (uvia_dia 0.3) 103 | (uvia_drill 0.1) 104 | (add_net "") 105 | ) 106 | 107 | 108 | 109 | 110 | ) 111 | 112 | -------------------------------------------------------------------------------- /src/anchor.js: -------------------------------------------------------------------------------- 1 | const u = require('./utils') 2 | const a = require('./assert') 3 | const Point = require('./point') 4 | 5 | const mirror_ref = exports.mirror = (ref, mirror) => { 6 | if (mirror) { 7 | if (ref.startsWith('mirror_')) { 8 | return ref.substring(7) 9 | } else { 10 | return 'mirror_' + ref 11 | } 12 | } 13 | return ref 14 | } 15 | 16 | const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, default_point=new Point(), mirror=false) => units => { 17 | if (a.type(raw)() == 'array') { 18 | // recursive call with incremental default_point mods, according to `affect`s 19 | let current = default_point.clone() 20 | for (const step of raw) { 21 | current = anchor(step, name, points, check_unexpected, current, mirror)(units) 22 | } 23 | return current 24 | } 25 | if (check_unexpected) a.unexpected(raw, name, ['ref', 'orient', 'shift', 'rotate', 'affect']) 26 | let point = default_point.clone() 27 | if (raw.ref !== undefined) { 28 | if (a.type(raw.ref)() == 'array') { 29 | // averaging multiple anchors 30 | let x = 0, y = 0, r = 0 31 | const len = raw.ref.length 32 | for (const ref of raw.ref) { 33 | const parsed_ref = mirror_ref(ref, mirror) 34 | a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`) 35 | const resolved = points[parsed_ref] 36 | x += resolved.x 37 | y += resolved.y 38 | r += resolved.r 39 | } 40 | point = new Point(x / len, y / len, r / len) 41 | } else { 42 | const parsed_ref = mirror_ref(raw.ref, mirror) 43 | a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`) 44 | point = points[parsed_ref].clone() 45 | } 46 | } 47 | if (raw.orient !== undefined) { 48 | let angle = a.sane(raw.orient, `${name}.orient`, 'number')(units) 49 | if (point.meta.mirrored) { 50 | angle = -angle 51 | } 52 | point.r += angle 53 | } 54 | if (raw.shift !== undefined) { 55 | let xyval = a.wh(raw.shift, `${name}.shift`)(units) 56 | if (point.meta.mirrored) { 57 | xyval[0] = -xyval[0] 58 | } 59 | point.shift(xyval, true) 60 | } 61 | if (raw.rotate !== undefined) { 62 | let angle = a.sane(raw.rotate, `${name}.rotate`, 'number')(units) 63 | if (point.meta.mirrored) { 64 | angle = -angle 65 | } 66 | point.r += angle 67 | } 68 | if (raw.affect !== undefined) { 69 | const candidate = point.clone() 70 | point = default_point.clone() 71 | point.meta = candidate.meta 72 | let affect = raw.affect 73 | if (a.type(affect)() == 'string') affect = affect.split('') 74 | affect = a.strarr(affect, `${name}.affect`) 75 | let i = 0 76 | for (const aff of affect) { 77 | a.in(aff, `${name}.affect[${++i}]`, ['x', 'y', 'r']) 78 | point[aff] = candidate[aff] 79 | } 80 | } 81 | return point 82 | } -------------------------------------------------------------------------------- /src/footprints/slider.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | from: undefined, 4 | to: undefined 5 | }, 6 | params: { 7 | class: 'T', // for Toggle 8 | side: 'F' 9 | }, 10 | body: p => { 11 | 12 | const left = p.param.side == 'F' ? '-' : '' 13 | const right = p.param.side == 'F' ? '' : '-' 14 | 15 | return ` 16 | 17 | (module E73:SPDT_C128955 (layer F.Cu) (tstamp 5BF2CC3C) 18 | 19 | ${p.at /* parametric position */} 20 | 21 | ${'' /* footprint reference */} 22 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 23 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 24 | 25 | ${'' /* outline */} 26 | (fp_line (start 1.95 -1.35) (end -1.95 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 27 | (fp_line (start 0 -1.35) (end -3.3 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 28 | (fp_line (start -3.3 -1.35) (end -3.3 1.5) (layer ${p.param.side}.SilkS) (width 0.15)) 29 | (fp_line (start -3.3 1.5) (end 3.3 1.5) (layer ${p.param.side}.SilkS) (width 0.15)) 30 | (fp_line (start 3.3 1.5) (end 3.3 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 31 | (fp_line (start 0 -1.35) (end 3.3 -1.35) (layer ${p.param.side}.SilkS) (width 0.15)) 32 | 33 | ${'' /* extra indicator for the slider */} 34 | (fp_line (start -1.95 -3.85) (end 1.95 -3.85) (layer Dwgs.User) (width 0.15)) 35 | (fp_line (start 1.95 -3.85) (end 1.95 -1.35) (layer Dwgs.User) (width 0.15)) 36 | (fp_line (start -1.95 -1.35) (end -1.95 -3.85) (layer Dwgs.User) (width 0.15)) 37 | 38 | ${'' /* bottom cutouts */} 39 | (pad "" np_thru_hole circle (at 1.5 0) (size 1 1) (drill 0.9) (layers *.Cu *.Mask)) 40 | (pad "" np_thru_hole circle (at -1.5 0) (size 1 1) (drill 0.9) (layers *.Cu *.Mask)) 41 | 42 | ${'' /* pins */} 43 | (pad 1 smd rect (at ${right}2.25 2.075 ${p.rot}) (size 0.9 1.25) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.from.str}) 44 | (pad 2 smd rect (at ${left}0.75 2.075 ${p.rot}) (size 0.9 1.25) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.to.str}) 45 | (pad 3 smd rect (at ${left}2.25 2.075 ${p.rot}) (size 0.9 1.25) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask)) 46 | 47 | ${'' /* side mounts */} 48 | (pad "" smd rect (at 3.7 -1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask)) 49 | (pad "" smd rect (at 3.7 1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask)) 50 | (pad "" smd rect (at -3.7 1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask)) 51 | (pad "" smd rect (at -3.7 -1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask)) 52 | ) 53 | 54 | ` 55 | } 56 | } -------------------------------------------------------------------------------- /src/footprints/trrs.js: -------------------------------------------------------------------------------- 1 | // TRRS-PJ-320A-dual 2 | // _________________ 3 | // | (1) (3) (4)| 4 | // | | 5 | // |___(2)__________| 6 | // 7 | // Nets 8 | // A: corresponds to pin 1 9 | // B: corresponds to pin 2 10 | // C: corresponds to pin 3 11 | // D: corresponds to pin 4 12 | // Params 13 | // reverse: default is false 14 | // if true, will flip the footprint such that the pcb can be reversible 15 | // symmetric: default is false 16 | // if true, will only work if reverse is also true 17 | // this will cause the footprint to be symmetrical on each half 18 | // pins 1 and 2 must be identical if symmetric is true, as they will overlap 19 | 20 | module.exports = { 21 | nets: { 22 | A: undefined, 23 | B: undefined, 24 | C: undefined, 25 | D: undefined 26 | }, 27 | params: { 28 | class: 'TRRS', 29 | reverse: false, 30 | symmetric: false 31 | }, 32 | body: p => { 33 | const standard = ` 34 | (module TRRS-PJ-320A-dual (layer F.Cu) (tedit 5970F8E5) 35 | 36 | ${p.at /* parametric position */} 37 | 38 | ${'' /* footprint reference */} 39 | (fp_text reference REF** (at 0 14.2) (layer Dwgs.User) (effects (font (size 1 1) (thickness 0.15)))) 40 | (fp_text value TRRS-PJ-320A-dual (at 0 -5.6) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15)))) 41 | 42 | ${''/* corner marks */} 43 | (fp_line (start 0.5 -2) (end -5.1 -2) (layer Dwgs.User) (width 0.15)) 44 | (fp_line (start -5.1 0) (end -5.1 -2) (layer Dwgs.User) (width 0.15)) 45 | (fp_line (start 0.5 0) (end 0.5 -2) (layer Dwgs.User) (width 0.15)) 46 | (fp_line (start -5.35 0) (end -5.35 12.1) (layer Dwgs.User) (width 0.15)) 47 | (fp_line (start 0.75 0) (end 0.75 12.1) (layer Dwgs.User) (width 0.15)) 48 | (fp_line (start 0.75 12.1) (end -5.35 12.1) (layer Dwgs.User) (width 0.15)) 49 | (fp_line (start 0.75 0) (end -5.35 0) (layer Dwgs.User) (width 0.15)) 50 | 51 | ${''/* stabilizers */} 52 | (pad "" np_thru_hole circle (at -2.3 8.6) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask)) 53 | (pad "" np_thru_hole circle (at -2.3 1.6) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask)) 54 | ` 55 | function pins(def_neg, def_pos) { 56 | return ` 57 | (pad 1 thru_hole oval (at ${def_neg} 11.3 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.A.str}) 58 | (pad 2 thru_hole oval (at ${def_pos} 10.2 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.B.str}) 59 | (pad 3 thru_hole oval (at ${def_pos} 6.2 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.C.str}) 60 | (pad 4 thru_hole oval (at ${def_pos} 3.2 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.D.str}) 61 | ` 62 | } 63 | if(p.param.reverse & p.param.symmetric) { 64 | return ` 65 | ${standard} 66 | ${pins('0', '-4.6')} 67 | ${pins('-4.6', '0')}) 68 | ` 69 | } else if(p.param.reverse) { 70 | return ` 71 | ${standard} 72 | ${pins('-4.6', '0')} 73 | ${pins('4.6', '0')}) 74 | ` 75 | } else { 76 | return ` 77 | ${standard} 78 | ${pins('-4.6', '0')}) 79 | ` 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /input/config-1april.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | key: 3 | padding: cy 4 | footprints: 5 | choc_hotswap: 6 | type: choc 7 | nets: 8 | from: =column_net 9 | to: GND 10 | params: 11 | keycaps: true 12 | reverse: true 13 | hotswap: true 14 | choc: 15 | type: choc 16 | anchor: 17 | rotate: 180 18 | nets: 19 | from: =column_net 20 | to: GND 21 | params: 22 | keycaps: true 23 | reverse: true 24 | zones: 25 | board: 26 | columns: 27 | pinkie: 28 | rows: 29 | home: 30 | bind: [1,2,1,1] 31 | column_net: P1 32 | outlines: 33 | exports: 34 | raw: 35 | - type: keys 36 | side: left 37 | size: [1cx , 1cx] 38 | - type: rectangle 39 | size: [41.5, 20] 40 | anchor: 41 | ref: board_pinkie_home 42 | shift: [-51.5, -10] 43 | cutouta: 44 | - type: outline 45 | name: raw 46 | fillet: 2 47 | cutoutb: 48 | - type: outline 49 | name: cutouta 50 | fillet: 1 51 | cutout: 52 | - type: outline 53 | name: cutoutb 54 | fillet: 0.5 55 | keycap_outlines: 56 | - type: keys 57 | side: left 58 | size: [1cx - 0.5, 1cy - 0.5] # Choc keycaps are 17.5 x 16.5 59 | bound: false 60 | pcbs: 61 | 1april: 62 | outlines: 63 | main: 64 | outline: cutout 65 | footprints: 66 | promicro: 67 | type: promicro_pretty 68 | anchor: 69 | ref: board_pinkie_home 70 | shift: [-36, 0] 71 | pcm12: 72 | type: pcm12 73 | anchor: 74 | ref: board_pinkie_home 75 | shift: [-15, 7.5] 76 | rotate: 180 77 | nets: 78 | from: pos 79 | to: RAW 80 | params: 81 | reverse: true 82 | via3: 83 | type: via 84 | anchor: 85 | ref: board_pinkie_home 86 | shift: [-16,4] 87 | nets: 88 | net: RAW 89 | via4: 90 | type: via 91 | anchor: 92 | ref: board_pinkie_home 93 | shift: [-14,4] 94 | nets: 95 | net: pos 96 | via5: 97 | type: via 98 | anchor: 99 | ref: board_pinkie_home 100 | shift: [-15,-3.5] 101 | nets: 102 | net: GND 103 | via6: 104 | type: via 105 | anchor: 106 | ref: board_pinkie_home 107 | shift: [-13,-3.5] 108 | nets: 109 | net: RST 110 | b3u1000p: 111 | type: b3u1000p 112 | nets: 113 | r1: RST 114 | r2: GND 115 | anchor: 116 | ref: board_pinkie_home 117 | shift: [-14.5, -7] 118 | rotate: 0 119 | params: 120 | reverse: true 121 | bat: 122 | type: bat 123 | nets: 124 | neg: GND 125 | anchor: 126 | ref: board_pinkie_home 127 | shift: [-13 , 0] 128 | rotate: 0 129 | -------------------------------------------------------------------------------- /test/unit/prepare.js: -------------------------------------------------------------------------------- 1 | const p = require('../../src/prepare') 2 | 3 | describe('Prepare', function() { 4 | it('unnest', function() { 5 | p.unnest({'a.b.c': 1}).should.deep.equal({a: {b: {c: 1}}}) 6 | p.unnest({'a.b.c': { 7 | d: 2, 8 | 'e.f': 3 9 | }}).should.deep.equal({a: {b: {c: {d: 2, e: {f: 3}}}}}) 10 | p.unnest({'root': [{ 11 | 'a.b': 1 12 | }]}).should.deep.equal({root: [{a: {b: 1}}]}) 13 | }) 14 | 15 | it('extend', function() { 16 | p.extend('something', undefined).should.equal('something') 17 | should.equal(p.extend('something', '$unset'), undefined) 18 | p.extend(undefined, 'something').should.equal('something') 19 | p.extend(28, 'something').should.equal('something') 20 | p.extend('something', 28).should.equal(28) 21 | p.extend(27, 28).should.equal(28) 22 | p.extend({a: 1, c: 1, d: 1}, {b: 2, c: 2, d: '$unset'}).should.deep.equal({a: 1, b: 2, c: 2}) 23 | p.extend([3, 2, 1], [null, 4, 5]).should.deep.equal([3, 4, 5]) 24 | }) 25 | 26 | it('inherit', function() { 27 | p.inherit({ 28 | a: { 29 | x: 1, 30 | y: 2 31 | }, 32 | b: { 33 | $extends: 'a', 34 | z: 3 35 | }, 36 | c: { 37 | $extends: ['b'], 38 | w: 4 39 | } 40 | }).c.should.deep.equal({ 41 | x: 1, 42 | y: 2, 43 | z: 3, 44 | w: 4 45 | }) 46 | }) 47 | 48 | it('parameterize', function() { 49 | p.parameterize(1).should.equal(1) 50 | 51 | p.parameterize({ 52 | unused: { 53 | $params: ['PAR'] 54 | }, 55 | skip: { 56 | $skip: true 57 | } 58 | }).should.deep.equal({}) 59 | 60 | p.parameterize({ 61 | decl: { 62 | a: 'PAR', 63 | $params: ['PAR'], 64 | $args: [1] 65 | } 66 | }).decl.should.deep.equal({ 67 | a: '1' 68 | }) 69 | 70 | p.parameterize({ 71 | decl: { 72 | normal_use: 'PAR1', 73 | sub: { 74 | nested_use: 'PAR2 * 2' 75 | }, 76 | $params: ['PAR1', 'PAR2'], 77 | $args: ['text', 14] 78 | } 79 | }).decl.should.deep.equal({ 80 | normal_use: 'text', 81 | sub: { 82 | nested_use: '14 * 2', 83 | } 84 | }) 85 | 86 | p.parameterize.bind(this, { 87 | decl: { 88 | $args: [1] 89 | } 90 | }).should.throw('missing') 91 | 92 | p.parameterize.bind(this, { 93 | decl: { 94 | $params: ['PAR1', 'PAR2'], 95 | $args: [1] 96 | } 97 | }).should.throw('match') 98 | 99 | p.parameterize.bind(this, { 100 | decl: { 101 | a: 'PAR', 102 | $params: ['PAR'], 103 | $args: ['in"jection'] 104 | } 105 | }).should.throw('valid') 106 | }) 107 | }) -------------------------------------------------------------------------------- /src/footprints/pcm12.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | nets: { 3 | from: 'from', 4 | to: 'to' 5 | }, 6 | params: { 7 | class: 'T', 8 | reverse: false 9 | }, 10 | body: p => { 11 | const standard = ` 12 | (module Button_Switch_SMD:SW_SPDT_PCM12 (layer F.Cu) (tedit 5A02FC95) 13 | ${p.at /* parametric position */} 14 | (descr "Ultraminiature Surface Mount Slide Switch, right-angle, https://www.ckswitches.com/media/1424/pcm.pdf") 15 | (attr smd) 16 | (fp_line (start -4.4 2.1) (end -4.4 -2.45) (layer F.CrtYd) (width 0.05)) 17 | (fp_line (start -1.65 2.1) (end -4.4 2.1) (layer F.CrtYd) (width 0.05)) 18 | (fp_line (start -1.65 3.4) (end -1.65 2.1) (layer F.CrtYd) (width 0.05)) 19 | (fp_line (start 1.65 3.4) (end -1.65 3.4) (layer F.CrtYd) (width 0.05)) 20 | (fp_line (start 1.65 2.1) (end 1.65 3.4) (layer F.CrtYd) (width 0.05)) 21 | (fp_line (start 4.4 2.1) (end 1.65 2.1) (layer F.CrtYd) (width 0.05)) 22 | (fp_line (start 4.4 -2.45) (end 4.4 2.1) (layer F.CrtYd) (width 0.05)) 23 | (fp_line (start -4.4 -2.45) (end 4.4 -2.45) (layer F.CrtYd) (width 0.05)) 24 | (fp_line (start 3.35 -1) (end -3.35 -1) (layer F.Fab) (width 0.1)) 25 | (fp_line (start 3.35 1.6) (end 3.35 -1) (layer F.Fab) (width 0.1)) 26 | (fp_line (start -3.35 1.6) (end 3.35 1.6) (layer F.Fab) (width 0.1)) 27 | (fp_line (start -3.35 -1) (end -3.35 1.6) (layer F.Fab) (width 0.1)) 28 | (fp_line (start -0.1 2.9) (end -0.1 1.6) (layer F.Fab) (width 0.1)) 29 | (fp_line (start -0.15 2.95) (end -0.1 2.9) (layer F.Fab) (width 0.1)) 30 | (fp_line (start -0.35 3.15) (end -0.15 2.95) (layer F.Fab) (width 0.1)) 31 | (fp_line (start -1.2 3.15) (end -0.35 3.15) (layer F.Fab) (width 0.1)) 32 | (fp_line (start -1.4 2.95) (end -1.2 3.15) (layer F.Fab) (width 0.1)) 33 | (fp_line (start -1.4 1.65) (end -1.4 2.95) (layer F.Fab) (width 0.1)) 34 | (fp_text user %R (at 0 -3.2) (layer F.Fab) 35 | (effects (font (size 1 1) (thickness 0.15))) 36 | ) 37 | 38 | ` 39 | function pins(def_neg, def_pos, def_side) { 40 | return ` 41 | ${''/* pins */} 42 | (pad "" np_thru_hole circle (at -1.5 0.33) (size 0.9 0.9) (drill 0.9) (layers *.Cu *.Mask)) 43 | (pad "" np_thru_hole circle (at 1.5 0.33) (size 0.9 0.9) (drill 0.9) (layers *.Cu *.Mask)) 44 | (pad 1 smd rect (at ${def_neg}2.25 -1.43) (size 0.7 1.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.from.str}) 45 | 46 | (pad 2 smd rect (at ${def_pos}0.75 -1.43) (size 0.7 1.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.to.str}) 47 | (pad 3 smd rect (at ${def_pos}2.25 -1.43) (size 0.7 1.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask)) 48 | (pad "" smd rect (at ${def_neg}3.65 1.43) (size 1 0.8) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask)) 49 | (pad "" smd rect (at ${def_pos}3.65 1.43) (size 1 0.8) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask)) 50 | (pad "" smd rect (at ${def_pos}3.65 -0.78) (size 1 0.8) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask)) 51 | (pad "" smd rect (at ${def_neg}3.65 -0.78) (size 1 0.8) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask)) 52 | ` 53 | } 54 | if(p.param.reverse) { 55 | return ` 56 | ${standard} 57 | ${pins('-', '', 'F')} 58 | ${pins('', '-', 'B')}) 59 | ` 60 | } else { 61 | return ` 62 | ${standard} 63 | ${pins('-', '', 'B')}) 64 | ` 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs-extra') 4 | const path = require('path') 5 | const yaml = require('js-yaml') 6 | const yargs = require('yargs') 7 | const ergogen = require('./ergogen') 8 | const pkg = require('../package.json') 9 | 10 | // command line args 11 | 12 | const args = yargs 13 | .option('output', { 14 | alias: 'o', 15 | default: path.resolve('output'), 16 | describe: 'Output folder', 17 | type: 'string' 18 | }) 19 | .option('debug', { 20 | alias: 'd', 21 | default: false, 22 | describe: 'Debug mode', 23 | type: 'boolean' 24 | }) 25 | .option('clean', { 26 | default: false, 27 | describe: 'Clean output dir before parsing', 28 | type: 'boolean' 29 | }) 30 | .argv 31 | 32 | // config reading 33 | 34 | const config_file = args._[0] 35 | if (!config_file) { 36 | console.error('Usage: ergogen [options]') 37 | process.exit(1) 38 | } 39 | 40 | let config_text 41 | try { 42 | config_text = fs.readFileSync(config_file).toString() 43 | } catch (err) { 44 | console.error(`Could not read config file "${config_file}": ${err}`) 45 | process.exit(2) 46 | } 47 | 48 | const title_suffix = args.debug ? ' (Debug Mode)' : '' 49 | console.log(`Ergogen v${pkg.version} CLI${title_suffix}`) 50 | console.log() 51 | 52 | ;(async () => { 53 | 54 | // processing 55 | 56 | let results 57 | try { 58 | results = await ergogen.process(config_text, args.debug, s => console.log(s)) 59 | } catch (err) { 60 | console.error(err) 61 | process.exit(3) 62 | } 63 | 64 | // helpers 65 | 66 | const single = (data, rel) => { 67 | if (!data) return 68 | const abs = path.join(args.o, rel) 69 | fs.mkdirpSync(path.dirname(abs)) 70 | if (abs.endsWith('.yaml')) { 71 | fs.writeFileSync(abs, yaml.dump(data, {indent: 4})) 72 | } else { 73 | fs.writeFileSync(abs, data) 74 | } 75 | } 76 | 77 | const composite = (data, rel) => { 78 | if (!data) return 79 | const abs = path.join(args.o, rel) 80 | if (data.yaml) { 81 | fs.mkdirpSync(path.dirname(abs)) 82 | fs.writeFileSync(abs + '.yaml', yaml.dump(data.yaml, {indent: 4})) 83 | } 84 | for (const format of ['svg', 'dxf', 'jscad', 'stl']) { 85 | if (data[format]) { 86 | fs.mkdirpSync(path.dirname(abs)) 87 | fs.writeFileSync(abs + '.' + format, data[format]) 88 | } 89 | } 90 | } 91 | 92 | // output 93 | 94 | if (args.clean) { 95 | console.log('Cleaning output folder...') 96 | fs.removeSync(args.o) 97 | } 98 | 99 | console.log('Writing output to disk...') 100 | fs.mkdirpSync(args.o) 101 | 102 | single(results.raw, 'source/raw.txt') 103 | single(results.canonical, 'source/canonical.yaml') 104 | 105 | single(results.units, 'points/units.yaml') 106 | single(results.points, 'points/points.yaml') 107 | composite(results.demo, 'points/demo') 108 | 109 | for (const [name, outline] of Object.entries(results.outlines)) { 110 | composite(outline, `outlines/${name}`) 111 | } 112 | 113 | for (const [name, _case] of Object.entries(results.cases)) { 114 | composite(_case, `cases/${name}`) 115 | } 116 | 117 | for (const [name, pcb] of Object.entries(results.pcbs)) { 118 | single(pcb, `pcbs/${name}.kicad_pcb`) 119 | } 120 | 121 | // goodbye 122 | 123 | console.log('Done.') 124 | console.log() 125 | 126 | })() 127 | -------------------------------------------------------------------------------- /test/points/basic_2x2___points.json: -------------------------------------------------------------------------------- 1 | { 2 | "matrix_left_bottom": { 3 | "x": 0, 4 | "y": 0, 5 | "r": 0, 6 | "meta": { 7 | "shift": [ 8 | 0, 9 | 0 10 | ], 11 | "rotate": 0, 12 | "padding": 19, 13 | "width": 1, 14 | "height": 1, 15 | "skip": false, 16 | "asym": "both", 17 | "name": "matrix_left_bottom", 18 | "colrow": "left_bottom", 19 | "col": { 20 | "stagger": 0, 21 | "spread": 0, 22 | "rotate": 0, 23 | "origin": [ 24 | 0, 25 | 0 26 | ], 27 | "rows": {}, 28 | "key": {}, 29 | "name": "left" 30 | }, 31 | "row": "bottom" 32 | } 33 | }, 34 | "matrix_left_top": { 35 | "x": 0, 36 | "y": 19, 37 | "r": 0, 38 | "meta": { 39 | "shift": [ 40 | 0, 41 | 0 42 | ], 43 | "rotate": 0, 44 | "padding": 19, 45 | "width": 1, 46 | "height": 1, 47 | "skip": false, 48 | "asym": "both", 49 | "name": "matrix_left_top", 50 | "colrow": "left_top", 51 | "col": { 52 | "stagger": 0, 53 | "spread": 0, 54 | "rotate": 0, 55 | "origin": [ 56 | 0, 57 | 0 58 | ], 59 | "rows": {}, 60 | "key": {}, 61 | "name": "left" 62 | }, 63 | "row": "top" 64 | } 65 | }, 66 | "matrix_right_bottom": { 67 | "x": 19, 68 | "y": 0, 69 | "r": 0, 70 | "meta": { 71 | "shift": [ 72 | 0, 73 | 0 74 | ], 75 | "rotate": 0, 76 | "padding": 19, 77 | "width": 1, 78 | "height": 1, 79 | "skip": false, 80 | "asym": "both", 81 | "name": "matrix_right_bottom", 82 | "colrow": "right_bottom", 83 | "col": { 84 | "stagger": 0, 85 | "spread": 19, 86 | "rotate": 0, 87 | "origin": [ 88 | 0, 89 | 0 90 | ], 91 | "rows": {}, 92 | "key": {}, 93 | "name": "right" 94 | }, 95 | "row": "bottom" 96 | } 97 | }, 98 | "matrix_right_top": { 99 | "x": 19, 100 | "y": 19, 101 | "r": 0, 102 | "meta": { 103 | "shift": [ 104 | 0, 105 | 0 106 | ], 107 | "rotate": 0, 108 | "padding": 19, 109 | "width": 1, 110 | "height": 1, 111 | "skip": false, 112 | "asym": "both", 113 | "name": "matrix_right_top", 114 | "colrow": "right_top", 115 | "col": { 116 | "stagger": 0, 117 | "spread": 19, 118 | "rotate": 0, 119 | "origin": [ 120 | 0, 121 | 0 122 | ], 123 | "rows": {}, 124 | "key": {}, 125 | "name": "right" 126 | }, 127 | "row": "top" 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/unit/assert.js: -------------------------------------------------------------------------------- 1 | const a = require('../../src/assert') 2 | 3 | describe('Assert', function() { 4 | 5 | it('mathnum', function() { 6 | a.mathnum('1')().should.equal(1) 7 | a.mathnum('1 + 2')().should.equal(3) 8 | a.mathnum('nope').should.throw('symbol') 9 | }) 10 | 11 | it('assert', function() { 12 | a.assert.bind(this, false, 'msg').should.throw('msg') 13 | a.assert.bind(this, true, 'msg').should.not.throw('msg') 14 | }) 15 | 16 | it('type', function() { 17 | // a more complete `typeof` operator 18 | a.type(undefined)().should.equal('undefined') 19 | a.type(null)().should.equal('null') 20 | a.type(false)().should.equal('boolean') 21 | a.type(0)().should.equal('number') 22 | a.type(3.14)().should.equal('number') 23 | a.type('arst')().should.equal('string') 24 | a.type('1 + 2')().should.equal('number') // formulas are also numbers here! 25 | a.type('arst')({arst: 2}).should.equal('number') // and variables count, too! 26 | a.type([])().should.equal('array') 27 | a.type({})().should.equal('object') 28 | }) 29 | 30 | it('sane', function() { 31 | a.sane('arst', 'name', 'string')().should.equal('arst') 32 | a.sane('arst', 'name', 'number').should.throw('name') 33 | a.sane('arst', 'name', 'number')({arst: 2}).should.equal(2) 34 | }) 35 | 36 | it('unexpected', function() { 37 | const good_keys = ['good'] 38 | a.unexpected.bind(this, [], 'name', good_keys).should.throw('object') 39 | a.unexpected.bind(this, {}, 'name', good_keys).should.not.throw() 40 | a.unexpected.bind(this, {good: true}, 'name', good_keys).should.not.throw() 41 | a.unexpected.bind(this, {good: true, bad: true}, 'name', good_keys).should.throw('bad') 42 | }) 43 | 44 | it('in', function() { 45 | a.in('match', 'name', ['match']).should.equal('match') 46 | a.in.bind(this, 'not.a.match', 'name', ['match']).should.throw('name') 47 | }) 48 | 49 | it('arr', function() { 50 | // non-arrays are bad 51 | a.arr('val', 'name', 0, 'string', '').should.throw('array') 52 | // arrays are good 53 | a.arr(['val'], 'name', 0, 'string', '')().should.deep.equal(['val']) 54 | // length check works 55 | a.arr(['val'], 'name', 2, 'string', '').should.throw('length') 56 | // defaults get filled 57 | a.arr([undefined, 'val'], 'name', 2, 'string', 'def')().should.deep.equal(['def', 'val']) 58 | // mathnums are supported 59 | a.arr(['1 + 2'], 'name', 0, 'number', '')().should.deep.equal([3]) 60 | 61 | // arr derivatives 62 | a.strarr(['val'], 'name').should.deep.equal(a.arr(['val'], 'name', 0, 'string', '')()) 63 | a.numarr([1], 'name', 1)().should.deep.equal(a.arr([1], 'name', 1, 'number', 0)()) 64 | a.xy([1, 2], 'name')().should.deep.equal(a.arr([1, 2], 'name', 2, 'number', 0)()) 65 | 66 | // wh adds array expansion 67 | a.wh([1, 2], 'name')().should.deep.equal(a.arr([1, 2], 'name', 2, 'number', 0)()) 68 | a.wh(1, 'name')().should.deep.equal(a.arr([1, 1], 'name', 2, 'number', 0)()) 69 | 70 | // trbl adds 1->4 and 2->4 array expansions 71 | a.trbl([1, 2, 3, 4], 'name')().should.deep.equal(a.arr([1, 2, 3, 4], 'name', 4, 'number', 0)()) 72 | a.trbl(1, 'name')().should.deep.equal(a.arr([1, 1, 1, 1], 'name', 4, 'number', 0)()) 73 | // the 2->4 is "inverted", because 2 number usually mean x,y, so the first is the horizontal 74 | // while for 4, it start with top, which is vertical 75 | a.trbl([1, 2], 'name')().should.deep.equal(a.arr([2, 1, 2, 1], 'name', 4, 'number', 0)()) 76 | }) 77 | }) -------------------------------------------------------------------------------- /test/points/adjustments___points.json: -------------------------------------------------------------------------------- 1 | { 2 | "matrix_left_bottom": { 3 | "x": 0, 4 | "y": 0, 5 | "r": 0, 6 | "meta": { 7 | "shift": [ 8 | 0, 9 | 0 10 | ], 11 | "rotate": 0, 12 | "padding": 19, 13 | "width": 1, 14 | "height": 1, 15 | "skip": false, 16 | "asym": "both", 17 | "name": "matrix_left_bottom", 18 | "colrow": "left_bottom", 19 | "col": { 20 | "stagger": 0, 21 | "spread": 0, 22 | "rotate": 0, 23 | "origin": [ 24 | 0, 25 | 0 26 | ], 27 | "rows": {}, 28 | "key": {}, 29 | "name": "left" 30 | }, 31 | "row": "bottom" 32 | } 33 | }, 34 | "matrix_left_top": { 35 | "x": 0, 36 | "y": 19, 37 | "r": 0, 38 | "meta": { 39 | "shift": [ 40 | 0, 41 | 0 42 | ], 43 | "rotate": 0, 44 | "padding": 19, 45 | "width": 1, 46 | "height": 1, 47 | "skip": false, 48 | "asym": "both", 49 | "name": "matrix_left_top", 50 | "colrow": "left_top", 51 | "col": { 52 | "stagger": 0, 53 | "spread": 0, 54 | "rotate": 0, 55 | "origin": [ 56 | 0, 57 | 0 58 | ], 59 | "rows": {}, 60 | "key": {}, 61 | "name": "left" 62 | }, 63 | "row": "top" 64 | } 65 | }, 66 | "matrix_right_bottom": { 67 | "x": 24.181350600000002, 68 | "y": 5.750154, 69 | "r": 5, 70 | "meta": { 71 | "shift": [ 72 | 0, 73 | 0 74 | ], 75 | "rotate": 0, 76 | "padding": 19, 77 | "width": 1, 78 | "height": 1, 79 | "skip": false, 80 | "asym": "both", 81 | "name": "matrix_right_bottom", 82 | "colrow": "right_bottom", 83 | "col": { 84 | "stagger": 5, 85 | "spread": 25, 86 | "rotate": 5, 87 | "origin": [ 88 | -9, 89 | -9 90 | ], 91 | "rows": {}, 92 | "key": {}, 93 | "name": "right" 94 | }, 95 | "row": "bottom" 96 | } 97 | }, 98 | "matrix_right_top": { 99 | "x": 22.525391499999998, 100 | "y": 24.6778532, 101 | "r": 5, 102 | "meta": { 103 | "shift": [ 104 | 0, 105 | 0 106 | ], 107 | "rotate": 0, 108 | "padding": 19, 109 | "width": 1, 110 | "height": 1, 111 | "skip": false, 112 | "asym": "both", 113 | "name": "matrix_right_top", 114 | "colrow": "right_top", 115 | "col": { 116 | "stagger": 5, 117 | "spread": 25, 118 | "rotate": 5, 119 | "origin": [ 120 | -9, 121 | -9 122 | ], 123 | "rows": {}, 124 | "key": {}, 125 | "name": "right" 126 | }, 127 | "row": "top" 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/ergogen.js: -------------------------------------------------------------------------------- 1 | const u = require('./utils') 2 | const io = require('./io') 3 | const prepare = require('./prepare') 4 | const units_lib = require('./units') 5 | const points_lib = require('./points') 6 | const outlines_lib = require('./outlines') 7 | const cases_lib = require('./cases') 8 | const pcbs_lib = require('./pcbs') 9 | 10 | const semver = require('semver') 11 | const version = require('../package.json').version 12 | 13 | const process = async (raw, debug=false, logger=()=>{}) => { 14 | 15 | const prefix = 'Interpreting format: ' 16 | let empty = true 17 | let [config, format] = io.interpret(raw, logger) 18 | let suffix = format 19 | if (format == 'KLE') { 20 | suffix = `${format} (Auto-debug)` 21 | debug = true 22 | } 23 | logger(prefix + suffix) 24 | 25 | logger('Preprocessing input...') 26 | config = prepare.unnest(config) 27 | config = prepare.inherit(config) 28 | config = prepare.parameterize(config) 29 | const results = {} 30 | if (debug) { 31 | results.raw = raw 32 | results.canonical = u.deepcopy(config) 33 | } 34 | 35 | if (config.meta && config.meta.engine) { 36 | logger('Checking compatibility...') 37 | const engine = semver.validRange(config.meta.engine) 38 | if (!engine) { 39 | throw new Error('Invalid config engine declaration!') 40 | } 41 | if (!semver.satisfies(version, engine)) { 42 | throw new Error(`Current ergogen version (${version}) doesn\'t satisfy config's engine requirement (${engine})!`) 43 | } 44 | } 45 | 46 | logger('Calculating variables...') 47 | const units = units_lib.parse(config) 48 | if (debug) { 49 | results.units = units 50 | } 51 | 52 | logger('Parsing points...') 53 | if (!config.points) { 54 | throw new Error('Input does not contain a points clause!') 55 | } 56 | const points = points_lib.parse(config.points, units) 57 | if (!Object.keys(points).length) { 58 | throw new Error('Input does not contain any points!') 59 | } 60 | if (debug) { 61 | results.points = points 62 | results.demo = io.twodee(points_lib.visualize(points, units), debug) 63 | } 64 | 65 | logger('Generating outlines...') 66 | const outlines = outlines_lib.parse(config.outlines || {}, points, units) 67 | results.outlines = {} 68 | for (const [name, outline] of Object.entries(outlines)) { 69 | if (!debug && name.startsWith('_')) continue 70 | results.outlines[name] = io.twodee(outline, debug) 71 | empty = false 72 | } 73 | 74 | logger('Extruding cases...') 75 | const cases = cases_lib.parse(config.cases || {}, outlines, units) 76 | results.cases = {} 77 | for (const [case_name, case_script] of Object.entries(cases)) { 78 | if (!debug && case_name.startsWith('_')) continue 79 | results.cases[case_name] = await io.threedee(case_script, debug) 80 | empty = false 81 | } 82 | 83 | logger('Scaffolding PCBs...') 84 | const pcbs = pcbs_lib.parse(config, points, outlines, units) 85 | results.pcbs = {} 86 | for (const [pcb_name, pcb_text] of Object.entries(pcbs)) { 87 | if (!debug && pcb_name.startsWith('_')) continue 88 | results.pcbs[pcb_name] = pcb_text 89 | empty = false 90 | } 91 | 92 | if (!debug && empty) { 93 | logger('Output would be empty, rerunning in debug mode...') 94 | return process(raw, true, () => {}) 95 | } 96 | return results 97 | } 98 | 99 | module.exports = { 100 | version, 101 | process, 102 | inject_footprint: pcbs_lib.inject_footprint 103 | } -------------------------------------------------------------------------------- /test/unit/anchor.js: -------------------------------------------------------------------------------- 1 | const {parse} = require('../../src/anchor') 2 | const Point = require('../../src/point') 3 | const {check} = require('../helpers/point') 4 | 5 | describe('Anchor', function() { 6 | 7 | const points = { 8 | o: new Point(0, 0, 0, {label: 'o'}), 9 | ten: new Point(10, 10, 10, {label: 'ten'}), 10 | mirror: new Point(20, 0, 0, {mirrored: true}) 11 | } 12 | 13 | it('params', function() { 14 | // an empty anchor definition leads to the default point 15 | check( 16 | parse({}, 'name')(), 17 | [0, 0, 0, {}] 18 | ) 19 | // unexpected check can be disabled 20 | check( 21 | parse({unexpected_key: true}, 'name', {}, false)(), 22 | [0, 0, 0, {}] 23 | ) 24 | // default point can be overridden 25 | check( 26 | parse({}, 'name', {}, true, new Point(1, 1))(), 27 | [1, 1, 0, {}] 28 | ) 29 | }) 30 | 31 | it('ref', function() { 32 | // single reference 33 | check( 34 | parse({ref: 'o'}, 'name', points)(), 35 | [0, 0, 0, {label: 'o'}] 36 | ) 37 | // average of multiple references (metadata gets ignored) 38 | check( 39 | parse({ref: ['o', 'ten']}, 'name', points)(), 40 | [5, 5, 5, {}] 41 | ) 42 | }) 43 | 44 | it('shift', function() { 45 | // normal shift 46 | check( 47 | parse({shift: [1, 1]}, 'name')(), 48 | [1, 1, 0, {}] 49 | ) 50 | // shift should respect mirrored points (and invert along the x axis) 51 | check( 52 | parse({ref: 'mirror', shift: [1, 1]}, 'name', points)(), 53 | [19, 1, 0, {mirrored: true}] 54 | ) 55 | }) 56 | 57 | it('orient', function() { 58 | // an orient by itself is equal to rotation 59 | check( 60 | parse({orient: 10}, 'name')(), 61 | [0, 0, 10, {}] 62 | ) 63 | // orient acts before shifting 64 | // so when we orient to the right, an upward shift goes to the right 65 | check( 66 | parse({orient: -90, shift: [0, 1]}, 'name')(), 67 | [1, 0, -90, {}] 68 | ) 69 | }) 70 | 71 | it('rotate', function() { 72 | // basic rotation 73 | check( 74 | parse({rotate: 10}, 'name')(), 75 | [0, 0, 10, {}] 76 | ) 77 | // rotate acts *after* shifting 78 | // so even tho we rotate to the right, an upward shift does go upward 79 | check( 80 | parse({shift: [0, 1], rotate: -90}, 'name')(), 81 | [0, 1, -90, {}] 82 | ) 83 | }) 84 | 85 | it('affect', function() { 86 | // affect can restrict which point fields (x, y, r) are affected by the transformations 87 | check( 88 | parse({orient: -90, shift: [0, 1], rotate: 10, affect: 'r'}, 'name')(), 89 | [0, 0, -80, {}] 90 | ) 91 | check( 92 | parse({orient: -90, shift: [0, 1], rotate: 10, affect: 'xy'}, 'name')(), 93 | [1, 0, 0, {}] 94 | ) 95 | // affects can also be arrays (example same as above) 96 | check( 97 | parse({orient: -90, shift: [0, 1], rotate: 10, affect: ['x', 'y']}, 'name')(), 98 | [1, 0, 0, {}] 99 | ) 100 | }) 101 | 102 | it('array', function() { 103 | // basic multi-anchor 104 | check( 105 | parse([ 106 | {shift: [1, 1]}, 107 | {rotate: 10} 108 | ], 'name')(), 109 | [1, 1, 10, {}] 110 | ) 111 | }) 112 | }) -------------------------------------------------------------------------------- /test/unit/point.js: -------------------------------------------------------------------------------- 1 | const m = require('makerjs') 2 | const Point = require('../../src/point') 3 | const {check} = require('../helpers/point') 4 | 5 | describe('Point', function() { 6 | 7 | it('construction', function() { 8 | // increasingly verbose parametrization 9 | check(new Point(), [0, 0, 0, {}]) 10 | check(new Point(1), [1, 0, 0, {}]) 11 | check(new Point(1, 2), [1, 2, 0, {}]) 12 | check(new Point(1, 2, 3), [1, 2, 3, {}]) 13 | check(new Point(1, 2, 3, {arst: 'neio'}), [1, 2, 3, {arst: 'neio'}]) 14 | // point-like instantiation 15 | check(new Point([1, 2]), [1, 2, 0, {}]) 16 | }) 17 | 18 | it('access', function() { 19 | // getting and setting through the virtual `p` field 20 | const p = new Point(1, 2) 21 | p.p.should.deep.equal([1, 2]) 22 | p.p = [3, 4] 23 | p.p.should.deep.equal([3, 4]) 24 | }) 25 | 26 | it('cloning', function() { 27 | const p = new Point(1, 2) 28 | const clone = p.clone() 29 | // make sure cloning works 30 | check(clone, [1, 2, 0, {}]) 31 | clone.p = [3, 4] 32 | // and make sure it's then independent of the original 33 | check(p, [1, 2, 0, {}]) 34 | check(clone, [3, 4, 0, {}]) 35 | }) 36 | 37 | it('shifting', function() { 38 | const p = new Point(0, 0, -90) // at origin, "looking right" 39 | // absolute shift up and left, should be up and left 40 | check(p.clone().shift([-1, 1], false), [-1, 1, -90, {}]) 41 | // relative shift up and left, should be up and right 42 | check(p.clone().shift([-1, 1]), [1, 1, -90, {}]) 43 | }) 44 | 45 | it('rotation', function() { 46 | const p = new Point(0, 1, 0) // above origin, "looking up" 47 | // around default origin 48 | check(p.clone().rotate(-90), [1, 0, -90, {}]) 49 | // around custom origin 50 | check(p.clone().rotate(-90, [1, 1]), [1, 2, -90, {}]) 51 | }) 52 | 53 | it('mirroring', function() { 54 | const p = new Point(0, 1, 0) 55 | // make sure mirroring inverts rotation, as well as positions correctly 56 | check((new Point(-1, 0, 10)).mirror(0), [1, 0, -10, {}]) 57 | }) 58 | 59 | it('positioning', function() { 60 | const p = new Point(1, 1, -90) // above origin, "looking right" 61 | const model = { 62 | paths: { 63 | line: new m.paths.Line([0, 0], [0, 1]), 64 | } 65 | } 66 | // it's both rotated and shifted to the correct position 67 | p.position(model).should.deep.equal({ 68 | paths: { 69 | line: { 70 | type: 'line', 71 | origin: [0, 0], 72 | end: [1, 0] 73 | } 74 | }, 75 | origin: [1, 1] 76 | }) 77 | // `rect` does the same, only with built-in rectangles 78 | const expected_rect = { 79 | origin: [-6, -6], 80 | paths: { 81 | bottom: { 82 | end: [0, 14], 83 | origin: [0, 0], 84 | type: 'line' 85 | }, 86 | left: { 87 | end: [14, 14], 88 | origin: [0, 14], 89 | type: 'line' 90 | }, 91 | right: { 92 | end: [0, 0], 93 | origin: [14, 0], 94 | type: 'line' 95 | }, 96 | top: { 97 | end: [14, 0], 98 | origin: [14, 14], 99 | type: 'line' 100 | } 101 | } 102 | } 103 | p.rect(14).should.deep.equal(expected_rect) 104 | // default size is 14 for keyboard hole reasons 105 | p.rect().should.deep.equal(expected_rect) 106 | }) 107 | }) -------------------------------------------------------------------------------- /src/footprints/choc.js: -------------------------------------------------------------------------------- 1 | // Kailh Choc PG1350 2 | // Nets 3 | // from: corresponds to pin 1 4 | // to: corresponds to pin 2 5 | // Params 6 | // hotswap: default is false 7 | // if true, will include holes and pads for Kailh choc hotswap sockets 8 | // reverse: default is false 9 | // if true, will flip the footprint such that the pcb can be reversible 10 | // keycaps: default is false 11 | // if true, will add choc sized keycap box around the footprint 12 | // 13 | // note: hotswap and reverse can be used simultaneously 14 | 15 | module.exports = { 16 | nets: { 17 | from: undefined, 18 | to: undefined 19 | }, 20 | params: { 21 | class: 'S', 22 | hotswap: false, 23 | reverse: false, 24 | keycaps: false 25 | }, 26 | body: p => { 27 | const standard = ` 28 | (module PG1350 (layer F.Cu) (tedit 5DD50112) 29 | ${p.at /* parametric position */} 30 | 31 | ${'' /* footprint reference */} 32 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 33 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 34 | 35 | ${''/* corner marks */} 36 | (fp_line (start -7 -6) (end -7 -7) (layer Dwgs.User) (width 0.15)) 37 | (fp_line (start -7 7) (end -6 7) (layer Dwgs.User) (width 0.15)) 38 | (fp_line (start -6 -7) (end -7 -7) (layer Dwgs.User) (width 0.15)) 39 | (fp_line (start -7 7) (end -7 6) (layer Dwgs.User) (width 0.15)) 40 | (fp_line (start 7 6) (end 7 7) (layer Dwgs.User) (width 0.15)) 41 | (fp_line (start 7 -7) (end 6 -7) (layer Dwgs.User) (width 0.15)) 42 | (fp_line (start 6 7) (end 7 7) (layer Dwgs.User) (width 0.15)) 43 | (fp_line (start 7 -7) (end 7 -6) (layer Dwgs.User) (width 0.15)) 44 | 45 | ${''/* middle shaft */} 46 | (pad "" np_thru_hole circle (at 0 0) (size 3.429 3.429) (drill 3.429) (layers *.Cu *.Mask)) 47 | 48 | ${''/* stabilizers */} 49 | (pad "" np_thru_hole circle (at 5.5 0) (size 1.7018 1.7018) (drill 1.7018) (layers *.Cu *.Mask)) 50 | (pad "" np_thru_hole circle (at -5.5 0) (size 1.7018 1.7018) (drill 1.7018) (layers *.Cu *.Mask)) 51 | ` 52 | const keycap = ` 53 | ${'' /* keycap marks */} 54 | (fp_line (start -9 -8.5) (end 9 -8.5) (layer Dwgs.User) (width 0.15)) 55 | (fp_line (start 9 -8.5) (end 9 8.5) (layer Dwgs.User) (width 0.15)) 56 | (fp_line (start 9 8.5) (end -9 8.5) (layer Dwgs.User) (width 0.15)) 57 | (fp_line (start -9 8.5) (end -9 -8.5) (layer Dwgs.User) (width 0.15)) 58 | ` 59 | function pins(def_neg, def_pos, def_side) { 60 | if(p.param.hotswap) { 61 | return ` 62 | ${'' /* holes */} 63 | (pad "" np_thru_hole circle (at ${def_pos}5 -3.75) (size 3 3) (drill 3) (layers *.Cu *.Mask)) 64 | (pad "" np_thru_hole circle (at 0 -5.95) (size 3 3) (drill 3) (layers *.Cu *.Mask)) 65 | 66 | ${'' /* net pads */} 67 | (pad 1 smd rect (at ${def_neg}3.275 -5.95 ${p.rot}) (size 2.6 2.6) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.from.str}) 68 | (pad 2 smd rect (at ${def_pos}8.275 -3.75 ${p.rot}) (size 2.6 2.6) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.to.str}) 69 | ` 70 | } else { 71 | return ` 72 | ${''/* pins */} 73 | (pad 1 thru_hole circle (at ${def_pos}5 -3.8) (size 2.032 2.032) (drill 1.27) (layers *.Cu *.Mask) ${p.net.from.str}) 74 | (pad 2 thru_hole circle (at ${def_pos}0 -5.9) (size 2.032 2.032) (drill 1.27) (layers *.Cu *.Mask) ${p.net.to.str}) 75 | ` 76 | } 77 | } 78 | if(p.param.reverse) { 79 | return ` 80 | ${standard} 81 | ${p.param.keycaps ? keycap : ''} 82 | ${pins('-', '', 'B')} 83 | ${pins('', '-', 'F')}) 84 | ` 85 | } else { 86 | return ` 87 | ${standard} 88 | ${p.param.keycaps ? keycap : ''} 89 | ${pins('-', '', 'B')}) 90 | ` 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/footprints/mx.js: -------------------------------------------------------------------------------- 1 | // Any MX switch 2 | // Nets 3 | // from: corresponds to pin 1 4 | // to: corresponds to pin 2 5 | // Params 6 | // hotswap: default is false 7 | // if true, will include holes and pads for Kailh MX hotswap sockets 8 | // reverse: default is false 9 | // if true, will flip the footprint such that the pcb can be reversible 10 | // keycaps: default is false 11 | // if true, will add choc sized keycap box around the footprint 12 | // 13 | // note: hotswap and reverse can be used simultaneously 14 | 15 | module.exports = { 16 | nets: { 17 | from: undefined, 18 | to: undefined 19 | }, 20 | params: { 21 | class: 'S', 22 | hotswap: false, 23 | reverse: false, 24 | keycaps: false 25 | }, 26 | body: p => { 27 | const standard = ` 28 | (module MX (layer F.Cu) (tedit 5DD4F656) 29 | ${p.at /* parametric position */} 30 | 31 | ${'' /* footprint reference */} 32 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 33 | (fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15)))) 34 | 35 | ${''/* corner marks */} 36 | (fp_line (start -7 -6) (end -7 -7) (layer Dwgs.User) (width 0.15)) 37 | (fp_line (start -7 7) (end -6 7) (layer Dwgs.User) (width 0.15)) 38 | (fp_line (start -6 -7) (end -7 -7) (layer Dwgs.User) (width 0.15)) 39 | (fp_line (start -7 7) (end -7 6) (layer Dwgs.User) (width 0.15)) 40 | (fp_line (start 7 6) (end 7 7) (layer Dwgs.User) (width 0.15)) 41 | (fp_line (start 7 -7) (end 6 -7) (layer Dwgs.User) (width 0.15)) 42 | (fp_line (start 6 7) (end 7 7) (layer Dwgs.User) (width 0.15)) 43 | (fp_line (start 7 -7) (end 7 -6) (layer Dwgs.User) (width 0.15)) 44 | 45 | ${''/* middle shaft */} 46 | (pad "" np_thru_hole circle (at 0 0) (size 3.9878 3.9878) (drill 3.9878) (layers *.Cu *.Mask)) 47 | 48 | ${''/* stabilizers */} 49 | (pad "" np_thru_hole circle (at 5.08 0) (size 1.7018 1.7018) (drill 1.7018) (layers *.Cu *.Mask)) 50 | (pad "" np_thru_hole circle (at -5.08 0) (size 1.7018 1.7018) (drill 1.7018) (layers *.Cu *.Mask)) 51 | ` 52 | const keycap = ` 53 | ${'' /* keycap marks */} 54 | (fp_line (start -9.5 -9.5) (end 9.5 -9.5) (layer Dwgs.User) (width 0.15)) 55 | (fp_line (start 9.5 -9.5) (end 9.5 9.5) (layer Dwgs.User) (width 0.15)) 56 | (fp_line (start 9.5 9.5) (end -9.5 9.5) (layer Dwgs.User) (width 0.15)) 57 | (fp_line (start -9.5 9.5) (end -9.5 -9.5) (layer Dwgs.User) (width 0.15)) 58 | ` 59 | function pins(def_neg, def_pos, def_side) { 60 | if(p.param.hotswap) { 61 | return ` 62 | ${'' /* holes */} 63 | (pad "" np_thru_hole circle (at ${def_pos}2.54 -5.08) (size 3 3) (drill 3) (layers *.Cu *.Mask)) 64 | (pad "" np_thru_hole circle (at ${def_neg}3.81 -2.54) (size 3 3) (drill 3) (layers *.Cu *.Mask)) 65 | 66 | ${'' /* net pads */} 67 | (pad 1 smd rect (at ${def_neg}7.085 -2.54 180) (size 2.55 2.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.from.str}) 68 | (pad 2 smd rect (at ${def_pos}5.842 -5.08 180) (size 2.55 2.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.to.str}) 69 | ` 70 | } else { 71 | return ` 72 | ${''/* pins */} 73 | (pad 1 thru_hole circle (at ${def_pos}2.54 -5.08) (size 2.286 2.286) (drill 1.4986) (layers *.Cu *.Mask) ${p.net.from.str}) 74 | (pad 2 thru_hole circle (at ${def_neg}3.81 -2.54) (size 2.286 2.286) (drill 1.4986) (layers *.Cu *.Mask) ${p.net.to.str}) 75 | ` 76 | } 77 | } 78 | if(p.param.reverse){ 79 | return ` 80 | ${standard} 81 | ${p.param.keycaps ? keycap : ''} 82 | ${pins('-', '', 'B')} 83 | ${pins('', '-', 'F')}) 84 | ` 85 | } else { 86 | return ` 87 | ${standard} 88 | ${p.param.keycaps ? keycap : ''} 89 | ${pins('-', '', 'B')}) 90 | ` 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /test/unit/interface.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const yaml = require('js-yaml') 4 | const ergogen = require('../../src/ergogen') 5 | const version = require('../../package.json').version 6 | 7 | // fixtures 8 | const load = name => yaml.safeLoad(fs.readFileSync( 9 | path.join(__dirname, `../fixtures/${name}`) 10 | ).toString()) 11 | const minimal = load('minimal.yaml') 12 | const big = load('big.yaml') 13 | const kle = load('atreus_kle.json') 14 | 15 | describe('Interface', function() { 16 | 17 | this.timeout(120000) 18 | this.slow(120000) 19 | 20 | it('debug', async function() { 21 | // to check whether the output has "private" exports 22 | const underscore = obj => { 23 | for (const val of Object.values(obj)) { 24 | for (const key of Object.keys(val)) { 25 | if (key.startsWith('_')) return true 26 | } 27 | } 28 | return false 29 | } 30 | underscore(await ergogen.process(minimal)).should.be.false 31 | underscore(await ergogen.process(big, false)).should.be.false 32 | underscore(await ergogen.process(big, true)).should.be.true 33 | }) 34 | 35 | it('formats', async function() { 36 | const logger = msg => { 37 | if (msg.startsWith('Interpreting format:')) { 38 | throw msg.split(':')[1].trim() 39 | } 40 | } 41 | return Promise.all([ 42 | ergogen.process(minimal, true, logger).should.be.rejectedWith('OBJ'), 43 | ergogen.process(yaml.dump(minimal), true, logger).should.be.rejectedWith('YAML'), 44 | ergogen.process(` 45 | //: 46 | return {points: {}} 47 | `, true, logger).should.be.rejectedWith('JS'), 48 | ergogen.process(` 49 | //: 50 | return 'not an object'; 51 | `, true, logger).should.be.rejectedWith('not valid'), 52 | ergogen.process(kle, true, logger).should.be.rejectedWith('KLE'), 53 | ergogen.process('not an object', true, logger).should.be.rejectedWith('object'), 54 | ergogen.process({}, true, logger).should.be.rejectedWith('empty'), 55 | ergogen.process({not_points: {}}, true, () => {}).should.be.rejectedWith('points clause'), 56 | ergogen.process({points: {zones: {}}}, true, () => {}).should.be.rejectedWith('any points') 57 | ]) 58 | }) 59 | 60 | it('preprocessor', async function() { 61 | return Promise.all([ 62 | // unnesting 63 | ergogen.process({'points.zones.matrix': {}}).should.eventually.have.deep.property('canonical', { 64 | points: {zones: {matrix: {}}} 65 | }), 66 | // inheritance 67 | ergogen.process({ 68 | 'points.zones.parent.key.a': 1, 69 | 'points.zones.child': { 70 | '$extends': 'points.zones.parent', 71 | 'key.b': 2 72 | } 73 | }).should.eventually.have.deep.nested.property('canonical.points.zones.child.key', { 74 | a: 1, 75 | b: 2 76 | }), 77 | // parameterization 78 | ergogen.process({ 79 | 'points.zones.matrix.key': { 80 | a: 'PAR', 81 | $params: ['PAR'], 82 | $args: [1] 83 | } 84 | }).should.eventually.have.deep.nested.property('canonical.points.zones.matrix.key', { 85 | a: '1' 86 | }) 87 | ]) 88 | }) 89 | 90 | it('engine', async function() { 91 | return Promise.all([ 92 | ergogen.process({'meta.engine': 'invalid'}).should.be.rejectedWith('Invalid'), 93 | ergogen.process({'meta.engine': '^0.1.2'}).should.be.rejectedWith('satisfy'), 94 | // no "points clause" means we're over the engine check, so it "succeeded" 95 | ergogen.process({'meta.engine': `^${version}`}).should.be.rejectedWith('points clause') 96 | ]) 97 | }) 98 | 99 | }) -------------------------------------------------------------------------------- /src/footprints/scrollwheel.js: -------------------------------------------------------------------------------- 1 | // Panasonic EVQWGD001 horizontal rotary encoder 2 | // 3 | // __________________ 4 | // (f) (t) | | 5 | // | (1) | | 6 | // | (2) | | 7 | // | (3) | | 8 | // | (4) | | 9 | // |_( )___________|_| 10 | // 11 | // Nets 12 | // from: corresponds to switch pin 1 (for button presses) 13 | // to: corresponds to switch pin 2 (for button presses) 14 | // A: corresponds to pin 1 (for rotary) 15 | // B: corresponds to pin 2 (for rotary, should be GND) 16 | // C: corresponds to pin 3 (for rotary) 17 | // D: corresponds to pin 4 (for rotary, unused) 18 | // Params 19 | // reverse: default is false 20 | // if true, will flip the footprint such that the pcb can be reversible 21 | 22 | 23 | module.exports = { 24 | nets: { 25 | from: undefined, 26 | to: undefined, 27 | A: undefined, 28 | B: undefined, 29 | C: undefined, 30 | D: undefined 31 | }, 32 | params: { 33 | class: 'S', 34 | reverse: false 35 | }, 36 | body: p => { 37 | const standard = ` 38 | (module RollerEncoder_Panasonic_EVQWGD001 (layer F.Cu) (tedit 6040A10C) 39 | ${p.at /* parametric position */} 40 | (fp_text reference REF** (at 0 0 ${p.rot}) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15)))) 41 | (fp_text value RollerEncoder_Panasonic_EVQWGD001 (at -0.1 9 ${p.rot}) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15)))) 42 | 43 | ${'' /* corner marks */} 44 | (fp_line (start -8.4 -6.4) (end 8.4 -6.4) (layer Dwgs.User) (width 0.12)) 45 | (fp_line (start 8.4 -6.4) (end 8.4 7.4) (layer Dwgs.User) (width 0.12)) 46 | (fp_line (start 8.4 7.4) (end -8.4 7.4) (layer Dwgs.User) (width 0.12)) 47 | (fp_line (start -8.4 7.4) (end -8.4 -6.4) (layer Dwgs.User) (width 0.12)) 48 | ` 49 | function pins(def_neg, def_pos) { 50 | return ` 51 | ${'' /* edge cuts */} 52 | (fp_line (start ${def_pos}9.8 7.3) (end ${def_pos}9.8 -6.3) (layer Edge.Cuts) (width 0.15)) 53 | (fp_line (start ${def_pos}7.4 -6.3) (end ${def_pos}7.4 7.3) (layer Edge.Cuts) (width 0.15)) 54 | (fp_line (start ${def_pos}9.5 -6.6) (end ${def_pos}7.7 -6.6) (layer Edge.Cuts) (width 0.15)) 55 | (fp_line (start ${def_pos}7.7 7.6) (end ${def_pos}9.5 7.6) (layer Edge.Cuts) (width 0.15)) 56 | (fp_arc (start ${def_pos}7.7 7.3) (end ${def_pos}7.4 7.3) (angle ${def_neg}90) (layer Edge.Cuts) (width 0.15)) 57 | (fp_arc (start ${def_pos}9.5 7.3) (end ${def_pos}9.5 7.6) (angle ${def_neg}90) (layer Edge.Cuts) (width 0.15)) 58 | (fp_arc (start ${def_pos}7.7 -6.3) (end ${def_pos}7.7 -6.6) (angle ${def_neg}90) (layer Edge.Cuts) (width 0.15)) 59 | (fp_arc (start ${def_pos}9.5 -6.3) (end ${def_pos}9.8 -6.3) (angle ${def_neg}90) (layer Edge.Cuts) (width 0.15)) 60 | 61 | ${'' /* pins */} 62 | (pad S1 thru_hole circle (at ${def_neg}6.85 -6.2 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.from.str}) 63 | (pad S2 thru_hole circle (at ${def_neg}5 -6.2 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.to.str}) 64 | (pad A thru_hole circle (at ${def_neg}5.625 -3.81 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.A.str}) 65 | (pad B thru_hole circle (at ${def_neg}5.625 -1.27 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.B.str}) 66 | (pad C thru_hole circle (at ${def_neg}5.625 1.27 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.C.str}) 67 | (pad D thru_hole circle (at ${def_neg}5.625 3.81 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.D.str}) 68 | 69 | ${'' /* stabilizer */} 70 | (pad "" np_thru_hole circle (at ${def_neg}5.625 6.3 ${p.rot}) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask)) 71 | ` 72 | } 73 | if(p.param.reverse) { 74 | return ` 75 | ${standard} 76 | ${pins('-', '')} 77 | ${pins('', '-')}) 78 | ` 79 | } else { 80 | return ` 81 | ${standard} 82 | ${pins('-', '')}) 83 | ` 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | const m = require('makerjs') 2 | const u = require('../../src/utils') 3 | 4 | describe('Utils', function() { 5 | 6 | it('deepcopy', function() { 7 | // basic deep copying 8 | u.deepcopy({arst: 'neio'}).should.deep.equal({arst: 'neio'}) 9 | // undefined should be handled properly (would otherwise error out with "undefined") 10 | should.equal(u.deepcopy(), undefined) 11 | }) 12 | 13 | it('deep', function() { 14 | const obj = {a: {b: {}}} 15 | // deep setting 16 | const res = u.deep(obj, 'a.b.c', 42) 17 | res.should.equal(obj) 18 | obj.a.b.c.should.equal(42) 19 | // deep access 20 | u.deep(obj, 'a.b.c').should.equal(42) 21 | // deep access on nonexistent keys should be undefined without error 22 | should.equal(u.deep(obj, 'non.existent.key'), undefined) 23 | }) 24 | 25 | it('eq', function() { 26 | // basic point usage 27 | u.eq([1, 2], [1, 2]).should.equal(true) 28 | u.eq([1, 2], [3, 4]).should.equal(false) 29 | // default params are empty arrays 30 | u.eq().should.equal(true) 31 | }) 32 | 33 | it('line', function() { 34 | u.line([0, 0], [0, 1]).should.deep.equal({ 35 | type: 'line', 36 | origin: [0, 0], 37 | end: [0, 1] 38 | }) 39 | }) 40 | 41 | it('circle', function() { 42 | u.circle([0, 0], 1).should.deep.equal({ 43 | paths: { 44 | circle: { 45 | type: 'circle', 46 | origin: [0, 0], 47 | radius: 1 48 | } 49 | } 50 | }) 51 | }) 52 | 53 | it('rect', function() { 54 | const expected = { 55 | bottom: { 56 | end: [0, 0], 57 | origin: [1, 0], 58 | type: 'line' 59 | }, 60 | left: { 61 | end: [0, 1], 62 | origin: [0, 0], 63 | type: 'line' 64 | }, 65 | right: { 66 | end: [1, 0], 67 | origin: [1, 1], 68 | type: 'line' 69 | }, 70 | top: { 71 | end: [1, 1], 72 | origin: [0, 1], 73 | type: 'line' 74 | } 75 | } 76 | u.rect(1, 1).should.deep.equal({paths: expected, origin: [0, 0]}) 77 | u.rect(1, 1, [2, 2]).should.deep.equal({paths: expected, origin: [2, 2]}) 78 | }) 79 | 80 | it('poly', function() { 81 | const expected = { 82 | paths: { 83 | p1: { 84 | end: [0, 0], 85 | origin: [1, 1], 86 | type: 'line' 87 | }, 88 | p2: { 89 | end: [1, 0], 90 | origin: [0, 0], 91 | type: 'line' 92 | }, 93 | p3: { 94 | end: [1, 1], 95 | origin: [1, 0], 96 | type: 'line' 97 | } 98 | } 99 | } 100 | u.poly([[0, 0], [1, 0], [1, 1]]).should.deep.equal(expected) 101 | // consecutive duplications shouldn't matter 102 | u.poly([[0, 0], [1, 0], [1, 0], [1, 1]]).should.deep.equal(expected) 103 | // empty in, empty out 104 | u.poly([]).should.deep.equal({paths: {}}) 105 | 106 | }) 107 | 108 | it('combine helpers', function() { 109 | const a = u.rect(2, 2, [0, 0]) 110 | const b = u.rect(2, 2, [1, 0]) 111 | const farPoint = [1234.1234, 2143.56789] 112 | u.union(a, b).should.deep.equal(m.model.combine(a, b, false, true, false, true, {farPoint})) 113 | u.subtract(a, b).should.deep.equal(m.model.combine(a, b, false, true, true, false, {farPoint})) 114 | u.intersect(a, b).should.deep.equal(m.model.combine(a, b, true, false, true, false, {farPoint})) 115 | u.stack(a, b).should.deep.equal({ 116 | models: { 117 | a, b 118 | } 119 | }) 120 | }) 121 | 122 | }) -------------------------------------------------------------------------------- /test/helpers/mock_footprints.js: -------------------------------------------------------------------------------- 1 | exports.inject = (ergogen) => { 2 | ergogen.inject_footprint('trace_test', { 3 | nets: { 4 | P1: 'P1' 5 | }, 6 | params: { 7 | class: 'T', 8 | side: 'F' 9 | }, 10 | body: p => { 11 | return ` 12 | 13 | (module trace_test (layer F.Cu) (tedit 5CF31DEF) 14 | 15 | ${p.at /* parametric position */} 16 | 17 | (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) 18 | ${p.net.P1.str} (solder_mask_margin 0.2)) 19 | 20 | (pad 2 smd rect (at 5 5 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) 21 | ${p.net.P1.str} (solder_mask_margin 0.2)) 22 | 23 | ) 24 | 25 | (segment (start ${p.xy(0, 0)}) (end ${p.xy(5, 5)}) (width 0.25) (layer F.Cu) (net ${p.net.P1.index})) 26 | 27 | ` 28 | } 29 | }) 30 | 31 | ergogen.inject_footprint('zone_test', { 32 | nets: { 33 | P1: 'P1' 34 | }, 35 | params: { 36 | class: 'T', 37 | side: 'F' 38 | }, 39 | body: p => { 40 | return ` 41 | 42 | (module zone_test (layer F.Cu) (tedit 5CF31DEF) 43 | 44 | ${p.at /* parametric position */} 45 | 46 | (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) 47 | ${p.net.P1.str} (solder_mask_margin 0.2)) 48 | 49 | (pad 2 smd rect (at 5 5 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) 50 | ${p.net.P1.str} (solder_mask_margin 0.2)) 51 | 52 | ) 53 | 54 | (zone (net ${p.net.P1.index}) (net_name ${p.net.P1.name}) (layer ${p.param.side}.Cu) (tstamp 0) (hatch full 0.508) 55 | (connect_pads (clearance 0.508)) 56 | (min_thickness 0.254) 57 | (fill yes (arc_segments 32) (thermal_gap 0.508) (thermal_bridge_width 0.508)) 58 | (polygon (pts (xy ${p.xy(5, 5)}) (xy ${p.xy(5, -5)}) (xy ${p.xy(-5, -5)}) (xy ${p.xy(-5, 5)}))) 59 | ) 60 | 61 | ` 62 | } 63 | }) 64 | 65 | ergogen.inject_footprint('dynamic_net_test', { 66 | nets: {}, 67 | params: { 68 | class: 'T', 69 | side: 'F' 70 | }, 71 | body: p => { 72 | return ` 73 | 74 | (module dynamic_net_test (layer F.Cu) (tedit 5CF31DEF) 75 | 76 | ${p.at /* parametric position */} 77 | 78 | (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) 79 | ${p.local_net('1').str} (solder_mask_margin 0.2)) 80 | 81 | (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) 82 | ${p.local_net('2').str} (solder_mask_margin 0.2)) 83 | 84 | (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) 85 | ${p.local_net('3').str} (solder_mask_margin 0.2)) 86 | 87 | ) 88 | 89 | ` 90 | } 91 | }) 92 | 93 | ergogen.inject_footprint('anchor_test', { 94 | nets: {}, 95 | params: { 96 | class: 'T', 97 | side: 'F' 98 | }, 99 | anchors: { 100 | end: undefined 101 | }, 102 | body: p => { 103 | return ` 104 | 105 | (module anchor_test (layer F.Cu) (tedit 5CF31DEF) 106 | 107 | ${p.at /* parametric position */} 108 | 109 | (fp_line (start 0 0) (end ${p.anchors.end.x} ${p.anchors.end.y}) (layer Dwgs.User) (width 0.05)) 110 | 111 | ) 112 | 113 | ` 114 | } 115 | }) 116 | 117 | ergogen.inject_footprint('references_test', { 118 | nets: {}, 119 | params: {}, 120 | body: p => { 121 | return `references ${p.ref_hide ? 'hidden' : 'shown'}` 122 | } 123 | }) 124 | } -------------------------------------------------------------------------------- /test/fixtures/atreus_kle.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | { 4 | "r": 10, 5 | "rx": 1, 6 | "y": -0.09999999999999998, 7 | "x": 2 8 | }, 9 | "E" 10 | ], 11 | [ 12 | { 13 | "y": -0.65, 14 | "x": 1 15 | }, 16 | "W", 17 | { 18 | "x": 1 19 | }, 20 | "R" 21 | ], 22 | [ 23 | { 24 | "y": -0.75 25 | }, 26 | "Q" 27 | ], 28 | [ 29 | { 30 | "y": -0.9, 31 | "x": 4 32 | }, 33 | "T" 34 | ], 35 | [ 36 | { 37 | "y": -0.7000000000000001, 38 | "x": 2 39 | }, 40 | "D" 41 | ], 42 | [ 43 | { 44 | "y": -0.6499999999999999, 45 | "x": 1 46 | }, 47 | "S", 48 | { 49 | "x": 1 50 | }, 51 | "F" 52 | ], 53 | [ 54 | { 55 | "y": -0.75 56 | }, 57 | "A" 58 | ], 59 | [ 60 | { 61 | "y": -0.8999999999999999, 62 | "x": 4 63 | }, 64 | "G" 65 | ], 66 | [ 67 | { 68 | "y": -0.7000000000000002, 69 | "x": 2 70 | }, 71 | "C" 72 | ], 73 | [ 74 | { 75 | "y": -0.6499999999999999, 76 | "x": 1 77 | }, 78 | "X", 79 | { 80 | "x": 1 81 | }, 82 | "V" 83 | ], 84 | [ 85 | { 86 | "y": -0.75 87 | }, 88 | "Z" 89 | ], 90 | [ 91 | { 92 | "y": -0.8999999999999999, 93 | "x": 4 94 | }, 95 | "B" 96 | ], 97 | [ 98 | { 99 | "y": -0.75, 100 | "x": 5, 101 | "h": 1.5 102 | }, 103 | "Ctrl" 104 | ], 105 | [ 106 | { 107 | "y": -0.9500000000000002, 108 | "x": 2 109 | }, 110 | "super" 111 | ], 112 | [ 113 | { 114 | "y": -0.6499999999999999, 115 | "x": 1 116 | }, 117 | "Tab", 118 | { 119 | "x": 1 120 | }, 121 | "Shift" 122 | ], 123 | [ 124 | { 125 | "y": -0.75 126 | }, 127 | "Esc" 128 | ], 129 | [ 130 | { 131 | "y": -0.8999999999999999, 132 | "x": 4 133 | }, 134 | "Bksp" 135 | ], 136 | [ 137 | { 138 | "r": -10, 139 | "rx": 7, 140 | "ry": 0.965, 141 | "y": -0.20000000000000018, 142 | "x": 2 143 | }, 144 | "I" 145 | ], 146 | [ 147 | { 148 | "y": -0.6499999999999999, 149 | "x": 1 150 | }, 151 | "U", 152 | { 153 | "x": 1 154 | }, 155 | "O" 156 | ], 157 | [ 158 | { 159 | "y": -0.75, 160 | "x": 4 161 | }, 162 | "P" 163 | ], 164 | [ 165 | { 166 | "y": -0.8999999999999999 167 | }, 168 | "Y" 169 | ], 170 | [ 171 | { 172 | "y": -0.7000000000000002, 173 | "x": 2 174 | }, 175 | "K" 176 | ], 177 | [ 178 | { 179 | "y": -0.6499999999999999, 180 | "x": 1 181 | }, 182 | "J", 183 | { 184 | "x": 1 185 | }, 186 | "L" 187 | ], 188 | [ 189 | { 190 | "y": -0.75, 191 | "x": 4 192 | }, 193 | ":\n;" 194 | ], 195 | [ 196 | { 197 | "y": -0.8999999999999999 198 | }, 199 | "H" 200 | ], 201 | [ 202 | { 203 | "y": -0.7000000000000002, 204 | "x": 2 205 | }, 206 | "<\n," 207 | ], 208 | [ 209 | { 210 | "y": -0.6499999999999999, 211 | "x": 1 212 | }, 213 | "M", 214 | { 215 | "x": 1 216 | }, 217 | ">\n." 218 | ], 219 | [ 220 | { 221 | "y": -0.7500000000000004, 222 | "x": 4 223 | }, 224 | "?\n/" 225 | ], 226 | [ 227 | { 228 | "y": -0.9000000000000004 229 | }, 230 | "N" 231 | ], 232 | [ 233 | { 234 | "y": -0.7499999999999996, 235 | "x": -1, 236 | "h": 1.5 237 | }, 238 | "Alt" 239 | ], 240 | [ 241 | { 242 | "y": -0.9499999999999997, 243 | "x": 2 244 | }, 245 | "_\n-" 246 | ], 247 | [ 248 | { 249 | "y": -0.6500000000000004, 250 | "x": 1 251 | }, 252 | "fn", 253 | { 254 | "x": 1 255 | }, 256 | "\"\n'" 257 | ], 258 | [ 259 | { 260 | "y": -0.75, 261 | "x": 4 262 | }, 263 | "Enter" 264 | ], 265 | [ 266 | { 267 | "y": -0.9000000000000004 268 | }, 269 | "Space" 270 | ] 271 | ] -------------------------------------------------------------------------------- /test/outlines/affect_mirror___outlines_test_dxf.dxf: -------------------------------------------------------------------------------- 1 | 0 2 | SECTION 3 | 2 4 | HEADER 5 | 9 6 | $INSUNITS 7 | 70 8 | 4 9 | 0 10 | ENDSEC 11 | 0 12 | SECTION 13 | 2 14 | TABLES 15 | 0 16 | TABLE 17 | 2 18 | LTYPE 19 | 0 20 | LTYPE 21 | 72 22 | 65 23 | 70 24 | 64 25 | 2 26 | CONTINUOUS 27 | 3 28 | ______ 29 | 73 30 | 0 31 | 40 32 | 0 33 | 0 34 | ENDTAB 35 | 0 36 | TABLE 37 | 2 38 | LAYER 39 | 0 40 | ENDTAB 41 | 0 42 | ENDSEC 43 | 0 44 | SECTION 45 | 2 46 | ENTITIES 47 | 0 48 | LINE 49 | 8 50 | 0 51 | 10 52 | -7 53 | 20 54 | -7 55 | 11 56 | 7 57 | 21 58 | -7 59 | 0 60 | LINE 61 | 8 62 | 0 63 | 10 64 | 7 65 | 20 66 | -7 67 | 11 68 | 7 69 | 21 70 | 7 71 | 0 72 | LINE 73 | 8 74 | 0 75 | 10 76 | 7 77 | 20 78 | 7 79 | 11 80 | -7 81 | 21 82 | 7 83 | 0 84 | LINE 85 | 8 86 | 0 87 | 10 88 | -7 89 | 20 90 | 7 91 | 11 92 | -7 93 | 21 94 | -7 95 | 0 96 | LINE 97 | 8 98 | 0 99 | 10 100 | -7 101 | 20 102 | 12 103 | 11 104 | 7 105 | 21 106 | 12 107 | 0 108 | LINE 109 | 8 110 | 0 111 | 10 112 | 7 113 | 20 114 | 12 115 | 11 116 | 7 117 | 21 118 | 26 119 | 0 120 | LINE 121 | 8 122 | 0 123 | 10 124 | 7 125 | 20 126 | 26 127 | 11 128 | -7 129 | 21 130 | 26 131 | 0 132 | LINE 133 | 8 134 | 0 135 | 10 136 | -7 137 | 20 138 | 26 139 | 11 140 | -7 141 | 21 142 | 12 143 | 0 144 | LINE 145 | 8 146 | 0 147 | 10 148 | 23 149 | 20 150 | -7 151 | 11 152 | 37 153 | 21 154 | -7 155 | 0 156 | LINE 157 | 8 158 | 0 159 | 10 160 | 37 161 | 20 162 | -7 163 | 11 164 | 37 165 | 21 166 | 7 167 | 0 168 | LINE 169 | 8 170 | 0 171 | 10 172 | 37 173 | 20 174 | 7 175 | 11 176 | 23 177 | 21 178 | 7 179 | 0 180 | LINE 181 | 8 182 | 0 183 | 10 184 | 23 185 | 20 186 | 7 187 | 11 188 | 23 189 | 21 190 | -7 191 | 0 192 | LINE 193 | 8 194 | 0 195 | 10 196 | 23 197 | 20 198 | 12 199 | 11 200 | 37 201 | 21 202 | 12 203 | 0 204 | LINE 205 | 8 206 | 0 207 | 10 208 | 37 209 | 20 210 | 12 211 | 11 212 | 37 213 | 21 214 | 26 215 | 0 216 | LINE 217 | 8 218 | 0 219 | 10 220 | 37 221 | 20 222 | 26 223 | 11 224 | 23 225 | 21 226 | 26 227 | 0 228 | LINE 229 | 8 230 | 0 231 | 10 232 | 23 233 | 20 234 | 26 235 | 11 236 | 23 237 | 21 238 | 12 239 | 0 240 | LINE 241 | 8 242 | 0 243 | 10 244 | 31.0980762 245 | 20 246 | -4.0980762 247 | 11 248 | 36.2942286 249 | 21 250 | -7.0980762 251 | 0 252 | LINE 253 | 8 254 | 0 255 | 10 256 | 36.2942286 257 | 20 258 | -7.0980762 259 | 11 260 | 39.2942286 261 | 21 262 | -1.9019238 263 | 0 264 | LINE 265 | 8 266 | 0 267 | 10 268 | 39.2942286 269 | 20 270 | -1.9019238 271 | 11 272 | 34.0980762 273 | 21 274 | 1.0980762 275 | 0 276 | LINE 277 | 8 278 | 0 279 | 10 280 | 34.0980762 281 | 20 282 | 1.0980762 283 | 11 284 | 31.0980762 285 | 21 286 | -4.0980762 287 | 0 288 | LINE 289 | 8 290 | 0 291 | 10 292 | 31.0980762 293 | 20 294 | 14.9019238 295 | 11 296 | 36.2942286 297 | 21 298 | 11.9019238 299 | 0 300 | LINE 301 | 8 302 | 0 303 | 10 304 | 36.2942286 305 | 20 306 | 11.9019238 307 | 11 308 | 39.2942286 309 | 21 310 | 17.0980762 311 | 0 312 | LINE 313 | 8 314 | 0 315 | 10 316 | 39.2942286 317 | 20 318 | 17.0980762 319 | 11 320 | 34.0980762 321 | 21 322 | 20.0980762 323 | 0 324 | LINE 325 | 8 326 | 0 327 | 10 328 | 34.0980762 329 | 20 330 | 20.0980762 331 | 11 332 | 31.0980762 333 | 21 334 | 14.9019238 335 | 0 336 | LINE 337 | 8 338 | 0 339 | 10 340 | -1.0980762 341 | 20 342 | -4.0980762 343 | 11 344 | 4.0980762 345 | 21 346 | -1.0980762 347 | 0 348 | LINE 349 | 8 350 | 0 351 | 10 352 | 4.0980762 353 | 20 354 | -1.0980762 355 | 11 356 | 1.0980762 357 | 21 358 | 4.0980762 359 | 0 360 | LINE 361 | 8 362 | 0 363 | 10 364 | 1.0980762 365 | 20 366 | 4.0980762 367 | 11 368 | -4.0980762 369 | 21 370 | 1.0980762 371 | 0 372 | LINE 373 | 8 374 | 0 375 | 10 376 | -4.0980762 377 | 20 378 | 1.0980762 379 | 11 380 | -1.0980762 381 | 21 382 | -4.0980762 383 | 0 384 | LINE 385 | 8 386 | 0 387 | 10 388 | -1.0980762 389 | 20 390 | 14.9019238 391 | 11 392 | 4.0980762 393 | 21 394 | 17.9019238 395 | 0 396 | LINE 397 | 8 398 | 0 399 | 10 400 | 4.0980762 401 | 20 402 | 17.9019238 403 | 11 404 | 1.0980762 405 | 21 406 | 23.0980762 407 | 0 408 | LINE 409 | 8 410 | 0 411 | 10 412 | 1.0980762 413 | 20 414 | 23.0980762 415 | 11 416 | -4.0980762 417 | 21 418 | 20.0980762 419 | 0 420 | LINE 421 | 8 422 | 0 423 | 10 424 | -4.0980762 425 | 20 426 | 20.0980762 427 | 11 428 | -1.0980762 429 | 21 430 | 14.9019238 431 | 0 432 | ENDSEC 433 | 0 434 | EOF -------------------------------------------------------------------------------- /src/footprints/rotary.js: -------------------------------------------------------------------------------- 1 | // EC11 rotary encoder 2 | // 3 | // Nets 4 | // from: corresponds to switch pin 1 (for button presses) 5 | // to: corresponds to switch pin 2 (for button presses) 6 | // A: corresponds to pin 1 (for rotary) 7 | // B: corresponds to pin 2 (for rotary, should be GND) 8 | // C: corresponds to pin 3 (for rotary) 9 | 10 | module.exports = { 11 | nets: { 12 | from: undefined, 13 | to: undefined, 14 | A: undefined, 15 | B: undefined, 16 | C: undefined 17 | }, 18 | params: { 19 | class: 'ROT' 20 | }, 21 | body: p => ` 22 | (module rotary_encoder (layer F.Cu) (tedit 603326DE) 23 | 24 | ${p.at /* parametric position */} 25 | 26 | ${'' /* footprint reference */} 27 | (fp_text reference "${p.ref}" (at 0 0.5) (layer F.SilkS) 28 | ${p.ref_hide} (effects (font (size 1 1) (thickness 0.15)))) 29 | (fp_text value "" (at 0 8.89) (layer F.Fab) 30 | (effects (font (size 1 1) (thickness 0.15)))) 31 | 32 | ${''/* component outline */} 33 | (fp_line (start -0.62 -0.04) (end 0.38 -0.04) (layer F.SilkS) (width 0.12)) 34 | (fp_line (start -0.12 -0.54) (end -0.12 0.46) (layer F.SilkS) (width 0.12)) 35 | (fp_line (start 5.98 3.26) (end 5.98 5.86) (layer F.SilkS) (width 0.12)) 36 | (fp_line (start 5.98 -1.34) (end 5.98 1.26) (layer F.SilkS) (width 0.12)) 37 | (fp_line (start 5.98 -5.94) (end 5.98 -3.34) (layer F.SilkS) (width 0.12)) 38 | (fp_line (start -3.12 -0.04) (end 2.88 -0.04) (layer F.Fab) (width 0.12)) 39 | (fp_line (start -0.12 -3.04) (end -0.12 2.96) (layer F.Fab) (width 0.12)) 40 | (fp_line (start -7.32 -4.14) (end -7.62 -3.84) (layer F.SilkS) (width 0.12)) 41 | (fp_line (start -7.92 -4.14) (end -7.32 -4.14) (layer F.SilkS) (width 0.12)) 42 | (fp_line (start -7.62 -3.84) (end -7.92 -4.14) (layer F.SilkS) (width 0.12)) 43 | (fp_line (start -6.22 -5.84) (end -6.22 5.86) (layer F.SilkS) (width 0.12)) 44 | (fp_line (start -2.12 -5.84) (end -6.22 -5.84) (layer F.SilkS) (width 0.12)) 45 | (fp_line (start -2.12 5.86) (end -6.22 5.86) (layer F.SilkS) (width 0.12)) 46 | (fp_line (start 5.98 5.86) (end 1.88 5.86) (layer F.SilkS) (width 0.12)) 47 | (fp_line (start 1.88 -5.94) (end 5.98 -5.94) (layer F.SilkS) (width 0.12)) 48 | (fp_line (start -6.12 -4.74) (end -5.12 -5.84) (layer F.Fab) (width 0.12)) 49 | (fp_line (start -6.12 5.76) (end -6.12 -4.74) (layer F.Fab) (width 0.12)) 50 | (fp_line (start 5.88 5.76) (end -6.12 5.76) (layer F.Fab) (width 0.12)) 51 | (fp_line (start 5.88 -5.84) (end 5.88 5.76) (layer F.Fab) (width 0.12)) 52 | (fp_line (start -5.12 -5.84) (end 5.88 -5.84) (layer F.Fab) (width 0.12)) 53 | (fp_line (start -8.87 -6.89) (end 7.88 -6.89) (layer F.CrtYd) (width 0.05)) 54 | (fp_line (start -8.87 -6.89) (end -8.87 6.81) (layer F.CrtYd) (width 0.05)) 55 | (fp_line (start 7.88 6.81) (end 7.88 -6.89) (layer F.CrtYd) (width 0.05)) 56 | (fp_line (start 7.88 6.81) (end -8.87 6.81) (layer F.CrtYd) (width 0.05)) 57 | (fp_circle (center -0.12 -0.04) (end 2.88 -0.04) (layer F.SilkS) (width 0.12)) 58 | (fp_circle (center -0.12 -0.04) (end 2.88 -0.04) (layer F.Fab) (width 0.12)) 59 | 60 | ${''/* pin names */} 61 | (pad A thru_hole rect (at -7.62 -2.54 ${p.rot}) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.net.A.str}) 62 | (pad C thru_hole circle (at -7.62 -0.04) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.net.C.str}) 63 | (pad B thru_hole circle (at -7.62 2.46) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.net.B.str}) 64 | (pad 1 thru_hole circle (at 6.88 -2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask) ${p.net.from.str}) 65 | (pad 2 thru_hole circle (at 6.88 2.46) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask) ${p.net.to.str}) 66 | 67 | ${''/* Legs */} 68 | (pad "" thru_hole rect (at -0.12 -5.64 ${p.rot}) (size 3.2 2) (drill oval 2.8 1.5) (layers *.Cu *.Mask)) 69 | (pad "" thru_hole rect (at -0.12 5.56 ${p.rot}) (size 3.2 2) (drill oval 2.8 1.5) (layers *.Cu *.Mask)) 70 | ) 71 | ` 72 | } 73 | -------------------------------------------------------------------------------- /src/footprints/chocmini.js: -------------------------------------------------------------------------------- 1 | // Kailh Choc PG1232 2 | // Nets 3 | // from: corresponds to pin 1 4 | // to: corresponds to pin 2 5 | // Params 6 | // reverse: default is false 7 | // if true, will flip the footprint such that the pcb can be reversible 8 | // keycaps: default is false 9 | // if true, will add choc sized keycap box around the footprint 10 | 11 | module.exports = { 12 | nets: { 13 | from: undefined, 14 | to: undefined 15 | }, 16 | params: { 17 | class: 'S', 18 | side: 'F', 19 | reverse: false, 20 | keycaps: false 21 | }, 22 | body: p => { 23 | const standard = ` 24 | (module lib:Kailh_PG1232 (layer F.Cu) (tedit 5E1ADAC2) 25 | ${p.at /* parametric position */} 26 | 27 | ${'' /* footprint reference */} 28 | (fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15)))) 29 | (fp_text value Kailh_PG1232 (at 0 -7.3) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15)))) 30 | 31 | ${'' /* corner marks */} 32 | (fp_line (start -7.25 -6.75) (end -6.25 -6.75) (layer Dwgs.User) (width 0.15)) 33 | (fp_line (start -7.25 -6.75) (end -7.25 -5.75) (layer Dwgs.User) (width 0.15)) 34 | 35 | (fp_line (start -7.25 6.75) (end -6.25 6.75) (layer Dwgs.User) (width 0.15)) 36 | (fp_line (start -7.25 6.75) (end -7.25 5.75) (layer Dwgs.User) (width 0.15)) 37 | 38 | (fp_line (start 7.25 -6.75) (end 6.25 -6.75) (layer Dwgs.User) (width 0.15)) 39 | (fp_line (start 7.25 -6.75) (end 7.25 -5.75) (layer Dwgs.User) (width 0.15)) 40 | 41 | (fp_line (start 7.25 6.75) (end 6.25 6.75) (layer Dwgs.User) (width 0.15)) 42 | (fp_line (start 7.25 6.75) (end 7.25 5.75) (layer Dwgs.User) (width 0.15)) 43 | 44 | 45 | (fp_line (start 2.8 -5.35) (end -2.8 -5.35) (layer Dwgs.User) (width 0.15)) 46 | (fp_line (start -2.8 -3.2) (end 2.8 -3.2) (layer Dwgs.User) (width 0.15)) 47 | (fp_line (start 2.8 -3.2) (end 2.8 -5.35) (layer Dwgs.User) (width 0.15)) 48 | (fp_line (start -2.8 -3.2) (end -2.8 -5.35) (layer Dwgs.User) (width 0.15)) 49 | 50 | ${''/* middle shaft */} 51 | (fp_line (start 2.25 2.6) (end 5.8 2.6) (layer Edge.Cuts) (width 0.12)) 52 | (fp_line (start -2.25 2.6) (end -5.8 2.6) (layer Edge.Cuts) (width 0.12)) 53 | (fp_line (start 2.25 3.6) (end 2.25 2.6) (layer Edge.Cuts) (width 0.12)) 54 | (fp_line (start -2.25 3.6) (end 2.25 3.6) (layer Edge.Cuts) (width 0.12)) 55 | (fp_line (start -2.25 2.6) (end -2.25 3.6) (layer Edge.Cuts) (width 0.12)) 56 | (fp_line (start -5.8 2.6) (end -5.8 -2.95) (layer Edge.Cuts) (width 0.12)) 57 | (fp_line (start 5.8 -2.95) (end 5.8 2.6) (layer Edge.Cuts) (width 0.12)) 58 | (fp_line (start -5.8 -2.95) (end 5.8 -2.95) (layer Edge.Cuts) (width 0.12)) 59 | 60 | ${''/* stabilizers */} 61 | (pad 3 thru_hole circle (at 5.3 -4.75) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) (clearance 0.2)) 62 | (pad 4 thru_hole circle (at -5.3 -4.75) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) (clearance 0.2)) 63 | ` 64 | const keycap = ` 65 | ${'' /* keycap marks */} 66 | (fp_line (start -9 -8.5) (end 9 -8.5) (layer Dwgs.User) (width 0.15)) 67 | (fp_line (start 9 -8.5) (end 9 8.5) (layer Dwgs.User) (width 0.15)) 68 | (fp_line (start 9 8.5) (end -9 8.5) (layer Dwgs.User) (width 0.15)) 69 | (fp_line (start -9 8.5) (end -9 -8.5) (layer Dwgs.User) (width 0.15)) 70 | ` 71 | function pins(def_neg, def_pos) { 72 | return ` 73 | ${''/* pins */} 74 | (pad 1 thru_hole circle (at ${def_neg}4.58 5.1) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) ${p.net.from.str} (clearance 0.2)) 75 | (pad 2 thru_hole circle (at ${def_pos}2 5.4) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) ${p.net.to.str} (clearance 0.2)) 76 | ` 77 | } 78 | if(p.param.reverse){ 79 | return ` 80 | ${standard} 81 | ${p.param.keycaps ? keycap : ''} 82 | ${pins('-', '')} 83 | ${pins('', '-')}) 84 | 85 | ` 86 | } else { 87 | return ` 88 | ${standard} 89 | ${p.param.keycaps ? keycap : ''} 90 | ${pins('-', '')}) 91 | ` 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | # Ergogen TODO 2 | 3 | 4 | 5 | ## CLI 6 | 7 | ### Major 8 | 9 | - Move column-level attributes like spread to key-level to unify the structure 10 | - Generalize what shapes to be repeated when outlining `keys` 11 | - Place rectangles by their centers 12 | - Full per-point anchors 13 | - Collapse any raw shift or rotation under the anchor infrastructure 14 | - Merge, generalize, and uniform-ize footprints 15 | - Template for creating them, built-in variables they can use, documentation, external links, etc. 16 | - Also considering how (or, on which layer) they define their silks, universal mirroring behaviour, etc. 17 | 18 | ### Minor 19 | 20 | - Allow shift/rotate for outlines (via `anchor_def`, probably) 21 | - More generic anchors or distances? 22 | - Intersect support for anchor affects clauses, which (combined with the math formulas and possible trigonometric functions) should allow for every use case we've discussed so far 23 | - Allow both object (as well as arrays) in multiple anchor refs 24 | - SVG input (for individual outlines, or even combinations parsed by line color, etc.) 25 | - And once that's done, possibly even STL or other input for cases or pcb renders 26 | - Support text silk output to PCBs (in configurable fonts, through SVG?) 27 | - Maybe a partial markdown preprocess to support bold and italic? 28 | - Look into gr_curve to possibly add beziers to the kicad conversion 29 | - Support curves (arcs as well as Béziers) in polygons 30 | - Support specifying keys/labels for the pcb section (not just blindly assuming all) 31 | - Add snappable line footprint 32 | - Layer-aware export from Maker.JS, so we can debug in the webui more easily 33 | - Add filleting syntax with `@`? 34 | - Eeschema support for pcbs 35 | - Outline expand and shrink access from makerjs 36 | - Resurrect and/or add wider tagging support 37 | - Also add subtractive tagging filters (exclude) 38 | - Also expand this to footprints (so, which footprints get applied to which pcb) 39 | - Or, at least, allow skipping per-key footprints 40 | - Generate ZMK shield from config 41 | - Export **to** KLE? 42 | - Per-footprint mirror support 43 | - A flag for footprints to be able to "resist" the mirroring-related special treatment of negative X shift, rotation, etc. 44 | - Include 3D models for kicad output for visualization 45 | - Look into kicad 5 vs. 6 output format 46 | - Update json schema and add syntax highlight to editors 47 | 48 | 49 | ### Patch 50 | 51 | - Better error handling for the fillet option? 52 | - Implement `glue.extra` 53 | - Integration and end2end tests to get coverage to 100% 54 | - Fix the intersection of parallel lines when gluing 55 | - Add custom fillet implementation that considers line-line connections only 56 | 57 | 58 | 59 | ## WEBUI 60 | 61 | ### Major 62 | 63 | - Change over to Cache's live preview implementation 64 | - Add missing KLE functionality 65 | - Create browserified version of semver lib 66 | 67 | ### Minor 68 | 69 | - Propagate log output from the ergogen module 70 | - Attempt to auto-compile (if inactive for n secs, or whatever) 71 | - Support saving to gists 72 | - Add kicad_pcb visualization as well 73 | - Expand the config dropdown with opensource stuff: corne, lily, ergodox, atreus... 74 | 75 | ### Patch 76 | 77 | - Streamlining (and documenting) an update pipeline 78 | - Puppeteer tests 79 | 80 | 81 | 82 | ## DOCS 83 | 84 | - n00b tutorials 85 | - With a progression of increasingly complex steps 86 | - And lots of illustrations! 87 | - Complete reference 88 | - some known deficiencies: 89 | - Units separated to their own block at the front 90 | - Key-level `width` and `height` are supported during visualization 91 | - This key-level example should probably be added from discord: https://discord.com/channels/714176584269168732/759825860617437204/773104093546676244 92 | - Change outline fields to have their full anchor support documented 93 | - Mention the ability to opt out of gluing! 94 | - Key-level defaults are based around u's, not 19! 95 | - change over to built, per-chapter docs, like how Cache has them 96 | - Contribution guidelines 97 | - including test commands (npm test, npm run coverage, --what switch, --dump switch) 98 | - Changelog, Roadmap 99 | - A public catalog of real-life ergogen configs 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/prepare.js: -------------------------------------------------------------------------------- 1 | const u = require('./utils') 2 | const a = require('./assert') 3 | 4 | const _extend = exports._extend = (to, from) => { 5 | const to_type = a.type(to)() 6 | const from_type = a.type(from)() 7 | if (from === undefined || from === null) return to 8 | if (from === '$unset') return undefined 9 | if (to_type != from_type) return from 10 | if (from_type == 'object') { 11 | const res = u.deepcopy(to) 12 | for (const key of Object.keys(from)) { 13 | res[key] = _extend(to[key], from[key]) 14 | if (res[key] === undefined) delete res[key] 15 | } 16 | return res 17 | } else if (from_type == 'array') { 18 | const res = u.deepcopy(to) 19 | for (const [i, val] of from.entries()) { 20 | res[i] = _extend(res[i], val) 21 | } 22 | return res 23 | } else return from 24 | } 25 | 26 | const extend = exports.extend = (...args) => { 27 | let res = args[0] 28 | for (const arg of args) { 29 | if (res == arg) continue 30 | res = _extend(res, arg) 31 | } 32 | return res 33 | } 34 | 35 | const traverse = exports.traverse = (config, root, breadcrumbs, op) => { 36 | if (a.type(config)() == 'object') { 37 | const result = {} 38 | for (const [key, val] of Object.entries(config)) { 39 | breadcrumbs.push(key) 40 | op(result, key, traverse(val, root, breadcrumbs, op), root, breadcrumbs) 41 | breadcrumbs.pop() 42 | } 43 | return result 44 | } else if (a.type(config)() == 'array') { 45 | const result = [] 46 | let index = 0 47 | for (const val of config) { 48 | breadcrumbs.push(`[${index}]`) 49 | result[index] = traverse(val, root, breadcrumbs, op) 50 | breadcrumbs.pop() 51 | index++ 52 | } 53 | return result 54 | } 55 | return config 56 | } 57 | 58 | exports.unnest = config => traverse(config, config, [], (target, key, val) => { 59 | u.deep(target, key, val) 60 | }) 61 | 62 | exports.inherit = config => traverse(config, config, [], (target, key, val, root, breadcrumbs) => { 63 | if (val && val.$extends !== undefined) { 64 | let candidates = u.deepcopy(val.$extends) 65 | if (a.type(candidates)() !== 'array') candidates = [candidates] 66 | const list = [val] 67 | while (candidates.length) { 68 | const path = candidates.shift() 69 | const other = u.deepcopy(u.deep(root, path)) 70 | a.assert(other, `"${path}" (reached from "${breadcrumbs.join('.')}.$extends") does not name a valid inheritance target!`) 71 | let parents = other.$extends || [] 72 | if (a.type(parents)() !== 'array') parents = [parents] 73 | candidates = candidates.concat(parents) 74 | list.unshift(other) 75 | } 76 | val = extend.apply(this, list) 77 | delete val.$extends 78 | } 79 | target[key] = val 80 | }) 81 | 82 | exports.parameterize = config => traverse(config, config, [], (target, key, val, root, breadcrumbs) => { 83 | 84 | // we only care about objects 85 | if (a.type(val)() !== 'object') { 86 | target[key] = val 87 | return 88 | } 89 | 90 | let params = val.$params 91 | let args = val.$args 92 | 93 | // explicitly skipped (probably intermediate) template, remove (by not setting it) 94 | if (val.$skip) return 95 | 96 | // nothing to do here, just pass the original value through 97 | if (!params && !args) { 98 | target[key] = val 99 | return 100 | } 101 | 102 | // unused template, remove (by not setting it) 103 | if (params && !args) return 104 | 105 | if (!params && args) { 106 | throw new Error(`Trying to parameterize through "${breadcrumbs}.$args", but the corresponding "$params" field is missing!`) 107 | } 108 | 109 | params = a.strarr(params, `${breadcrumbs}.$params`) 110 | args = a.sane(args, `${breadcrumbs}.$args`, 'array')() 111 | if (params.length !== args.length) { 112 | throw new Error(`The number of "$params" and "$args" don't match for "${breadcrumbs}"!`) 113 | } 114 | 115 | let str = JSON.stringify(val) 116 | const zip = rows => rows[0].map((_, i) => rows.map(row => row[i])) 117 | for (const [par, arg] of zip([params, args])) { 118 | str = str.replace(new RegExp(`${par}`, 'g'), arg) 119 | } 120 | try { 121 | val = JSON.parse(str) 122 | } catch (ex) { 123 | throw new Error(`Replacements didn't lead to a valid JSON object at "${breadcrumbs}"! ` + ex) 124 | } 125 | 126 | delete val.$params 127 | delete val.$args 128 | target[key] = val 129 | }) -------------------------------------------------------------------------------- /test/pcbs/references___pcbs.json: -------------------------------------------------------------------------------- 1 | { 2 | "shown": "\n \n(kicad_pcb (version 20171130) (host pcbnew 5.1.6)\n\n (page A3)\n (title_block\n (title shown)\n (rev v1.0.0)\n (company Unknown)\n )\n\n (general\n (thickness 1.6)\n )\n\n (layers\n (0 F.Cu signal)\n (31 B.Cu signal)\n (32 B.Adhes user)\n (33 F.Adhes user)\n (34 B.Paste user)\n (35 F.Paste user)\n (36 B.SilkS user)\n (37 F.SilkS user)\n (38 B.Mask user)\n (39 F.Mask user)\n (40 Dwgs.User user)\n (41 Cmts.User user)\n (42 Eco1.User user)\n (43 Eco2.User user)\n (44 Edge.Cuts user)\n (45 Margin user)\n (46 B.CrtYd user)\n (47 F.CrtYd user)\n (48 B.Fab user)\n (49 F.Fab user)\n )\n\n (setup\n (last_trace_width 0.25)\n (trace_clearance 0.2)\n (zone_clearance 0.508)\n (zone_45_only no)\n (trace_min 0.2)\n (via_size 0.8)\n (via_drill 0.4)\n (via_min_size 0.4)\n (via_min_drill 0.3)\n (uvia_size 0.3)\n (uvia_drill 0.1)\n (uvias_allowed no)\n (uvia_min_size 0.2)\n (uvia_min_drill 0.1)\n (edge_width 0.05)\n (segment_width 0.2)\n (pcb_text_width 0.3)\n (pcb_text_size 1.5 1.5)\n (mod_edge_width 0.12)\n (mod_text_size 1 1)\n (mod_text_width 0.15)\n (pad_size 1.524 1.524)\n (pad_drill 0.762)\n (pad_to_mask_clearance 0.05)\n (aux_axis_origin 0 0)\n (visible_elements FFFFFF7F)\n (pcbplotparams\n (layerselection 0x010fc_ffffffff)\n (usegerberextensions false)\n (usegerberattributes true)\n (usegerberadvancedattributes true)\n (creategerberjobfile true)\n (excludeedgelayer true)\n (linewidth 0.100000)\n (plotframeref false)\n (viasonmask false)\n (mode 1)\n (useauxorigin false)\n (hpglpennumber 1)\n (hpglpenspeed 20)\n (hpglpendiameter 15.000000)\n (psnegative false)\n (psa4output false)\n (plotreference true)\n (plotvalue true)\n (plotinvisibletext false)\n (padsonsilk false)\n (subtractmaskfromsilk false)\n (outputformat 1)\n (mirror false)\n (drillshape 1)\n (scaleselection 1)\n (outputdirectory \"\"))\n )\n\n (net 0 \"\")\n \n (net_class Default \"This is the default net class.\"\n (clearance 0.2)\n (trace_width 0.25)\n (via_dia 0.8)\n (via_drill 0.4)\n (uvia_dia 0.3)\n (uvia_drill 0.1)\n (add_net \"\")\n )\n\n references shown\n \n \n)\n\n ", 3 | "hidden": "\n \n(kicad_pcb (version 20171130) (host pcbnew 5.1.6)\n\n (page A3)\n (title_block\n (title hidden)\n (rev v1.0.0)\n (company Unknown)\n )\n\n (general\n (thickness 1.6)\n )\n\n (layers\n (0 F.Cu signal)\n (31 B.Cu signal)\n (32 B.Adhes user)\n (33 F.Adhes user)\n (34 B.Paste user)\n (35 F.Paste user)\n (36 B.SilkS user)\n (37 F.SilkS user)\n (38 B.Mask user)\n (39 F.Mask user)\n (40 Dwgs.User user)\n (41 Cmts.User user)\n (42 Eco1.User user)\n (43 Eco2.User user)\n (44 Edge.Cuts user)\n (45 Margin user)\n (46 B.CrtYd user)\n (47 F.CrtYd user)\n (48 B.Fab user)\n (49 F.Fab user)\n )\n\n (setup\n (last_trace_width 0.25)\n (trace_clearance 0.2)\n (zone_clearance 0.508)\n (zone_45_only no)\n (trace_min 0.2)\n (via_size 0.8)\n (via_drill 0.4)\n (via_min_size 0.4)\n (via_min_drill 0.3)\n (uvia_size 0.3)\n (uvia_drill 0.1)\n (uvias_allowed no)\n (uvia_min_size 0.2)\n (uvia_min_drill 0.1)\n (edge_width 0.05)\n (segment_width 0.2)\n (pcb_text_width 0.3)\n (pcb_text_size 1.5 1.5)\n (mod_edge_width 0.12)\n (mod_text_size 1 1)\n (mod_text_width 0.15)\n (pad_size 1.524 1.524)\n (pad_drill 0.762)\n (pad_to_mask_clearance 0.05)\n (aux_axis_origin 0 0)\n (visible_elements FFFFFF7F)\n (pcbplotparams\n (layerselection 0x010fc_ffffffff)\n (usegerberextensions false)\n (usegerberattributes true)\n (usegerberadvancedattributes true)\n (creategerberjobfile true)\n (excludeedgelayer true)\n (linewidth 0.100000)\n (plotframeref false)\n (viasonmask false)\n (mode 1)\n (useauxorigin false)\n (hpglpennumber 1)\n (hpglpenspeed 20)\n (hpglpendiameter 15.000000)\n (psnegative false)\n (psa4output false)\n (plotreference true)\n (plotvalue true)\n (plotinvisibletext false)\n (padsonsilk false)\n (subtractmaskfromsilk false)\n (outputformat 1)\n (mirror false)\n (drillshape 1)\n (scaleselection 1)\n (outputdirectory \"\"))\n )\n\n (net 0 \"\")\n \n (net_class Default \"This is the default net class.\"\n (clearance 0.2)\n (trace_width 0.25)\n (via_dia 0.8)\n (via_drill 0.4)\n (uvia_dia 0.3)\n (uvia_drill 0.1)\n (add_net \"\")\n )\n\n references hidden\n \n \n)\n\n " 4 | } 5 | -------------------------------------------------------------------------------- /input/config-flipper.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | key: 3 | padding: cy 4 | footprints: 5 | choc_hotswap: 6 | type: choc 7 | nets: 8 | from: =column_net 9 | to: GND 10 | params: 11 | keycaps: true 12 | reverse: true 13 | hotswap: true 14 | choc: 15 | type: choc 16 | anchor: 17 | rotate: 180 18 | nets: 19 | from: =column_net 20 | to: GND 21 | params: 22 | keycaps: true 23 | reverse: true 24 | zones: 25 | board: 26 | rows: 27 | bottom: 28 | home: 29 | top: 30 | columns: 31 | pinkie: 32 | spread: cx 33 | row_overrides: 34 | bottom: 35 | bind: [0, 0, 0, 1] 36 | column_net: P21 37 | home: 38 | bind: [0.5, 3, 3, 1] 39 | column_net: P20 40 | ring: 41 | stagger: 13 42 | spread: cx 43 | rows: 44 | bottom: 45 | bind: [0, 0, 2, 3] 46 | column_net: P19 47 | home: 48 | bind: [3,0,3,1] 49 | column_net: P18 50 | top: 51 | bind: [0.5,3,0,1] 52 | column_net: P15 53 | middle: 54 | stagger: 8 55 | spread: cx 56 | rows: 57 | bottom: 58 | bind: [0, 3, 20, 3] 59 | column_net: P14 60 | home: 61 | bind: [3,0,3,0] 62 | column_net: P16 63 | top: 64 | column_net: P10 65 | bind: [0.5,1,0,1] 66 | index: 67 | stagger: -12 68 | spread: cx 69 | rows: 70 | bottom: 71 | bind: [0, 3, 0, 0] 72 | column_net: P4 73 | home: 74 | bind: [3,0,3,0] 75 | column_net: P5 76 | top: 77 | bind: [0.5,1,3,3] 78 | column_net: P6 79 | far: 80 | stagger: -cy - 1 81 | spread: cx 82 | rows: 83 | bottom: 84 | bind: [3,1, 0, 73] 85 | column_net: P9 86 | home: 87 | bind: [3,1,3,0] 88 | column_net: P7 89 | top: 90 | bind: [0.5,1,3,3] 91 | column_net: P8 92 | outlines: 93 | exports: 94 | raw: 95 | - type: keys 96 | side: left 97 | size: [1cx , 1cx] 98 | cutout: 99 | - type: outline 100 | name: raw 101 | fillet: 3 102 | keycap_outlines: 103 | - type: keys 104 | side: left 105 | size: [1cx - 0.5, 1cy - 0.5] # Choc keycaps are 17.5 x 16.5 106 | bound: false 107 | pcbs: 108 | flipper: 109 | outlines: 110 | main: 111 | outline: cutout 112 | footprints: 113 | promicro: 114 | type: promicro 115 | anchor: 116 | ref: board_far_home 117 | shift: [-25, -17] 118 | rotate: 0 119 | params: 120 | orientation: down 121 | pcm12: 122 | type: pcm12 123 | anchor: 124 | ref: board_far_home 125 | shift: [-75, -22] 126 | rotate: 0 127 | nets: 128 | from: pos 129 | to: RAW 130 | params: 131 | reverse: true 132 | via3: 133 | type: via 134 | anchor: 135 | ref: board_pinkie_bottom 136 | shift: [-2,-10] 137 | nets: 138 | net: RAW 139 | via4: 140 | type: via 141 | anchor: 142 | ref: board_pinkie_bottom 143 | shift: [-4,-10] 144 | nets: 145 | net: pos 146 | via5: 147 | type: via 148 | anchor: 149 | ref: board_pinkie_bottom 150 | shift: [8,-10] 151 | nets: 152 | net: GND 153 | via6: 154 | type: via 155 | anchor: 156 | ref: board_pinkie_bottom 157 | shift: [6,-10] 158 | nets: 159 | net: RST 160 | b3u1000p: 161 | type: b3u1000p 162 | nets: 163 | r1: RST 164 | r2: GND 165 | anchor: 166 | ref: board_pinkie_bottom 167 | shift: [7, -15] 168 | rotate: 0 169 | params: 170 | reverse: true 171 | bat: 172 | type: bat 173 | nets: 174 | neg: GND 175 | anchor: 176 | ref: board_far_bottom 177 | shift: [-42 , -0] 178 | rotate: 0 179 | -------------------------------------------------------------------------------- /input/config-flame.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | key: 3 | padding: cy 4 | footprints: 5 | choc_hotswap: 6 | type: choc 7 | nets: 8 | from: =column_net 9 | to: GND 10 | params: 11 | keycaps: true 12 | reverse: true 13 | hotswap: true 14 | choc: 15 | type: choc 16 | anchor: 17 | rotate: 180 18 | nets: 19 | from: =column_net 20 | to: GND 21 | params: 22 | keycaps: true 23 | reverse: true 24 | zones: 25 | board: 26 | rows: 27 | home: 28 | top: 29 | columns: 30 | pinkie: 31 | rotate: 30 32 | row_overrides: 33 | home: 34 | bind: [1,2,1,1] 35 | column_net: P1 36 | ring: 37 | spread: cx 38 | stagger: 16 39 | rows: 40 | home: 41 | bind: [0,0,3,1] 42 | column_net: P21 43 | top: 44 | bind: [0,0,0,1] 45 | column_net: P20 46 | middle: 47 | spread: cx 48 | stagger: -5 49 | rows: 50 | home: 51 | bind: [0,0,3,0] 52 | column_net: P15 53 | top: 54 | column_net: P14 55 | bind: [0,1,0,1] 56 | index: 57 | spread: cx 58 | stagger: -10 59 | rotate: -5 60 | rows: 61 | home: 62 | bind: [0,1,3,0] 63 | column_net: P8 64 | top: 65 | bind: [0,1,3,3] 66 | column_net: P16 67 | thumbfan: 68 | anchor: 69 | ref: board_index_home 70 | shift: [-6, -7] 71 | rows: 72 | home: 73 | columns: 74 | home: 75 | spread: 21.25 76 | rotate: -25 77 | origin: [-11.75, -10] 78 | rows: 79 | home: 80 | column_net: P9 81 | far: 82 | spread: 18.5 83 | rotate: -0 84 | origin: [-7.5, -9] 85 | rows: 86 | home: 87 | column_net: P10 88 | outlines: 89 | exports: 90 | raw: 91 | - type: keys 92 | side: left 93 | size: [1cx , 1cy] 94 | - type: rectangle 95 | size: [90.5, 37] 96 | anchor: 97 | ref: board_index_home 98 | rotate: -25 99 | shift: [-49.5, -12] 100 | cutouta: 101 | - type: outline 102 | name: raw 103 | fillet: 2 104 | cutoutb: 105 | - type: outline 106 | name: cutouta 107 | fillet: 1 108 | cutout: 109 | - type: outline 110 | name: cutoutb 111 | fillet: 0.5 112 | keycap_outlines: 113 | - type: keys 114 | side: left 115 | size: [1cx - 0.5, 1cy - 0.5] # Choc keycaps are 17.5 x 16.5 116 | bound: false 117 | pcbs: 118 | flame: 119 | outlines: 120 | main: 121 | outline: cutout 122 | footprints: 123 | promicro: 124 | type: promicro_pretty 125 | anchor: 126 | ref: thumbfan_home_home 127 | shift: [-39, -18] 128 | rotate: 180 129 | pcm12: 130 | type: pcm12 131 | anchor: 132 | ref: thumbfan_home_home 133 | shift: [-11, -25.5] 134 | rotate: 0 135 | nets: 136 | from: pos 137 | to: RAW 138 | params: 139 | reverse: true 140 | via3: 141 | type: via 142 | anchor: 143 | ref: thumbfan_home_home 144 | shift: [-9,-19] 145 | nets: 146 | net: RAW 147 | via4: 148 | type: via 149 | anchor: 150 | ref: thumbfan_home_home 151 | shift: [-13,-19] 152 | nets: 153 | net: pos 154 | via5: 155 | type: via 156 | anchor: 157 | ref: thumbfan_home_home 158 | shift: [-16,-3.5] 159 | nets: 160 | net: GND 161 | via6: 162 | type: via 163 | anchor: 164 | ref: thumbfan_home_home 165 | shift: [-13,-3.5] 166 | nets: 167 | net: RST 168 | b3u1000p: 169 | type: b3u1000p 170 | nets: 171 | r1: RST 172 | r2: GND 173 | anchor: 174 | ref: thumbfan_home_home 175 | shift: [-14.5, -7] 176 | rotate: 0 177 | params: 178 | reverse: true 179 | bat: 180 | type: bat 181 | nets: 182 | neg: GND 183 | anchor: 184 | ref: thumbfan_home_home 185 | shift: [-15 , -12] 186 | rotate: 90 187 | -------------------------------------------------------------------------------- /input/config-card.yaml: -------------------------------------------------------------------------------- 1 | points: 2 | key: 3 | padding: cy 4 | footprints: 5 | choc_hotswap: 6 | type: choc 7 | nets: 8 | from: =column_net 9 | to: GND 10 | params: 11 | keycaps: true 12 | reverse: true 13 | hotswap: true 14 | choc: 15 | type: choc 16 | anchor: 17 | rotate: 180 18 | nets: 19 | from: =column_net 20 | to: GND 21 | params: 22 | keycaps: true 23 | reverse: true 24 | zones: 25 | board: 26 | rows: 27 | bottom: 28 | home: 29 | top: 30 | columns: 31 | pinkie: 32 | spread: cx 33 | rows: 34 | bottom: 35 | bind: [0, 0, 0, 1] 36 | column_net: P5 37 | home: 38 | bind: [0.5, 3, 0, 1] 39 | column_net: P10 40 | top: 41 | bind: [0, 0, 0, 1] 42 | column_net: P18 43 | ring: 44 | spread: cx 45 | rows: 46 | bottom: 47 | bind: [0, 0, 0, 3] 48 | column_net: P6 49 | home: 50 | bind: [0,0,3,1] 51 | column_net: P16 52 | top: 53 | bind: [0,3,0,1] 54 | column_net: P19 55 | middle: 56 | spread: cx 57 | rows: 58 | bottom: 59 | bind: [0, 3, 0, 3] 60 | column_net: P7 61 | home: 62 | bind: [0,0,3,0] 63 | column_net: P14 64 | top: 65 | column_net: P20 66 | bind: [0,1,0,1] 67 | index: 68 | spread: cx 69 | rows: 70 | bottom: 71 | bind: [0, 3, 0, 0] 72 | column_net: P8 73 | home: 74 | bind: [0,0,3,0] 75 | column_net: P15 76 | top: 77 | bind: [0,1,3,3] 78 | column_net: P21 79 | far: 80 | spread: cx 81 | row_overrides: 82 | bottom: 83 | bind: [34,1, 0, 0] 84 | column_net: P9 85 | outlines: 86 | exports: 87 | raw: 88 | - type: keys 89 | side: left 90 | size: [1cx , 1cx] 91 | - type: rectangle 92 | size: [7, 5] 93 | anchor: 94 | ref: board_index_top 95 | shift: [21, 6] 96 | cutouta: 97 | - type: outline 98 | name: raw 99 | fillet: 3 100 | cutoutb: 101 | - type: outline 102 | name: cutouta 103 | fillet: 1 104 | cutout: 105 | - type: outline 106 | name: cutoutb 107 | fillet: 0.5 108 | keycap_outlines: 109 | - type: keys 110 | side: left 111 | size: [1cx - 0.5, 1cy - 0.5] # Choc keycaps are 17.5 x 16.5 112 | bound: false 113 | pcbs: 114 | card: 115 | outlines: 116 | main: 117 | outline: cutout 118 | footprints: 119 | promicro: 120 | type: promicro 121 | anchor: 122 | ref: board_far_bottom 123 | shift: [0.6, cx+6.3] 124 | rotate: -90 125 | params: 126 | orientation: down 127 | pcm12: 128 | type: pcm12 129 | anchor: 130 | ref: board_index_top 131 | shift: [16.5, 6.5] 132 | rotate: 180 133 | nets: 134 | from: pos 135 | to: RAW 136 | params: 137 | reverse: true 138 | via3: 139 | type: via 140 | anchor: 141 | ref: board_index_top 142 | shift: [16,3] 143 | nets: 144 | net: RAW 145 | via4: 146 | type: via 147 | anchor: 148 | ref: board_index_top 149 | shift: [18,3] 150 | nets: 151 | net: pos 152 | via5: 153 | type: via 154 | anchor: 155 | ref: board_index_top 156 | shift: [23,6.5] 157 | nets: 158 | net: GND 159 | via6: 160 | type: via 161 | anchor: 162 | ref: board_index_top 163 | shift: [25,6.5] 164 | nets: 165 | net: RST 166 | b3u1000p: 167 | type: b3u1000p 168 | nets: 169 | r1: RST 170 | r2: GND 171 | anchor: 172 | ref: board_index_top 173 | shift: [24.5, 9] 174 | rotate: 0 175 | params: 176 | reverse: true 177 | bat: 178 | type: bat 179 | nets: 180 | neg: GND 181 | anchor: 182 | ref: board_far_bottom 183 | shift: [0.2 , 9] 184 | rotate: 90 185 | --------------------------------------------------------------------------------