From fe30b91309b41f5e42cafe9deeb079c045a781f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1n=20D=C3=A9nes?= Date: Sun, 11 Jul 2021 20:36:11 +0200 Subject: [PATCH] PCB net and parameter overhaul - merge static and parametric nets - allow dynamic nets on the fly with `p.local_net()` - support local-to-global position calculations with `p.xy()` - this also enables intra-footprint traces and zones - add anchor type parameters to footprints --- src/cli.js | 4 +- src/ergogen.js | 4 +- src/pcbs.js | 96 ++++++++++++------ test/helpers/mock_footprints.js | 116 ++++++++++++++++++++++ test/index.js | 1 + test/pcbs/001_mock_footprints.yaml | 37 +++++++ test/pcbs/001_mock_footprints___pcbs.json | 3 + 7 files changed, 225 insertions(+), 36 deletions(-) create mode 100644 test/helpers/mock_footprints.js create mode 100644 test/pcbs/001_mock_footprints.yaml create mode 100644 test/pcbs/001_mock_footprints___pcbs.json diff --git a/src/cli.js b/src/cli.js index 3cfc6d7..ca2de3e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -70,8 +70,8 @@ const results = ergogen.process(config, args.debug, s => console.log(s)) console.log('Writing output to disk...') if (args.debug) { - io.dump_model(results.points.demo, path.join(args.o, 'points/demo'), args.debug) - fs.writeJSONSync(path.join(args.o, 'points/data.json'), results.points.data, {spaces: 4}) + io.dump_model(results.demo, path.join(args.o, 'points/demo'), args.debug) + fs.writeJSONSync(path.join(args.o, 'points/data.json'), results.points, {spaces: 4}) } for (const [name, outline] of Object.entries(results.outlines)) { diff --git a/src/ergogen.js b/src/ergogen.js index 5a35f1a..71da8f8 100644 --- a/src/ergogen.js +++ b/src/ergogen.js @@ -27,6 +27,7 @@ module.exports = { const points = points_lib.parse(config.points, units) if (debug) { results.points = points + results.demo = points_lib.visualize(points) } logger('Generating outlines...') @@ -55,5 +56,6 @@ module.exports = { return results }, - visualize: points_lib.visualize + visualize: points_lib.visualize, + inject_footprint: pcbs_lib.inject_footprint } \ No newline at end of file diff --git a/src/pcbs.js b/src/pcbs.js index 665ea60..14450f3 100644 --- a/src/pcbs.js +++ b/src/pcbs.js @@ -1,5 +1,6 @@ const m = require('makerjs') const a = require('./assert') +const prep = require('./prepare') const make_anchor = require('./anchor') const kicad_prefix = ` @@ -143,62 +144,91 @@ const makerjs2kicad = exports._makerjs2kicad = (model, layer='Edge.Cuts') => { } const footprint_types = require('./footprints') + +exports.inject_footprint = (name, fp) => { + footprint_types[name] = fp +} + const footprint = exports._footprint = (config, name, points, point, net_indexer, component_indexer, units) => { if (config === false) return '' - + // config sanitization - a.unexpected(config, name, ['type', 'anchor', 'nets', 'params']) + a.unexpected(config, name, ['type', 'anchor', 'nets', 'anchors', 'params']) const type = a.in(config.type, `${name}.type`, Object.keys(footprint_types)) let anchor = make_anchor(config.anchor || {}, `${name}.anchor`, points, true, point)(units) const nets = a.sane(config.nets || {}, `${name}.nets`, 'object')() + const anchors = a.sane(config.anchors || {}, `${name}.anchors`, 'object')() const params = a.sane(config.params || {}, `${name}.params`, 'object')() // basic setup const fp = footprint_types[type] const parsed_params = {} - // footprint positioning - parsed_params.at = `(at ${anchor.x} ${-anchor.y} ${anchor.r})` - parsed_params.rot = anchor.r - - // connecting static nets - parsed_params.net = {} - for (const net of (fp.static_nets || [])) { - const index = net_indexer(net) - parsed_params.net[net] = `(net ${index} "${net}")` - } - - // connecting parametric nets - for (const net_ref of (fp.nets || [])) { - let net = nets[net_ref] - a.sane(net, `${name}.nets.${net_ref}`, 'string')() - if (net.startsWith('=') && point) { - const indirect = net.substring(1) - net = point.meta[indirect] - a.sane(net, `${name}.nets.${net_ref} --> ${point.meta.name}.${indirect}`, 'string')() - } - const index = net_indexer(net) - parsed_params.net[net_ref] = `(net ${index} "${net}")` - } - - // connecting other, non-net parameters + // connecting other, non-net, non-anchor parameters parsed_params.param = {} - for (const param of (Object.keys(fp.params || {}))) { - let value = params[param] === undefined ? fp.params[param] : params[param] - if (value === undefined) throw new Error(`Field "${name}.params.${param}" is missing!`) + for (const [param_name, param_value] of Object.entries(prep.extend(fp.params || {}, params))) { + let value = a.sane(param_value, `${name}.nets.${param_name}`, 'string')() if (a.type(value)() == 'string' && value.startsWith('=') && point) { const indirect = value.substring(1) value = point.meta[indirect] - if (value === undefined) throw new Error(`Field "${name}.params.${param} --> ${point.meta.name}.${indirect}" is missing!`) + value = a.sane(value, `${name}.params.${param} --> ${point.meta.name}.${indirect}`, 'string')() } - parsed_params.param[param] = value + parsed_params.param[param_name] = value } // reference - parsed_params.ref = component_indexer(parsed_params.param.class || '_') + const component_ref = parsed_params.ref = component_indexer(parsed_params.param.class || '_') parsed_params.ref_hide = 'hide' // TODO: make this parametric? + // footprint positioning + parsed_params.at = `(at ${anchor.x} ${-anchor.y} ${anchor.r})` + parsed_params.rot = anchor.r + parsed_params.xy = (x, y) => { + const new_anchor = make_anchor({ + shift: [x, -y] + }, '_internal_footprint_xy', points, true, anchor)(units) + return `${new_anchor.x} ${-new_anchor.y}` + } + + // connecting nets + parsed_params.net = {} + for (const [net_name, net_value] of Object.entries(prep.extend(fp.nets || {}, nets))) { + let net = a.sane(net_value, `${name}.nets.${net_name}`, 'string')() + if (net.startsWith('=') && point) { + const indirect = net.substring(1) + net = point.meta[indirect] + net = a.sane(net, `${name}.nets.${net_name} --> ${point.meta.name}.${indirect}`, 'string')() + } + const index = net_indexer(net) + parsed_params.net[net_name] = { + name: net, + index: index, + str: `(net ${index} "${net}")` + } + } + + // allowing footprints to add dynamic nets + parsed_params.local_net = suffix => { + const net = `${component_ref}_${suffix}` + const index = net_indexer(net) + return { + name: net, + index: index, + str: `(net ${index} "${net}")` + } + } + + // parsing anchor-type parameters + parsed_params.anchors = {} + console.log(name, config, fp.anchors, anchors) + for (const [anchor_name, anchor_config] of Object.entries(prep.extend(fp.anchors || {}, anchors))) { + let parsed_anchor = make_anchor(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, true, anchor)(units) + console.log(anchor_name, anchor_config, anchor, parsed_anchor) + parsed_anchor.y = -parsed_anchor.y + parsed_params.anchors[anchor_name] = parsed_anchor + } + return fp.body(parsed_params) } diff --git a/test/helpers/mock_footprints.js b/test/helpers/mock_footprints.js new file mode 100644 index 0000000..c822eb1 --- /dev/null +++ b/test/helpers/mock_footprints.js @@ -0,0 +1,116 @@ +exports.inject = (ergogen) => { + ergogen.inject_footprint('trace_test', { + nets: { + P1: 'P1' + }, + params: { + class: 'T', + side: 'F' + }, + body: p => { + return ` + + (module trace_test (layer F.Cu) (tedit 5CF31DEF) + + ${p.at /* parametric position */} + + (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) + ${p.net.P1.str} (solder_mask_margin 0.2)) + + (pad 2 smd rect (at 5 5 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) + ${p.net.P1.str} (solder_mask_margin 0.2)) + + ) + + (segment (start ${p.xy(0, 0)}) (end ${p.xy(5, 5)}) (width 0.25) (layer F.Cu) (net ${p.net.P1.index})) + + ` + } + }) + + ergogen.inject_footprint('zone_test', { + nets: { + P1: 'P1' + }, + params: { + class: 'T', + side: 'F' + }, + body: p => { + return ` + + (module zone_test (layer F.Cu) (tedit 5CF31DEF) + + ${p.at /* parametric position */} + + (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) + ${p.net.P1.str} (solder_mask_margin 0.2)) + + (pad 2 smd rect (at 5 5 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) + ${p.net.P1.str} (solder_mask_margin 0.2)) + + ) + + (zone (net ${p.net.P1.index}) (net_name ${p.net.P1.name}) (layer ${p.param.side}.Cu) (tstamp 0) (hatch full 0.508) + (connect_pads (clearance 0.508)) + (min_thickness 0.254) + (fill yes (arc_segments 32) (thermal_gap 0.508) (thermal_bridge_width 0.508)) + (polygon (pts (xy ${p.xy(5, 5)}) (xy ${p.xy(5, -5)}) (xy ${p.xy(-5, -5)}) (xy ${p.xy(-5, 5)}))) + ) + + ` + } + }) + + ergogen.inject_footprint('dynamic_net_test', { + nets: {}, + params: { + class: 'T', + side: 'F' + }, + body: p => { + return ` + + (module dynamic_net_test (layer F.Cu) (tedit 5CF31DEF) + + ${p.at /* parametric position */} + + (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) + ${p.local_net('1').str} (solder_mask_margin 0.2)) + + (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) + ${p.local_net('2').str} (solder_mask_margin 0.2)) + + (pad 1 smd rect (at 0 0 ${p.rot}) (size 1 1) (layers F.Cu F.Paste F.Mask) + ${p.local_net('3').str} (solder_mask_margin 0.2)) + + ) + + ` + } + }) + + ergogen.inject_footprint('anchor_test', { + nets: {}, + params: { + class: 'T', + side: 'F' + }, + anchors: { + end: undefined + }, + body: p => { + return ` + + (module anchor_test (layer F.Cu) (tedit 5CF31DEF) + + ${p.at /* parametric position */} + + (fp_line (start 0 0) (end ${p.anchors.end.x} ${p.anchors.end.y}) (layer Dwgs.User) (width 0.05)) + + ) + + ` + } + }) +} \ No newline at end of file diff --git a/test/index.js b/test/index.js index 75094de..c603558 100644 --- a/test/index.js +++ b/test/index.js @@ -4,6 +4,7 @@ const yaml = require('js-yaml') const glob = require('glob') const u = require('../src/utils') const ergogen = require('../src/ergogen') +require('./helpers/mock_footprints').inject(ergogen) let what = process.env.npm_config_what let first = process.env.npm_config_first diff --git a/test/pcbs/001_mock_footprints.yaml b/test/pcbs/001_mock_footprints.yaml new file mode 100644 index 0000000..9611264 --- /dev/null +++ b/test/pcbs/001_mock_footprints.yaml @@ -0,0 +1,37 @@ +points: + zones: + matrix: + columns: + one: + rows: + only: +outlines: + exports: + edge: + - type: keys + side: left + size: [u, u] +pcbs: + main: + outlines: + edge: + outline: edge + footprints: + trace: + type: trace_test + anchor: + shift: [1, 1] + rotate: 30 + zone: + type: zone_test + anchor: + shift: [1, 1] + rotate: 30 + dyn: + type: dynamic_net_test + anc: + type: anchor_test + anchors: + end: + ref: matrix_one_only + shift: [10, 10] \ No newline at end of file diff --git a/test/pcbs/001_mock_footprints___pcbs.json b/test/pcbs/001_mock_footprints___pcbs.json new file mode 100644 index 0000000..fda7b7d --- /dev/null +++ b/test/pcbs/001_mock_footprints___pcbs.json @@ -0,0 +1,3 @@ +{ + "main": "\n \n(kicad_pcb (version 20171130) (host pcbnew 5.1.6)\n\n (page A3)\n (title_block\n (title KEYBOARD_NAME_HERE)\n (rev VERSION_HERE)\n (company YOUR_NAME_HERE)\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(net 1 \"P1\")\n(net 2 \"T3_1\")\n(net 3 \"T3_2\")\n(net 4 \"T3_3\")\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(add_net \"P1\")\n(add_net \"T3_1\")\n(add_net \"T3_2\")\n(add_net \"T3_3\")\n )\n\n \n\n (module trace_test (layer F.Cu) (tedit 5CF31DEF)\n\n (at 1 -1 30)\n\n (pad 1 smd rect (at 0 0 30) (size 1 1) (layers F.Cu F.Paste F.Mask)\n (net 1 \"P1\") (solder_mask_margin 0.2))\n\n (pad 2 smd rect (at 5 5 30) (size 1 1) (layers F.Cu F.Paste F.Mask)\n (net 1 \"P1\") (solder_mask_margin 0.2))\n\n )\n\n (segment (start 1 -1) (end 7.830127 0.8301270000000001) (width 0.25) (layer F.Cu) (net 1))\n\n \n\n\n (module zone_test (layer F.Cu) (tedit 5CF31DEF)\n\n (at 1 -1 30)\n\n (pad 1 smd rect (at 0 0 30) (size 1 1) (layers F.Cu F.Paste F.Mask)\n (net 1 \"P1\") (solder_mask_margin 0.2))\n\n (pad 2 smd rect (at 5 5 30) (size 1 1) (layers F.Cu F.Paste F.Mask)\n (net 1 \"P1\") (solder_mask_margin 0.2))\n\n )\n\n (zone (net 1) (net_name P1) (layer F.Cu) (tstamp 0) (hatch full 0.508)\n (connect_pads (clearance 0.508))\n (min_thickness 0.254)\n (fill yes (arc_segments 32) (thermal_gap 0.508) (thermal_bridge_width 0.508))\n (polygon (pts (xy 7.830127 0.8301270000000001) (xy 2.830127 -7.830127) (xy -5.830127 -2.830127) (xy -0.8301270000000001 5.830127)))\n )\n\n \n \n\n (module dynamic_net_test (layer F.Cu) (tedit 5CF31DEF)\n\n (at 0 0 0)\n\n (pad 1 smd rect (at 0 0 0) (size 1 1) (layers F.Cu F.Paste F.Mask)\n (net 2 \"T3_1\") (solder_mask_margin 0.2))\n\n (pad 1 smd rect (at 0 0 0) (size 1 1) (layers F.Cu F.Paste F.Mask)\n (net 3 \"T3_2\") (solder_mask_margin 0.2))\n\n (pad 1 smd rect (at 0 0 0) (size 1 1) (layers F.Cu F.Paste F.Mask)\n (net 4 \"T3_3\") (solder_mask_margin 0.2))\n\n )\n\n \n \n\n (module anchor_test (layer F.Cu) (tedit 5CF31DEF)\n\n (at 0 0 0)\n\n (fp_line (start 0 0) (end 10 -10) (layer Dwgs.User) (width 0.05))\n\n )\n\n \n (gr_line (start -9.5 9.5) (end 9.5 9.5) (angle 90) (layer Edge.Cuts) (width 0.15))\n(gr_line (start 9.5 9.5) (end 9.5 -9.5) (angle 90) (layer Edge.Cuts) (width 0.15))\n(gr_line (start 9.5 -9.5) (end -9.5 -9.5) (angle 90) (layer Edge.Cuts) (width 0.15))\n(gr_line (start -9.5 -9.5) (end -9.5 9.5) (angle 90) (layer Edge.Cuts) (width 0.15))\n \n)\n\n " +} \ No newline at end of file