Generalize part objects/arrays, add combination shorthands, update docs

This commit is contained in:
Bán Dénes 2020-10-17 19:05:38 +02:00
parent 5b1da540ac
commit d60c9dbc94
5 changed files with 104 additions and 134 deletions

154
README.md
View file

@ -46,7 +46,7 @@ The `outlines` section then uses these points to generate plate, case, and PCB o
The `cases` section details how the case outlines are to be 3D-ized to form a 3D-printable object.
Finally, the `pcbs` section is used to configure KiCAD PCB templates.
In the following, we'll have an in-depth discussion about each of these, with an additional running example of how the [Absolem](https://github.com/mrzealot/absolem/blob/master/absolem.yaml)'s config was created.
In the following, we'll have an in-depth discussion about each of these.
@ -236,48 +236,7 @@ It will use the same extension mechanism as it did for the 5 levels before.
And this concludes point definitions.
This should be generic enough to describe any ergo layout, yet easy enough so that you'll appreciate not having to work in raw CAD.
## A concrete points example
TODO: Absolem points here, with pics
<br />
<br>
@ -415,15 +374,39 @@ Using these, we define exports as follows:
exports:
my_name:
- operation: add | subtract | intersect | stack # default = add
type: <one of the types>
type: <one of the types> # default = outline
<type-specific params>
- ...
```
Individual parts can also be specified as an object instead of an array (which could be useful when YAML or built-in inheritance is used), like so:
```yaml
exports:
my_name:
first_phase:
operation: add | subtract | intersect | stack # default = add
type: <one of the types> # default = outline
<type-specific params>
second:
...
```
Operations are performed in order, and the resulting shape is exported as an output.
Additionally, it is going to be available for further export declarations to use (through the `ref` type) under the name specified (`my_name`, in this case).
If we only want to use it as a building block for further exports, we can start the name with an underscore (e.g., `_my_name`) to prevent it from being actually exported.
A shorthand version of a part can be given when the elements of the above arrays/objects are simple strings instead of further objects.
The syntax is a symbol from `[+, -, ~, ^]`, followed by a name, and is equivalent to adding/subtracting/intersecting/stacking an outline of that name, respectively.
More specifically, `~something` is equivalent to:
```yaml
type: outline
name: something
operation: intersect
```
<br>
@ -443,36 +426,6 @@ If we only want to use it as a building block for further exports, we can start
## A concrete outline example
<br />
@ -497,7 +450,7 @@ Declarations might look like this:
```yaml
cases:
case_name:
- type: outline
- type: outline # default option
name: <outline ref>
extrude: num # default = 1
shift: [x, y, z] # default = [0, 0, 0]
@ -518,7 +471,12 @@ When the `type` is `case`, `name` specifies which case to use.
After having established our base 3D object, it is (relatively!) `rotate`d, `shift`ed, and combined with what we have so far according to `operation`.
If we only want to use an object as a building block for further objects, we can employ the same "start with an underscore" trick we learned at the outlines section to make it "private".
Individual case parts can again be listed as an object instead of an array, if that's more comfortable for inheritance/reuse (just like for outlines).
And speaking of outline similarities, the `[+, -, ~]` plus name shorthand is available again.
First it will try to look up cases, and then outlines by the name given.
Stacking is omitted as it makes no sense here.
<br>
@ -537,43 +495,6 @@ If we only want to use an object as a building block for further objects, we can
## A concrete case example
<br />
@ -602,12 +523,16 @@ The differences between the two footprint types are:
- an omitted `ref` in the anchor means the current key for key-level declarations, while here it defaults to `[0, 0]`
- a parameter starting with an exclamation point is an indirect reference to an eponymous key-level attribute -- so, for example, `from = !column_net` would mean that the key's `column_net` attribute is read there.
Additionally, the edge cut of the PCB can be specified using a previously defined outline name under the `edge` key.
Additionally, the edge cut of the PCB (or other decorative outlines for the silkscreen) can be specified using a previously defined outline name under the `outlines` key.
```yaml
pcbs:
pcb_name:
edge: <outline reference>
outlines:
pcb_outline_name:
outline: <outline reference>
layer: <kicad layer to export to> # default = Edge.Cuts
...
footprints:
- type: <footprint type>
anchor: <anchor declaration>
@ -617,6 +542,7 @@ pcbs:
...
```
Defining both the `outlines` and the `footprints` can be done either as arrays or objects, whichever is more convenient.
Currently, the following footprint types are supported:
- **`mx`**, **`alps`**, **`choc`**: mechanical switch footprints. Common nets:

View file

@ -162,4 +162,24 @@ const inherit = exports.inherit = (name_prefix, name, set) => {
result = extend.apply(this, list)
}
return result
}
const op_prefix = exports.op_prefix = str => {
const suffix = str.slice(1)
if (str.startsWith('+')) return {name: suffix, operation: 'add'}
if (str.startsWith('-')) return {name: suffix, operation: 'subtract'}
if (str.startsWith('~')) return {name: suffix, operation: 'intersect'}
if (str.startsWith('^')) return {name: suffix, operation: 'stack'}
return {name: str, operation: 'add'}
}
exports.op_str = (str, choices={}, order=Object.keys(choices)) => {
let res = op_prefix(str)
for (const key of order) {
if (choices[key].includes(res.name)) {
res.type = key
break
}
}
return res
}

View file

@ -36,30 +36,40 @@ exports.parse = (config, outlines) => {
return result.join('')
}
for (const [case_name, case_config] of Object.entries(cases_config)) {
for (let [case_name, case_config] of Object.entries(cases_config)) {
// config sanitization
const parts = a.sane(case_config, `cases.${case_name}`, 'array')
case_config = a.inherit('cases', case_name, cases_config)
if (a.type(case_config) == 'array') {
case_config = {...case_config}
}
const parts = a.sane(case_config, `cases.${case_name}`, 'object')
const body = []
const case_dependencies = []
const outline_dependencies = []
let part_index = 0
for (const part of parts) {
const part_name = `cases.${case_name}[${++part_index}]`
const part_var = `${case_name}__part_${part_index}`
a.detect_unexpected(part, part_name, ['type', 'name', 'extrude', 'shift', 'rotate', 'operation'])
const type = a.in(part.type, `${part_name}.type`, ['outline', 'case'])
const name = a.sane(part.name, `${part_name}.name`, 'string')
const shift = a.numarr(part.shift || [0, 0, 0], `${part_name}.shift`, 3)
const rotate = a.numarr(part.rotate || [0, 0, 0], `${part_name}.rotate`, 3)
const operation = a.in(part.operation || 'add', `${part_name}.operation`, ['add', 'subtract', 'intersect'])
let first = true
for (let [part_name, part] of Object.entries(parts)) {
if (a.type(part) == 'string') {
part = a.op_str(part, {
outline: Object.keys(outlines),
case: Object.keys(cases)
}, ['case', 'outline'])
}
const part_qname = `cases.${case_name}.${part_name}`
const part_var = `${case_name}__part_${part_name}`
a.detect_unexpected(part, part_qname, ['type', 'name', 'extrude', 'shift', 'rotate', 'operation'])
const type = a.in(part.type || 'outline', `${part_qname}.type`, ['outline', 'case'])
const name = a.sane(part.name, `${part_qname}.name`, 'string')
const shift = a.numarr(part.shift || [0, 0, 0], `${part_qname}.shift`, 3)
const rotate = a.numarr(part.rotate || [0, 0, 0], `${part_qname}.rotate`, 3)
const operation = a.in(part.operation || 'add', `${part_qname}.operation`, ['add', 'subtract', 'intersect'])
let base
if (type == 'outline') {
const extrude = a.sane(part.extrude || 1, `${part_name}.extrude`, 'number')
const extrude = a.sane(part.extrude || 1, `${part_qname}.extrude`, 'number')
const outline = outlines[name]
a.assert(outline, `Field "${part_name}.name" does not name a valid outline!`)
a.assert(outline, `Field "${part_qname}.name" does not name a valid outline!`)
if (!scripts[name]) {
scripts[name] = m.exporter.toJscadScript(outline, {
functionName: `${name}_outline_fn`,
@ -70,8 +80,8 @@ exports.parse = (config, outlines) => {
outline_dependencies.push(name)
base = `${name}_outline_fn()`
} else {
a.assert(part.extrude === undefined, `Field "${part_name}.extrude" should not be used when type=case!`)
a.in(name, `${part_name}.name`, Object.keys(cases))
a.assert(part.extrude === undefined, `Field "${part_qname}.extrude" should not be used when type=case!`)
a.in(name, `${part_qname}.name`, Object.keys(cases))
case_dependencies.push(name)
base = `${name}_case_fn()`
}
@ -81,13 +91,14 @@ exports.parse = (config, outlines) => {
else if (operation == 'intersect') op = 'intersect'
let op_statement = `let result = ${part_var};`
if (part_index > 1) {
if (!first) {
op_statement = `result = result.${op}(${part_var});`
}
first = false
body.push(`
// creating part ${part_index} of case ${case_name}
// creating part ${part_name} of case ${case_name}
let ${part_var} = ${base};
// make sure that rotations are relative

View file

@ -212,11 +212,18 @@ exports.parse = (config = {}, points = {}) => {
const ex = a.sane(config.exports || {}, 'outlines.exports', 'object')
for (let [key, parts] of Object.entries(ex)) {
parts = a.inherit('outlines.exports', key, ex)
if (a.type(parts) == 'array') {
parts = {...parts}
}
parts = a.sane(parts, `outlines.exports.${key}`, 'object')
let result = {models: {}}
for (const [part_name, part] of Object.entries(parts)) {
for (let [part_name, part] of Object.entries(parts)) {
const name = `outlines.exports.${key}.${part_name}`
if (a.type(part) == 'string') {
part = a.op_str(part, {outline: Object.keys(outlines)})
}
const expected = ['type', 'operation']
part.type = a.in(part.type, `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'outline'])
part.type = a.in(part.type || 'outline', `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'outline'])
part.operation = a.in(part.operation || 'add', `${name}.operation`, ['add', 'subtract', 'intersect', 'stack'])
let op = u.union

View file

@ -215,6 +215,9 @@ exports.parse = (config, points, outlines) => {
a.detect_unexpected(pcb_config, `pcbs.${pcb_name}`, ['outlines', 'footprints'])
// outline conversion
if (a.type(pcb_config.outlines) == 'array') {
pcb_config.outlines = {...pcb_config.outlines}
}
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)) {
@ -250,6 +253,9 @@ exports.parse = (config, points, outlines) => {
}
// global one-off footprints
if (a.type(pcb_config.footprints) == 'array') {
pcb_config.footprints = {...pcb_config.footprints}
}
const global_footprints = a.sane(pcb_config.footprints || {}, `pcbs.${pcb_name}.footprints`, 'object')
for (const [gf_name, gf] of Object.entries(global_footprints)) {
footprints.push(footprint(gf, `pcbs.${pcb_name}.footprints.${gf_name}`, points, undefined, net_indexer, component_indexer))