Mixed progress, vol. 2
This commit is contained in:
parent
c3d7643371
commit
5a380fa58c
7 changed files with 192 additions and 157 deletions
24
README.md
24
README.md
|
@ -36,15 +36,15 @@ The important thing is that the data should contain the following keys:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
points: <points config...>
|
points: <points config...>
|
||||||
outline: <outline config...>
|
outlines: <outline config...>
|
||||||
case: <case config...>
|
cases: <case config...>
|
||||||
pcb: <pcb config...>
|
pcbs: <pcb config...>
|
||||||
```
|
```
|
||||||
|
|
||||||
The `points` section describes the core of the layout: the positions of the keys.
|
The `points` section describes the core of the layout: the positions of the keys.
|
||||||
The `outline` section then uses these points to generate plate, case, and PCB outlines.
|
The `outlines` section then uses these points to generate plate, case, and PCB outlines.
|
||||||
The `case` section details how the case outlines are to be 3D-ized to form a 3D-printable object.
|
The `cases` section details how the case outlines are to be 3D-ized to form a 3D-printable object.
|
||||||
Finally, the `pcb` section is used to configure a KiCAD PCB template.
|
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](#TODO-link-to-config-yaml)'s config was created.
|
In the following, we'll have an in-depth discussion about each of these, with an additional running example of how the [Absolem](#TODO-link-to-config-yaml)'s config was created.
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ TODO: Absolem points here, with pics
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Outline
|
## Outlines
|
||||||
|
|
||||||
Once the raw points are available, we want to turn them into solid, continuous outlines.
|
Once the raw points are available, we want to turn them into solid, continuous outlines.
|
||||||
The points are enough to create properly positioned and rotated rectangles (with parametric side lengths), but they won't combine since there won't be any overlap.
|
The points are enough to create properly positioned and rotated rectangles (with parametric side lengths), but they won't combine since there won't be any overlap.
|
||||||
|
@ -480,7 +480,7 @@ If we only want to use it as a building block for further exports, we can start
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Case
|
## Cases
|
||||||
|
|
||||||
Cases add a pretty basic and minimal 3D aspect to the generation process.
|
Cases add a pretty basic and minimal 3D aspect to the generation process.
|
||||||
In this phase, we take different outlines (exported from the above section, even the "private" ones), extrude and position them in space, and combine them into one 3D-printable object.
|
In this phase, we take different outlines (exported from the above section, even the "private" ones), extrude and position them in space, and combine them into one 3D-printable object.
|
||||||
|
@ -488,7 +488,7 @@ That's it.
|
||||||
Declarations might look like this:
|
Declarations might look like this:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
case:
|
cases:
|
||||||
case_name:
|
case_name:
|
||||||
- outline: <outline ref>
|
- outline: <outline ref>
|
||||||
extrude: num # default = 1
|
extrude: num # default = 1
|
||||||
|
@ -575,7 +575,7 @@ If we only want to use an object as a building block for further objects, we can
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## PCB
|
## PCBs
|
||||||
|
|
||||||
Everything should be ready for a handwire, but if you'd like the design to be more accessible and easily replicable, you probably want a PCB as well.
|
Everything should be ready for a handwire, but if you'd like the design to be more accessible and easily replicable, you probably want a PCB as well.
|
||||||
To help you get started, the necessary footprints and an edge cut can be automatically positioned so that all you need to do manually is the routing.
|
To help you get started, the necessary footprints and an edge cut can be automatically positioned so that all you need to do manually is the routing.
|
||||||
|
@ -589,7 +589,8 @@ The differences between the two footprint types are:
|
||||||
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 can be specified using a previously defined outline name under the `edge` key.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
pcb:
|
pcbs:
|
||||||
|
pcb_name:
|
||||||
edge: <outline reference>
|
edge: <outline reference>
|
||||||
footprints:
|
footprints:
|
||||||
- type: <footprint type>
|
- type: <footprint type>
|
||||||
|
@ -597,6 +598,7 @@ pcb:
|
||||||
nets: <type-specific net params>
|
nets: <type-specific net params>
|
||||||
params: <type-specific (non-net) footprint params>
|
params: <type-specific (non-net) footprint params>
|
||||||
- ...
|
- ...
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
Currently, the following footprint types are supported:
|
Currently, the following footprint types are supported:
|
||||||
|
|
|
@ -106,7 +106,7 @@ const extend_pair = exports.extend_pair = (to, from) => {
|
||||||
} else return from
|
} else return from
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.extend = (...args) => {
|
const extend = exports.extend = (...args) => {
|
||||||
let res = args[0]
|
let res = args[0]
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
if (res == arg) continue
|
if (res == arg) continue
|
||||||
|
@ -115,18 +115,22 @@ exports.extend = (...args) => {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const inherit = exports.inherit = (config, name_prefix, name, set) => {
|
const inherit = exports.inherit = (name_prefix, name, set) => {
|
||||||
let result = u.deepcopy(config)
|
let result = u.deepcopy(set[name])
|
||||||
if (config.extends !== undefined) {
|
if (result.extends !== undefined) {
|
||||||
let list = config.extends
|
let candidates = [name]
|
||||||
if (type(list) !== 'array') list = [list]
|
const list = []
|
||||||
for (const item of list) {
|
while (candidates.length) {
|
||||||
const other = set[item]
|
const item = candidates.shift()
|
||||||
assert(other, `Field "${name_prefix}.${name}" does not name a valid target!`)
|
const other = u.deepcopy(set[item])
|
||||||
result = extend_pair(inherit(other, name_prefix, config.extends, set), result)
|
assert(other, `"${item}" (reached from "${name_prefix}.${name}.extends") does not name a valid target!`)
|
||||||
|
let parents = other.extends || []
|
||||||
|
if (type(parents) !== 'array') parents = [parents]
|
||||||
|
candidates = candidates.concat(parents)
|
||||||
|
delete other.extends
|
||||||
|
list.unshift(other)
|
||||||
}
|
}
|
||||||
delete result.extends
|
result = extend.apply(this, list)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
22
src/cli.js
22
src/cli.js
|
@ -12,8 +12,8 @@ const yargs = require('yargs')
|
||||||
const u = require('./utils')
|
const u = require('./utils')
|
||||||
const io = require('./io')
|
const io = require('./io')
|
||||||
const points_lib = require('./points')
|
const points_lib = require('./points')
|
||||||
const outline_lib = require('./outline')
|
const outlines_lib = require('./outlines')
|
||||||
const pcb_lib = require('./pcb')
|
const pcbs_lib = require('./pcbs')
|
||||||
|
|
||||||
// command line args
|
// command line args
|
||||||
|
|
||||||
|
@ -68,19 +68,21 @@ if (args.debug) {
|
||||||
// outlines
|
// outlines
|
||||||
|
|
||||||
console.log('Generating outlines...')
|
console.log('Generating outlines...')
|
||||||
const outlines = outline_lib.parse(config.outline, points)
|
const outlines = outlines_lib.parse(config.outlines, points)
|
||||||
for (const [name, outline] of Object.entries(outlines)) {
|
for (const [name, outline] of Object.entries(outlines)) {
|
||||||
if (!args.debug && name.startsWith('_')) continue
|
if (!args.debug && name.startsWith('_')) continue
|
||||||
io.dump_model(outline, path.join(args.o, `outline/${name}`), args.debug)
|
io.dump_model(outline, path.join(args.o, `outlines/${name}`), args.debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pcb
|
// pcbs
|
||||||
|
|
||||||
console.log('Scaffolding PCB...')
|
console.log('Scaffolding PCBs...')
|
||||||
const pcb = pcb_lib.parse(config.pcb, points, outlines)
|
const pcbs = pcbs_lib.parse(config.pcbs, points, outlines)
|
||||||
const pcb_file = path.join(args.o, `pcb/pcb.kicad_pcb`)
|
for (const [pcb_name, pcb_text] of Object.entries(pcbs)) {
|
||||||
fs.mkdirpSync(path.dirname(pcb_file))
|
const pcb_file = path.join(args.o, `pcbs/${pcb_name}.kicad_pcb`)
|
||||||
fs.writeFileSync(pcb_file, pcb)
|
fs.mkdirpSync(path.dirname(pcb_file))
|
||||||
|
fs.writeFileSync(pcb_file, pcb_text)
|
||||||
|
}
|
||||||
|
|
||||||
// goodbye
|
// goodbye
|
||||||
|
|
||||||
|
|
|
@ -35,23 +35,23 @@ const layout = exports._layout = (config = {}, points = {}) => {
|
||||||
|
|
||||||
// Glue config sanitization
|
// Glue config sanitization
|
||||||
|
|
||||||
const parsed_glue = u.deepcopy(a.sane(config, 'outline.glue', 'object'))
|
const parsed_glue = u.deepcopy(a.sane(config, 'outlines.glue', 'object'))
|
||||||
for (let [gkey, gval] of Object.entries(parsed_glue)) {
|
for (let [gkey, gval] of Object.entries(parsed_glue)) {
|
||||||
gval = a.inherit(gval, 'outline.glue', gkey, config)
|
gval = a.inherit('outlines.glue', gkey, config)
|
||||||
a.detect_unexpected(gval, `outline.glue.${gkey}`, ['top', 'bottom', 'waypoints', 'extra'])
|
a.detect_unexpected(gval, `outlines.glue.${gkey}`, ['top', 'bottom', 'waypoints', 'extra'])
|
||||||
|
|
||||||
for (const y of ['top', 'bottom']) {
|
for (const y of ['top', 'bottom']) {
|
||||||
a.detect_unexpected(gval[y], `outline.glue.${gkey}.${y}`, ['left', 'right'])
|
a.detect_unexpected(gval[y], `outlines.glue.${gkey}.${y}`, ['left', 'right'])
|
||||||
gval[y].left = relative_anchor(gval[y].left, `outline.glue.${gkey}.${y}.left`, points)
|
gval[y].left = relative_anchor(gval[y].left, `outlines.glue.${gkey}.${y}.left`, points)
|
||||||
if (a.type(gval[y].right) != 'number') {
|
if (a.type(gval[y].right) != 'number') {
|
||||||
gval[y].right = relative_anchor(gval[y].right, `outline.glue.${gkey}.${y}.right`, points)
|
gval[y].right = relative_anchor(gval[y].right, `outlines.glue.${gkey}.${y}.right`, points)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gval.waypoints = a.sane(gval.waypoints || [], `outline.glue.${gkey}.waypoints`, 'array')
|
gval.waypoints = a.sane(gval.waypoints || [], `outlines.glue.${gkey}.waypoints`, 'array')
|
||||||
let wi = 0
|
let wi = 0
|
||||||
gval.waypoints = gval.waypoints.map(w => {
|
gval.waypoints = gval.waypoints.map(w => {
|
||||||
const name = `outline.glue.${gkey}.waypoints[${++wi}]`
|
const name = `outlines.glue.${gkey}.waypoints[${++wi}]`
|
||||||
a.detect_unexpected(w, name, ['percent', 'width'])
|
a.detect_unexpected(w, name, ['percent', 'width'])
|
||||||
w.percent = a.sane(w.percent, name + '.percent', 'number')
|
w.percent = a.sane(w.percent, name + '.percent', 'number')
|
||||||
w.width = a.wh(w.width, name + '.width')
|
w.width = a.wh(w.width, name + '.width')
|
||||||
|
@ -208,12 +208,12 @@ exports.parse = (config = {}, points = {}) => {
|
||||||
|
|
||||||
const outlines = {}
|
const outlines = {}
|
||||||
|
|
||||||
const ex = a.sane(config.exports, 'outline.exports', 'object')
|
const ex = a.sane(config.exports, 'outlines.exports', 'object')
|
||||||
for (const [key, parts] of Object.entries(ex)) {
|
for (let [key, parts] of Object.entries(ex)) {
|
||||||
let index = 0
|
parts = a.inherit('outlines.exports', key, ex)
|
||||||
let result = {models: {}}
|
let result = {models: {}}
|
||||||
for (const part of parts) {
|
for (const [part_name, part] of Object.entries(parts)) {
|
||||||
const name = `outline.exports.${key}[${++index}]`
|
const name = `outlines.exports.${key}.${part_name}`
|
||||||
const expected = ['type', 'operation']
|
const expected = ['type', 'operation']
|
||||||
part.type = a.in(part.type, `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'outline'])
|
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'])
|
part.operation = a.in(part.operation || 'add', `${name}.operation`, ['add', 'subtract', 'intersect', 'stack'])
|
|
@ -204,10 +204,15 @@ const footprint = exports._footprint = (config, name, points, net_indexer, point
|
||||||
|
|
||||||
exports.parse = (config, points, outlines) => {
|
exports.parse = (config, points, outlines) => {
|
||||||
|
|
||||||
|
const pcbs = a.sane(config, 'pcb', 'object')
|
||||||
|
const results = {}
|
||||||
|
|
||||||
|
for (const [pcb_name, pcb_config] of Object.entries(pcbs)) {
|
||||||
|
|
||||||
// config sanitization
|
// config sanitization
|
||||||
a.detect_unexpected(config, 'pcb', ['edge', 'footprints'])
|
a.detect_unexpected(pcb_config, `pcb.${pcb_name}`, ['edge', 'footprints'])
|
||||||
const edge = outlines[config.edge]
|
const edge = outlines[pcb_config.edge]
|
||||||
if (!edge) throw new Error(`Field "pcb.edge" doesn't name a valid outline!`)
|
if (!edge) throw new Error(`Field "pcb.${pcb_name}.edge" doesn't name a valid outline!`)
|
||||||
|
|
||||||
// Edge.Cuts conversion
|
// Edge.Cuts conversion
|
||||||
const kicad_edge = makerjs2kicad(edge)
|
const kicad_edge = makerjs2kicad(edge)
|
||||||
|
@ -223,16 +228,16 @@ exports.parse = (config, points, outlines) => {
|
||||||
const footprints = []
|
const footprints = []
|
||||||
|
|
||||||
// key-level footprints
|
// key-level footprints
|
||||||
for (const [pname, point] of Object.entries(points)) {
|
for (const [p_name, point] of Object.entries(points)) {
|
||||||
for (const [f_name, f] of Object.entries(point.meta.footprints || {})) {
|
for (const [f_name, f] of Object.entries(point.meta.footprints || {})) {
|
||||||
footprints.push(footprint(f, `${pname}.footprints.${f_name}`, points, net_indexer, point))
|
footprints.push(footprint(f, `${p_name}.footprints.${f_name}`, points, net_indexer, point))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// global one-off footprints
|
// global one-off footprints
|
||||||
const global_footprints = a.sane(config.footprints || {}, 'pcb.footprints', 'object')
|
const global_footprints = a.sane(pcb_config.footprints || {}, `pcb.${pcb_name}.footprints`, 'object')
|
||||||
for (const [gf_name, gf] of Object.entries(global_footprints)) {
|
for (const [gf_name, gf] of Object.entries(global_footprints)) {
|
||||||
footprints.push(footprint(gf, `pcb.footprints.${gf_name}`, points, net_indexer))
|
footprints.push(footprint(gf, `pcb.${pcb_name}.footprints.${gf_name}`, points, net_indexer))
|
||||||
}
|
}
|
||||||
|
|
||||||
// finalizing nets
|
// finalizing nets
|
||||||
|
@ -246,14 +251,15 @@ exports.parse = (config, points, outlines) => {
|
||||||
const netclass = kicad_netclass.replace('__ADD_NET', add_nets_arr.join('\n'))
|
const netclass = kicad_netclass.replace('__ADD_NET', add_nets_arr.join('\n'))
|
||||||
const nets_text = nets_arr.join('\n')
|
const nets_text = nets_arr.join('\n')
|
||||||
const footprint_text = footprints.join('\n')
|
const footprint_text = footprints.join('\n')
|
||||||
return `
|
results[pcb_name] = `
|
||||||
|
|
||||||
${kicad_prefix}
|
${kicad_prefix}
|
||||||
${nets_text}
|
${nets_text}
|
||||||
${netclass}
|
${netclass}
|
||||||
${footprint_text}
|
${footprint_text}
|
||||||
${kicad_edge}
|
${kicad_edge}
|
||||||
${kicad_suffix}
|
${kicad_suffix}
|
||||||
|
|
||||||
`
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
}
|
}
|
|
@ -180,7 +180,7 @@ exports.parse = (config = {}) => {
|
||||||
for (let [zone_name, zone] of Object.entries(zones)) {
|
for (let [zone_name, zone] of Object.entries(zones)) {
|
||||||
|
|
||||||
// handle zone-level `extends` clauses
|
// handle zone-level `extends` clauses
|
||||||
zone = a.inherit(zone, 'points.zones', zone_name, zones)
|
zone = a.inherit('points.zones', zone_name, zones)
|
||||||
|
|
||||||
const anchor = a.anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, points)
|
const anchor = a.anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, points)
|
||||||
points = Object.assign(points, render_zone(zone_name, zone, anchor, global_key))
|
points = Object.assign(points, render_zone(zone_name, zone, anchor, global_key))
|
||||||
|
|
117
test/fixtures/absolem.yaml
vendored
117
test/fixtures/absolem.yaml
vendored
|
@ -10,9 +10,9 @@ points:
|
||||||
rows:
|
rows:
|
||||||
bottom:
|
bottom:
|
||||||
home:
|
home:
|
||||||
bind: [,15]
|
bind: [,15,-1]
|
||||||
top:
|
top:
|
||||||
bind: [,15]
|
bind: [,15,-1]
|
||||||
key:
|
key:
|
||||||
column_net: P1
|
column_net: P1
|
||||||
ring:
|
ring:
|
||||||
|
@ -70,6 +70,10 @@ points:
|
||||||
mirror:
|
mirror:
|
||||||
row_net: P6
|
row_net: P6
|
||||||
top:
|
top:
|
||||||
|
footprints:
|
||||||
|
mx:
|
||||||
|
anchor:
|
||||||
|
rotate: 180
|
||||||
row_net: P15
|
row_net: P15
|
||||||
mirror:
|
mirror:
|
||||||
row_net: P5
|
row_net: P5
|
||||||
|
@ -87,6 +91,8 @@ points:
|
||||||
tags:
|
tags:
|
||||||
s19: false
|
s19: false
|
||||||
s18: true
|
s18: true
|
||||||
|
footprints:
|
||||||
|
diode: '!!unset'
|
||||||
thumbfan:
|
thumbfan:
|
||||||
anchor:
|
anchor:
|
||||||
ref: matrix_inner_bottom
|
ref: matrix_inner_bottom
|
||||||
|
@ -203,9 +209,9 @@ points:
|
||||||
mirror:
|
mirror:
|
||||||
ref: matrix_pinky_home
|
ref: matrix_pinky_home
|
||||||
distance: 223.7529778
|
distance: 223.7529778
|
||||||
outline:
|
outlines:
|
||||||
glue:
|
glue:
|
||||||
classic:
|
classic_s19:
|
||||||
top:
|
top:
|
||||||
left:
|
left:
|
||||||
ref: matrix_inner_top
|
ref: matrix_inner_top
|
||||||
|
@ -227,95 +233,110 @@ outline:
|
||||||
width: 50
|
width: 50
|
||||||
- percent: 90
|
- percent: 90
|
||||||
width: 25
|
width: 25
|
||||||
uniform:
|
uniform_s19:
|
||||||
extends: classic
|
extends: classic_s19
|
||||||
bottom:
|
bottom:
|
||||||
left:
|
left:
|
||||||
ref: unifar_far1u_thumb
|
ref: unifar_far1u_thumb
|
||||||
right:
|
right:
|
||||||
ref: mirror_unifar_far1u_thumb
|
ref: mirror_unifar_far1u_thumb
|
||||||
choc:
|
classic_s18:
|
||||||
extends: classic
|
extends: classic_s19
|
||||||
top:
|
top:
|
||||||
left:
|
left:
|
||||||
ref: choc_inner_top
|
ref: choc_inner_top
|
||||||
right:
|
right:
|
||||||
ref: mirror_choc_inner_top
|
ref: mirror_choc_inner_top
|
||||||
uniform_choc:
|
uniform_s18:
|
||||||
extends:
|
extends:
|
||||||
- uniform
|
- uniform_s19
|
||||||
- choc
|
- classic_s18
|
||||||
exports:
|
exports:
|
||||||
classic_outline:
|
classic_s19_outline:
|
||||||
- type: keys
|
main:
|
||||||
|
type: keys
|
||||||
side: both
|
side: both
|
||||||
tags:
|
tags:
|
||||||
- s19
|
- s19
|
||||||
- classic
|
- classic
|
||||||
glue: classic
|
glue: classic_s19
|
||||||
size: 13.5
|
size: 13.5
|
||||||
corner: .5
|
corner: .5
|
||||||
uniform_outline:
|
uniform_s19_outline:
|
||||||
- type: keys
|
extends: classic_s19_outline
|
||||||
side: both
|
main:
|
||||||
tags:
|
tags:
|
||||||
- s19
|
- s19
|
||||||
- uniform
|
- uniform
|
||||||
glue: uniform
|
glue: uniform_s19
|
||||||
size: 13.5
|
uniform_s18_outline:
|
||||||
corner: .5
|
extends: uniform_s19_outline
|
||||||
|
main:
|
||||||
|
tags:
|
||||||
|
- s18
|
||||||
|
- uniform
|
||||||
|
glue: uniform_s18
|
||||||
intersected_outline:
|
intersected_outline:
|
||||||
- type: outline
|
one:
|
||||||
name: classic_outline
|
type: outline
|
||||||
- type: outline
|
name: classic_s19_outline
|
||||||
name: uniform_outline
|
two:
|
||||||
|
type: outline
|
||||||
|
name: uniform_s18_outline
|
||||||
operation: intersect
|
operation: intersect
|
||||||
classic_holes:
|
classic_s19_switches:
|
||||||
- type: keys
|
main:
|
||||||
|
type: keys
|
||||||
side: both
|
side: both
|
||||||
tags:
|
tags:
|
||||||
- s19
|
|
||||||
- classic
|
- classic
|
||||||
glue: classic
|
glue: classic_s19
|
||||||
size: 14
|
size: 14
|
||||||
bound: false
|
bound: false
|
||||||
classic_middle:
|
uniform_s19_switches:
|
||||||
- type: keys
|
main:
|
||||||
|
type: keys
|
||||||
|
side: both
|
||||||
|
tags:
|
||||||
|
- uniform
|
||||||
|
glue: uniform_s19
|
||||||
|
size: 14
|
||||||
|
bound: false
|
||||||
|
classic_s19_middle:
|
||||||
|
raw:
|
||||||
|
type: keys
|
||||||
side: middle
|
side: middle
|
||||||
tags:
|
tags:
|
||||||
- s19
|
- s19
|
||||||
- classic
|
- classic
|
||||||
glue: classic
|
glue: classic_s19
|
||||||
size: 24
|
size: 24
|
||||||
- type: rectangle
|
helper1:
|
||||||
|
type: rectangle
|
||||||
size: [25, 5]
|
size: [25, 5]
|
||||||
ref: thumbfan_home_thumb
|
ref: thumbfan_home_thumb
|
||||||
shift: [0, 12]
|
shift: [0, 12]
|
||||||
- type: rectangle
|
helper2:
|
||||||
|
type: rectangle
|
||||||
size: [25, 5]
|
size: [25, 5]
|
||||||
ref: thumbfan_far_thumb
|
ref: thumbfan_far_thumb
|
||||||
shift: [25, 12]
|
shift: [-25, 12]
|
||||||
- type: rectangle
|
helper3:
|
||||||
|
type: rectangle
|
||||||
size: [25, 5]
|
size: [25, 5]
|
||||||
ref: mirror_thumbfan_home_thumb
|
ref: mirror_thumbfan_home_thumb
|
||||||
shift: [25, 12]
|
shift: [25, 12]
|
||||||
- type: rectangle
|
helper4:
|
||||||
|
type: rectangle
|
||||||
size: [25, 5]
|
size: [25, 5]
|
||||||
ref: mirror_thumbfan_far_thumb
|
ref: mirror_thumbfan_far_thumb
|
||||||
shift: [0, 12]
|
shift: [0, 12]
|
||||||
- type: outline
|
outer_bounds:
|
||||||
name: classic_outline
|
type: outline
|
||||||
|
name: classic_s19_outline
|
||||||
operation: intersect
|
operation: intersect
|
||||||
complex:
|
pcbs:
|
||||||
- type: outline
|
main:
|
||||||
name: classic_outline
|
|
||||||
- type: outline
|
|
||||||
name: classic_holes
|
|
||||||
operation: stack
|
|
||||||
- type: outline
|
|
||||||
name: classic_middle
|
|
||||||
operation: stack
|
|
||||||
pcb:
|
|
||||||
edge: intersected_outline
|
edge: intersected_outline
|
||||||
footprints:
|
footprints:
|
||||||
mcu:
|
mcu:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue