2020-07-11 20:30:20 +02:00
|
|
|
const m = require('makerjs')
|
|
|
|
|
const a = require('./assert')
|
2021-07-11 20:36:11 +02:00
|
|
|
const prep = require('./prepare')
|
2021-07-18 16:03:45 +02:00
|
|
|
const anchor_lib = require('./anchor')
|
2022-11-22 21:26:48 +01:00
|
|
|
const filter = require('./filter').parse
|
2020-07-12 23:23:30 +02:00
|
|
|
|
|
|
|
|
const kicad_prefix = `
|
|
|
|
|
(kicad_pcb (version 20171130) (host pcbnew 5.1.6)
|
|
|
|
|
|
|
|
|
|
(page A3)
|
|
|
|
|
(title_block
|
|
|
|
|
(title KEYBOARD_NAME_HERE)
|
|
|
|
|
(rev VERSION_HERE)
|
|
|
|
|
(company YOUR_NAME_HERE)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
(general
|
|
|
|
|
(thickness 1.6)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
(layers
|
|
|
|
|
(0 F.Cu signal)
|
|
|
|
|
(31 B.Cu signal)
|
|
|
|
|
(32 B.Adhes user)
|
|
|
|
|
(33 F.Adhes user)
|
|
|
|
|
(34 B.Paste user)
|
|
|
|
|
(35 F.Paste user)
|
|
|
|
|
(36 B.SilkS user)
|
|
|
|
|
(37 F.SilkS user)
|
|
|
|
|
(38 B.Mask user)
|
|
|
|
|
(39 F.Mask user)
|
|
|
|
|
(40 Dwgs.User user)
|
|
|
|
|
(41 Cmts.User user)
|
|
|
|
|
(42 Eco1.User user)
|
|
|
|
|
(43 Eco2.User user)
|
|
|
|
|
(44 Edge.Cuts user)
|
|
|
|
|
(45 Margin user)
|
|
|
|
|
(46 B.CrtYd user)
|
|
|
|
|
(47 F.CrtYd user)
|
|
|
|
|
(48 B.Fab user)
|
|
|
|
|
(49 F.Fab user)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
(setup
|
|
|
|
|
(last_trace_width 0.25)
|
|
|
|
|
(trace_clearance 0.2)
|
|
|
|
|
(zone_clearance 0.508)
|
|
|
|
|
(zone_45_only no)
|
|
|
|
|
(trace_min 0.2)
|
|
|
|
|
(via_size 0.8)
|
|
|
|
|
(via_drill 0.4)
|
|
|
|
|
(via_min_size 0.4)
|
|
|
|
|
(via_min_drill 0.3)
|
|
|
|
|
(uvia_size 0.3)
|
|
|
|
|
(uvia_drill 0.1)
|
|
|
|
|
(uvias_allowed no)
|
|
|
|
|
(uvia_min_size 0.2)
|
|
|
|
|
(uvia_min_drill 0.1)
|
|
|
|
|
(edge_width 0.05)
|
|
|
|
|
(segment_width 0.2)
|
|
|
|
|
(pcb_text_width 0.3)
|
|
|
|
|
(pcb_text_size 1.5 1.5)
|
|
|
|
|
(mod_edge_width 0.12)
|
|
|
|
|
(mod_text_size 1 1)
|
|
|
|
|
(mod_text_width 0.15)
|
|
|
|
|
(pad_size 1.524 1.524)
|
|
|
|
|
(pad_drill 0.762)
|
|
|
|
|
(pad_to_mask_clearance 0.05)
|
|
|
|
|
(aux_axis_origin 0 0)
|
|
|
|
|
(visible_elements FFFFFF7F)
|
|
|
|
|
(pcbplotparams
|
|
|
|
|
(layerselection 0x010fc_ffffffff)
|
|
|
|
|
(usegerberextensions false)
|
|
|
|
|
(usegerberattributes true)
|
|
|
|
|
(usegerberadvancedattributes true)
|
|
|
|
|
(creategerberjobfile true)
|
|
|
|
|
(excludeedgelayer true)
|
|
|
|
|
(linewidth 0.100000)
|
|
|
|
|
(plotframeref false)
|
|
|
|
|
(viasonmask false)
|
|
|
|
|
(mode 1)
|
|
|
|
|
(useauxorigin false)
|
|
|
|
|
(hpglpennumber 1)
|
|
|
|
|
(hpglpenspeed 20)
|
|
|
|
|
(hpglpendiameter 15.000000)
|
|
|
|
|
(psnegative false)
|
|
|
|
|
(psa4output false)
|
|
|
|
|
(plotreference true)
|
|
|
|
|
(plotvalue true)
|
|
|
|
|
(plotinvisibletext false)
|
|
|
|
|
(padsonsilk false)
|
|
|
|
|
(subtractmaskfromsilk false)
|
|
|
|
|
(outputformat 1)
|
|
|
|
|
(mirror false)
|
|
|
|
|
(drillshape 1)
|
|
|
|
|
(scaleselection 1)
|
|
|
|
|
(outputdirectory ""))
|
|
|
|
|
)
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const kicad_suffix = `
|
|
|
|
|
)
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const kicad_netclass = `
|
|
|
|
|
(net_class Default "This is the default net class."
|
|
|
|
|
(clearance 0.2)
|
|
|
|
|
(trace_width 0.25)
|
|
|
|
|
(via_dia 0.8)
|
|
|
|
|
(via_drill 0.4)
|
|
|
|
|
(uvia_dia 0.3)
|
|
|
|
|
(uvia_drill 0.1)
|
|
|
|
|
__ADD_NET
|
|
|
|
|
)
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const makerjs2kicad = exports._makerjs2kicad = (model, layer='Edge.Cuts') => {
|
2020-07-11 20:30:20 +02:00
|
|
|
const grs = []
|
2020-07-14 23:03:44 +02:00
|
|
|
const xy = val => `${val[0]} ${-val[1]}`
|
2020-07-11 20:30:20 +02:00
|
|
|
m.model.walk(model, {
|
|
|
|
|
onPath: wp => {
|
|
|
|
|
const p = wp.pathContext
|
|
|
|
|
switch (p.type) {
|
|
|
|
|
case 'line':
|
2020-07-12 23:23:30 +02:00
|
|
|
grs.push(`(gr_line (start ${xy(p.origin)}) (end ${xy(p.end)}) (angle 90) (layer ${layer}) (width 0.15))`)
|
2020-07-11 20:30:20 +02:00
|
|
|
break
|
|
|
|
|
case 'arc':
|
2020-07-18 22:58:44 +02:00
|
|
|
const arc_center = p.origin
|
2020-07-12 23:23:30 +02:00
|
|
|
const angle_start = p.startAngle > p.endAngle ? p.startAngle - 360 : p.startAngle
|
|
|
|
|
const angle_diff = Math.abs(p.endAngle - angle_start)
|
2020-07-18 22:58:44 +02:00
|
|
|
const arc_end = m.point.rotate(m.point.add(arc_center, [p.radius, 0]), angle_start, arc_center)
|
|
|
|
|
grs.push(`(gr_arc (start ${xy(arc_center)}) (end ${xy(arc_end)}) (angle ${-angle_diff}) (layer ${layer}) (width 0.15))`)
|
2020-07-11 20:30:20 +02:00
|
|
|
break
|
|
|
|
|
case 'circle':
|
2020-07-18 22:58:44 +02:00
|
|
|
const circle_center = p.origin
|
|
|
|
|
const circle_end = m.point.add(circle_center, [p.radius, 0])
|
|
|
|
|
grs.push(`(gr_circle (center ${xy(circle_center)}) (end ${xy(circle_end)}) (layer ${layer}) (width 0.15))`)
|
2020-07-11 20:30:20 +02:00
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Can't convert path type "${p.type}" to kicad!`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
2020-07-14 23:03:44 +02:00
|
|
|
return grs.join('\n')
|
2020-07-11 20:30:20 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-12 23:23:30 +02:00
|
|
|
const footprint_types = require('./footprints')
|
2021-07-11 20:36:11 +02:00
|
|
|
|
|
|
|
|
exports.inject_footprint = (name, fp) => {
|
|
|
|
|
footprint_types[name] = fp
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 21:26:48 +01:00
|
|
|
const footprint = exports._footprint = (points, net_indexer, component_indexer, units, extra) => (config, name, point) => {
|
2021-07-11 20:36:11 +02:00
|
|
|
|
2020-07-12 23:23:30 +02:00
|
|
|
// config sanitization
|
2022-11-22 21:26:48 +01:00
|
|
|
a.unexpected(config, name, ['what', 'nets', 'anchors', 'params'])
|
|
|
|
|
const what = a.in(config.what, `${name}.what`, Object.keys(footprint_types))
|
2021-01-01 21:46:01 +01:00
|
|
|
const nets = a.sane(config.nets || {}, `${name}.nets`, 'object')()
|
2021-07-11 20:36:11 +02:00
|
|
|
const anchors = a.sane(config.anchors || {}, `${name}.anchors`, 'object')()
|
2021-01-01 21:46:01 +01:00
|
|
|
const params = a.sane(config.params || {}, `${name}.params`, 'object')()
|
2020-07-12 23:23:30 +02:00
|
|
|
|
|
|
|
|
// basic setup
|
2022-11-22 21:26:48 +01:00
|
|
|
const fp = footprint_types[what]
|
2020-07-18 22:58:44 +02:00
|
|
|
const parsed_params = {}
|
2020-07-12 23:23:30 +02:00
|
|
|
|
2021-07-11 20:36:11 +02:00
|
|
|
// connecting other, non-net, non-anchor parameters
|
|
|
|
|
parsed_params.param = {}
|
|
|
|
|
for (const [param_name, param_value] of Object.entries(prep.extend(fp.params || {}, params))) {
|
2021-07-14 19:33:33 +02:00
|
|
|
let value = param_value
|
2021-07-11 20:36:11 +02:00
|
|
|
if (a.type(value)() == 'string' && value.startsWith('=') && point) {
|
|
|
|
|
const indirect = value.substring(1)
|
|
|
|
|
value = point.meta[indirect]
|
2021-07-14 19:33:33 +02:00
|
|
|
if (value === undefined) {
|
|
|
|
|
throw new Error(`Indirection "${name}.params.${param}" --> "${point.meta.name}.${indirect}" to undefined value!`)
|
|
|
|
|
}
|
2021-07-11 20:36:11 +02:00
|
|
|
}
|
|
|
|
|
parsed_params.param[param_name] = value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reference
|
|
|
|
|
const component_ref = parsed_params.ref = component_indexer(parsed_params.param.class || '_')
|
2021-12-15 21:42:37 +01:00
|
|
|
parsed_params.ref_hide = extra.references ? '' : 'hide'
|
2021-07-11 20:36:11 +02:00
|
|
|
|
2020-07-12 23:23:30 +02:00
|
|
|
// footprint positioning
|
2022-11-22 21:26:48 +01:00
|
|
|
parsed_params.at = `(at ${point.x} ${-point.y} ${point.r})`
|
|
|
|
|
parsed_params.rot = point.r
|
2021-07-11 20:36:11 +02:00
|
|
|
parsed_params.xy = (x, y) => {
|
2021-07-18 16:03:45 +02:00
|
|
|
const new_anchor = anchor_lib.parse({
|
2021-07-11 20:36:11 +02:00
|
|
|
shift: [x, -y]
|
2022-11-22 21:26:48 +01:00
|
|
|
}, '_internal_footprint_xy', points, point)(units)
|
2021-07-11 20:36:11 +02:00
|
|
|
return `${new_anchor.x} ${-new_anchor.y}`
|
2020-07-12 23:23:30 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-11 20:36:11 +02:00
|
|
|
// 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')()
|
2021-01-02 18:38:57 +01:00
|
|
|
if (net.startsWith('=') && point) {
|
2020-07-14 23:03:44 +02:00
|
|
|
const indirect = net.substring(1)
|
|
|
|
|
net = point.meta[indirect]
|
2021-07-11 20:36:11 +02:00
|
|
|
net = a.sane(net, `${name}.nets.${net_name} --> ${point.meta.name}.${indirect}`, 'string')()
|
2020-07-14 23:03:44 +02:00
|
|
|
}
|
2020-07-12 23:23:30 +02:00
|
|
|
const index = net_indexer(net)
|
2021-07-11 20:36:11 +02:00
|
|
|
parsed_params.net[net_name] = {
|
|
|
|
|
name: net,
|
|
|
|
|
index: index,
|
|
|
|
|
str: `(net ${index} "${net}")`
|
|
|
|
|
}
|
2020-07-14 23:03:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-11 20:36:11 +02:00
|
|
|
// 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}")`
|
2020-07-14 23:03:44 +02:00
|
|
|
}
|
2020-07-12 23:23:30 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-11 20:36:11 +02:00
|
|
|
// parsing anchor-type parameters
|
|
|
|
|
parsed_params.anchors = {}
|
|
|
|
|
for (const [anchor_name, anchor_config] of Object.entries(prep.extend(fp.anchors || {}, anchors))) {
|
2022-11-22 21:26:48 +01:00
|
|
|
let parsed_anchor = anchor_lib.parse(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, point)(units)
|
2021-07-11 20:36:11 +02:00
|
|
|
parsed_anchor.y = -parsed_anchor.y
|
|
|
|
|
parsed_params.anchors[anchor_name] = parsed_anchor
|
|
|
|
|
}
|
2020-07-21 19:35:33 +02:00
|
|
|
|
2020-07-18 22:58:44 +02:00
|
|
|
return fp.body(parsed_params)
|
2020-07-12 23:23:30 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-01 21:46:01 +01:00
|
|
|
exports.parse = (config, points, outlines, units) => {
|
2020-07-12 23:23:30 +02:00
|
|
|
|
2021-12-15 20:55:09 +01:00
|
|
|
const pcbs = a.sane(config.pcbs || {}, 'pcbs', 'object')()
|
2020-07-17 23:20:49 +02:00
|
|
|
const results = {}
|
2020-07-12 23:23:30 +02:00
|
|
|
|
2020-07-17 23:20:49 +02:00
|
|
|
for (const [pcb_name, pcb_config] of Object.entries(pcbs)) {
|
2020-07-11 20:30:20 +02:00
|
|
|
|
2020-07-17 23:20:49 +02:00
|
|
|
// config sanitization
|
2021-12-15 21:42:37 +01:00
|
|
|
a.unexpected(pcb_config, `pcbs.${pcb_name}`, ['outlines', 'footprints', 'references'])
|
|
|
|
|
const references = a.sane(pcb_config.references || false, `pcbs.${pcb_name}.references`, 'boolean')()
|
2020-07-12 23:23:30 +02:00
|
|
|
|
2020-07-19 20:47:22 +02:00
|
|
|
// outline conversion
|
2021-01-01 21:46:01 +01:00
|
|
|
if (a.type(pcb_config.outlines)() == 'array') {
|
2020-10-17 19:05:38 +02:00
|
|
|
pcb_config.outlines = {...pcb_config.outlines}
|
|
|
|
|
}
|
2021-01-01 21:46:01 +01:00
|
|
|
const config_outlines = a.sane(pcb_config.outlines || {}, `pcbs.${pcb_name}.outlines`, 'object')()
|
2020-07-19 20:47:22 +02:00
|
|
|
const kicad_outlines = {}
|
|
|
|
|
for (const [outline_name, outline] of Object.entries(config_outlines)) {
|
2020-07-25 22:50:30 +02:00
|
|
|
const ref = a.in(outline.outline, `pcbs.${pcb_name}.outlines.${outline_name}.outline`, Object.keys(outlines))
|
2021-01-01 21:46:01 +01:00
|
|
|
const layer = a.sane(outline.layer || 'Edge.Cuts', `pcbs.${pcb_name}.outlines.${outline_name}.outline`, 'string')()
|
2020-07-19 20:47:22 +02:00
|
|
|
kicad_outlines[outline_name] = makerjs2kicad(outlines[ref], layer)
|
|
|
|
|
}
|
2020-07-12 23:23:30 +02:00
|
|
|
|
2020-07-17 23:20:49 +02:00
|
|
|
// making a global net index registry
|
|
|
|
|
const nets = {"": 0}
|
|
|
|
|
const net_indexer = net => {
|
|
|
|
|
if (nets[net] !== undefined) return nets[net]
|
|
|
|
|
const index = Object.keys(nets).length
|
|
|
|
|
return nets[net] = index
|
2020-07-12 23:23:30 +02:00
|
|
|
}
|
2020-07-21 19:35:33 +02:00
|
|
|
// and a component indexer
|
|
|
|
|
const component_registry = {}
|
|
|
|
|
const component_indexer = _class => {
|
|
|
|
|
if (!component_registry[_class]) {
|
|
|
|
|
component_registry[_class] = 0
|
|
|
|
|
}
|
|
|
|
|
component_registry[_class]++
|
|
|
|
|
return `${_class}${component_registry[_class]}`
|
|
|
|
|
}
|
2020-07-12 23:23:30 +02:00
|
|
|
|
2020-07-17 23:20:49 +02:00
|
|
|
const footprints = []
|
2022-11-22 21:26:48 +01:00
|
|
|
const footprint_factory = footprint(points, net_indexer, component_indexer, units, {references})
|
2020-07-17 23:20:49 +02:00
|
|
|
|
2022-11-22 21:26:48 +01:00
|
|
|
// generate footprints
|
2021-01-01 21:46:01 +01:00
|
|
|
if (a.type(pcb_config.footprints)() == 'array') {
|
2020-10-17 19:05:38 +02:00
|
|
|
pcb_config.footprints = {...pcb_config.footprints}
|
|
|
|
|
}
|
2022-11-22 21:26:48 +01:00
|
|
|
const footprints_config = a.sane(pcb_config.footprints || {}, `pcbs.${pcb_name}.footprints`, 'object')()
|
|
|
|
|
for (const [f_name, f] of Object.entries(footprints_config)) {
|
|
|
|
|
const name = `pcbs.${pcb_name}.footprints.${f_name}`
|
|
|
|
|
a.sane(f, name, 'object')()
|
2022-12-03 12:38:07 +01:00
|
|
|
const asym = a.asym(f.asym || 'source', `${name}.asym`)
|
|
|
|
|
const where = filter(f.where, `${name}.where`, points, units, asym)
|
|
|
|
|
delete f.asym
|
2022-11-22 21:26:48 +01:00
|
|
|
delete f.where
|
|
|
|
|
for (const w of where) {
|
|
|
|
|
footprints.push(footprint_factory(f, name, w))
|
|
|
|
|
}
|
2020-07-17 23:20:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// finalizing nets
|
|
|
|
|
const nets_arr = []
|
|
|
|
|
const add_nets_arr = []
|
|
|
|
|
for (const [net, index] of Object.entries(nets)) {
|
|
|
|
|
nets_arr.push(`(net ${index} "${net}")`)
|
|
|
|
|
add_nets_arr.push(`(add_net "${net}")`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const netclass = kicad_netclass.replace('__ADD_NET', add_nets_arr.join('\n'))
|
|
|
|
|
const nets_text = nets_arr.join('\n')
|
|
|
|
|
const footprint_text = footprints.join('\n')
|
2020-07-19 20:47:22 +02:00
|
|
|
const outline_text = Object.values(kicad_outlines).join('\n')
|
2021-12-15 20:55:09 +01:00
|
|
|
const personalized_prefix = kicad_prefix
|
|
|
|
|
.replace('KEYBOARD_NAME_HERE', pcb_name)
|
|
|
|
|
.replace('VERSION_HERE', config.meta && config.meta.version || 'v1.0.0')
|
|
|
|
|
.replace('YOUR_NAME_HERE', config.meta && config.meta.author || 'Unknown')
|
2020-07-17 23:20:49 +02:00
|
|
|
results[pcb_name] = `
|
2021-12-15 20:55:09 +01:00
|
|
|
${personalized_prefix}
|
2020-07-17 23:20:49 +02:00
|
|
|
${nets_text}
|
|
|
|
|
${netclass}
|
|
|
|
|
${footprint_text}
|
2020-07-19 20:47:22 +02:00
|
|
|
${outline_text}
|
2020-07-17 23:20:49 +02:00
|
|
|
${kicad_suffix}
|
|
|
|
|
`
|
2020-07-12 23:23:30 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-17 23:20:49 +02:00
|
|
|
return results
|
2020-07-11 20:30:20 +02:00
|
|
|
}
|