ergogen/src/pcbs.js

324 lines
11 KiB
JavaScript
Raw Normal View History

2020-07-11 20:30:20 +02:00
const m = require('makerjs')
const a = require('./assert')
const prep = require('./prepare')
2022-12-03 20:41:55 +01:00
const anchor = require('./anchor').parse
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')
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) => {
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')()
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
// connecting other, non-net, non-anchor parameters
parsed_params.param = {}
for (const [param_name, param_value] of Object.entries(prep.extend(fp.params || {}, params))) {
let value = param_value
if (a.type(value)() == 'string' && value.startsWith('=') && point) {
const indirect = value.substring(1)
value = point.meta[indirect]
2022-12-04 16:32:14 +01:00
// if (value === undefined) {
// throw new Error(`Indirection "${name}.params.${param_name}" --> "${point.meta.name}.${indirect}" to undefined value!`)
// }
}
parsed_params.param[param_name] = value
}
// reference
const component_ref = parsed_params.ref = component_indexer(parsed_params.param.class || '_')
parsed_params.ref_hide = extra.references ? '' : 'hide'
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
parsed_params.xy = (x, y) => {
2022-12-03 20:41:55 +01:00
const new_anchor = anchor({
shift: [x, -y]
2022-11-22 21:26:48 +01:00
}, '_internal_footprint_xy', points, point)(units)
return `${new_anchor.x} ${-new_anchor.y}`
2020-07-12 23:23:30 +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')()
if (net.startsWith('=') && point) {
2020-07-14 23:03:44 +02:00
const indirect = net.substring(1)
net = point.meta[indirect]
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)
parsed_params.net[net_name] = {
name: net,
index: index,
str: `(net ${index} "${net}")`
}
2020-07-14 23:03:44 +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
}
// parsing anchor-type parameters
parsed_params.anchors = {}
for (const [anchor_name, anchor_config] of Object.entries(prep.extend(fp.anchors || {}, anchors))) {
2022-12-03 20:41:55 +01:00
let parsed_anchor = anchor(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, point)(units)
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
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
// outline conversion
2021-01-01 21:46:01 +01:00
if (a.type(pcb_config.outlines)() == 'array') {
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')()
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')()
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') {
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)
const original_adjust = f.adjust // need to save, so the delete's don't get rid of it below
const adjust = start => anchor(original_adjust || {}, `${name}.adjust`, points, start)(units)
2022-12-03 12:38:07 +01:00
delete f.asym
2022-11-22 21:26:48 +01:00
delete f.where
2022-12-03 20:41:55 +01:00
delete f.adjust
2022-11-22 21:26:48 +01:00
for (const w of where) {
const aw = adjust(w.clone())
2022-12-03 20:41:55 +01:00
footprints.push(footprint_factory(f, name, aw))
2022-11-22 21:26:48 +01:00
}
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')
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}
${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
}