Mixed progress

- point tagging
- extends clauses for drier config
- classic/uniform support
- mx/choc spacing support
- intersect outlines
- per-key footprint customization
This commit is contained in:
Bán Dénes 2020-07-16 23:55:37 +02:00
parent 55d60ba599
commit c3d7643371
6 changed files with 305 additions and 141 deletions

View file

@ -324,19 +324,21 @@ This is where the following section comes into play:
```yaml
glue:
top:
left: <anchor>
right: <anchor> | num
bottom:
left: <anchor>
right: <anchor> | num
waypoints:
- percent: num
width: num | [num_left, num_right]
- ...
extra:
- <primitive shape>
- ...
glue_name:
top:
left: <anchor>
right: <anchor> | num
bottom:
left: <anchor>
right: <anchor> | num
waypoints:
- percent: num
width: num | [num_left, num_right]
- ...
extra:
- <primitive shape>
- ...
...
```
...where an `<anchor>` is (mostly) the same as it was for points:
@ -348,7 +350,7 @@ rotate: num # default = 0
relative: boolean # default = true
```
The section's `top` and `bottom` are both formatted the same, and describe the center line's top and bottom intersections, respectively.
The `top` and `bottom` fields in each glue's section are both formatted the same, and describe the center line's top and bottom intersections, respectively.
In a one-piece case, this means that we project a line from a left-side reference point (optionally rotated and translated), another from the right, and converge them to where they meet.
Split designs can specify `right` as a single number to mean the x coordinate where the side should be "cut off".
The `relative` flag means that the `shift` is interpreted in layout size units instead of mms (see below).
@ -366,7 +368,7 @@ If this is insufficient (maybe because it would leave holes), the `waypoints` ca
Here, `percent` means the y coordinate along the centerline (going from the top intersection to the bottom intersection), and `width` means the offset on the x axis.
If this is somehow _still_ insufficient (or there were problems with the binding phase), we can specify additional primitive shapes under the `extra` key (similarly to how we would use them in the exports; see below).
These are then added to what we have so far to finish out the glue.
These are then added to what we have so far to finish out the glue. TODO!
<hr />
@ -381,6 +383,8 @@ Now we can configure what we want to "export" as outlines from this phase, given
- `middle` means an "ideal" version of the glue (meaning that instead of the `outline.glue` we defined above, we get `both` - `left` - `right`, so the _exact_ middle piece we would have needed to glue everything together
- `both` means both sides, held together by the glue
- `glue` is just the raw glue shape we defined above under `outline.glue`
- `tag: <array of tags>` : optional tags to filter which points to consider in this step, where tags can be specified as key-level attributes.
- `glue: <glue_name>` : the name of the glue to use, if applicable
- `size: num | [num_x, num_y]` : the width/height of the rectangles to lay onto the points. Note that the `relative` flag for the glue declaration above meant this size as the basis of the shift. So during a `keys` layout with a size of 18, for example, a relative shift of `[.5, .5]` actually means `[9, 9]` in mms.
- `corner: num # default = 0)` : corner radius of the rectangles
- `bevel: num # default = 0)` : corner bevel of the rectangles, can be combined with rounding
@ -395,7 +399,7 @@ Now we can configure what we want to "export" as outlines from this phase, given
- `radius: num` : the radius of the circle
- `polygon` : an independent polygon primitive. Parameters:
- `points: [<point_def>, ...]` : the points of the polygon. Each `<point_def>` can have its own `ref` and `shift`, all of which are still the same as above. If `ref` is unspecified, the previous point's will be assumed. For the first, it's `[0, 0]` by default.
- `ref` : a previously defined outline, see below.
- `outline` : a previously defined outline, see below.
- `name: outline_name` : the name of the referenced outline
Using these, we define exports as follows:

View file

@ -1,4 +1,5 @@
const m = require('makerjs')
const u = require('./utils')
const Point = require('./point')
const assert = exports.assert = (exp, msg) => {
@ -79,11 +80,53 @@ exports.anchor = (raw, name, points={}, check_unexpected=true, default_point=new
point.shift(xyval, true)
}
if (raw.rotate !== undefined) {
let rot = sane(raw.rotate || 0, name + '.rotate', 'number')
if (point.meta.mirrored) {
rot = -rot
}
point.r += rot
point.r += sane(raw.rotate || 0, name + '.rotate', 'number')
}
return point
}
const extend_pair = exports.extend_pair = (to, from) => {
const to_type = type(to)
const from_type = type(from)
if (from === undefined || from === null) return to
if (from === '!!unset') return undefined
if (to_type != from_type) return from
if (from_type == 'object') {
const res = u.deepcopy(to)
for (const key of Object.keys(from)) {
res[key] = extend_pair(to[key], from[key])
}
return res
} else if (from_type == 'array') {
const res = u.deepcopy(to)
for (const [i, val] of from.entries()) {
res[i] = extend_pair(res[i], val)
}
return res
} else return from
}
exports.extend = (...args) => {
let res = args[0]
for (const arg of args) {
if (res == arg) continue
res = extend_pair(res, arg)
}
return res
}
const inherit = exports.inherit = (config, name_prefix, name, set) => {
let result = u.deepcopy(config)
if (config.extends !== undefined) {
let list = config.extends
if (type(list) !== 'array') list = [list]
for (const item of list) {
const other = set[item]
assert(other, `Field "${name_prefix}.${name}" does not name a valid target!`)
result = extend_pair(inherit(other, name_prefix, config.extends, set), result)
}
delete result.extends
}
return result
}

View file

@ -35,27 +35,32 @@ const layout = exports._layout = (config = {}, points = {}) => {
// Glue config sanitization
a.detect_unexpected(config, 'outline.glue', ['top', 'bottom', 'waypoints', 'extra'])
const parsed_glue = u.deepcopy(a.sane(config, 'outline.glue', 'object'))
for (let [gkey, gval] of Object.entries(parsed_glue)) {
gval = a.inherit(gval, 'outline.glue', gkey, config)
a.detect_unexpected(gval, `outline.glue.${gkey}`, ['top', 'bottom', 'waypoints', 'extra'])
for (const y of ['top', 'bottom']) {
a.detect_unexpected(config[y], `outline.glue.${y}`, ['left', 'right'])
config[y].left = relative_anchor(config[y].left, `outline.glue.${y}.left`, points)
if (a.type(config[y].right) != 'number') {
config[y].right = relative_anchor(config[y].right, `outline.glue.${y}.right`, points)
for (const y of ['top', 'bottom']) {
a.detect_unexpected(gval[y], `outline.glue.${gkey}.${y}`, ['left', 'right'])
gval[y].left = relative_anchor(gval[y].left, `outline.glue.${gkey}.${y}.left`, points)
if (a.type(gval[y].right) != 'number') {
gval[y].right = relative_anchor(gval[y].right, `outline.glue.${gkey}.${y}.right`, points)
}
}
gval.waypoints = a.sane(gval.waypoints || [], `outline.glue.${gkey}.waypoints`, 'array')
let wi = 0
gval.waypoints = gval.waypoints.map(w => {
const name = `outline.glue.${gkey}.waypoints[${++wi}]`
a.detect_unexpected(w, name, ['percent', 'width'])
w.percent = a.sane(w.percent, name + '.percent', 'number')
w.width = a.wh(w.width, name + '.width')
return w
})
parsed_glue[gkey] = gval
}
config.waypoints = a.sane(config.waypoints || [], 'outline.glue.waypoints', 'array')
let wi = 0
config.waypoints = config.waypoints.map(w => {
const name = `outline.glue.waypoints[${++wi}]`
a.detect_unexpected(w, name, ['percent', 'width'])
w.percent = a.sane(w.percent, name + '.percent', 'number')
w.width = a.wh(w.width, name + '.width')
return w
})
// TODO: handle glue.extra (or revoke it from the docs)
@ -63,8 +68,12 @@ const layout = exports._layout = (config = {}, points = {}) => {
// Layout params sanitization
a.detect_unexpected(params, `${export_name}`, expected.concat(['side', 'size', 'corner', 'bevel', 'bound']))
a.detect_unexpected(params, `${export_name}`, expected.concat(['side', 'tags', 'glue', 'size', 'corner', 'bevel', 'bound']))
const side = a.in(params.side, `${export_name}.side`, ['left', 'right', 'middle', 'both', 'glue'])
const tags = a.sane(params.tags || [], `${export_name}.tags`, 'array')
const default_glue_name = Object.keys(parsed_glue)[0]
const glue_def = parsed_glue[a.sane(params.glue || default_glue_name, `${export_name}.glue`, 'string')]
a.assert(glue_def, `Field "${export_name}.glue" does not name a valid glue!`)
const size = a.wh(params.size, `${export_name}.size`)
const corner = a.sane(params.corner || 0, `${export_name}.corner`, 'number')
const bevel = a.sane(params.bevel || 0, `${export_name}.bevel`, 'number')
@ -77,6 +86,14 @@ const layout = exports._layout = (config = {}, points = {}) => {
if (['left', 'right', 'middle', 'both'].includes(side)) {
for (const [pname, p] of Object.entries(points)) {
// filter by tags, if necessary
if (tags.length) {
const source = p.meta.tags || {}
const point_tags = Object.keys(source).filter(t => !!source[t])
const relevant = point_tags.some(pt => tags.includes(pt))
if (!relevant) continue
}
let from_x = -size[0] / 2, to_x = size[0] / 2
let from_y = -size[1] / 2, to_y = size[1] / 2
@ -130,15 +147,15 @@ const layout = exports._layout = (config = {}, points = {}) => {
return u.line(from.p, to.p)
}
const tll = get_line(config.top.left)
const trl = get_line(config.top.right)
const tll = get_line(glue_def.top.left)
const trl = get_line(glue_def.top.right)
const tip = m.path.converge(tll, trl)
const tlp = u.eq(tll.origin, tip) ? tll.end : tll.origin
const trp = u.eq(trl.origin, tip) ? trl.end : trl.origin
const bll = get_line(config.bottom.left)
const brl = get_line(config.bottom.right)
const bll = get_line(glue_def.bottom.left)
const brl = get_line(glue_def.bottom.right)
const bip = m.path.converge(bll, brl)
const blp = u.eq(bll.origin, bip) ? bll.end : bll.origin
const brp = u.eq(brl.origin, bip) ? brl.end : brl.origin
@ -146,7 +163,7 @@ const layout = exports._layout = (config = {}, points = {}) => {
const left_waypoints = []
const right_waypoints = []
for (const w of config.waypoints) {
for (const w of glue_def.waypoints) {
const percent = w.percent / 100
const center_x = tip[0] + percent * (bip[0] - tip[0])
const center_y = tip[1] + percent * (bip[1] - tip[1])
@ -157,7 +174,7 @@ const layout = exports._layout = (config = {}, points = {}) => {
}
let waypoints
const is_split = a.type(config.top.right) == 'number'
const is_split = a.type(glue_def.top.right) == 'number'
if (is_split) {
waypoints = [tip, tlp]
.concat(left_waypoints)
@ -198,7 +215,7 @@ exports.parse = (config = {}, points = {}) => {
for (const part of parts) {
const name = `outline.exports.${key}[${++index}]`
const expected = ['type', 'operation']
part.type = a.in(part.type, `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'ref'])
part.type = a.in(part.type, `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'outline'])
part.operation = a.in(part.operation || 'add', `${name}.operation`, ['add', 'subtract', 'intersect', 'stack'])
let op = u.union
@ -240,10 +257,12 @@ exports.parse = (config = {}, points = {}) => {
}
arg = u.poly(parsed_points)
break
case 'ref':
case 'outline':
a.assert(outlines[part.name], `Field "${name}.name" does not name an existing outline!`)
arg = u.deepcopy(outlines[part.name])
break
default:
throw new Error(`Field "${name}.type" (${part.type}) does not name a valid outline part type!`)
}
result = op(result, arg)

View file

@ -143,6 +143,8 @@ const makerjs2kicad = exports._makerjs2kicad = (model, layer='Edge.Cuts') => {
const footprint_types = require('./footprints')
const footprint = exports._footprint = (config, name, points, net_indexer, point) => {
if (config === false) return ''
// config sanitization
a.detect_unexpected(config, name, ['type', 'anchor', 'nets', 'params'])

View file

@ -2,35 +2,6 @@ const m = require('makerjs')
const u = require('./utils')
const a = require('./assert')
const extend_pair = exports._extend_pair = (to, from) => {
const to_type = a.type(to)
const from_type = a.type(from)
if (from === undefined || from === null) return to
if (to_type != from_type) return from
if (from_type == 'object') {
const res = u.deepcopy(to)
for (const key of Object.keys(from)) {
res[key] = extend_pair(to[key], from[key])
}
return res
} else if (from_type == 'array') {
const res = u.deepcopy(to)
for (const [i, val] of from.entries()) {
res[i] = extend_pair(res[i], val)
}
return res
} else return from
}
const extend = exports._extend = (...args) => {
let res = args[0]
for (const arg of args) {
if (res == arg) continue
res = extend_pair(res, arg)
}
return res
}
const push_rotation = exports._push_rotation = (list, angle, origin) => {
let candidate = origin
for (const r of list) {
@ -139,7 +110,7 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key)
asym: 'both'
}
for (const row of Object.keys(actual_rows)) {
const key = extend(
const key = a.extend(
default_key,
global_key,
zone_wide_key,
@ -148,7 +119,8 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key)
col.rows[row] || {}
)
key.name = key.name || `${col_name}_${row}`
key.name = key.name || `${zone_name}_${col_name}_${row}`
key.colrow = `${col_name}_${row}`
key.shift = a.xy(key.shift, `${key.name}.shift`)
key.rotate = a.sane(key.rotate, `${key.name}.rotate`, 'number')
key.padding = a.sane(key.padding, `${key.name}.padding`, 'number')
@ -195,8 +167,6 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key)
return points
}
exports.parse = (config = {}) => {
a.detect_unexpected(config, 'points', ['zones', 'key', 'rotate', 'mirror'])
@ -207,7 +177,11 @@ exports.parse = (config = {}) => {
const zones = a.sane(config.zones || {}, 'points.zones', 'object')
const global_key = a.sane(config.key || {}, 'points.key', 'object')
for (const [zone_name, zone] of Object.entries(zones)) {
for (let [zone_name, zone] of Object.entries(zones)) {
// handle zone-level `extends` clauses
zone = a.inherit(zone, 'points.zones', zone_name, zones)
const anchor = a.anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, points)
points = Object.assign(points, render_zone(zone_name, zone, anchor, global_key))
}
@ -238,9 +212,11 @@ exports.parse = (config = {}) => {
for (const [name, p] of Object.entries(points)) {
if (p.meta.asym == 'left') continue
const mp = p.clone().mirror(axis)
mp.meta = extend(mp.meta, mp.meta.mirror || {})
mp.meta = a.extend(mp.meta, mp.meta.mirror || {})
mp.meta.mirrored = true
mirrored_points[`mirror_${name}`] = mp
const new_name = `mirror_${name}`
mp.meta.name = new_name
mirrored_points[new_name] = mp
if (p.meta.asym == 'right') {
p.meta.skip = true
}

View file

@ -10,9 +10,9 @@ points:
rows:
bottom:
home:
bind: [,10]
bind: [,15]
top:
bind: [,10]
bind: [,15]
key:
column_net: P1
ring:
@ -61,21 +61,35 @@ points:
rows:
bottom:
bind: [10]
row_net: P7
row_net: P16
mirror:
row_net: P16
row_net: P7
home:
bind: [10]
row_net: P6
row_net: P14
mirror:
row_net: P14
row_net: P6
top:
row_net: P5
row_net: P15
mirror:
row_net: P15
row_net: P5
key:
tags:
s19: true
choc:
extends: matrix
columns:
pinky:
stagger: 1
origin: [7, -8]
key:
padding: 18
tags:
s19: false
s18: true
thumbfan:
anchor:
ref: inner_bottom
ref: matrix_inner_bottom
shift: [-7, -19]
columns:
near:
@ -84,9 +98,11 @@ points:
origin: [9.5, -9]
rows:
thumb:
bind: [10,1,,]
bind: [10,5,,]
key:
column_net: P2
tags:
classic: true
home:
spread: 21.25
rotate: -28
@ -96,17 +112,71 @@ points:
bind: [,10,,15]
key:
column_net: P3
tags:
classic: true
uniform: true
far:
rows:
thumb:
bind: [-1,,,5]
key:
column_net: P4
tags:
classic: true
rows:
thumb:
row_net: P8
row_net: P10
mirror:
row_net: P10
row_net: P8
unifar:
anchor:
ref: thumbfan_home_thumb
columns:
home_again:
rotate: -28
origin: [9.5, -9]
key:
skip: true
far1u:
rows:
thumb:
bind: [-1,,,5]
key:
column_net: P4
footprints:
diode: false
tags:
uniform: true
rows:
thumb:
row_net: P10
mirror:
row_net: P8
uninear:
anchor:
ref: thumbfan_home_thumb
columns:
home_again:
spread: -19
rotate: 28
origin: [-9.5, -9]
key:
skip: true
near1u:
rows:
thumb:
bind: [10,5,,]
key:
column_net: P2
footprints:
diode: false
tags:
uniform: true
rows:
thumb:
row_net: P10
mirror:
row_net: P8
key:
bind: [0,0,0,0]
footprints:
@ -114,95 +184,145 @@ points:
type: mx
nets:
from: '!column_net'
to: '!name'
to: '!colrow'
diode:
type: diode
anchor:
rotate: 90
shift: [-8, 0]
shift: [8, 0]
nets:
from: '!name'
to: '!row_net'
mirror:
footprints:
mx:
nets:
from: '!colrow'
to: '!column_net'
rotate: -20
mirror:
ref: pinky_home
ref: matrix_pinky_home
distance: 223.7529778
outline:
glue:
top:
left:
ref: inner_top
shift: [, 0.5]
right:
ref: mirror_inner_top
shift: [, 0.5]
bottom:
left:
ref: far_thumb
shift: [0.5, 0]
rotate: 90
right:
ref: mirror_far_thumb
shift: [0.5, 0]
rotate: 90
waypoints:
- percent: 50
width: 50
- percent: 90
width: 25
classic:
top:
left:
ref: matrix_inner_top
shift: [, 0.5]
right:
ref: mirror_matrix_inner_top
shift: [, 0.5]
bottom:
left:
ref: thumbfan_far_thumb
shift: [0.5, 0]
rotate: 90
right:
ref: mirror_thumbfan_far_thumb
shift: [0.5, 0]
rotate: 90
waypoints:
- percent: 50
width: 50
- percent: 90
width: 25
uniform:
extends: classic
bottom:
left:
ref: unifar_far1u_thumb
right:
ref: mirror_unifar_far1u_thumb
choc:
extends: classic
top:
left:
ref: choc_inner_top
right:
ref: mirror_choc_inner_top
uniform_choc:
extends:
- uniform
- choc
exports:
outline:
classic_outline:
- type: keys
side: both
size: 18
tags:
- s19
- classic
glue: classic
size: 13.5
corner: .5
holes:
uniform_outline:
- type: keys
operation: stack
side: both
tags:
- s19
- uniform
glue: uniform
size: 13.5
corner: .5
intersected_outline:
- type: outline
name: classic_outline
- type: outline
name: uniform_outline
operation: intersect
classic_holes:
- type: keys
side: both
tags:
- s19
- classic
glue: classic
size: 14
bound: false
middle:
classic_middle:
- type: keys
operation: stack
side: middle
tags:
- s19
- classic
glue: classic
size: 24
- type: rectangle
size: [25, 5]
ref: home_thumb
ref: thumbfan_home_thumb
shift: [0, 12]
- type: rectangle
size: [25, 5]
ref: far_thumb
ref: thumbfan_far_thumb
shift: [25, 12]
- type: rectangle
size: [25, 5]
ref: mirror_home_thumb
ref: mirror_thumbfan_home_thumb
shift: [25, 12]
- type: rectangle
size: [25, 5]
ref: mirror_far_thumb
ref: mirror_thumbfan_far_thumb
shift: [0, 12]
- type: ref
name: outline
- type: outline
name: classic_outline
operation: intersect
complex:
- type: ref
name: outline
- type: ref
name: holes
- type: outline
name: classic_outline
- type: outline
name: classic_holes
operation: stack
- type: ref
name: middle
- type: outline
name: classic_middle
operation: stack
pcb:
edge: outline
edge: intersected_outline
footprints:
mcu:
type: promicro
anchor:
ref:
- inner_top
- mirror_inner_top
- choc_inner_top
- mirror_choc_inner_top
shift: [0, -20]
rotate: 270