Compare commits

...
Sign in to create a new pull request.

120 commits

Author SHA1 Message Date
73775d6999 pre update 2024-02-23 17:55:10 +01:00
5060864b08 fix 4.0 2024-02-23 17:55:10 +01:00
efc7329646 add choc version 2024-02-23 17:55:10 +01:00
85bd46a4d8 version 003 2024-02-23 17:55:10 +01:00
c57440a231 fix matrix :< 2024-02-23 17:55:10 +01:00
a48fb3c064 allow keeping of gerber files 2024-02-23 17:55:10 +01:00
56a81c20b8 add v002 2024-02-23 17:55:10 +01:00
9370da75b7 add v2 with support for powerswitch and reset 2024-02-23 17:55:10 +01:00
fd3fd6b5c0 rename board 2024-02-23 17:55:10 +01:00
Stefan Schwarz
73483e290b add groundplane 2024-02-23 17:55:10 +01:00
Stefan Schwarz
35f2684348 add a switchplate 2024-02-23 17:55:10 +01:00
Stefan Schwarz
ce65a9ef5d cleanup 2024-02-23 17:55:10 +01:00
Stefan Schwarz
3032e6b3d1 rearrange via and connect grounds 2024-02-23 17:55:10 +01:00
Stefan Schwarz
57e8173848 remove bak files from github 2024-02-23 17:55:10 +01:00
Stefan Schwarz
85a13fa4e7 add first routed version 2024-02-23 17:55:10 +01:00
6afbf0385a rotate the diode for cleaner wireing 2024-02-23 17:55:10 +01:00
bec14b1705 remove reverse option 2024-02-23 17:55:10 +01:00
99acabbd12 fix pin assignments 2024-02-23 17:55:10 +01:00
1190f35c66 add makefile and kicad project 2024-02-23 17:55:10 +01:00
52af7f0a20 fix case 2024-02-23 17:55:10 +01:00
Stefan Schwarz
96d043126a wip 2024-02-23 17:55:10 +01:00
Bán Dénes
21e50cb11d 4.0.5 2024-01-22 17:34:20 +01:00
Bán Dénes
353e07654d Package audit 2024-01-22 17:33:56 +01:00
Bán Dénes
e1697e367f Autobind vs. mirroring bugfix (#119) 2024-01-22 15:56:45 +01:00
Bán Dénes
1f57ec4e19 Footprint param streamlining 2023-08-14 19:00:37 +02:00
Kim
6e520e745a
Add intersect aggregator (#102)
* Added intersect aggregator

* Addressed stylistic PR feedback

* Set the rotation of the intersected point to the second point’s rotation

* Revert "Set the rotation of the intersected point to the second point’s rotation"

This reverts commit d210a98f79a8866519fc9695487a9ea235167b8b.

* Adjusted aggregate function to not need axis1 and axis2 parameters

* Add assert to check for exactly 2 parts

* Add unit tests for the aggregate intersect method

* Change the assert string to use ${name.length}

* Change test group name to align with style guide

* Update intersect logic to consider negative Y axis

---------

Co-authored-by: Marco Massarelli <60667061+ceoloide@users.noreply.github.com>
2023-07-19 23:00:44 +02:00
Bán Dénes
e924352763 Allow outline adjusts to use shape-specific units 2023-05-31 11:01:43 +02:00
Bán Dénes
14cd499182 4.0.4 2023-05-20 22:07:22 +02:00
Bán Dénes
7baa6a3b3a 4.0.3 2023-05-20 22:03:21 +02:00
Bán Dénes
9f644c2e2b Independent per-point adjustment 2023-05-20 22:03:21 +02:00
Bán Dénes
9832489d41
Sponsor fixes 2023-05-08 18:46:26 +02:00
Bán Dénes
89981199d9
Initial list of distinguished sponsors 2023-05-08 17:42:43 +02:00
Bán Dénes
63684e33d7
Add donate button to readme 2023-05-01 13:37:48 +02:00
Bán Dénes
d74f657f24
Add sponsorship button 2023-05-01 13:25:16 +02:00
Bán Dénes
daaef0af79 4.0.2 2023-03-18 16:24:47 +01:00
Bán Dénes
4d65eb19a6 Filter negation bugfix 2023-03-18 16:23:05 +01:00
Bán Dénes
3eac7f8e6d Rollup CJS changes 2023-03-18 00:04:38 +01:00
Bán Dénes
5761266260 4.0.1 2023-03-17 23:44:22 +01:00
Luke Kershaw
244fc53eae Handle backslashes in windows tests (#82) 2023-03-17 10:45:04 +01:00
Bán Dénes
9ad080d24c Templating bugfix (#86) 2023-03-17 10:29:55 +01:00
Bán Dénes
f0d22328cd Filter/mirror bugfix 2023-02-09 23:50:18 +01:00
Bán Dénes
480c362c1e Preprocessing bugfix 2023-01-30 15:01:52 +01:00
Bán Dénes
c45523f38a Getting coverage to 100% 2023-01-23 23:34:06 +01:00
Anarion
b27e10374e
Add array to footprint param types (#76) 2023-01-23 21:21:49 +01:00
Luke Kershaw
75f907917b
Extra test coverage for expand_shorthand (#80) 2023-01-23 14:46:57 +01:00
Luke Kershaw
86a74945ca
Fix typo in error message if using incorrect type for stagger (#75) 2023-01-23 12:22:59 +01:00
Bán Dénes
c480a33fa3 Readme postprocessing 2023-01-23 12:19:45 +01:00
Bán Dénes
841addb9ad Roadmap update 2023-01-23 11:17:27 +01:00
Bán Dénes
d2c3999d41 Fix multiple extrusions of the same outline in cases 2023-01-23 11:17:27 +01:00
Bán Dénes
77778249d2 Dependency update 2023-01-23 11:17:27 +01:00
トトも
357e01d639
Improve readme formatting (#62)
* Fixed LICENSE File Name
* Adjusted README Name
* Formatted Contribution Section
* Added License Badge
* Formatted Getting Started
* Moved Getting Started Into Dedicated File
* Added Quicklinks
* Fixed File Name
* Formatted Description + Added Showcase
* Adjusted Image Padding
* Adjusted Spacing
2023-01-23 11:17:01 +01:00
Luke Kershaw
e0eb43566f
Expand test coverage (#77)
* ignore line endings in cli tests
* ignore line endings in integration tests
* expand code coverage for `choc` footprint
* expand code coverage for `chocmini` footprint
* expand code coverage for `mx` footprint
* expand code coverage for `pad` footprint
* expand code coverage for rest of footprints
* expand code coverage for `anchor.js`
* expand code coverage for `units.js`
* expand code coverage for `points.js`
* expand code coverage for `filter.js`
* expand code coverage for `outlines.js`
* expand code coverage for `pcbs.js`
* expand code coverage for `ergogen.js`
* expand code coverage for `kle.js`
* more code coverage for `outlines.js`
* expand code coverage for `cases.js`
2023-01-23 10:02:08 +01:00
Luke Kershaw
3746900490
Bugfix for expand_shorthand (#79) 2023-01-23 09:32:22 +01:00
Bán Dénes
68df6aee0a Version bump 2022-12-18 12:44:16 +01:00
Bán Dénes
5006f5b862 Missing const for web deployment 2022-12-18 12:38:08 +01:00
Bán Dénes
da1417ce2f Type/what consistency for shorthands and cases 2022-12-18 12:37:44 +01:00
Bán Dénes
83b7dc1bb8 Roadmap update 2022-12-18 11:44:16 +01:00
Bán Dénes
e076b62190 Per-footprint mirror overrides, better coordinate support 2022-12-04 20:58:46 +01:00
Bán Dénes
58bb16ed28 Footprint parameter flattening done 2022-12-04 18:40:06 +01:00
Bán Dénes
3a0f326a31 Footprint parameter flattening, round 1 2022-12-04 16:33:48 +01:00
Bán Dénes
1de68843ce Extend footprint test coverage 2022-12-04 16:32:14 +01:00
Bán Dénes
3aef729465 Allow points/anchors to resist special mirror treatment 2022-12-04 12:24:17 +01:00
Bán Dénes
2cfdf10327 Make outline/pcb anchor adjustments more generic 2022-12-04 12:22:17 +01:00
Bán Dénes
158dc33212 Basic footprint tests 2022-12-03 21:23:45 +01:00
Miigon
05a489fa6d more compact trrs footprint 2022-12-03 20:43:52 +01:00
Miigon
561309139a TRRS: properly showing p.ref instead of 'REF**' 2022-12-03 20:43:52 +01:00
Miigon
02dac3316b TRRS: add missing stabilizer hole for reverse footprint 2022-12-03 20:43:52 +01:00
Bán Dénes
f5f787edce Fix diode THT pad order 2022-12-03 20:42:19 +01:00
Bán Dénes
820a1a2888 Add support for footprint adjustment 2022-12-03 20:41:55 +01:00
Bán Dénes
07d6fcfb34 Improve PCB test readability 2022-12-03 18:11:11 +01:00
Michael van Eerd
8a57891b24 fix: Remove superfluous rotation of 180 on a rectangular pad 2022-12-03 17:51:28 +01:00
Michael van Eerd
71419145ad fix (mx): Rotate hotswap pads along with point 2022-12-03 17:51:28 +01:00
Bán Dénes
53b6a98edc De-python-ization 2022-12-03 17:40:01 +01:00
torik42
17dc5ab169 (fix) allow mirroring around origin 2022-12-03 17:28:49 +01:00
Bán Dénes
1da986e609 Minor comment fixes 2022-12-03 17:19:51 +01:00
Bán Dénes
cf9007aa50 Generalize asym usage 2022-12-03 12:38:07 +01:00
Bán Dénes
40406fbc03 Filtering applied to PCBs as well 2022-11-22 21:26:48 +01:00
Bán Dénes
0d73c59538 Footprint sideloading tests 2022-11-14 22:24:07 +01:00
Bán Dénes
24466eb01d Footprint sideloading progress 2022-11-14 00:40:05 +01:00
Bán Dénes
4446a60380 Roadmap update 2022-10-30 21:50:36 +01:00
Bán Dénes
5e68bdb630 Outlining improvements 2022-05-29 20:25:52 +02:00
Bán Dénes
5a25c1c423 Make within-column layout cumulative as well 2022-05-29 14:12:11 +02:00
Bán Dénes
802a988705 Roadmap update 2022-05-29 14:09:47 +02:00
Bán Dénes
a586d9058c Outline scaling 2022-04-16 16:14:29 +02:00
Bán Dénes
5bfa3b5932 Package lock update + vulnerability fix 2022-04-16 13:13:16 +02:00
Bán Dénes
bbab283850 3D de-standalone-ification 2022-04-16 13:12:51 +02:00
Bán Dénes
c6f4832ee4 Single key column name simplification 2022-04-16 12:44:55 +02:00
Bán Dénes
8748a37c04 Yet another roadmap update 2022-04-10 22:44:27 +02:00
Bán Dénes
891f55ace1 Roadmap update 2022-04-05 20:35:12 +02:00
Bán Dénes
d505b19a0f Changelog started 2022-02-27 20:45:53 +01:00
Bán Dénes
fdc2adf30e Dependency and roadmap housecleaning 2022-02-27 19:10:02 +01:00
Bán Dénes
e13927d050 Make reference dumping easier during testing 2022-02-27 18:19:50 +01:00
Bán Dénes
b928cbd35d Implement a simple autobind 2022-02-27 18:18:16 +01:00
Bán Dénes
d23bd71b7a Support orient/rotate towards other points 2022-02-27 13:44:24 +01:00
Bán Dénes
6dc6b5d8e9 Anchor recursivization 2022-02-27 11:11:45 +01:00
Bán Dénes
b8c71bef0f Improve readme 2022-01-24 20:55:35 +01:00
Bán Dénes
cff15dd3b9 Add missing bbox test 2022-01-23 22:04:38 +01:00
Bán Dénes
d5129832b9 More flexible semver handling 2022-01-23 22:02:05 +01:00
Bán Dénes
e0f5c910eb Fix rollup build warning 2022-01-23 21:46:36 +01:00
Bán Dénes
06d2ae4a7f Switch to handrolled semver implementation 2022-01-23 21:45:09 +01:00
Bán Dénes
2b98b502d6 Support string templating for key-level attributes 2022-01-22 23:13:18 +01:00
Bán Dénes
6225013828 Indicate dev version in package.json 2022-01-20 11:58:56 +01:00
Bán Dénes
7f5e5e7544 Roadmap update 2022-01-18 22:07:34 +01:00
Bán Dénes
28d076ea38 Remove accidentally added folder 2022-01-16 20:36:39 +01:00
Bán Dénes
d6f83232a8 Outlines rewrite actually done 2022-01-16 20:36:19 +01:00
Bán Dénes
4844a044df Outlines rewrite theoretically done 2022-01-11 22:40:09 +01:00
Bán Dénes
df7b76c610 Outlines rewrite, part 2 2022-01-10 13:44:57 +01:00
Bán Dénes
6504b2b952 Outlines rewrite in progress 2022-01-09 22:56:05 +01:00
Bán Dénes
bd6b5a0ca6 Add filter tests 2022-01-08 23:51:03 +01:00
Bán Dénes
a7f333c9bc Filter implementation progress 2022-01-03 14:43:45 +01:00
Bán Dénes
534ac4b75d Filter implementation started 2021-12-26 14:06:30 +01:00
Bán Dénes
e48631fac8 Add key-level orient 2021-12-25 21:22:37 +01:00
Bán Dénes
f2bd0d23a1 Roadmap update 2021-12-18 22:15:57 +01:00
Bán Dénes
2c18902e9f Simplify the names in individual point "zones" 2021-12-18 20:28:33 +01:00
Bán Dénes
9ee099b16c Minor test adjustments 2021-12-18 19:53:33 +01:00
Bán Dénes
0ed29e7e1d Place rectangles by their centers 2021-12-18 19:51:26 +01:00
Bán Dénes
73045e4754 Move column-level attributes to key-level 2021-12-18 18:37:26 +01:00
Bán Dénes
86c00eb62d Bump patch version 2022-01-20 11:43:54 +01:00
Bán Dénes
3570be2184 Delete old docs 2022-01-20 11:42:59 +01:00
Bán Dénes
f326594743 De-dupe readme, point usage to docs 2022-01-20 11:33:11 +01:00
191 changed files with 51494 additions and 6645 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
eval "$(lorri direnv)"

13
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [mrzealot] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

249
.gitignore vendored
View file

@ -1,120 +1,129 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Project specific
output
temp*
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Project specific
output
temp*
kicad/45treus/main.pro
kicad/45treus/main.kicad_pcb
*.kicad_pcb-bak
kicad/45treus/gerber_v*/*.gbr
kicad/45treus/gerber_v*/*.gbrjob
kicad/45treus/gerber_v*/*.drl
kicad/23creus/23creus-backups
kicad/23creus/fp-info-cache

271
23creus.yaml Normal file
View file

@ -0,0 +1,271 @@
# yaml-language-server: $schema=./meta/schema.json
units:
kx: cx
ky: cy
px: 2
py: 2
step: .5
points:
zones:
matrix:
columns:
pinky:
key:
column_net: P21
mirror: { column_net: P2 }
ring:
key:
stagger: 4
spread: kx + step
column_net: P20
mirror: { column_net: P3 }
middle:
key:
stagger: 5
spread: kx + step
column_net: P19
mirror: { column_net: P4 }
index:
key:
stagger: -5
spread: kx + step
column_net: P18
mirror: { column_net: P5 }
inner:
key:
stagger: -6
spread: kx + step
column_net: P15
mirror: { column_net: P6 }
thumb:
rows:
bottom:
skip: false
key:
skip: true
spread: kx + step
stagger: 10
column_net: P14
mirror: { column_net: P7 }
rows:
bottom:
row_net: P16
home:
spread: ky + step
row_net: P10
top:
spread: ky + step
row_net: P9
num:
spread: ky + step
row_net: P8
rotate: -23
mirror:
ref: matrix_thumb_bottom
distance: 42
outlines:
# outer border
farouter:
- what: polygon
points:
- ref: matrix_inner_num
shift: [13, 13]
- ref: matrix_middle_num
shift: [13, 13]
- ref: matrix_middle_num
shift: [-13, 13]
- ref: matrix_pinky_num
shift: [-13, 13]
- ref: matrix_pinky_bottom
shift: [-13, -13]
- ref: matrix_pinky_bottom
shift: [13, -13]
- ref: matrix_inner_bottom
shift: [-13, -13]
- ref: matrix_inner_bottom
shift: [13, -13]
- ref: mirror_matrix_inner_bottom
shift: [13, -13]
- ref: mirror_matrix_inner_bottom
shift: [-13, -13]
- ref: mirror_matrix_pinky_bottom
shift: [13, -13]
- ref: mirror_matrix_pinky_bottom
shift: [-13, -13]
- ref: mirror_matrix_pinky_num
shift: [-13, 13]
- ref: mirror_matrix_middle_num
shift: [-13, 13]
- ref: mirror_matrix_middle_num
shift: [13, 13]
- ref: mirror_matrix_inner_num
shift: [13, 13]
compatouter:
- what: polygon
points:
- ref: matrix_inner_num
shift: [11, 11]
- ref: matrix_middle_num
shift: [11, 11]
- ref: matrix_middle_num
shift: [-11, 11]
- ref: matrix_pinky_num
shift: [-11, 11]
- ref: matrix_pinky_bottom
shift: [-11, -11]
- ref: matrix_pinky_bottom
shift: [11, -11]
- ref: matrix_inner_bottom
shift: [-11, -11]
- ref: matrix_inner_bottom
shift: [11, -11]
- ref: mirror_matrix_inner_bottom
shift: [11, -11]
- ref: mirror_matrix_inner_bottom
shift: [-11, -11]
- ref: mirror_matrix_pinky_bottom
shift: [11, -11]
- ref: mirror_matrix_pinky_bottom
shift: [-11, -11]
- ref: mirror_matrix_pinky_num
shift: [-11, 11]
- ref: mirror_matrix_middle_num
shift: [-11, 11]
- ref: mirror_matrix_middle_num
shift: [11, 11]
- ref: mirror_matrix_inner_num
shift: [11, 11]
outer:
- what: polygon
points:
- ref: matrix_inner_num
shift: [10, 10]
- ref: matrix_middle_num
shift: [10, 10]
- ref: matrix_middle_num
shift: [-10, 10]
- ref: matrix_pinky_num
shift: [-10, 10]
- ref: matrix_pinky_bottom
shift: [-10, -10]
- ref: matrix_pinky_bottom
shift: [10, -10]
- ref: matrix_inner_bottom
shift: [-10, -10]
- ref: matrix_inner_bottom
shift: [10, -10]
- ref: mirror_matrix_inner_bottom
shift: [10, -10]
- ref: mirror_matrix_inner_bottom
shift: [-10, -10]
- ref: mirror_matrix_pinky_bottom
shift: [10, -10]
- ref: mirror_matrix_pinky_bottom
shift: [-10, -10]
- ref: mirror_matrix_pinky_num
shift: [-10, 10]
- ref: mirror_matrix_middle_num
shift: [-10, 10]
- ref: mirror_matrix_middle_num
shift: [10, 10]
- ref: mirror_matrix_inner_num
shift: [10, 10]
# chip
chip:
- what: rectangle
size: [18, 50]
where:
ref:
aggregate.parts:
- matrix_inner_bottom
- mirror_matrix_inner_bottom
shift: [0, 40]
# 14mm holes for cherry switches
keyholes:
- what: rectangle
where: true
asym: source
size: 13.8
# keycaps
keycaps:
- what: rectangle
where: true
asym: source
size: [17.95, 17.5]
# switchplate
switchplate:
- outer
- -keyholes
- -chip
# far outer switchplate
farouterswitchplate:
- farouter
- -keyholes
- -chip
cases:
laser:
- name: farouterswitchplate
extrude: 6.5
- name: compatouter
extrude: 5.2
shift: [0, 0, 1.3]
operation: subtract
pcbs:
main:
outlines:
outer:
outline: outer
footprints:
keys:
what: choc
where: true
params:
from: "{{colrow}}"
to: "{{column_net}}"
keycaps: true
hotswap: true
diode:
what: diode
where: true
params:
from: "{{colrow}}"
to: "{{row_net}}"
adjust:
shift: [0, -5]
rotate: 180
reset:
what: button
params:
from: RST
to: GND
where:
ref:
aggregate.parts:
- matrix_inner_bottom
- mirror_matrix_inner_bottom
rotate: 90
mcu:
what: promicro
where:
rotate: 270
ref:
aggregate.parts:
- matrix_inner_bottom
- mirror_matrix_inner_bottom
shift: [0, -4]

169
23treus.yaml Normal file
View file

@ -0,0 +1,169 @@
points:
key:
footprints:
mx_hotswap:
type: mx
params:
hotswap: true
reverse: false
keycaps: true
nets:
from: =colrow
to: =column_net
diode:
type: diode
nets:
from: =colrow
to: =row_net
anchor:
shift: [0, -5]
rotate: 180
zones:
matrix:
columns:
pinky:
key:
column_net: P21
mirror: { column_net: P2 }
ring:
key:
stagger: 4
column_net: P20
mirror: { column_net: P3 }
middle:
key:
stagger: 5
column_net: P19
mirror: { column_net: P4 }
index:
key:
stagger: -5
column_net: P18
mirror: { column_net: P5 }
inner:
key:
stagger: -6
column_net: P15
mirror: { column_net: P6 }
thumb:
row_overrides:
bottom:
key:
stagger: 10
column_net: P14
mirror: { column_net: P7}
rows:
bottom:
row_net: P16
home:
row_net: P10
top:
row_net: P9
num:
row_net: P8
rotate: -23
mirror:
ref: matrix_thumb_bottom
distance: 42
outlines:
exports:
# outer border
outer:
- type: polygon
points:
- ref: matrix_inner_num
shift: [10, 10]
- ref: matrix_middle_num
shift: [10, 10]
- ref: matrix_middle_num
shift: [-10, 10]
- ref: matrix_pinky_num
shift: [-10, 10]
- ref: matrix_pinky_bottom
shift: [-10, -10]
- ref: matrix_pinky_bottom
shift: [10, -10]
- ref: matrix_inner_bottom
shift: [-10, -10]
- ref: matrix_inner_bottom
shift: [10, -10]
- ref: mirror_matrix_inner_bottom
shift: [10, -10]
- ref: mirror_matrix_inner_bottom
shift: [-10, -10]
- ref: mirror_matrix_pinky_bottom
shift: [10, -10]
- ref: mirror_matrix_pinky_bottom
shift: [-10, -10]
- ref: mirror_matrix_pinky_num
shift: [-10, 10]
- ref: mirror_matrix_middle_num
shift: [-10, 10]
- ref: mirror_matrix_middle_num
shift: [10, 10]
- ref: mirror_matrix_inner_num
shift: [10, 10]
#- type: keys
# side: both
# size: 20
# corner: 1
#- type: polygon
# points:
# - ref: matrix_inner_num
# shift: [0, -5]
# - ref: matrix_thumb_bottom
# shift: [-10, -10]
# - ref: mirror_matrix_thumb_bottom
# shift: [-10, -10]
# - ref: mirror_matrix_inner_num
# shift: [0, -5]
chip:
# cutout for the chip itself
- type: rectangle
size: [18, 33]
anchor:
ref: [matrix_inner_top, mirror_matrix_inner_top]
shift: [-9, -24.5]
# cutout for the cable (looks)
- type: rectangle
size: [8, 50]
anchor:
ref: [matrix_inner_top, mirror_matrix_inner_top]
shift: [-4, -24.5]
# 14mm holes for cherry switches
keyholes:
- type: keys
side: both
size: 14
bound: false
# switchplate
switchplate:
- outer
- -keyholes
- -chip
cases:
pcbs:
main:
outlines:
outer:
outline: outer
footprints:
reset:
type: button
nets:
from: RST
to: GND
anchor:
ref: [matrix_inner_bottom, mirror_matrix_inner_bottom]
rotate: 90
mcu:
type: promicro
anchor:
rotate: 270
ref: [matrix_inner_top, mirror_matrix_inner_top]
shift: [0, -4]

View file

6
Makefile Normal file
View file

@ -0,0 +1,6 @@
all: node_modules
node src/cli.js 23creus.yaml -d -o output
cp output/pcbs/main.kicad_pcb kicad/23creus/
node_modules:
npm ci

111
README.md Normal file
View file

@ -0,0 +1,111 @@
# Ergogen
***Ergonomic Keyboard Generator***
<br>
<img
src = 'showcase.png'
width = 400
align = right
/>
The project aims to provide a common configuration format to describe ***ergonomic*** 2D layouts and generate automatic plates, cases, as well as un-routed PCBs for them. The project grew out of (and is an integral part of) the [Absolem keyboard], and shares its [Discord] server as well.
<div align = center>
<br>
<br>
<br>
---
[![Button WebUI]][WebUI]
[![Button Documentation]][Documentation]
[![Button Discord]][Discord]
[![Button Donate]][Donate]
---
</div>
<br>
<br>
## Getting Started
Until there's a proper "Getting started" guide, try getting acquainted with **Ergogen** by following these steps in order:
<br>
1. Read the **[Documentation]**.
D'uuh.
They're not complete by any measure, but should give you a fairly good idea what you're dealing with here.
<br>
2. Try one of the web-based deployments.
[![Button Official]][WebUI]
[![Button Unofficial]][Unofficial]
The unofficial deployment is probably better, tbh, and will soon be replacing the official one.
Choose either one, then click things, look at outputs and see if things start to make sense.
There is no need for you to download the **CLI** unless you want to do one of the following:
- Preview in-development features
- Use custom modifications
- Contribute code
<br>
3. Search the [`ergogen`][Topic] topic on GitHub.
There, you can look at (and reverse engineer) a variety of real life configs using **Ergogen**.
Pop them into the web UI to see what they do, tinker with them and things should start to make more sense.
<br>
4. If a question persists after all of the above, feel free to ask it over on **[Discord]** and we'll do our best to help you out.
<br>
## Contributions
Feature ideas, documentation improvements, examples, tests, or pull requests welcome!
Get in touch on our **[Discord]**, and we can definitely find something you can help with, if you'd like to.
<br>
## Sponsors
Huge thanks go to everyone who chooses to support my work!
But even huger thanks are due to the following, *distinguished* sponsors:
- [perce](https://madebyperce.com/)
- [Cache](https://github.com/MvEerd)
- [Neil Gilmour](https://github.com/neilgilmour)
- [ochief](https://github.com/ochief)
- [Alyx Brett](https://github.com/alyx-brett)
<!----------------------------------------------------------------------------->
[Absolem keyboard]: https://zealot.hu/absolem
[Documentation]: https://docs.ergogen.xyz
[Discord]: http://discord.ergogen.xyz
[WebUI]: https://ergogen.xyz
[Unofficial]: https://ergogen.cache.works/
[Topic]: https://github.com/topics/ergogen
[Donate]: https://github.com/sponsors/mrzealot
<!--------------------------------{ Buttons }---------------------------------->
[Button WebUI]: https://img.shields.io/badge/Deployment-37a779?style=for-the-badge&logoColor=white&logo=AppleArcade
[Button Unofficial]: https://img.shields.io/badge/Unofficial-yellow?style=for-the-badge
[Button Official]: https://img.shields.io/badge/Official-37a779?style=for-the-badge
[Button Documentation]: https://img.shields.io/badge/Documentation-1793D1?style=for-the-badge&logoColor=white&logo=GitBook
[Button Discord]: https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logoColor=white&logo=Discord
[Button Donate]: https://img.shields.io/badge/Donate-EA4AAA?style=for-the-badge&logoColor=white&logo=githubsponsors

195
changelog.md Normal file
View file

@ -0,0 +1,195 @@
# 4.0.0 (2022-??-??)
## :boom: Breaking Changes
## :seedling: Enhancements
## :skull: Bugfixes
## :wrench: Other
* fdc2adf - Dependency and roadmap housecleaning (2022-02-27)
* e13927d - Make reference dumping easier during testing (2022-02-27)
* b928cbd - Implement a simple autobind (2022-02-27)
* d23bd71 - Support orient/rotate towards other points (2022-02-27)
* 6dc6b5d - Anchor recursivization (2022-02-27)
* b8c71be - Improve readme (2022-01-24)
* cff15dd - Add missing bbox test (2022-01-23)
* d512983 - More flexible semver handling (2022-01-23)
* e0f5c91 - Fix rollup build warning (2022-01-23)
* 06d2ae4 - Switch to handrolled semver implementation (2022-01-23)
* 2b98b50 - Support string templating for key-level attributes (2022-01-22)
* 6225013 - Indicate dev version in package.json (2022-01-20)
* 7f5e5e7 - Roadmap update (2022-01-18)
* 28d076e - Remove accidentally added folder (2022-01-16)
* d6f8323 - Outlines rewrite actually done (2022-01-16)
* 4844a04 - Outlines rewrite theoretically done (2022-01-11)
* df7b76c - Outlines rewrite, part 2 (2022-01-10)
* 6504b2b - Outlines rewrite in progress (2022-01-09)
* bd6b5a0 - Add filter tests (2022-01-08)
* a7f333c - Filter implementation progress (2022-01-03)
* 534ac4b - Filter implementation started (2021-12-26)
* e48631f - Add key-level orient (2021-12-25)
* f2bd0d2 - Roadmap update (2021-12-18)
* 2c18902 - Simplify the names in individual point "zones" (2021-12-18)
* 9ee099b - Minor test adjustments (2021-12-18)
* 0ed29e7 - Place rectangles by their centers (2021-12-18)
* 73045e4 - Move column-level attributes to key-level (2021-12-18)
# 3.1.2 (2022-01-20)
* 86c00eb - (tag: v3.1.2, origin/master, backup/master, master) Bump patch version (2022-01-20)
* 3570be2 - Delete old docs (2022-01-20)
* f326594 - De-dupe readme, point usage to docs (2022-01-20)
# 3.1.1 (2022-01-14)
* 47d15c0 - (tag: v3.1.1) Hotfix parameterization (2022-01-14)
# 3.1.0 (2021-12-15)
* 488fba0 - (tag: v3.1.0) 3.1.0 (2021-12-15)
* f16ccd4 - Refresh roadmap (2021-12-15)
* 21cfc1f - Allow glue opt-out, even when bound (2021-12-15)
* c46a0bc - Add U for 19.05 spacing, closes #48 (2021-12-15)
* 3745cf3 - Parameterize PCB component reference hiding (2021-12-15)
* 13052cd - PCB metadata propagation (2021-12-15)
* 90db02f - Roadmap update (2021-12-15)
* 3e0dc84 - Support semver checks (2021-12-15)
# 3.0.3 (2021-12-13)
* d5ebd70 - (tag: v3.0.3) 3.0.3 (2021-12-13)
* e45e563 - Further dependency restructure (2021-12-13)
# 3.0.2 (2021-12-13)
* 96c7caa - (tag: v3.0.2) 3.0.2 (2021-12-13)
* 16c9243 - Move custom dependencies to ergogen org + fix metadata (2021-12-13)
* 5046667 - Suppress npm warnings (2021-12-13)
* e2535da - Update dependencies (2021-12-13)
* d193af6 - Add fresh TODO items, change to gitflow (2021-12-12)
# 3.0.1 (2021-12-12)
* 9a1f9ec - (tag: v3.0.1) 3.0.1 (2021-12-12)
* 16f6b81 - Fillet all outline chains, closes #34 (2021-12-12)
* 8490f14 - Add const in scrollwheel footprint (2021-08-16) <Michael van Eerd>
* 21e173e - Make spread: 0 possible (2021-08-13) <Michael van Eerd>
* 38a8fc5 - Anchor affect bugfix, partially fixes #33 (2021-12-12)
* 483f214 - Array unnest bugfix, fixes #50 (2021-12-12)
* e58b80c - Redirect readme links to new domain (2021-11-27)
* e29613d - Dump accumulated TODOs (2021-11-27)
* 0ec16db - Minor fix for mirrored anchor rotations (2021-11-27)
* a593f61 - Fixed rotary encoder footprint (2021-11-07) <Albert Engelbrecht>
* 68102e8 - Fix ReferenceError: *_unit is not defined (2021-09-03) <Michael van Eerd>
* bf2dee6 - docs(readme): fix typo in web ui url link (2021-08-08) <Michael van Eerd>
* ad5ac9c - Visualization fix (2021-07-25)
# 3.0.0 (2021-07-21)
* 0658057 - (tag: v3.0.0) 3.0.0 (2021-07-21)
* fa850a6 - YAML-ification and minor output restructure (2021-07-21)
* 282e117 - Web release prep (2021-07-21)
* 67e01ed - Base key-level defaults around `u` (2021-07-18)
* 9bdcd36 - Some outline testing (2021-07-18)
* 26128f8 - Support full anchors in outline shapes (2021-07-18)
* 677fae0 - Add roadmap (2021-07-18)
* b159cfa - Further testing (2021-07-18)
* 4105718 - Complete CLI testing (2021-07-17)
* 4d88dac - End-to-end CLI tests (2021-07-17)
* 1f3ecb5 - Improved interface testing (2021-07-16)
* 7d841b2 - Dependency updates (2021-07-16)
* 98d7f66 - Make old jscad stuff audit friendly (2021-07-16)
* 58cadde - Minor case fix + test (2021-07-16)
* f955aac - Support KLE to PCB metadata and nets (2021-07-16)
* cd0ae6d - Smarter dump test switch (2021-07-16)
* b43b719 - Auto-debug when output would be empty (2021-07-16)
* bc75781 - Adjust tests for async interface (2021-07-15)
* 1cb9fdc - Basic KLE support (2021-07-15)
* d09b3fd - Smarter dump switch for the test runner, reference adjustments (2021-07-14)
* 42a3e2d - CLI and output restructure, SVG/DXF/STL integration (2021-07-14)
* c49881c - Remove leftover debug statements (2021-07-11)
* c12e8d2 - Update footprints to match new structure (2021-07-11)
* fe30b91 - PCB net and parameter overhaul (2021-07-11)
* 452d7c1 - Units separated to their own block, and properly tested (2021-07-11)
* 71efdbe - Test switch overhaul (2021-07-11)
* ea12df2 - Fix typo (2021-07-11)
* 2425b06 - Package audit (2021-07-06)
* 2f6b9ab - Added new footprints for OLEDs, jumpers, and Omron B3F-4055 switches (2021-07-06) <tapioki>
* a431bc4 - Added documentation for switch footprints (2021-07-06) <tapioki>
* ee43a93 - Added keycaps option to display mx or choc keycaps on Dwgs.User, and fixed mx footprint (2021-06-12) <tapioki>
* 1ff7e07 - Added new switch footprints, updated reversibility functionality, integrated hotswap footprints, added orientation to promicro, alphabetized index, and added net and paramter documentation (2021-05-23) <tapioki>
* 3e78e6e - Add files via upload (2021-04-26) <tapioki>
* 5a8a66b - Add files via upload (2021-04-26) <tapioki>
* 9baae15 - Add unit test for assertions (2021-05-22)
* c7b86c7 - Add unit test for anchors (2021-05-22)
* 76bb071 - Add footprints to coverage, streamline coverage script call (2021-05-22)
* 7f8b1c4 - Add basic interface tests (2021-05-16)
* 5cd8985 - Add unit test for operation parsing (2021-05-16)
* 2cae521 - Add unit test for utils (2021-05-16)
* 2d6ad02 - Add unit test for point class (2021-05-16)
* 806a7ec - Package audit (2021-05-16)
* de0f61b - Filleting, bevel fix, poly mirroring (2021-04-18)
* 18a76e5 - Added p.rot param to pads (#20) (2021-04-18) <adt>
* 5754824 - fixed net ref to pad (2021-03-22) <adt>
* c0eea2a - Fix makerjs dep and audit (2021-04-01)
# 2.0.0 (2021-03-15)
## :boom: Breaking Changes
- Change indirection and preprocessing symbols
- Rename `reset` footprint to `button`
## :seedling: Enhancements
- Added support for variables and/or units
- String typed math formulas can now be used anywhere numbers are expected
- For example, `spread: 19` is the same as `spread: u`
- Added support for nested (a.k.a. deep) key notations
- So `zones: {columns: {key: {flag: true}}}` can now be simplified to `zones.columns.key.flag: true`
- Added support for parametric declarations
- ...
- Added MX and choc hotswap footprints
- Added rotary encoder footprint - [**@adt**](https://github.com/adt)
## :skull: Bugfixes
- Various footprint fixes - [**@adt**](https://github.com/adt)
- Documentation typos, fixes, examples - [**@Albert-IV**](https://github.com/Albert-IV) , [**@FanchGadjo**](https://github.com/FanchGadjo)
## :wrench: Other
- Lots and lots of structural refactoring, housecleaning, etc.
- Testing infrastructure improvements
- Better glue-related error messages
- Propagate point visualization to the webui
- Output SVG alongside DXF in debug mode - [**@brow**](https://github.com/brow)
- Added part no. and updated URL for `reset` footprint - [**@brow**](https://github.com/brow)
# 1.0.0 (2020-10-18)
## :sunglasses: Initial release!
- Ergogen declares its independence from the [Absolem](https://github.com/mrzealot/absolem) project
- Thanks to [**Merlin04**](https://github.com/Merlin04) for an initial JSON schema

View file

@ -1,644 +0,0 @@
## Overview
The whole Ergogen config is a single YAML file.
If you prefer JSON over YAML, feel free to use it, conversion is trivial and the generator will detect the input format.
The important thing is that the data should contain the following keys:
```yaml
points: <points config...>
outlines: <outline config...>
cases: <case config...>
pcbs: <pcb config...>
```
The `points` section describes the core of the layout: the positions of the keys.
The `outlines` section then uses these points to generate plate, case, and PCB outlines.
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.
There's also a completely separate "preprocessing" step to help reduce unnecessary repetition.
Of course, if the declarative nature of the config is still not terse enough (despite the preprocessor, the built-in YAML references, and the Ergogen-based inheritance detailed below), there's nothing stopping you from writing code that generates the config.
It brings the game to yet another abstraction level higher, so that you can use branching, loops, and parametric functions to compose a "drier" keyboard definition.
<br>
## Preprocessing
Ergogen does a separate preprocessor pass on the config before starting to interpret it.
This consists of the following steps:
- **Unnesting**: any object key containing dots (as in, `.`s) will be unnested. This allows the use of the so called "dot notation". For example, `nested.key.definition: value` will turn into `{nested: {key: {definition: value}}}` in the preprocessed config object.
- **Inheritance**: the `$extends` key can be used in any declaration to inherit values from another declaration. Extension happens according to the following rules:
- if the new value is `undefined`, the old value will be used as a default;
- if both values are defined (and have the same type), the new one will override the old;
- if both values have different types, the new value will take precedence;
- if the new value is `$unset`, the resulting value will be `undefined`, regardless of previous type;
- for arrays or objects, extension is called for each child element recursively.
The actual value of the `$extends` key should be the full absolute path of the declaration we wish to inherit from (using the above mentioned, nested "dot notation" if necessary). For example:
```yaml
top:
parent:
a: 1
b: 2
child:
$extends: top.parent
c: 3
```
This declaration will lead to `child` containing all three letter keys: `{a: 1, b: 2, c: 3}`.
- **Parameterization**: allows regex replacements within declarations. Take the following declaration as a starting point:
```yaml
top:
value: placeholder
double_value: placeholder * 2
$params: [placeholder]
$args: [3]
```
In this case, every occurrence of the value "placeholder" will be replaced with "3", which allows us to define it only once and still use it in multiple places (kind of like a pseudo-variable).
- **Skipping**: the `$skip` key can be used anywhere to, well, skip (or "comment out" entire declarations). It can also be useful when combining inheritance and parameterization. For example:
```yaml
grandparent:
a: placeholder1
b: placeholder2
$params: [placeholder1, placeholder2]
parent:
$extends: grandparent
$args: [value1]
child:
$extends: parent
$args: [,value2]
```
Here, the grandparent defines two different parameters, but only the child knows both arguments that should be substituted. This would lead to an error at the parent's level, because it has two parameters, and only one argument. But, assuming that this is just an intermediary abstract declaration and we wouldn't want to use it anyway, we can just declare `$skip: true`.
The result of the preprocessor is *almost* just a plain JSON object.
The only semantic difference is how numbers are handled. For example, the value `3 * 2` would lead to a string type in JSON, but since it's a mathematical formula, it can also be interpreted as a number.
Ergogen tries this interpretation for every string value, and if it succeeds, it calculates the results and converts them to JSON numbers.
This syntax also works with variables, which we can use to define units (see below).
Otherwise, we can begin with the actual keyboard-related layout...
<br>
## Points
A point in this context refers to a 2D point `[x,y]` with a rotation/orientation `r` added in.
These can be thought of as the middle points of the keycaps in a resulting keyboard layout, with an additional handling of the angle of the keycap.
What makes this generator "ergo" is the implicit focus on the column-stagger.
Of course we could simulate the traditional row-stagger by defining everything with a 90 degree rotation, but that's really not the goal here.
Since we're focusing on column-stagger, keys are laid out in columns, and a collection of columns is called a "zone".
For example, we can define multiple, independent zones to make it easy to differentiate between the keywell and the thumb fan/cluster.
Points can be described as follows:
```yaml
points:
units:
name: val
...
zones:
my_zone_name:
anchor:
ref: <point reference>
orient: num # default = 0
shift: [x, y] # default = [0, 0]
rotate: num # default = 0
affects: string containing any of x, y, or r # default = xyr
columns:
column_name: <column def>
...
rows:
row_name: <row-level key def>
...
key: <zone-level key def>
...
```
We start with a `units` clause, where we can define units to use in relative calculations.
The three predefined ones are `u` (=19mm), `cx` (=18mm, named for "Choc X distance"), and `cy` (=17mm, named for "Choc Y distance").
But we can add any other (or modify these predefined ones), or even use an existing measure in calculating a new value (for example, `double: 2 u`).
Recall how each string that can be interpreted as a math formula will be treated like a number, so this is a great way to add math-level variables to your config.
Then comes the `zones` key, under which we can define the individual, named zones.
`anchors` are used to, well, anchor the zone to something.
It's the `[0, 0]` origin with a 0 degree orientation by default, but it can be changed to any other pre-existing point. (Consequently, the first zone can't use a ref, because there isn't any yet.)
The `ref` field can also be an array of references, in which case their average is used -- mostly useful for anchoring to the center, by averaging a key and its mirror; see later.
This initial position can then be changed with the `orient`, `shift`, and `rotate` options.
`shift` adds extra translation, while the difference between `orient` and `rotate` is whether they add their rotation before or after the translation.
Also note that anywhere an anchor can be specified, a list of anchors is also valid.
It would be meaningless, though, if each subsequent anchor would override the previous one, so the `affects` clause helps to affect only certain dimensions of the anchor.
It can be declared using a string containing any of `x`, `y`, or `r`, which stand for the x and y coordinates and the rotation of the anchor, respectively.
Once we know _where_ to start, we can describe the `columns` of our layout.
```yaml
columns:
column_name:
stagger: num # default = 0
spread: num # default = 19
rotate: num # default = 0
origin: [x, y] # relative to center of column's first key, default = [0, 0]
rows:
row_name: <key-specific key def>
...
key: <column-level key def>
...
```
`stagger` means an extra vertical shift to the starting point of the whole column compared to the previous one (initially 0, cumulative afterwards).
The layout of the column then proceeds according to the appropriate key declarations (more on this in a minute).
Once the column has been laid out, `spread` (the horizontal space between this column and the next) is applied, and an optional (cumulative) rotation is added around the `origin` if `rotate` is specified.
We repeat this until the end of the column definitions, then move on to the next zone.
<hr />
Regarding lower level layout, rows appear both in zones and columns, and keys can be defined in five (!) different places. So what gives?
Don't worry, all this is there just so that we can keep repetition to a minimum.
We could safely remove the `rows` and `key` options from zones, and the `key` option from column definitions, without losing any of the functionality.
But we'd have to repeat ourselves a lot more.
Let's start with rows.
`zone.rows` can give an overall picture about how many rows we'll have, and set key-related options on a per-row basis.
But what if we want to extend this initial picture with some column-specific details? (More on "extension" in a bit.)
For example, we want an outer pinky column where padding is tighter than it is for the others.
That's where `column.rows` can help, specifying a row "extension" for just that column.
And what if we want to **override** the zone-level declarations in a certain column?
For example, we don't just want to modify a row's attributes for a given column, but actually override the amount of rows there are.
Like an outer pinky column with just two keys instead of the regular three.
That's where `column.row_overrides` can help, specifying a row-level override that disregards the zone-level defaults.
Easy.
Now for the trickier part: keys.
There are five ways to set key-related options (again, to minimize the need for repetition):
1. at the global-level (affecting all zones)
2. at the zone-level
3. at the column-level
4. at the row-level
5. at the key-level
These "extend" each other in this order so by the time we reach a specific key, every level had an opportunity to modify something.
Note that unlike the overriding for rows, key-related extension is additive.
For example, let's suppose that a key-related attribute is already defined at the column-level.
When we later encounter a key-level extension for this key that specifies a few things but not this exact key, its value will stay the same instead of disappearing.
When there is a "collision", simple values (like booleans, numbers, or strings) replace the old ones, while composites (arrays or objects) apply this same extension recursively, element-wise.
So when `key = 1` is extended by `key = 2`, the result is `key = 2`.
But if `key = {a: 1}` is extended by `key = {b: 2}`, the result is `key = {a: 1, b: 2}`.
Lastly, while there are a few key-specific attributes that have special meaning in the context of points (listed below), any key with any data can be specified here.
This can be useful for storing arbitrary meta-info about the keys, or just configuring later stages with key-level parameters.
So, for example, when the outline phase specifies `bind` as a key-level parameter (see below), it means that it can be specified just like any other key-level attribute.
Now for the "official" key-level attributes:
```yaml
name: name_override # default = a concatenation of zone, column, and row
shift: [x, y] # default = [0, 0]
rotate: num # default = 0
padding: num # default = 19
skip: boolean # default = false
asym: left | right | both # default = both
mirror: <arbitrary key-level data>
```
`name` is the unique identifier of this specific key.
It defaults to a `<row>_<column>` format, but can be overridden if necessary.
`shift` and `rotate` declare an extra, key-level translation or rotation, respectively.
Then we leave `padding` amount of vertical space before moving on to the next key in the column.
`skip` signals that the point is just a "helper" and should not be included in the output.
This can happen when a _real_ point is more easily calculable through a "stepping stone", but then we don't actually want the stepping stone to be a key itself.
Finally, `asym` and `mirror` relate to mirroring, which we'll cover in a second.
<hr />
Since `zones` was only a single key within the `points` section, it's reasonable to expect something more.
Indeed:
```yaml
points:
units: <mentioned at the beginning...>
zones: <what we talked about so far...>
key: <global key def>
rotate: num # default = 0
mirror:
axis: num # default = 0
ref: <point reference> # and other anchor-level settings
distance: num # default = 0
```
Here, `rotate` can apply a global angle to all the points, which can simulate the inter-half angle of one-piece boards.
Then comes the mirroring step, where the generator automatically copies and mirrors each point.
If there's an `axis` set within the `mirror` key, points will be mirrored according to that.
If not, the axis will be calculated so that there will be exactly `distance` mms between the `ref`erenced point and its duplicate.
Now if our design is symmetric, we're done.
Otherwise, we need to use the `asym` key-level attribute to indicate which side the key should appear on.
If it's set as `left`, mirroring will simply skip this key.
If it's `right`, mirroring will "move" the point instead of copying it.
The default `both` assumes symmetry.
Using the _key-level_ `mirror` key (not to be confused with the global `mirror` setting we just discussed above), we can set additional data for the mirrored version of the key.
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.
<br>
## 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.
So the first part of the outline generation is "binding", where we make the individual holes _bind_ to each other.
We use a key-level declarations for this:
```yaml
bind: num | [num_x, num_y] | [num_t, num_r, num_b, num_l] # default = 0
```
Again, key-level declaration means that both of these should be specified in the `points` section, benefiting from the same extension process every key-level setting does.
This field declares how much we want to bind in each direction, i.e., the amount of overlap we want to make sure that we can reach the neighbor (`num` applies to all directions, `num_x` horizontally, `num_y` vertically, and the t/r/b/l versions to top/right/bottom/left, respectively).
Note that it might make sense to have negative `bind` values, in case we not only don't want to bind in the given direction, but also don't want to "cover up" a potential corner rounding or bevel (see below).
If it's a one-piece design, we also need to "glue" the halves together (or we might want to leave some extra space for the controller on the inner side for splits).
This is where the following section comes into play:
```yaml
glue:
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 the same as it was for points.
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 anchor, 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".
This leads to a gluing middle patch that can be used to meld the left and right sides together, given by the counter-clockwise polygon:
- Top intersection
- Left top point
- Left bottom point
- Bottom intersection
- Right bottom point
- Right top point
If this is insufficient (maybe because it would leave holes), the `waypoints` can be used to supplement the glue.
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.
(TODO: while the `extra` key is reserved for this purpose, it hasn't been needed, and therefore is unimplemented for now.)
<hr />
Once we're satisfied with the glue, the outline is generated by the union of the bound left/right halves and the glue polygon.
Note that this outline is still parametric, so that we can specify different width/height values for the rectangles.
Now we can configure what we want to "export" as outlines from this phase, given by the combination/subtraction of the following primitives:
- `keys` : the combined outline that we've just created. Its parameters include:
- `side: left | right | middle | both | glue` : the part we want to use
- `left` and `right` are just the appropriate side of the laid out keys, without the glue.
- `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 these values are added to the evaluation context as the variables `sx` and `sy`. So during a `keys` layout with a size of 18, for example, a relative shift of `[.5 sx, .5 sy]` 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
- `bound: boolean # default = true` : whether to use the binding declared previously
- `rectangle` : an independent rectangle primitive. Parameters:
- `ref`, `rotate`, and `shift`, etc. (the usual anchor settings)
- `size`, `corner` and `bevel`, just like for `keys`
- `circle` : an independent circle primitive. Parameters:
- `ref`, `rotate`, and `shift`, etc. (the usual anchor settings)
- `radius: num` : the radius of the circle
- `polygon` : an independent polygon primitive. Parameters:
- `points: [<anchor>, ...]` : the points of the polygon. Each `<anchor>` can have its own `ref`, `shift`, etc. (all of which are still the same as above). The only difference here is that if a `ref` is unspecified, the previous point will be assumed (as in a continuous chain). For the first, it's `[0, 0]` by default.
- `outline` : a previously defined outline, see below.
- `name: outline_name` : the name of the referenced outline
Using these, we define exports as follows:
```yaml
exports:
my_name:
- operation: add | subtract | intersect | stack # default = add
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 `outline` 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.
(By convention, a starting underscore is kind of like a "private" marker.)
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>
## Cases
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.
That's it.
Declarations might look like this:
```yaml
cases:
case_name:
- type: outline # default option
name: <outline ref>
extrude: num # default = 1
shift: [x, y, z] # default = [0, 0, 0]
rotate: [ax, ay, az] # default = [0, 0, 0]
operation: add | subtract | intersect # default = add
- type: case
name: <case_ref>
# extrude makes no sense here...
shift: # same as above
rotate: # same as above
operation: # same as above
- ...
...
```
When the `type` is `outline`, `name` specifies which outline to import onto the xy plane, while `extrude` specifies how much it should be extruded along the z axis.
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>
## 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.
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.
Footprints can be specified at the key-level (under the `points` section, like we discussed above), or here with manually given anchors.
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 equal sign `=` 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 (or other decorative outlines for the silkscreen maybe) can be specified using a previously defined outline name under the `outlines` key.
```yaml
pcbs:
pcb_name:
outlines:
pcb_outline_name:
outline: <outline reference>
layer: <kicad layer to export to> # default = Edge.Cuts
...
footprints:
- type: <footprint type>
anchor: <anchor declaration>
nets: <type-specific net params>
params: <type-specific (non-net) footprint params>
- ...
...
```
Defining both the `outlines` and the `footprints` can be done either as arrays or objects, whichever is more convenient.
The currently supported footprint types can be viewed in [this folder](https://github.com/mrzealot/ergogen/tree/master/src/footprints), where:
- `nets` represents the available PCB nets the footprint should connect to, and
- `params` represents other, non-net parameters to customize the footprint.
These can be specified in the eponymous keys within `pcbs.pcb_name.footprints`.
<br>
## Phew, that's it.
*Theoretically*, you should know everything to start making your own dream keyboard.
*Realistically* though, this might have been a bit dense, to say the least. But hey, this is the full reference, what did you expect?
If you want to look at an example that leverages the full power of Ergogen, you can find [the Absolem project](https://github.com/mrzealot/absolem/) and its [configuration file here.](https://github.com/mrzealot/absolem/blob/master/absolem.yaml)
If you'd like to get your feet wet with easier examples, and get gradually more hard-core, let me suggest the other tutorials in the docs folder (as they become available).
Alternatively, if you'd like to talk to a certified Ergogen representative, come join us [on Discord](https://discord.gg/nbKcAZB)!
It's also a great place to get in touch if you are already somewhat familiar with this whole shebang, and would like to contribute examples, tests, features, whatever.
See you there!

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
{
"board": {
"active_layer": 37,
"active_layer_preset": "All Layers",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"ratsnest_display_mode": 0,
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": true,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
0,
1,
2,
3,
4,
5,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36
],
"visible_layers": "fffffff_ffffffff",
"zone_display_mode": 0
},
"meta": {
"filename": "23creus.kicad_prl",
"version": 3
},
"project": {
"files": []
}
}

View file

@ -0,0 +1,731 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"board_outline_line_width": 0.049999999999999996,
"copper_line_width": 0.19999999999999998,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.049999999999999996,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.09999999999999999,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.09999999999999999,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.762,
"height": 1.524,
"width": 1.524
},
"silk_line_width": 0.12,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.508
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"filename": "board_design_settings.json",
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.075,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998,
"min_microvia_drill": 0.09999999999999999,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.19999999999999998,
"min_via_annular_width": 0.09999999999999999,
"min_via_diameter": 0.39999999999999997,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 5,
"td_on_pad_in_zone": false,
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"via_dimensions": [],
"zones_allow_external_fillets": false,
"zones_use_no_outline": true
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_label_syntax": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "23creus.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.25,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
}
],
"meta": {
"version": 3
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": [
{
"netclass": "Default",
"pattern": ""
},
{
"netclass": "Default",
"pattern": "pinky_bottom"
},
{
"netclass": "Default",
"pattern": "P21"
},
{
"netclass": "Default",
"pattern": "pinky_home"
},
{
"netclass": "Default",
"pattern": "pinky_top"
},
{
"netclass": "Default",
"pattern": "pinky_num"
},
{
"netclass": "Default",
"pattern": "ring_bottom"
},
{
"netclass": "Default",
"pattern": "P20"
},
{
"netclass": "Default",
"pattern": "ring_home"
},
{
"netclass": "Default",
"pattern": "ring_top"
},
{
"netclass": "Default",
"pattern": "ring_num"
},
{
"netclass": "Default",
"pattern": "middle_bottom"
},
{
"netclass": "Default",
"pattern": "P19"
},
{
"netclass": "Default",
"pattern": "middle_home"
},
{
"netclass": "Default",
"pattern": "middle_top"
},
{
"netclass": "Default",
"pattern": "middle_num"
},
{
"netclass": "Default",
"pattern": "index_bottom"
},
{
"netclass": "Default",
"pattern": "P18"
},
{
"netclass": "Default",
"pattern": "index_home"
},
{
"netclass": "Default",
"pattern": "index_top"
},
{
"netclass": "Default",
"pattern": "index_num"
},
{
"netclass": "Default",
"pattern": "inner_bottom"
},
{
"netclass": "Default",
"pattern": "P15"
},
{
"netclass": "Default",
"pattern": "inner_home"
},
{
"netclass": "Default",
"pattern": "inner_top"
},
{
"netclass": "Default",
"pattern": "inner_num"
},
{
"netclass": "Default",
"pattern": "thumb_bottom"
},
{
"netclass": "Default",
"pattern": "P14"
},
{
"netclass": "Default",
"pattern": "mirror_pinky_bottom"
},
{
"netclass": "Default",
"pattern": "P2"
},
{
"netclass": "Default",
"pattern": "mirror_pinky_home"
},
{
"netclass": "Default",
"pattern": "mirror_pinky_top"
},
{
"netclass": "Default",
"pattern": "mirror_pinky_num"
},
{
"netclass": "Default",
"pattern": "mirror_ring_bottom"
},
{
"netclass": "Default",
"pattern": "P3"
},
{
"netclass": "Default",
"pattern": "mirror_ring_home"
},
{
"netclass": "Default",
"pattern": "mirror_ring_top"
},
{
"netclass": "Default",
"pattern": "mirror_ring_num"
},
{
"netclass": "Default",
"pattern": "mirror_middle_bottom"
},
{
"netclass": "Default",
"pattern": "P4"
},
{
"netclass": "Default",
"pattern": "mirror_middle_home"
},
{
"netclass": "Default",
"pattern": "mirror_middle_top"
},
{
"netclass": "Default",
"pattern": "mirror_middle_num"
},
{
"netclass": "Default",
"pattern": "mirror_index_bottom"
},
{
"netclass": "Default",
"pattern": "P5"
},
{
"netclass": "Default",
"pattern": "mirror_index_home"
},
{
"netclass": "Default",
"pattern": "mirror_index_top"
},
{
"netclass": "Default",
"pattern": "mirror_index_num"
},
{
"netclass": "Default",
"pattern": "mirror_inner_bottom"
},
{
"netclass": "Default",
"pattern": "P6"
},
{
"netclass": "Default",
"pattern": "mirror_inner_home"
},
{
"netclass": "Default",
"pattern": "mirror_inner_top"
},
{
"netclass": "Default",
"pattern": "mirror_inner_num"
},
{
"netclass": "Default",
"pattern": "mirror_thumb_bottom"
},
{
"netclass": "Default",
"pattern": "P7"
},
{
"netclass": "Default",
"pattern": "P16"
},
{
"netclass": "Default",
"pattern": "P10"
},
{
"netclass": "Default",
"pattern": "P9"
},
{
"netclass": "Default",
"pattern": "P8"
},
{
"netclass": "Default",
"pattern": "RST"
},
{
"netclass": "Default",
"pattern": "GND"
},
{
"netclass": "Default",
"pattern": "RAW"
},
{
"netclass": "Default",
"pattern": "VCC"
},
{
"netclass": "Default",
"pattern": "P1"
},
{
"netclass": "Default",
"pattern": "P0"
}
]
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"drawing": {
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "",
"plot_directory": "",
"spice_adjust_passive_values": false,
"spice_external_command": "spice \"%I\"",
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [],
"text_variables": {}
}

View file

@ -0,0 +1,5 @@
(kicad_sch (version 20211123) (generator eeschema)
(paper "A4")
(lib_symbols)
(symbol_instances)
)

7254
kicad/23creus/main.kicad_pcb Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
(kicad_pcb (version 4) (host kicad "dummy file") )

33
kicad/45treus/45treus.pro Normal file
View file

@ -0,0 +1,33 @@
update=22/05/2015 07:44:53
version=1
last_client=kicad
[general]
version=1
RootSch=
BoardNm=
[pcbnew]
version=1
LastNetListRead=
UseCmpFile=1
PadDrill=0.600000000000
PadDrillOvalY=0.600000000000
PadSizeH=1.500000000000
PadSizeV=1.500000000000
PcbTextSizeV=1.500000000000
PcbTextSizeH=1.500000000000
PcbTextThickness=0.300000000000
ModuleTextSizeV=1.000000000000
ModuleTextSizeH=1.000000000000
ModuleTextSizeThickness=0.150000000000
SolderMaskClearance=0.000000000000
SolderMaskMinWidth=0.000000000000
DrawSegmentWidth=0.200000000000
BoardOutlineThickness=0.100000000000
ModuleOutlineThickness=0.150000000000
[cvpcb]
version=1
NetIExt=net
[eeschema]
version=1
LibDir=
[eeschema/libraries]

View file

@ -0,0 +1,4 @@
EESchema Schematic File Version 2
EELAYER 25 0
EELAYER END
$EndSCHEMATC

View file

@ -0,0 +1 @@
0

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
update=22/05/2015 07:44:53
version=1
last_client=kicad
[general]
version=1
RootSch=
BoardNm=
[pcbnew]
version=1
LastNetListRead=
UseCmpFile=1
PadDrill=0.600000000000
PadDrillOvalY=0.600000000000
PadSizeH=1.500000000000
PadSizeV=1.500000000000
PcbTextSizeV=1.500000000000
PcbTextSizeH=1.500000000000
PcbTextThickness=0.300000000000
ModuleTextSizeV=1.000000000000
ModuleTextSizeH=1.000000000000
ModuleTextSizeThickness=0.150000000000
SolderMaskClearance=0.000000000000
SolderMaskMinWidth=0.000000000000
DrawSegmentWidth=0.200000000000
BoardOutlineThickness=0.100000000000
ModuleOutlineThickness=0.150000000000
[cvpcb]
version=1
NetIExt=net
[eeschema]
version=1
LibDir=
[eeschema/libraries]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
update=22/05/2015 07:44:53
version=1
last_client=kicad
[general]
version=1
RootSch=
BoardNm=
[pcbnew]
version=1
LastNetListRead=
UseCmpFile=1
PadDrill=0.600000000000
PadDrillOvalY=0.600000000000
PadSizeH=1.500000000000
PadSizeV=1.500000000000
PcbTextSizeV=1.500000000000
PcbTextSizeH=1.500000000000
PcbTextThickness=0.300000000000
ModuleTextSizeV=1.000000000000
ModuleTextSizeH=1.000000000000
ModuleTextSizeThickness=0.150000000000
SolderMaskClearance=0.000000000000
SolderMaskMinWidth=0.000000000000
DrawSegmentWidth=0.200000000000
BoardOutlineThickness=0.100000000000
ModuleOutlineThickness=0.150000000000
[cvpcb]
version=1
NetIExt=net
[eeschema]
version=1
LibDir=
[eeschema/libraries]

File diff suppressed because it is too large Load diff

4164
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "ergogen",
"version": "3.1.1",
"version": "4.0.5",
"description": "Ergonomic keyboard layout generator",
"author": "Bán Dénes <mr@zealot.hu>",
"license": "MIT",
@ -15,25 +15,25 @@
"coverage": "nyc --reporter=html --reporter=text npm test"
},
"dependencies": {
"@jscad/openjscad": "github:ergogen/oldjscad",
"fs-extra": "^10.0.0",
"js-yaml": "^3.14.0",
"fs-extra": "^11.1.0",
"js-yaml": "^3.14.1",
"jszip": "^3.10.1",
"kle-serial": "github:ergogen/kle-serial#ergogen",
"makerjs": "github:ergogen/maker.js#ergogen",
"mathjs": "^10.0.0",
"semver": "^7.3.5",
"yargs": "^17.3.0"
"mathjs": "^11.5.0",
"yargs": "^17.6.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-json": "^4.1.0",
"chai": "^4.3.4",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-json": "^6.0.0",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"dir-compare": "^3.3.0",
"glob": "^7.2.0",
"mocha": "^9.1.3",
"dir-compare": "^4.0.0",
"glob": "^8.1.0",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"rollup": "^2.61.1"
"rollup": "^3.10.1",
"sinon": "^15.0.1"
},
"nyc": {
"all": true,

View file

@ -1,30 +0,0 @@
# Ergogen
Ergogen is a keyboard generator that aims to provide a common configuration format to describe **ergonomic** 2D layouts, and generate automatic plates, cases, and (un-routed) PCBs for them.
The project grew out of (and is an integral part of) the [Absolem keyboard](https://zealot.hu/absolem), and shares its [Discord server](https://discord.gg/nbKcAZB) as well.
## Usage
Supposing you have a config ready, you can use ergogen either on the command line, or through the [web UI](https://ergogen.xyz).
Command line usage requires `node v14.4.0+` with `npm v6.14.5+` to be installed, the repo to be checked out, `npm install` to be issued, and then simply calling the CLI interface through `node src/cli.js`.
The `--help` switch lists the available command line options.
The [web UI](https://ergogen.xyz) is a more accessible version of the same codebase, where everything happens in your browser.
It's been patched together on a fresh Chrome-derivative, and I didn't take any care to make it compatible with older stuff, so please use something modern!
As for how to prepare a valid config, please read the [reference](https://docs.ergogen.xyz).
## Contributions
Feature ideas, documentation improvements, examples, tests, or pull requests welcome!
Get in touch [on Discord](https://discord.gg/nbKcAZB), and we can definitely find something you can help with, if you'd like to.

View file

@ -6,54 +6,62 @@
### Major
- Move column-level attributes like spread to key-level to unify the structure
- Generalize what shapes to be repeated when outlining `keys`
- Place rectangles by their centers
- Full per-point anchors
- Collapse any raw shift or rotation under the anchor infrastructure
- Merge, generalize, and uniform-ize footprints
- Merge, generalize, uniform-ize and externalize footprints!
- onnx-like incremental opset versioning
- Template for creating them, built-in variables they can use, documentation, external links, etc.
- Also considering how (or, on which layer) they define their silks, universal mirroring behaviour, etc.
- Add access to whole set of points + filtering logic, so they can implement their own connection logic as well maybe (see daisy chaining)
- Also considering how (or, on which layer) they define their silks, universal mirroring behaviour (see ixy/xy/sxy note), etc.
### Minor
- Allow shift/rotate for outlines (via `anchor_def`, probably)
- More generic anchors or distances?
- Intersect support for anchor affects clauses, which (combined with the math formulas and possible trigonometric functions) should allow for every use case we've discussed so far
- Allow both object (as well as arrays) in multiple anchor refs
- Support "direct" anchors, as in, recognize num arrays and parse them as x/y/r
- Add `origin` to zone-wide and global rotation in points
- Handle unnecessary (but seemingly consistent, so easy to confuse) `key` subfield of row-level overrides
- Allow footprints to access raw array/object fields from points with templating
- Include raw kicad footprint integrations
- pull torik's script to be able to convert raw kicad footprints into positionable ergogen ones
- have a `dummy` footprint which can just be updated from schematic
- Allow footprints to publish outlines
- Make these usable in the `outlines` section through a new `what`
- Add footprint sanitization to check compatibility for externally loaded ones
- Or to double check internal ones for compliance
- 3D orient for cases
- Even more extreme anchor stuff
- Checkpoints, intersects, distances, weighted combinations?
- Extend the "turning towards" capabilities of `orient` and `rotate` to `shift` as well (to move as much as it would take the current anchor location to get there)
- SVG input (for individual outlines, or even combinations parsed by line color, etc.)
- And once that's done, possibly even STL or other input for cases or pcb renders
- Support text silk output to PCBs (in configurable fonts, through SVG?)
- Maybe a partial markdown preprocess to support bold and italic?
- Look into gr_curve to possibly add beziers to the kicad conversion
- Support curves (arcs as well as Béziers) in polygons
- Support specifying keys/labels for the pcb section (not just blindly assuming all)
- Also, three point arcs, tangents, earier "circle tools" in general
- Add snappable line footprint
- Layer-aware export from Maker.JS, so we can debug in the webui more easily
- Add filleting syntax with `@`?
- Figure out a manual, but still reasonably comfortable routing method directly from the config
- Eeschema support for pcbs
- Outline expand and shrink access from makerjs
- Resurrect and/or add wider tagging support
- Also add subtractive tagging filters (exclude)
- Also expand this to footprints (so, which footprints get applied to which pcb)
- Or, at least, allow skipping per-key footprints
- Generate ZMK shield from config
- Export **to** KLE?
- Per-footprint mirror support
- A flag for footprints to be able to "resist" the mirroring-related special treatment of negative X shift, rotation, etc.
- Include 3D models for kicad output for visualization
- Include 3D models paths in kicad output for visualization
- Also, provide 3D models for built-in footprints
- Look into kicad 5 vs. 6 output format
- Update json schema and add syntax highlight to editors
- Support different netclasses
- Allow a potential filter for filleting (only on angles =90°, <45°, left turn vs. right turn when going clockwise, etc.)
- Add `operation: skip` to allow easily "commenting out" whole outline parts
- Better error message for negative rectangle (it may not only be because of corner/bevel)
### Patch
- YAML lib v4 update - breaking changes in how undefined is handled!
- Prevent double mirroring (see discord "mirror_mirror_")
- Check unexpected keys at top level, too
- Better error handling for the fillet option?
- Implement `glue.extra`
- Integration and end2end tests to get coverage to 100%
- Fix the intersection of parallel lines when gluing
- Add custom fillet implementation that considers line-line connections only
- Add custom fillet implementation that considers line-line connections only?
- Add nicer filleting error messages when makerjs dies for some reason
- Empty nets should be allowed (to mean unconnected)
- Debug point (orient+shift) differences in circles vs. polygons (see Discord)
## WEBUI
@ -63,6 +71,9 @@
- Change over to Cache's live preview implementation
- Add missing KLE functionality
- Create browserified version of semver lib
- Or at least a shim with a console warning
- Visualizing multiple outlines at once, with different colors
- Add snapping/measurement capabilities for quicker iteration
### Minor
@ -70,12 +81,13 @@
- Attempt to auto-compile (if inactive for n secs, or whatever)
- Support saving to gists
- Add kicad_pcb visualization as well
- Get dropdown examples from a separate repo
- Expand the config dropdown with opensource stuff: corne, lily, ergodox, atreus...
### Patch
- Streamlining (and documenting) an update pipeline
- Puppeteer tests
- Streamline (and document) an update pipeline
- Add puppeteer tests
@ -85,18 +97,18 @@
- With a progression of increasingly complex steps
- And lots of illustrations!
- Complete reference
- some known deficiencies:
- Some known deficiencies:
- Units separated to their own block at the front
- Key-level `width` and `height` are supported during visualization
- This key-level example should probably be added from discord: https://discord.com/channels/714176584269168732/759825860617437204/773104093546676244
- Change outline fields to have their full anchor support documented
- Mention the ability to opt out of gluing!
- Key-level defaults are based around u's, not 19!
- change over to built, per-chapter docs, like how Cache has them
- Contribution guidelines
- including test commands (npm test, npm run coverage, --what switch, --dump switch)
- Include test commands (npm test, npm run coverage, --what switch, --dump switch)
- Changelog, Roadmap
- A public catalog of real-life ergogen configs
- Probably could be the same as the separate examples repo for the dropdown

View file

@ -1,22 +1,21 @@
import pkg from './package.json'
import pkg from './package.json' assert { type: 'json' }
import json from '@rollup/plugin-json'
import commonjs from '@rollup/plugin-commonjs'
export default {
input: 'src/ergogen.js',
external: ['makerjs', 'js-yaml', 'mathjs', 'kle-serial', '@jscad/openjscad', 'semver'],
external: ['makerjs', 'js-yaml', 'mathjs', 'kle-serial', 'jszip'],
output: {
name: 'ergogen',
file: 'dist/ergogen.js',
format: 'umd',
banner: `/*!\n * Ergogen v${pkg.version}\n * https://zealot.hu/ergogen\n */\n`,
banner: `/*!\n * Ergogen v${pkg.version}\n * https://ergogen.xyz\n */\n`,
globals: {
'makerjs': 'makerjs',
'js-yaml': 'jsyaml',
'mathjs': 'math',
'kle-serial': 'kle',
'@jscad/openjscad': 'myjscad',
'semver': 'semver'
'jszip': 'jszip'
}
},
plugins: [

8
shell.nix Normal file
View file

@ -0,0 +1,8 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
bashInteractive
nodejs-16_x
];
}

BIN
showcase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -1,73 +1,159 @@
const u = require('./utils')
const a = require('./assert')
const Point = require('./point')
const m = require('makerjs')
const mirror_ref = exports.mirror = (ref, mirror) => {
const mirror_ref = exports.mirror = (ref, mirror=true) => {
if (mirror) {
if (ref.startsWith('mirror_')) {
return ref.substring(7)
} else {
return 'mirror_' + ref
}
return 'mirror_' + ref
}
return ref
}
const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, default_point=new Point(), mirror=false) => units => {
if (a.type(raw)() == 'array') {
// recursive call with incremental default_point mods, according to `affect`s
let current = default_point.clone()
const aggregator_common = ['parts', 'method']
const aggregators = {
average: (config, name, parts) => {
a.unexpected(config, name, aggregator_common)
const len = parts.length
if (len == 0) {
return new Point()
}
let x = 0, y = 0, r = 0
for (const part of parts) {
x += part.x
y += part.y
r += part.r
}
return new Point(x / len, y / len, r / len)
},
intersect: (config, name, parts) => {
// a line is generated from a point by taking their
// (rotated) Y axis. The line is not extended to
// +/- Infinity as that doesn't work with makerjs.
// An arbitrary offset of 1 meter is considered
// sufficient for practical purposes, and the point
// coordinates are used as pivot point for the rotation.
const get_line_from_point = (point, offset=1000) => {
const origin = [point.x, point.y]
const p1 = [point.x, point.y - offset]
const p2 = [point.x, point.y + offset]
let line = new m.paths.Line(p1, p2)
line = m.path.rotate(line, point.r, origin)
return line
}
a.unexpected(config, name, aggregator_common)
a.assert(parts.length==2, `Intersect expects exactly two parts, but it got ${parts.length}!`)
const line1 = get_line_from_point(parts[0])
const line2 = get_line_from_point(parts[1])
const intersection = m.path.intersection(line1, line2)
a.assert(intersection, `The points under "${name}.parts" do not intersect!`)
const intersection_point_arr = intersection.intersectionPoints[0]
const intersection_point = new Point(
intersection_point_arr[0], intersection_point_arr[1], 0
)
return intersection_point
},
}
const anchor = exports.parse = (raw, name, points={}, start=new Point(), mirror=false) => units => {
//
// Anchor type handling
//
if (a.type(raw)() == 'string') {
raw = {ref: raw}
}
else if (a.type(raw)() == 'array') {
// recursive call with incremental start mods, according to `affect`s
let current = start.clone()
let index = 1
for (const step of raw) {
current = anchor(step, name, points, check_unexpected, current, mirror)(units)
current = anchor(step, `${name}[${index++}]`, points, current, mirror)(units)
}
return current
}
if (check_unexpected) a.unexpected(raw, name, ['ref', 'orient', 'shift', 'rotate', 'affect'])
let point = default_point.clone()
a.unexpected(raw, name, ['ref', 'aggregate', 'orient', 'shift', 'rotate', 'affect', 'resist'])
//
// Reference or aggregate handling
//
let point = start.clone()
if (raw.ref !== undefined && raw.aggregate !== undefined) {
throw new Error(`Fields "ref" and "aggregate" cannot appear together in anchor "${name}"!`)
}
if (raw.ref !== undefined) {
if (a.type(raw.ref)() == 'array') {
// averaging multiple anchors
let x = 0, y = 0, r = 0
const len = raw.ref.length
for (const ref of raw.ref) {
const parsed_ref = mirror_ref(ref, mirror)
a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`)
const resolved = points[parsed_ref]
x += resolved.x
y += resolved.y
r += resolved.r
}
point = new Point(x / len, y / len, r / len)
} else {
// base case, resolve directly
if (a.type(raw.ref)() == 'string') {
const parsed_ref = mirror_ref(raw.ref, mirror)
a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`)
point = points[parsed_ref].clone()
// recursive case
} else {
point = anchor(raw.ref, `${name}.ref`, points, start, mirror)(units)
}
}
if (raw.aggregate !== undefined) {
raw.aggregate = a.sane(raw.aggregate, `${name}.aggregate`, 'object')()
raw.aggregate.method = a.sane(raw.aggregate.method || 'average', `${name}.aggregate.method`, 'string')()
a.assert(aggregators[raw.aggregate.method], `Unknown aggregator method "${raw.aggregate.method}" in anchor "${name}"!`)
raw.aggregate.parts = a.sane(raw.aggregate.parts || [], `${name}.aggregate.parts`, 'array')()
const parts = []
let index = 1
for (const part of raw.aggregate.parts) {
parts.push(anchor(part, `${name}.aggregate.parts[${index++}]`, points, start, mirror)(units))
}
point = aggregators[raw.aggregate.method](raw.aggregate, `${name}.aggregate`, parts)
}
//
// Actual orient/shift/rotate/affect handling
//
const resist = a.sane(raw.resist || false, `${name}.resist`, 'boolean')()
const rotator = (config, name, point) => {
// simple case: number gets added to point rotation
if (a.type(config)(units) == 'number') {
let angle = a.sane(config, name, 'number')(units)
point.rotate(angle, false, resist)
// recursive case: points turns "towards" target anchor
} else {
const target = anchor(config, name, points, start, mirror)(units)
point.r = point.angle(target)
}
}
if (raw.orient !== undefined) {
let angle = a.sane(raw.orient, `${name}.orient`, 'number')(units)
if (point.meta.mirrored) {
angle = -angle
}
point.r += angle
rotator(raw.orient, `${name}.orient`, point)
}
if (raw.shift !== undefined) {
let xyval = a.wh(raw.shift, `${name}.shift`)(units)
if (point.meta.mirrored) {
xyval[0] = -xyval[0]
}
point.shift(xyval, true)
const xyval = a.wh(raw.shift, `${name}.shift`)(units)
point.shift(xyval, true, resist)
}
if (raw.rotate !== undefined) {
let angle = a.sane(raw.rotate, `${name}.rotate`, 'number')(units)
if (point.meta.mirrored) {
angle = -angle
}
point.r += angle
rotator(raw.rotate, `${name}.rotate`, point)
}
if (raw.affect !== undefined) {
const candidate = point.clone()
point = default_point.clone()
point = start.clone()
point.meta = candidate.meta
let affect = raw.affect
if (a.type(affect)() == 'string') affect = affect.split('')
@ -78,5 +164,6 @@ const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, def
point[aff] = candidate[aff]
}
}
return point
}

View file

@ -44,7 +44,7 @@ const _in = exports.in = (raw, name, arr) => {
const arr = exports.arr = (raw, name, length, _type, _default) => units => {
assert(type(raw)(units) == 'array', `Field "${name}" should be an array!`)
assert(length == 0 || raw.length == length, `Field "${name}" should be an array of length ${length}!`)
raw = raw.map(val => val || _default)
raw = raw.map(val => val === undefined ? _default : val)
raw.map(val => assert(type(val)(units) == _type, `Field "${name}" should contain ${_type}s!`))
if (_type == 'number') {
raw = raw.map(val => mathnum(val)(units))
@ -62,8 +62,19 @@ const wh = exports.wh = (raw, name) => units => {
return xy(raw, name)(units)
}
exports.trbl = (raw, name) => units => {
exports.trbl = (raw, name, _default=0) => units => {
if (!Array.isArray(raw)) raw = [raw, raw, raw, raw]
if (raw.length == 2) raw = [raw[1], raw[0], raw[1], raw[0]]
return numarr(raw, name, 4, 'number', 0)(units)
return arr(raw, name, 4, 'number', _default)(units)
}
exports.asym = (raw, name) => {
// allow different aliases
const source_aliases = ['source', 'origin', 'base', 'primary', 'left']
const clone_aliases = ['clone', 'image', 'derived', 'secondary', 'right']
_in(raw, name, ['both'].concat(source_aliases, clone_aliases))
// return aliases to canonical names
if (source_aliases.includes(raw)) return 'source'
if (clone_aliases.includes(raw)) return 'clone'
return raw
}

View file

@ -57,29 +57,33 @@ exports.parse = (config, outlines, units) => {
}
const part_qname = `cases.${case_name}.${part_name}`
const part_var = `${case_name}__part_${part_name}`
a.unexpected(part, part_qname, ['type', 'name', 'extrude', 'shift', 'rotate', 'operation'])
const type = a.in(part.type || 'outline', `${part_qname}.type`, ['outline', 'case'])
a.unexpected(part, part_qname, ['what', 'name', 'extrude', 'shift', 'rotate', 'operation'])
const what = a.in(part.what || 'outline', `${part_qname}.what`, ['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)(units)
const rotate = a.numarr(part.rotate || [0, 0, 0], `${part_qname}.rotate`, 3)(units)
const operation = a.in(part.operation || 'add', `${part_qname}.operation`, ['add', 'subtract', 'intersect'])
let base
if (type == 'outline') {
if (what == 'outline') {
const extrude = a.sane(part.extrude || 1, `${part_qname}.extrude`, 'number')(units)
const outline = outlines[name]
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`,
// This is a hack to separate multiple calls to the same outline with different extrude values
// I know it needlessly duplicates a lot of code, but it's the quickest fix in the short term
// And on the long run, we'll probably be moving to CADQuery anyway...
const extruded_name = `${name}_extrude_` + ('' + extrude).replace(/\D/g, '_')
if (!scripts[extruded_name]) {
scripts[extruded_name] = m.exporter.toJscadScript(outline, {
functionName: `${extruded_name}_outline_fn`,
extrude: extrude,
indent: 4
})
}
outline_dependencies.push(name)
base = `${name}_outline_fn()`
outline_dependencies.push(extruded_name)
base = `${extruded_name}_outline_fn()`
} else {
a.assert(part.extrude === undefined, `Field "${part_qname}.extrude" should not be used when type=case!`)
a.assert(part.extrude === undefined, `Field "${part_qname}.extrude" should not be used when what=case!`)
a.in(name, `${part_qname}.name`, Object.keys(cases))
case_dependencies.push(name)
base = `${name}_case_fn()`

View file

@ -1,11 +1,17 @@
#!/usr/bin/env node
const fs = require('fs-extra')
const fsp = require('fs/promises')
const path = require('path')
const yaml = require('js-yaml')
const yargs = require('yargs')
const ergogen = require('./ergogen')
const jszip = require('jszip')
const io = require('./io')
const pkg = require('../package.json')
const ergogen = require('./ergogen')
;(async () => {
// command line args
@ -29,7 +35,52 @@ const args = yargs
})
.argv
// config reading
// greetings
const title_suffix = args.debug ? ' (Debug Mode)' : ''
console.log(`Ergogen v${pkg.version} CLI${title_suffix}`)
console.log()
// input helpers
// zip handling is baked in at the io level, so that both the cli and the webui can use it
// if, for local development, we want to use a folder as input, we temporarily zip it in
// memory so that it can be handled the exact same way
// functions shamelessly repurposed from https://github.com/Stuk/jszip/issues/386
// return a flat array of absolute paths of all files recursively contained in the dir
const list_files_in_dir = async (dir) => {
const list = await fsp.readdir(dir)
const statPromises = list.map(async (file) => {
const fullPath = path.resolve(dir, file)
const stat = await fsp.stat(fullPath)
if (stat && stat.isDirectory()) {
return list_files_in_dir(fullPath)
}
return fullPath
})
return (await Promise.all(statPromises)).flat(Infinity)
}
// create an in-memory zip stream from a folder in the file system
const zip_from_dir = async (dir) => {
const absRoot = path.resolve(dir)
const filePaths = await list_files_in_dir(dir)
return filePaths.reduce((z, filePath) => {
const relative = filePath.replace(absRoot, '')
// create folder trees manually :(
const zipFolder = path
.dirname(relative)
.split(path.sep)
.reduce((zf, dirName) => zf.folder(dirName), z)
zipFolder.file(path.basename(filePath), fs.createReadStream(filePath))
return z
}, new jszip())
}
// input reading
const config_file = args._[0]
if (!config_file) {
@ -37,19 +88,37 @@ if (!config_file) {
process.exit(1)
}
let config_text
try {
config_text = fs.readFileSync(config_file).toString()
} catch (err) {
console.error(`Could not read config file "${config_file}": ${err}`)
if (!fs.existsSync(config_file)) {
console.error(`Could not read config file "${config_file}": File does not exist!`)
process.exit(2)
}
const title_suffix = args.debug ? ' (Debug Mode)' : ''
console.log(`Ergogen v${pkg.version} CLI${title_suffix}`)
console.log()
let config_text = ''
let injections = []
;(async () => {
try {
if (config_file.endsWith('.zip') || config_file.endsWith('.ekb')) {
console.log('Analyzing bundle...');
[config_text, injections] = await io.unpack(
await (new jszip()).loadAsync(fs.readFileSync(config_file))
)
} else if (fs.statSync(config_file).isDirectory()) {
console.log('Analyzing folder...');
[config_text, injections] = await io.unpack(
await zip_from_dir(config_file)
)
} else {
config_text = fs.readFileSync(config_file).toString()
// no injections...
}
for (const [type, name, value] of injections) {
ergogen.inject(type, name, value)
}
} catch (err) {
console.error(`Could not read config file "${config_file}"!`)
console.error(err)
process.exit(2)
}
// processing
@ -61,14 +130,16 @@ try {
process.exit(3)
}
// helpers
// output helpers
const yamldump = data => yaml.dump(data, {indent: 4, noRefs: true})
const single = (data, rel) => {
if (!data) return
const abs = path.join(args.o, rel)
fs.mkdirpSync(path.dirname(abs))
if (abs.endsWith('.yaml')) {
fs.writeFileSync(abs, yaml.dump(data, {indent: 4}))
fs.writeFileSync(abs, yamldump(data))
} else {
fs.writeFileSync(abs, data)
}
@ -79,9 +150,9 @@ const composite = (data, rel) => {
const abs = path.join(args.o, rel)
if (data.yaml) {
fs.mkdirpSync(path.dirname(abs))
fs.writeFileSync(abs + '.yaml', yaml.dump(data.yaml, {indent: 4}))
fs.writeFileSync(abs + '.yaml', yamldump(data.yaml))
}
for (const format of ['svg', 'dxf', 'jscad', 'stl']) {
for (const format of ['svg', 'dxf', 'jscad']) {
if (data[format]) {
fs.mkdirpSync(path.dirname(abs))
fs.writeFileSync(abs + '.' + format, data[format])
@ -89,7 +160,7 @@ const composite = (data, rel) => {
}
}
// output
// output generation
if (args.clean) {
console.log('Cleaning output folder...')

View file

@ -7,7 +7,6 @@ const outlines_lib = require('./outlines')
const cases_lib = require('./cases')
const pcbs_lib = require('./pcbs')
const semver = require('semver')
const version = require('../package.json').version
const process = async (raw, debug=false, logger=()=>{}) => {
@ -16,6 +15,8 @@ const process = async (raw, debug=false, logger=()=>{}) => {
let empty = true
let [config, format] = io.interpret(raw, logger)
let suffix = format
// KLE conversion warrants automaticly engaging debug mode
// as, usually, we're only interested in the points anyway
if (format == 'KLE') {
suffix = `${format} (Auto-debug)`
debug = true
@ -34,12 +35,9 @@ const process = async (raw, debug=false, logger=()=>{}) => {
if (config.meta && config.meta.engine) {
logger('Checking compatibility...')
const engine = semver.validRange(config.meta.engine)
if (!engine) {
throw new Error('Invalid config engine declaration!')
}
if (!semver.satisfies(version, engine)) {
throw new Error(`Current ergogen version (${version}) doesn\'t satisfy config's engine requirement (${engine})!`)
const engine = u.semver(config.meta.engine, 'config.meta.engine')
if (!u.satisfies(version, engine)) {
throw new Error(`Current ergogen version (${version}) doesn\'t satisfy config's engine requirement (${config.meta.engine})!`)
}
}
@ -71,12 +69,12 @@ const process = async (raw, debug=false, logger=()=>{}) => {
empty = false
}
logger('Extruding cases...')
logger('Modeling cases...')
const cases = cases_lib.parse(config.cases || {}, outlines, units)
results.cases = {}
for (const [case_name, case_script] of Object.entries(cases)) {
if (!debug && case_name.startsWith('_')) continue
results.cases[case_name] = await io.threedee(case_script, debug)
results.cases[case_name] = {jscad: case_script}
empty = false
}
@ -96,8 +94,22 @@ const process = async (raw, debug=false, logger=()=>{}) => {
return results
}
const inject = (type, name, value) => {
if (value === undefined) {
value = name
name = type
type = 'footprint'
}
switch (type) {
case 'footprint':
return pcbs_lib.inject_footprint(name, value)
default:
throw new Error(`Unknown injection type "${type}" with name "${name}" and value "${value}"!`)
}
}
module.exports = {
version,
process,
inject_footprint: pcbs_lib.inject_footprint
inject
}

156
src/filter.js Normal file
View file

@ -0,0 +1,156 @@
const u = require('./utils')
const a = require('./assert')
const anchor_lib = require('./anchor')
const Point = require('./point')
const anchor = anchor_lib.parse
const _true = () => true
const _false = () => false
const _and = arr => p => arr.map(e => e(p)).reduce((a, b) => a && b)
const _or = arr => p => arr.map(e => e(p)).reduce((a, b) => a || b)
const similar = (keys, reference, name, units) => {
let neg = false
if (reference.startsWith('-')) {
neg = true
reference = reference.slice(1)
}
// support both string or regex as reference
let internal_tester = val => (''+val) == reference
if (reference.startsWith('/')) {
try {
const regex_parts = reference.split('/')
regex_parts.shift() // remove starting slash
const flags = regex_parts.pop()
const regex = new RegExp(regex_parts.join('/'), flags)
internal_tester = val => regex.test(''+val)
} catch (ex) {
throw new Error(`Invalid regex "${reference}" found at filter "${name}"!`)
}
}
// support strings, arrays, or objects as key
const external_tester = (point, key) => {
const value = u.deep(point, key)
if (a.type(value)() == 'array') {
return value.some(subkey => internal_tester(subkey))
} else if (a.type(value)() == 'object') {
return Object.keys(value).some(subkey => internal_tester(subkey))
} else {
return internal_tester(value)
}
}
// consider negation
if (neg) {
return point => keys.every(key => !external_tester(point, key))
} else {
return point => keys.some(key => external_tester(point, key))
}
}
const comparators = {
'~': similar
// TODO: extension point for other operators...
}
const symbols = Object.keys(comparators)
const simple = (exp, name, units) => {
let keys = ['meta.name', 'meta.tags']
let op = '~'
let value
const parts = exp.split(/\s+/g)
// full case
if (symbols.includes(parts[1])) {
keys = parts[0].split(',')
op = parts[1]
value = parts.slice(2).join(' ')
// middle case, just an operator spec, default "keys"
} else if (symbols.includes(parts[0])) {
op = parts[0]
value = parts.slice(1).join(' ')
// basic case, only "value"
} else {
value = exp
}
return point => comparators[op](keys, value, name, units)(point)
}
const complex = (config, name, units, aggregator=_or) => {
// we branch by type
const type = a.type(config)(units)
switch(type) {
// boolean --> either all or nothing
case 'boolean':
return config ? _true : _false
// string --> base case, meaning a simple/single filter
case 'string':
return simple(config, name, units)
// array --> aggregated simple filters with alternating and/or conditions
case 'array':
const alternate = aggregator == _and ? _or : _and
return aggregator(config.map(elem => complex(elem, name, units, alternate)))
default:
throw new Error(`Unexpected type "${type}" found at filter "${name}"!`)
}
}
const contains_object = (val) => {
if (a.type(val)() == 'object') return true
if (a.type(val)() == 'array') return val.some(el => contains_object(el))
return false
}
exports.parse = (config, name, points={}, units={}, asym='source') => {
let result = []
// if a filter decl is undefined, it's just the default point at [0, 0]
if (config === undefined) {
result.push(new Point())
// if a filter decl is an object, or an array that contains an object at any depth, it is an anchor
} else if (contains_object(config)) {
if (['source', 'both'].includes(asym)) {
result.push(anchor(config, name, points)(units))
}
if (['clone', 'both'].includes(asym)) {
// this is strict: if the ref of the anchor doesn't have a mirror pair, it will error out
// also, we check for duplicates as ref-less anchors mirror to themselves
const clone = anchor(config, name, points, undefined, true)(units)
if (result.every(p => !p.equals(clone))) {
result.push(clone)
}
}
// otherwise, it is treated as a condition to filter all available points
} else {
const source = Object.values(points).filter(complex(config, name, units))
if (['source', 'both'].includes(asym)) {
result = result.concat(source)
}
if (['clone', 'both'].includes(asym)) {
// this is permissive: we only include mirrored versions if they exist, and don't fuss if they don't
// also, we check for duplicates as clones can potentially refer back to their sources, too
const pool = result.map(p => p.meta.name)
result = result.concat(
source.map(p => points[anchor_lib.mirror(p.meta.name)])
.filter(p => !!p)
.filter(p => !pool.includes(p.meta.name))
)
}
}
return result
}

View file

@ -1,11 +1,9 @@
module.exports = {
nets: {
params: {
designator: 'S',
from: undefined,
to: undefined
},
params: {
class: 'S'
},
body: p => `
(module ALPS (layer F.Cu) (tedit 5CF31DEF)
@ -27,8 +25,8 @@ module.exports = {
(fp_line (start 7 -7) (end 7 -6) (layer Dwgs.User) (width 0.15))
${''/* pins */}
(pad 1 thru_hole circle (at 2.5 -4.5) (size 2.25 2.25) (drill 1.47) (layers *.Cu *.Mask) ${p.net.from.str})
(pad 2 thru_hole circle (at -2.5 -4) (size 2.25 2.25) (drill 1.47) (layers *.Cu *.Mask) ${p.net.to.str})
(pad 1 thru_hole circle (at 2.5 -4.5) (size 2.25 2.25) (drill 1.47) (layers *.Cu *.Mask) ${p.from})
(pad 2 thru_hole circle (at -2.5 -4) (size 2.25 2.25) (drill 1.47) (layers *.Cu *.Mask) ${p.to})
)
`

View file

@ -1,12 +1,10 @@
module.exports = {
nets: {
params: {
designator: 'B', // for Button
side: 'F',
from: undefined,
to: undefined
},
params: {
class: 'B', // for Button
side: 'F'
},
body: p => `
(module E73:SW_TACT_ALPS_SKQGABE010 (layer F.Cu) (tstamp 5BF2CC94)
@ -20,20 +18,20 @@ module.exports = {
(fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15))))
${'' /* outline */}
(fp_line (start 2.75 1.25) (end 1.25 2.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.75 -1.25) (end 1.25 -2.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.75 -1.25) (end 2.75 1.25) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -1.25 2.75) (end 1.25 2.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -1.25 -2.75) (end 1.25 -2.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.75 1.25) (end -1.25 2.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.75 -1.25) (end -1.25 -2.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.75 -1.25) (end -2.75 1.25) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.75 1.25) (end 1.25 2.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.75 -1.25) (end 1.25 -2.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.75 -1.25) (end 2.75 1.25) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -1.25 2.75) (end 1.25 2.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -1.25 -2.75) (end 1.25 -2.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.75 1.25) (end -1.25 2.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.75 -1.25) (end -1.25 -2.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.75 -1.25) (end -2.75 1.25) (layer ${p.side}.SilkS) (width 0.15))
${'' /* pins */}
(pad 1 smd rect (at -3.1 -1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.from.str})
(pad 1 smd rect (at 3.1 -1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.from.str})
(pad 2 smd rect (at -3.1 1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.to.str})
(pad 2 smd rect (at 3.1 1.85 ${p.rot}) (size 1.8 1.1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.to.str})
(pad 1 smd rect (at -3.1 -1.85 ${p.r}) (size 1.8 1.1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.from})
(pad 1 smd rect (at 3.1 -1.85 ${p.r}) (size 1.8 1.1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.from})
(pad 2 smd rect (at -3.1 1.85 ${p.r}) (size 1.8 1.1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.to})
(pad 2 smd rect (at 3.1 1.85 ${p.r}) (size 1.8 1.1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.to})
)
`

View file

@ -13,15 +13,13 @@
// note: hotswap and reverse can be used simultaneously
module.exports = {
nets: {
from: undefined,
to: undefined
},
params: {
class: 'S',
designator: 'S',
hotswap: false,
reverse: false,
keycaps: false
keycaps: false,
from: undefined,
to: undefined
},
body: p => {
const standard = `
@ -57,35 +55,35 @@ module.exports = {
(fp_line (start -9 8.5) (end -9 -8.5) (layer Dwgs.User) (width 0.15))
`
function pins(def_neg, def_pos, def_side) {
if(p.param.hotswap) {
if(p.hotswap) {
return `
${'' /* holes */}
(pad "" np_thru_hole circle (at ${def_pos}5 -3.75) (size 3 3) (drill 3) (layers *.Cu *.Mask))
(pad "" np_thru_hole circle (at 0 -5.95) (size 3 3) (drill 3) (layers *.Cu *.Mask))
${'' /* net pads */}
(pad 1 smd rect (at ${def_neg}3.275 -5.95 ${p.rot}) (size 2.6 2.6) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.from.str})
(pad 2 smd rect (at ${def_pos}8.275 -3.75 ${p.rot}) (size 2.6 2.6) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.to.str})
(pad 1 smd rect (at ${def_neg}3.275 -5.95 ${p.r}) (size 2.6 2.6) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.from})
(pad 2 smd rect (at ${def_pos}8.275 -3.75 ${p.r}) (size 2.6 2.6) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.to})
`
} else {
return `
${''/* pins */}
(pad 1 thru_hole circle (at ${def_pos}5 -3.8) (size 2.032 2.032) (drill 1.27) (layers *.Cu *.Mask) ${p.net.from.str})
(pad 2 thru_hole circle (at ${def_pos}0 -5.9) (size 2.032 2.032) (drill 1.27) (layers *.Cu *.Mask) ${p.net.to.str})
(pad 1 thru_hole circle (at ${def_pos}5 -3.8) (size 2.032 2.032) (drill 1.27) (layers *.Cu *.Mask) ${p.from})
(pad 2 thru_hole circle (at ${def_pos}0 -5.9) (size 2.032 2.032) (drill 1.27) (layers *.Cu *.Mask) ${p.to})
`
}
}
if(p.param.reverse) {
if(p.reverse) {
return `
${standard}
${p.param.keycaps ? keycap : ''}
${p.keycaps ? keycap : ''}
${pins('-', '', 'B')}
${pins('', '-', 'F')})
`
} else {
return `
${standard}
${p.param.keycaps ? keycap : ''}
${p.keycaps ? keycap : ''}
${pins('-', '', 'B')})
`
}

View file

@ -9,15 +9,13 @@
// if true, will add choc sized keycap box around the footprint
module.exports = {
nets: {
from: undefined,
to: undefined
},
params: {
class: 'S',
designator: 'S',
side: 'F',
reverse: false,
keycaps: false
keycaps: false,
from: undefined,
to: undefined
},
body: p => {
const standard = `
@ -71,14 +69,14 @@ module.exports = {
function pins(def_neg, def_pos) {
return `
${''/* pins */}
(pad 1 thru_hole circle (at ${def_neg}4.58 5.1) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) ${p.net.from.str} (clearance 0.2))
(pad 2 thru_hole circle (at ${def_pos}2 5.4) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) ${p.net.to.str} (clearance 0.2))
(pad 1 thru_hole circle (at ${def_neg}4.58 5.1) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) ${p.from} (clearance 0.2))
(pad 2 thru_hole circle (at ${def_pos}2 5.4) (size 1.6 1.6) (drill 1.1) (layers *.Cu *.Mask) ${p.to} (clearance 0.2))
`
}
if(p.param.reverse){
if(p.reverse){
return `
${standard}
${p.param.keycaps ? keycap : ''}
${p.keycaps ? keycap : ''}
${pins('-', '')}
${pins('', '-')})
@ -86,7 +84,7 @@ module.exports = {
} else {
return `
${standard}
${p.param.keycaps ? keycap : ''}
${p.keycaps ? keycap : ''}
${pins('-', '')})
`
}

View file

@ -1,11 +1,9 @@
module.exports = {
nets: {
params: {
designator: 'D',
from: undefined,
to: undefined
},
params: {
class: 'D'
},
body: p => `
(module ComboDiode (layer F.Cu) (tedit 5B24D78E)
@ -34,14 +32,14 @@ module.exports = {
(fp_line (start -0.75 0) (end -0.35 0) (layer B.SilkS) (width 0.1))
${''/* SMD pads on both sides */}
(pad 1 smd rect (at -1.65 0 ${p.rot}) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ${p.net.to.str})
(pad 2 smd rect (at 1.65 0 ${p.rot}) (size 0.9 1.2) (layers B.Cu B.Paste B.Mask) ${p.net.from.str})
(pad 1 smd rect (at -1.65 0 ${p.rot}) (size 0.9 1.2) (layers B.Cu B.Paste B.Mask) ${p.net.to.str})
(pad 2 smd rect (at 1.65 0 ${p.rot}) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ${p.net.from.str})
(pad 1 smd rect (at -1.65 0 ${p.r}) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ${p.to})
(pad 2 smd rect (at 1.65 0 ${p.r}) (size 0.9 1.2) (layers B.Cu B.Paste B.Mask) ${p.from})
(pad 1 smd rect (at -1.65 0 ${p.r}) (size 0.9 1.2) (layers B.Cu B.Paste B.Mask) ${p.to})
(pad 2 smd rect (at 1.65 0 ${p.r}) (size 0.9 1.2) (layers F.Cu F.Paste F.Mask) ${p.from})
${''/* THT terminals */}
(pad 1 thru_hole circle (at 3.81 0 ${p.rot}) (size 1.905 1.905) (drill 0.9906) (layers *.Cu *.Mask) ${p.net.from.str})
(pad 2 thru_hole rect (at -3.81 0 ${p.rot}) (size 1.778 1.778) (drill 0.9906) (layers *.Cu *.Mask) ${p.net.to.str})
(pad 1 thru_hole rect (at -3.81 0 ${p.r}) (size 1.778 1.778) (drill 0.9906) (layers *.Cu *.Mask) ${p.to})
(pad 2 thru_hole circle (at 3.81 0 ${p.r}) (size 1.905 1.905) (drill 0.9906) (layers *.Cu *.Mask) ${p.from})
)
`

View file

@ -1,12 +1,10 @@
module.exports = {
nets: {
params: {
designator: 'JST',
side: 'F',
pos: undefined,
neg: undefined
},
params: {
class: 'JST',
side: 'F'
},
body: p => `
(module JST_PH_S2B-PH-K_02x2.00mm_Angled (layer F.Cu) (tedit 58D3FE32)
@ -20,20 +18,20 @@ module.exports = {
(fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15))))
(fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15))))
(fp_line (start -2.25 0.25) (end -2.25 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.25 -1.35) (end -2.95 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.95 -1.35) (end -2.95 6.25) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.95 6.25) (end 2.95 6.25) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.95 6.25) (end 2.95 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.95 -1.35) (end 2.25 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.25 -1.35) (end 2.25 0.25) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.25 0.25) (end -2.25 0.25) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.25 0.25) (end -2.25 -1.35) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.25 -1.35) (end -2.95 -1.35) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.95 -1.35) (end -2.95 6.25) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.95 6.25) (end 2.95 6.25) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.95 6.25) (end 2.95 -1.35) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.95 -1.35) (end 2.25 -1.35) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.25 -1.35) (end 2.25 0.25) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.25 0.25) (end -2.25 0.25) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -1 1.5) (end -1 2.0) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -1.25 1.75) (end -0.75 1.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -1 1.5) (end -1 2.0) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -1.25 1.75) (end -0.75 1.75) (layer ${p.side}.SilkS) (width 0.15))
(pad 1 thru_hole rect (at -1 0 ${p.rot}) (size 1.2 1.7) (drill 0.75) (layers *.Cu *.Mask) ${p.net.pos.str})
(pad 2 thru_hole oval (at 1 0 ${p.rot}) (size 1.2 1.7) (drill 0.75) (layers *.Cu *.Mask) ${p.net.neg.str})
(pad 1 thru_hole rect (at -1 0 ${p.r}) (size 1.2 1.7) (drill 0.75) (layers *.Cu *.Mask) ${p.pos})
(pad 2 thru_hole oval (at 1 0 ${p.r}) (size 1.2 1.7) (drill 0.75) (layers *.Cu *.Mask) ${p.neg})
)

View file

@ -1,12 +1,10 @@
module.exports = {
nets: {
params: {
designator: 'J',
side: 'F',
from: undefined,
to: undefined
},
params: {
class: 'J',
side: 'F'
},
body: p => `
(module lib:Jumper (layer F.Cu) (tedit 5E1ADAC2)
${p.at /* parametric position */}
@ -16,9 +14,9 @@ module.exports = {
(fp_text value Jumper (at 0 -7.3) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))))
${'' /* pins */}
(pad 1 smd rect (at -0.50038 0 ${p.rot}) (size 0.635 1.143) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask)
(clearance 0.1905) ${p.net.from.str})
(pad 2 smd rect (at 0.50038 0 ${p.rot}) (size 0.635 1.143) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask)
(clearance 0.1905) ${p.net.to.str}))
(pad 1 smd rect (at -0.50038 0 ${p.r}) (size 0.635 1.143) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask)
(clearance 0.1905) ${p.from})
(pad 2 smd rect (at 0.50038 0 ${p.r}) (size 0.635 1.143) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask)
(clearance 0.1905) ${p.to}))
`
}

View file

@ -13,16 +13,14 @@
// note: hotswap and reverse can be used simultaneously
module.exports = {
nets: {
params: {
designator: 'S',
hotswap: false,
reverse: false,
keycaps: false,
from: undefined,
to: undefined
},
params: {
class: 'S',
hotswap: false,
reverse: false,
keycaps: false
},
body: p => {
const standard = `
(module MX (layer F.Cu) (tedit 5DD4F656)
@ -57,35 +55,35 @@ module.exports = {
(fp_line (start -9.5 9.5) (end -9.5 -9.5) (layer Dwgs.User) (width 0.15))
`
function pins(def_neg, def_pos, def_side) {
if(p.param.hotswap) {
if(p.hotswap) {
return `
${'' /* holes */}
(pad "" np_thru_hole circle (at ${def_pos}2.54 -5.08) (size 3 3) (drill 3) (layers *.Cu *.Mask))
(pad "" np_thru_hole circle (at ${def_neg}3.81 -2.54) (size 3 3) (drill 3) (layers *.Cu *.Mask))
${'' /* net pads */}
(pad 1 smd rect (at ${def_neg}7.085 -2.54 180) (size 2.55 2.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.from.str})
(pad 2 smd rect (at ${def_pos}5.842 -5.08 180) (size 2.55 2.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.net.to.str})
(pad 1 smd rect (at ${def_neg}7.085 -2.54 ${p.r}) (size 2.55 2.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.from})
(pad 2 smd rect (at ${def_pos}5.842 -5.08 ${p.r}) (size 2.55 2.5) (layers ${def_side}.Cu ${def_side}.Paste ${def_side}.Mask) ${p.to})
`
} else {
return `
${''/* pins */}
(pad 1 thru_hole circle (at ${def_pos}2.54 -5.08) (size 2.286 2.286) (drill 1.4986) (layers *.Cu *.Mask) ${p.net.from.str})
(pad 2 thru_hole circle (at ${def_neg}3.81 -2.54) (size 2.286 2.286) (drill 1.4986) (layers *.Cu *.Mask) ${p.net.to.str})
(pad 1 thru_hole circle (at ${def_pos}2.54 -5.08) (size 2.286 2.286) (drill 1.4986) (layers *.Cu *.Mask) ${p.from})
(pad 2 thru_hole circle (at ${def_neg}3.81 -2.54) (size 2.286 2.286) (drill 1.4986) (layers *.Cu *.Mask) ${p.to})
`
}
}
if(p.param.reverse){
if(p.reverse){
return `
${standard}
${p.param.keycaps ? keycap : ''}
${p.keycaps ? keycap : ''}
${pins('-', '', 'B')}
${pins('', '-', 'F')})
`
} else {
return `
${standard}
${p.param.keycaps ? keycap : ''}
${p.keycaps ? keycap : ''}
${pins('-', '', 'B')})
`
}

View file

@ -1,13 +1,11 @@
module.exports = {
nets: {
SDA: undefined,
SCL: undefined,
VCC: 'VCC',
GND: 'GND'
},
params: {
class: 'OLED',
side: 'F'
designator: 'OLED',
side: 'F',
VCC: {type: 'net', value: 'VCC'},
GND: {type: 'net', value: 'GND'},
SDA: undefined,
SCL: undefined
},
body: p => `
(module lib:OLED_headers (layer F.Cu) (tedit 5E1ADAC2)
@ -18,14 +16,14 @@ module.exports = {
(fp_text value OLED (at 0 -7.3) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))))
${'' /* pins */}
(pad 4 thru_hole oval (at 1.6 2.18 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.net.SDA.str})
(pad 3 thru_hole oval (at 1.6 4.72 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.net.SCL.str})
(pad 2 thru_hole oval (at 1.6 7.26 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.net.VCC.str})
(pad 1 thru_hole rect (at 1.6 9.8 ${p.rot+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.net.GND.str})
(pad 4 thru_hole oval (at 1.6 2.18 ${p.r+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.SDA})
(pad 3 thru_hole oval (at 1.6 4.72 ${p.r+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.SCL})
(pad 2 thru_hole oval (at 1.6 7.26 ${p.r+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.VCC})
(pad 1 thru_hole rect (at 1.6 9.8 ${p.r+270}) (size 1.7 1.7) (drill 1) (layers *.Cu *.Mask)
${p.GND})
)
`
}

View file

@ -1,11 +1,9 @@
module.exports = {
nets: {
params: {
designator: 'S',
from: undefined,
to: undefined
},
params: {
class: 'S'
},
body: p => `
(module OMRON_B3F-4055 (layer F.Cu) (tstamp 5BF2CC94)
@ -26,10 +24,10 @@ module.exports = {
(fp_line (start -6 6) (end -6 -6) (layer Dwgs.User) (width 0.15))
${'' /* pins */}
(pad 1 np_thru_hole circle (at 6.25 -2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.from.str})
(pad 2 np_thru_hole circle (at -6.25 -2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.from.str})
(pad 3 np_thru_hole circle (at 6.25 2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.to.str})
(pad 4 np_thru_hole circle (at -6.25 2.5 ) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.net.to.str})
(pad 1 np_thru_hole circle (at 6.25 -2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.from})
(pad 2 np_thru_hole circle (at -6.25 -2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.from})
(pad 3 np_thru_hole circle (at 6.25 2.5) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.to})
(pad 4 np_thru_hole circle (at -6.25 2.5 ) (size 1.2 1.2) (drill 1.2) (layers *.Cu *.Mask) ${p.to})
)
`

View file

@ -1,16 +1,14 @@
module.exports = {
nets: {
net: undefined
},
params: {
class: 'PAD',
designator: 'PAD',
width: 1,
height: 1,
front: true,
back: true,
text: '',
align: 'left',
mirrored: '=mirrored'
mirrored: {type: 'boolean', value: '{{mirrored}}'},
net: undefined
},
body: p => {
@ -18,18 +16,21 @@ module.exports = {
if (!toggle) return ''
let x = 0, y = 0
const mirror = side == 'B' ? '(justify mirror)' : ''
const plus = (p.param.text.length + 1) * 0.5
let align = p.param.align
if (p.param.mirrored === true) {
const plus = (p.text.length + 1) * 0.5
let align = p.align
if (p.mirrored === true) {
if (align == 'left') align = 'right'
else if (align == 'right') align = 'left'
}
if (align == 'left') x -= p.param.width / 2 + plus
if (align == 'right') x += p.param.width / 2 + plus
if (align == 'up') y += p.param.height / 2 + plus
if (align == 'down') y -= p.param.height / 2 + plus
const text = `(fp_text user ${p.param.text} (at ${x} ${y} ${p.rot}) (layer ${side}.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15)) ${mirror}))`
return `(pad 1 smd rect (at 0 0 ${p.rot}) (size ${p.param.width} ${p.param.height}) (layers ${side}.Cu ${side}.Paste ${side}.Mask) ${p.net.net.str})\n${text}`
if (align == 'left') x -= p.width / 2 + plus
if (align == 'right') x += p.width / 2 + plus
if (align == 'up') y += p.height / 2 + plus
if (align == 'down') y -= p.height / 2 + plus
let text = ''
if (p.text.length) {
text = `(fp_text user ${p.text} (at ${x} ${y} ${p.r}) (layer ${side}.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15)) ${mirror}))`
}
return `(pad 1 smd rect (at 0 0 ${p.r}) (size ${p.width} ${p.height}) (layers ${side}.Cu ${side}.Paste ${side}.Mask) ${p.net})\n${text}`
}
return `
@ -43,8 +44,8 @@ module.exports = {
(fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15))))
${''/* SMD pads */}
${layout(p.param.front, 'F')}
${layout(p.param.back, 'B')}
${layout(p.front, 'F')}
${layout(p.back, 'B')}
)

View file

@ -5,33 +5,31 @@
// if up, power led will face away from pcb
module.exports = {
nets: {
RAW: 'RAW',
GND: 'GND',
RST: 'RST',
VCC: 'VCC',
P21: 'P21',
P20: 'P20',
P19: 'P19',
P18: 'P18',
P15: 'P15',
P14: 'P14',
P16: 'P16',
P10: 'P10',
P1: 'P1',
P0: 'P0',
P2: 'P2',
P3: 'P3',
P4: 'P4',
P5: 'P5',
P6: 'P6',
P7: 'P7',
P8: 'P8',
P9: 'P9',
},
params: {
class: 'MCU',
orientation: 'down'
designator: 'MCU',
orientation: 'down',
RAW: {type: 'net', value: 'RAW'},
GND: {type: 'net', value: 'GND'},
RST: {type: 'net', value: 'RST'},
VCC: {type: 'net', value: 'VCC'},
P21: {type: 'net', value: 'P21'},
P20: {type: 'net', value: 'P20'},
P19: {type: 'net', value: 'P19'},
P18: {type: 'net', value: 'P18'},
P15: {type: 'net', value: 'P15'},
P14: {type: 'net', value: 'P14'},
P16: {type: 'net', value: 'P16'},
P10: {type: 'net', value: 'P10'},
P1: {type: 'net', value: 'P1'},
P0: {type: 'net', value: 'P0'},
P2: {type: 'net', value: 'P2'},
P3: {type: 'net', value: 'P3'},
P4: {type: 'net', value: 'P4'},
P5: {type: 'net', value: 'P5'},
P6: {type: 'net', value: 'P6'},
P7: {type: 'net', value: 'P7'},
P8: {type: 'net', value: 'P8'},
P9: {type: 'net', value: 'P9'}
},
body: p => {
const standard = `
@ -62,61 +60,61 @@ module.exports = {
(fp_line (start -12.7 ${def_pos}6.35) (end -12.7 ${def_pos}8.89) (layer F.SilkS) (width 0.15))
${''/* pin names */}
(fp_text user RAW (at -13.97 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user GND (at -11.43 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user RST (at -8.89 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user VCC (at -6.35 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P21 (at -3.81 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P20 (at -1.27 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P19 (at 1.27 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P18 (at 3.81 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P15 (at 6.35 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P14 (at 8.89 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P16 (at 11.43 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P10 (at 13.97 ${def_pos}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user RAW (at -13.97 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user GND (at -11.43 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user RST (at -8.89 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user VCC (at -6.35 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P21 (at -3.81 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P20 (at -1.27 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P19 (at 1.27 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P18 (at 3.81 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P15 (at 6.35 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P14 (at 8.89 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P16 (at 11.43 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P10 (at 13.97 ${def_pos}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P01 (at -13.97 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P00 (at -11.43 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user GND (at -8.89 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user GND (at -6.35 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P02 (at -3.81 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P03 (at -1.27 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P04 (at 1.27 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P05 (at 3.81 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P06 (at 6.35 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P07 (at 8.89 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P08 (at 11.43 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P09 (at 13.97 ${def_neg}4.8 ${p.rot + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P01 (at -13.97 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P00 (at -11.43 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user GND (at -8.89 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user GND (at -6.35 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P02 (at -3.81 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P03 (at -1.27 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P04 (at 1.27 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P05 (at 3.81 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P06 (at 6.35 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P07 (at 8.89 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P08 (at 11.43 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
(fp_text user P09 (at 13.97 ${def_neg}4.8 ${p.r + 90}) (layer F.SilkS) (effects (font (size 0.8 0.8) (thickness 0.15))))
${''/* and now the actual pins */}
(pad 1 thru_hole rect (at -13.97 ${def_pos}7.62 ${p.rot}) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.RAW.str})
(pad 2 thru_hole circle (at -11.43 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.GND.str})
(pad 3 thru_hole circle (at -8.89 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.RST.str})
(pad 4 thru_hole circle (at -6.35 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.VCC.str})
(pad 5 thru_hole circle (at -3.81 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P21.str})
(pad 6 thru_hole circle (at -1.27 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P20.str})
(pad 7 thru_hole circle (at 1.27 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P19.str})
(pad 8 thru_hole circle (at 3.81 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P18.str})
(pad 9 thru_hole circle (at 6.35 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P15.str})
(pad 10 thru_hole circle (at 8.89 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P14.str})
(pad 11 thru_hole circle (at 11.43 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P16.str})
(pad 12 thru_hole circle (at 13.97 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P10.str})
(pad 1 thru_hole rect (at -13.97 ${def_pos}7.62 ${p.r}) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.RAW})
(pad 2 thru_hole circle (at -11.43 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.GND})
(pad 3 thru_hole circle (at -8.89 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.RST})
(pad 4 thru_hole circle (at -6.35 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.VCC})
(pad 5 thru_hole circle (at -3.81 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P21})
(pad 6 thru_hole circle (at -1.27 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P20})
(pad 7 thru_hole circle (at 1.27 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P19})
(pad 8 thru_hole circle (at 3.81 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P18})
(pad 9 thru_hole circle (at 6.35 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P15})
(pad 10 thru_hole circle (at 8.89 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P14})
(pad 11 thru_hole circle (at 11.43 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P16})
(pad 12 thru_hole circle (at 13.97 ${def_pos}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P10})
(pad 13 thru_hole circle (at -13.97 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P1.str})
(pad 14 thru_hole circle (at -11.43 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P0.str})
(pad 15 thru_hole circle (at -8.89 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.GND.str})
(pad 16 thru_hole circle (at -6.35 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.GND.str})
(pad 17 thru_hole circle (at -3.81 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P2.str})
(pad 18 thru_hole circle (at -1.27 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P3.str})
(pad 19 thru_hole circle (at 1.27 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P4.str})
(pad 20 thru_hole circle (at 3.81 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P5.str})
(pad 21 thru_hole circle (at 6.35 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P6.str})
(pad 22 thru_hole circle (at 8.89 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P7.str})
(pad 23 thru_hole circle (at 11.43 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P8.str})
(pad 24 thru_hole circle (at 13.97 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.net.P9.str})
(pad 13 thru_hole circle (at -13.97 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P1})
(pad 14 thru_hole circle (at -11.43 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P0})
(pad 15 thru_hole circle (at -8.89 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.GND})
(pad 16 thru_hole circle (at -6.35 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.GND})
(pad 17 thru_hole circle (at -3.81 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P2})
(pad 18 thru_hole circle (at -1.27 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P3})
(pad 19 thru_hole circle (at 1.27 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P4})
(pad 20 thru_hole circle (at 3.81 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P5})
(pad 21 thru_hole circle (at 6.35 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P6})
(pad 22 thru_hole circle (at 8.89 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P7})
(pad 23 thru_hole circle (at 11.43 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P8})
(pad 24 thru_hole circle (at 13.97 ${def_neg}7.62 0) (size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.SilkS *.Mask) ${p.P9})
`
}
if(p.param.orientation == 'down') {
if(p.orientation == 'down') {
return `
${standard}
${pins('-', '')})

View file

@ -1,13 +1,11 @@
module.exports = {
nets: {
params: {
designator: 'LED',
side: 'F',
din: undefined,
dout: undefined,
VCC: 'VCC',
GND: 'GND'
},
params: {
class: 'LED',
side: 'F'
VCC: {type: 'net', value: 'VCC'},
GND: {type: 'net', value: 'GND'}
},
body: p => `
@ -19,27 +17,27 @@ module.exports = {
(fp_text reference "${p.ref}" (at 0 0) (layer F.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15))))
(fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15))))
(fp_line (start -1.75 -1.75) (end -1.75 1.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -1.75 1.75) (end 1.75 1.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 1.75 1.75) (end 1.75 -1.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 1.75 -1.75) (end -1.75 -1.75) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -1.75 -1.75) (end -1.75 1.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -1.75 1.75) (end 1.75 1.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 1.75 1.75) (end 1.75 -1.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 1.75 -1.75) (end -1.75 -1.75) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.5 -2.5) (end -2.5 2.5) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.5 2.5) (end 2.5 2.5) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.5 2.5) (end 2.5 -2.5) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 2.5 -2.5) (end -2.5 -2.5) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -2.5 -2.5) (end -2.5 2.5) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -2.5 2.5) (end 2.5 2.5) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.5 2.5) (end 2.5 -2.5) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 2.5 -2.5) (end -2.5 -2.5) (layer ${p.side}.SilkS) (width 0.15))
(fp_poly (pts (xy 4 2.2) (xy 4 0.375) (xy 5 1.2875)) (layer ${p.param.side}.SilkS) (width 0.1))
(fp_poly (pts (xy 4 2.2) (xy 4 0.375) (xy 5 1.2875)) (layer ${p.side}.SilkS) (width 0.1))
(pad 1 smd rect (at -2.2 -0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.VCC.str})
(pad 2 smd rect (at -2.2 0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.dout.str})
(pad 3 smd rect (at 2.2 0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.GND.str})
(pad 4 smd rect (at 2.2 -0.875 ${p.rot}) (size 2.6 1) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.din.str})
(pad 1 smd rect (at -2.2 -0.875 ${p.r}) (size 2.6 1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.VCC})
(pad 2 smd rect (at -2.2 0.875 ${p.r}) (size 2.6 1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.dout})
(pad 3 smd rect (at 2.2 0.875 ${p.r}) (size 2.6 1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.GND})
(pad 4 smd rect (at 2.2 -0.875 ${p.r}) (size 2.6 1) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.din})
(pad 11 smd rect (at -2.5 -1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.VCC.str})
(pad 22 smd rect (at -2.5 1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.dout.str})
(pad 33 smd rect (at 2.5 1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.GND.str})
(pad 44 smd rect (at 2.5 -1.6 ${p.rot}) (size 2 1.2) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.din.str})
(pad 11 smd rect (at -2.5 -1.6 ${p.r}) (size 2 1.2) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.VCC})
(pad 22 smd rect (at -2.5 1.6 ${p.r}) (size 2 1.2) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.dout})
(pad 33 smd rect (at 2.5 1.6 ${p.r}) (size 2 1.2) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.GND})
(pad 44 smd rect (at 2.5 -1.6 ${p.r}) (size 2 1.2) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.din})
)

View file

@ -8,16 +8,14 @@
// C: corresponds to pin 3 (for rotary)
module.exports = {
nets: {
params: {
designator: 'ROT',
from: undefined,
to: undefined,
A: undefined,
B: undefined,
C: undefined
},
params: {
class: 'ROT'
},
body: p => `
(module rotary_encoder (layer F.Cu) (tedit 603326DE)
@ -58,15 +56,15 @@ module.exports = {
(fp_circle (center -0.12 -0.04) (end 2.88 -0.04) (layer F.Fab) (width 0.12))
${''/* pin names */}
(pad A thru_hole rect (at -7.62 -2.54 ${p.rot}) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.net.A.str})
(pad C thru_hole circle (at -7.62 -0.04) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.net.C.str})
(pad B thru_hole circle (at -7.62 2.46) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.net.B.str})
(pad 1 thru_hole circle (at 6.88 -2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask) ${p.net.from.str})
(pad 2 thru_hole circle (at 6.88 2.46) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask) ${p.net.to.str})
(pad A thru_hole rect (at -7.62 -2.54 ${p.r}) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.A})
(pad C thru_hole circle (at -7.62 -0.04) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.C})
(pad B thru_hole circle (at -7.62 2.46) (size 2 2) (drill 1) (layers *.Cu *.Mask) ${p.B})
(pad 1 thru_hole circle (at 6.88 -2.54) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask) ${p.from})
(pad 2 thru_hole circle (at 6.88 2.46) (size 1.5 1.5) (drill 1) (layers *.Cu *.Mask) ${p.to})
${''/* Legs */}
(pad "" thru_hole rect (at -0.12 -5.64 ${p.rot}) (size 3.2 2) (drill oval 2.8 1.5) (layers *.Cu *.Mask))
(pad "" thru_hole rect (at -0.12 5.56 ${p.rot}) (size 3.2 2) (drill oval 2.8 1.5) (layers *.Cu *.Mask))
(pad "" thru_hole rect (at -0.12 -5.64 ${p.r}) (size 3.2 2) (drill oval 2.8 1.5) (layers *.Cu *.Mask))
(pad "" thru_hole rect (at -0.12 5.56 ${p.r}) (size 3.2 2) (drill oval 2.8 1.5) (layers *.Cu *.Mask))
)
`
}

View file

@ -21,7 +21,9 @@
module.exports = {
nets: {
params: {
designator: 'S',
reverse: false,
from: undefined,
to: undefined,
A: undefined,
@ -29,16 +31,12 @@ module.exports = {
C: undefined,
D: undefined
},
params: {
class: 'S',
reverse: false
},
body: p => {
const standard = `
(module RollerEncoder_Panasonic_EVQWGD001 (layer F.Cu) (tedit 6040A10C)
${p.at /* parametric position */}
(fp_text reference REF** (at 0 0 ${p.rot}) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))))
(fp_text value RollerEncoder_Panasonic_EVQWGD001 (at -0.1 9 ${p.rot}) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))))
(fp_text reference REF** (at 0 0 ${p.r}) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))))
(fp_text value RollerEncoder_Panasonic_EVQWGD001 (at -0.1 9 ${p.r}) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))))
${'' /* corner marks */}
(fp_line (start -8.4 -6.4) (end 8.4 -6.4) (layer Dwgs.User) (width 0.12))
@ -59,18 +57,18 @@ module.exports = {
(fp_arc (start ${def_pos}9.5 -6.3) (end ${def_pos}9.8 -6.3) (angle ${def_neg}90) (layer Edge.Cuts) (width 0.15))
${'' /* pins */}
(pad S1 thru_hole circle (at ${def_neg}6.85 -6.2 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.from.str})
(pad S2 thru_hole circle (at ${def_neg}5 -6.2 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.to.str})
(pad A thru_hole circle (at ${def_neg}5.625 -3.81 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.A.str})
(pad B thru_hole circle (at ${def_neg}5.625 -1.27 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.B.str})
(pad C thru_hole circle (at ${def_neg}5.625 1.27 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.C.str})
(pad D thru_hole circle (at ${def_neg}5.625 3.81 ${p.rot}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.net.D.str})
(pad S1 thru_hole circle (at ${def_neg}6.85 -6.2 ${p.r}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.from})
(pad S2 thru_hole circle (at ${def_neg}5 -6.2 ${p.r}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.to})
(pad A thru_hole circle (at ${def_neg}5.625 -3.81 ${p.r}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.A})
(pad B thru_hole circle (at ${def_neg}5.625 -1.27 ${p.r}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.B})
(pad C thru_hole circle (at ${def_neg}5.625 1.27 ${p.r}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.C})
(pad D thru_hole circle (at ${def_neg}5.625 3.81 ${p.r}) (size 1.6 1.6) (drill 0.9) (layers *.Cu *.Mask) ${p.D})
${'' /* stabilizer */}
(pad "" np_thru_hole circle (at ${def_neg}5.625 6.3 ${p.rot}) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask))
(pad "" np_thru_hole circle (at ${def_neg}5.625 6.3 ${p.r}) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask))
`
}
if(p.param.reverse) {
if(p.reverse) {
return `
${standard}
${pins('-', '')}

View file

@ -1,16 +1,14 @@
module.exports = {
nets: {
params: {
designator: 'T', // for Toggle
side: 'F',
from: undefined,
to: undefined
},
params: {
class: 'T', // for Toggle
side: 'F'
},
body: p => {
const left = p.param.side == 'F' ? '-' : ''
const right = p.param.side == 'F' ? '' : '-'
const left = p.side == 'F' ? '-' : ''
const right = p.side == 'F' ? '' : '-'
return `
@ -23,12 +21,12 @@ module.exports = {
(fp_text value "" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15))))
${'' /* outline */}
(fp_line (start 1.95 -1.35) (end -1.95 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 0 -1.35) (end -3.3 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -3.3 -1.35) (end -3.3 1.5) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start -3.3 1.5) (end 3.3 1.5) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 3.3 1.5) (end 3.3 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 0 -1.35) (end 3.3 -1.35) (layer ${p.param.side}.SilkS) (width 0.15))
(fp_line (start 1.95 -1.35) (end -1.95 -1.35) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 0 -1.35) (end -3.3 -1.35) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -3.3 -1.35) (end -3.3 1.5) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start -3.3 1.5) (end 3.3 1.5) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 3.3 1.5) (end 3.3 -1.35) (layer ${p.side}.SilkS) (width 0.15))
(fp_line (start 0 -1.35) (end 3.3 -1.35) (layer ${p.side}.SilkS) (width 0.15))
${'' /* extra indicator for the slider */}
(fp_line (start -1.95 -3.85) (end 1.95 -3.85) (layer Dwgs.User) (width 0.15))
@ -40,15 +38,15 @@ module.exports = {
(pad "" np_thru_hole circle (at -1.5 0) (size 1 1) (drill 0.9) (layers *.Cu *.Mask))
${'' /* pins */}
(pad 1 smd rect (at ${right}2.25 2.075 ${p.rot}) (size 0.9 1.25) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.from.str})
(pad 2 smd rect (at ${left}0.75 2.075 ${p.rot}) (size 0.9 1.25) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask) ${p.net.to.str})
(pad 3 smd rect (at ${left}2.25 2.075 ${p.rot}) (size 0.9 1.25) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask))
(pad 1 smd rect (at ${right}2.25 2.075 ${p.r}) (size 0.9 1.25) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.from})
(pad 2 smd rect (at ${left}0.75 2.075 ${p.r}) (size 0.9 1.25) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask) ${p.to})
(pad 3 smd rect (at ${left}2.25 2.075 ${p.r}) (size 0.9 1.25) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask))
${'' /* side mounts */}
(pad "" smd rect (at 3.7 -1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask))
(pad "" smd rect (at 3.7 1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask))
(pad "" smd rect (at -3.7 1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask))
(pad "" smd rect (at -3.7 -1.1 ${p.rot}) (size 0.9 0.9) (layers ${p.param.side}.Cu ${p.param.side}.Paste ${p.param.side}.Mask))
(pad "" smd rect (at 3.7 -1.1 ${p.r}) (size 0.9 0.9) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask))
(pad "" smd rect (at 3.7 1.1 ${p.r}) (size 0.9 0.9) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask))
(pad "" smd rect (at -3.7 1.1 ${p.r}) (size 0.9 0.9) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask))
(pad "" smd rect (at -3.7 -1.1 ${p.r}) (size 0.9 0.9) (layers ${p.side}.Cu ${p.side}.Paste ${p.side}.Mask))
)
`

View file

@ -1,8 +1,24 @@
// TRRS-PJ-320A-dual
//
// Normal footprint:
// _________________
// | (1) (3) (4)|
// | (2) (3) (4)|
// | |
// |___(2)__________|
// | (1) |
// |________________|
//
// Reverse footprint:
// _________________
// | (2) (3) (4)|
// | (1) |
// | (1) |
// |___(2)___(3)_(4)|
//
// Reverse & symmetric footprint:
// _________________
// | (1|2) (3) (4)|
// | |
// |_(1|2)___(3)_(4)|
//
// Nets
// A: corresponds to pin 1
@ -18,17 +34,15 @@
// pins 1 and 2 must be identical if symmetric is true, as they will overlap
module.exports = {
nets: {
params: {
designator: 'TRRS',
reverse: false,
symmetric: false,
A: undefined,
B: undefined,
C: undefined,
D: undefined
},
params: {
class: 'TRRS',
reverse: false,
symmetric: false
},
body: p => {
const standard = `
(module TRRS-PJ-320A-dual (layer F.Cu) (tedit 5970F8E5)
@ -36,7 +50,7 @@ module.exports = {
${p.at /* parametric position */}
${'' /* footprint reference */}
(fp_text reference REF** (at 0 14.2) (layer Dwgs.User) (effects (font (size 1 1) (thickness 0.15))))
(fp_text reference "${p.ref}" (at 0 14.2) (layer Dwgs.User) (effects (font (size 1 1) (thickness 0.15))))
(fp_text value TRRS-PJ-320A-dual (at 0 -5.6) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))))
${''/* corner marks */}
@ -48,35 +62,42 @@ module.exports = {
(fp_line (start 0.75 12.1) (end -5.35 12.1) (layer Dwgs.User) (width 0.15))
(fp_line (start 0.75 0) (end -5.35 0) (layer Dwgs.User) (width 0.15))
${''/* stabilizers */}
(pad "" np_thru_hole circle (at -2.3 8.6) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask))
(pad "" np_thru_hole circle (at -2.3 1.6) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask))
`
function pins(def_neg, def_pos) {
function stabilizers(def_pos) {
return `
(pad 1 thru_hole oval (at ${def_neg} 11.3 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.A.str})
(pad 2 thru_hole oval (at ${def_pos} 10.2 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.B.str})
(pad 3 thru_hole oval (at ${def_pos} 6.2 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.C.str})
(pad 4 thru_hole oval (at ${def_pos} 3.2 ${p.rot}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.net.D.str})
(pad "" np_thru_hole circle (at ${def_pos} 8.6) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask))
(pad "" np_thru_hole circle (at ${def_pos} 1.6) (size 1.5 1.5) (drill 1.5) (layers *.Cu *.Mask))
`
}
if(p.param.reverse & p.param.symmetric) {
function pins(def_neg, def_pos) {
return `
(pad 1 thru_hole oval (at ${def_neg} 11.3 ${p.r}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.A})
(pad 2 thru_hole oval (at ${def_pos} 10.2 ${p.r}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.B})
(pad 3 thru_hole oval (at ${def_pos} 6.2 ${p.r}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.C})
(pad 4 thru_hole oval (at ${def_pos} 3.2 ${p.r}) (size 1.6 2.2) (drill oval 0.9 1.5) (layers *.Cu *.Mask) ${p.D})
`
}
if(p.reverse & p.symmetric) {
return `
${standard}
${stabilizers('-2.3')}
${pins('0', '-4.6')}
${pins('-4.6', '0')})
`
} else if(p.param.reverse) {
} else if(p.reverse) {
return `
${standard}
${pins('-4.6', '0')}
${pins('4.6', '0')})
${stabilizers('-2.3')}
${stabilizers('0')}
${pins('-2.3', '2.3')}
${pins('0', '-4.6')})
`
} else {
return `
${standard}
${pins('-4.6', '0')})
`
}
} else {
return `
${standard}
${stabilizers('-2.3')}
${pins('-4.6', '0')})
`
}
}
}

View file

@ -3,7 +3,7 @@
// net: the net this via should be connected to
module.exports = {
nets: {
params: {
net: undefined
},
body: p => `
@ -14,7 +14,7 @@ module.exports = {
(fp_text value VIA-0.6mm (at 0 -1.4) (layer F.Fab) hide (effects (font (size 1 1) (thickness 0.15))))
${'' /* via */}
(pad 1 thru_hole circle (at 0 0) (size 0.6 0.6) (drill 0.3) (layers *.Cu) (zone_connect 2) ${p.net.net.str})
(pad 1 thru_hole circle (at 0 0) (size 0.6 0.6) (drill 0.3) (layers *.Cu) (zone_connect 2) ${p.net})
)
`
}

View file

@ -1,11 +1,36 @@
const yaml = require('js-yaml')
const jszip = require('jszip')
const makerjs = require('makerjs')
const jscad = require('@jscad/openjscad')
const u = require('./utils')
const a = require('./assert')
const kle = require('./kle')
exports.unpack = async (zip) => {
// main config text (has to be called "config.ext" where ext is one of yaml/json/js)
const candidates = zip.file(/^config\.(yaml|json|js)$/)
if (candidates.length != 1) {
throw new Error('Ambiguous config in bundle!')
}
const config_text = await candidates[0].async('string')
const injections = []
// bundled footprints
const fps = zip.folder('footprints')
const module_prefix = 'const module = {};\n\n'
const module_suffix = '\n\nreturn module.exports;'
for (const fp of fps.file(/.*\.js$/)) {
const name = fp.name.slice('footprints/'.length).split('.')[0]
const text = await fp.async('string')
const parsed = new Function(module_prefix + text + module_suffix)()
// TODO: some sort of footprint validation?
injections.push(['footprint', name, parsed])
}
return [config_text, injections]
}
exports.interpret = (raw, logger) => {
let config = raw
let format = 'OBJ'
@ -65,18 +90,3 @@ exports.twodee = (model, debug) => {
}
return result
}
exports.threedee = async (script, debug) => {
const compiled = await new Promise((resolve, reject) => {
jscad.compile(script, {}).then(compiled => {
resolve(compiled)
})
})
const result = {
stl: jscad.generateOutput('stla', compiled).asBuffer().toString()
}
if (debug) {
result.jscad = script
}
return result
}

View file

@ -70,4 +70,4 @@ exports.convert = (config, logger) => {
}
return result
}
}

View file

@ -1,17 +1,23 @@
const op_prefix = exports.op_prefix = str => {
const prefix = str[0]
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'}
const result = {name: suffix, operation: 'add'}
if (prefix == '+') ; // noop
else if (prefix == '-') result.operation = 'subtract'
else if (prefix == '~') result.operation = 'intersect'
else if (prefix == '^') result.operation = 'stack'
else result.name = str // no prefix, so the name was the whole string
return result
}
exports.operation = (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
res.what = key
break
}
}

View file

@ -4,342 +4,251 @@ const a = require('./assert')
const o = require('./operation')
const Point = require('./point')
const prep = require('./prepare')
const anchor_lib = require('./anchor')
const anchor = require('./anchor').parse
const filter = require('./filter').parse
const rectangle = (w, h, corner, bevel, name='') => {
const error = (dim, val) => `Rectangle for "${name}" isn't ${dim} enough for its corner and bevel (${val} - 2 * ${corner} - 2 * ${bevel} <= 0)!`
const mod = 2 * (corner + bevel)
const cw = w - mod
a.assert(cw >= 0, error('wide', w))
const ch = h - mod
a.assert(ch >= 0, error('tall', h))
const binding = (base, bbox, point, units) => {
let res = new m.models.Rectangle(cw, ch)
if (bevel) {
res = u.poly([
[-bevel, 0],
[-bevel, ch],
[0, ch + bevel],
[cw, ch + bevel],
[cw + bevel, ch],
[cw + bevel, 0],
[cw, -bevel],
[0, -bevel]
])
let bind = a.trbl(point.meta.bind || 0, `${point.meta.name}.bind`)(units)
// if it's a mirrored key, we swap the left and right bind values
if (point.meta.mirrored) {
bind = [bind[0], bind[3], bind[2], bind[1]]
}
if (corner > 0) res = m.model.outline(res, corner, 0)
return m.model.moveRelative(res, [corner + bevel, corner + bevel])
const bt = Math.max(bbox.high[1], 0) + Math.max(bind[0], 0)
const br = Math.max(bbox.high[0], 0) + Math.max(bind[1], 0)
const bd = Math.min(bbox.low[1], 0) - Math.max(bind[2], 0)
const bl = Math.min(bbox.low[0], 0) - Math.max(bind[3], 0)
if (bind[0] || bind[1]) base = u.union(base, u.rect(br, bt))
if (bind[1] || bind[2]) base = u.union(base, u.rect(br, -bd, [0, bd]))
if (bind[2] || bind[3]) base = u.union(base, u.rect(-bl, -bd, [bl, bd]))
if (bind[3] || bind[0]) base = u.union(base, u.rect(-bl, bt, [bl, 0]))
return base
}
const layout = exports._layout = (config = {}, points = {}, units = {}) => {
const rectangle = (config, name, points, outlines, units) => {
// Glue config sanitization
// prepare params
a.unexpected(config, `${name}`, ['size', 'corner', 'bevel'])
const size = a.wh(config.size, `${name}.size`)(units)
const rec_units = prep.extend({
sx: size[0],
sy: size[1]
}, units)
const corner = a.sane(config.corner || 0, `${name}.corner`, 'number')(rec_units)
const bevel = a.sane(config.bevel || 0, `${name}.bevel`, 'number')(rec_units)
const parsed_glue = u.deepcopy(a.sane(config, 'outlines.glue', 'object')())
for (let [gkey, gval] of Object.entries(parsed_glue)) {
a.unexpected(gval, `outlines.glue.${gkey}`, ['top', 'bottom', 'waypoints', 'extra'])
for (const y of ['top', 'bottom']) {
a.unexpected(gval[y], `outlines.glue.${gkey}.${y}`, ['left', 'right'])
gval[y].left = anchor_lib.parse(gval[y].left, `outlines.glue.${gkey}.${y}.left`, points)
if (a.type(gval[y].right)(units) != 'number') {
gval[y].right = anchor_lib.parse(gval[y].right, `outlines.glue.${gkey}.${y}.right`, points)
}
// return shape function and its units
return [() => {
const error = (dim, val) => `Rectangle for "${name}" isn't ${dim} enough for its corner and bevel (${val} - 2 * ${corner} - 2 * ${bevel} <= 0)!`
const [w, h] = size
const mod = 2 * (corner + bevel)
const cw = w - mod
a.assert(cw >= 0, error('wide', w))
const ch = h - mod
a.assert(ch >= 0, error('tall', h))
let rect = new m.models.Rectangle(cw, ch)
if (bevel) {
rect = u.poly([
[-bevel, 0],
[-bevel, ch],
[0, ch + bevel],
[cw, ch + bevel],
[cw + bevel, ch],
[cw + bevel, 0],
[cw, -bevel],
[0, -bevel]
])
}
gval.waypoints = a.sane(gval.waypoints || [], `outlines.glue.${gkey}.waypoints`, 'array')(units)
let wi = 0
gval.waypoints = gval.waypoints.map(w => {
const name = `outlines.glue.${gkey}.waypoints[${++wi}]`
a.unexpected(w, name, ['percent', 'width'])
w.percent = a.sane(w.percent, name + '.percent', 'number')(units)
w.width = a.wh(w.width, name + '.width')(units)
return w
})
if (corner > 0) rect = m.model.outline(rect, corner, 0)
rect = m.model.moveRelative(rect, [-cw/2, -ch/2])
const bbox = {high: [w/2, h/2], low: [-w/2, -h/2]}
parsed_glue[gkey] = gval
return [rect, bbox]
}, rec_units]
}
const circle = (config, name, points, outlines, units) => {
// prepare params
a.unexpected(config, `${name}`, ['radius'])
const radius = a.sane(config.radius, `${name}.radius`, 'number')(units)
const circ_units = prep.extend({
r: radius
}, units)
// return shape function and its units
return [() => {
let circle = u.circle([0, 0], radius)
const bbox = {high: [radius, radius], low: [-radius, -radius]}
return [circle, bbox]
}, circ_units]
}
const polygon = (config, name, points, outlines, units) => {
// prepare params
a.unexpected(config, `${name}`, ['points'])
const poly_points = a.sane(config.points, `${name}.points`, 'array')()
// return shape function and its units
return [point => {
const parsed_points = []
// the poly starts at [0, 0] as it will be positioned later
// but we keep the point metadata for potential mirroring purposes
let last_anchor = new Point(0, 0, 0, point.meta)
let poly_index = -1
for (const poly_point of poly_points) {
const poly_name = `${name}.points[${++poly_index}]`
last_anchor = anchor(poly_point, poly_name, points, last_anchor)(units)
parsed_points.push(last_anchor.p)
}
let poly = u.poly(parsed_points)
const bbox = u.bbox(parsed_points)
return [poly, bbox]
}, units]
}
const outline = (config, name, points, outlines, units) => {
// prepare params
a.unexpected(config, `${name}`, ['name', 'origin'])
a.assert(outlines[config.name], `Field "${name}.name" does not name an existing outline!`)
const origin = anchor(config.origin || {}, `${name}.origin`, points)(units)
// return shape function and its units
return [() => {
let o = u.deepcopy(outlines[config.name])
o = origin.unposition(o)
const bbox = m.measure.modelExtents(o)
return [o, bbox]
}, units]
}
const whats = {
rectangle,
circle,
polygon,
outline
}
const expand_shorthand = (config, name, units) => {
if (a.type(config.expand)(units) == 'string') {
const prefix = config.expand.slice(0, -1)
const suffix = config.expand.slice(-1)
const valid_suffixes = [')', '>', ']']
a.assert(valid_suffixes.includes(suffix), `If field "${name}" is a string, ` +
`it should end with one of [${valid_suffixes.map(s => `'${s}'`).join(', ')}]!`)
config.expand = prefix
config.joints = config.joints || valid_suffixes.indexOf(suffix)
}
// TODO: handle glue.extra (or revoke it from the docs)
return (params, export_name, expected) => {
// Layout params sanitization
a.unexpected(params, `${export_name}`, expected.concat(['side', 'tags', 'glue', 'size', 'corner', 'bevel', 'bound']))
const size = a.wh(params.size, `${export_name}.size`)(units)
const relative_units = prep.extend({
sx: size[0],
sy: size[1]
}, units)
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 corner = a.sane(params.corner || 0, `${export_name}.corner`, 'number')(relative_units)
const bevel = a.sane(params.bevel || 0, `${export_name}.bevel`, 'number')(relative_units)
const bound = a.sane(params.bound === undefined ? true : params.bound, `${export_name}.bound`, 'boolean')()
// Actual layout
let left = {models: {}}
let right = {models: {}}
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
// the original position
let rect = rectangle(to_x - from_x, to_y - from_y, corner, bevel, `${export_name}.size`)
rect = m.model.moveRelative(rect, [from_x, from_y])
// extra binding "material", if necessary
if (bound) {
let bind = a.trbl(p.meta.bind || 0, `${pname}.bind`)(relative_units)
// if it's a mirrored key, we swap the left and right bind values
if (p.meta.mirrored) {
bind = [bind[0], bind[3], bind[2], bind[1]]
}
const bt = to_y + Math.max(bind[0], 0)
const br = to_x + Math.max(bind[1], 0)
const bd = from_y - Math.max(bind[2], 0)
const bl = from_x - Math.max(bind[3], 0)
if (bind[0] || bind[1]) rect = u.union(rect, u.rect(br, bt))
if (bind[1] || bind[2]) rect = u.union(rect, u.rect(br, -bd, [0, bd]))
if (bind[2] || bind[3]) rect = u.union(rect, u.rect(-bl, -bd, [bl, bd]))
if (bind[3] || bind[0]) rect = u.union(rect, u.rect(-bl, bt, [bl, 0]))
}
// positioning and unioning the resulting shape
rect = p.position(rect)
if (p.meta.mirrored) {
right = u.union(right, rect)
} else {
left = u.union(left, rect)
}
}
}
if (side == 'left') return left
if (side == 'right') return right
// allow opting out of gluing, when
// A) there are no glue definitions, or
// B) glue is explicitly set to false
const glue_opt_out = (!Object.keys(parsed_glue).length || params.glue === false)
let glue = {models: {}}
if (bound && ['middle', 'both', 'glue'].includes(side) && !glue_opt_out) {
const default_glue_name = Object.keys(parsed_glue)[0]
const computed_glue_name = a.sane(params.glue || default_glue_name, `${export_name}.glue`, 'string')()
const glue_def = parsed_glue[computed_glue_name]
a.assert(glue_def, `Field "${export_name}.glue" does not name a valid glue!`)
const get_line = (anchor) => {
if (a.type(anchor)(relative_units) == 'number') {
return u.line([anchor, -1000], [anchor, 1000])
}
// if it wasn't a number, then it's a (possibly relative) anchor
const from = anchor(relative_units).clone()
const to = from.clone().shift([from.meta.mirrored ? -1 : 1, 0])
return u.line(from.p, to.p)
}
const tll = get_line(glue_def.top.left)
const trl = get_line(glue_def.top.right)
const tip = m.path.converge(tll, trl)
if (!tip) {
throw new Error(`Top lines don't intersect in glue "${computed_glue_name}"!`)
}
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(glue_def.bottom.left)
const brl = get_line(glue_def.bottom.right)
const bip = m.path.converge(bll, brl)
if (!bip) {
throw new Error(`Bottom lines don't intersect in glue "${computed_glue_name}"!`)
}
const blp = u.eq(bll.origin, bip) ? bll.end : bll.origin
const brp = u.eq(brl.origin, bip) ? brl.end : brl.origin
const left_waypoints = []
const right_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])
const left_x = center_x - w.width[0]
const right_x = center_x + w.width[1]
left_waypoints.push([left_x, center_y])
right_waypoints.unshift([right_x, center_y])
}
let waypoints
const is_split = a.type(glue_def.top.right)(relative_units) == 'number'
if (is_split) {
waypoints = [tip, tlp]
.concat(left_waypoints)
.concat([blp, bip])
} else {
waypoints = [trp, tip, tlp]
.concat(left_waypoints)
.concat([blp, bip, brp])
.concat(right_waypoints)
}
glue = u.poly(waypoints)
}
if (side == 'glue') return glue
if (side == 'middle') {
let middle = u.subtract(glue, left)
middle = u.subtract(middle, right)
return middle
}
let both = u.union(u.deepcopy(left), glue)
both = u.union(both, u.deepcopy(right))
return both
if (a.type(config.joints)(units) == 'string') {
if (config.joints == 'round') config.joints = 0
if (config.joints == 'pointy') config.joints = 1
if (config.joints == 'beveled') config.joints = 2
}
}
exports.parse = (config = {}, points = {}, units = {}) => {
a.unexpected(config, 'outline', ['glue', 'exports'])
const layout_fn = layout(config.glue, points, units)
exports.parse = (config, points, units) => {
// output outlines will be collected here
const outlines = {}
const ex = a.sane(config.exports || {}, 'outlines.exports', 'object')()
for (let [key, parts] of Object.entries(ex)) {
// the config must be an actual object so that the exports have names
config = a.sane(config, 'outlines', 'object')()
for (let [outline_name, parts] of Object.entries(config)) {
// placeholder for the current outline
outlines[outline_name] = {models: {}}
// each export can consist of multiple parts
// either sub-objects or arrays are fine...
if (a.type(parts)() == 'array') {
parts = {...parts}
}
parts = a.sane(parts, `outlines.exports.${key}`, 'object')()
let result = {models: {}}
parts = a.sane(parts, `outlines.${outline_name}`, 'object')()
for (let [part_name, part] of Object.entries(parts)) {
const name = `outlines.exports.${key}.${part_name}`
const name = `outlines.${outline_name}.${part_name}`
// string part-shortcuts are expanded first
if (a.type(part)() == 'string') {
part = o.operation(part, {outline: Object.keys(outlines)})
}
const expected = ['type', 'operation']
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
if (part.operation == 'subtract') op = u.subtract
else if (part.operation == 'intersect') op = u.intersect
else if (part.operation == 'stack') op = u.stack
// process keys that are common to all part declarations
const operation = u[a.in(part.operation || 'add', `${name}.operation`, ['add', 'subtract', 'intersect', 'stack'])]
const what = a.in(part.what || 'outline', `${name}.what`, ['rectangle', 'circle', 'polygon', 'outline'])
const bound = !!part.bound
const asym = a.asym(part.asym || 'source', `${name}.asym`)
let arg
let anchor
const anchor_def = part.anchor || {}
switch (part.type) {
case 'keys':
arg = layout_fn(part, name, expected)
break
case 'rectangle':
a.unexpected(part, name, expected.concat(['anchor', 'size', 'corner', 'bevel', 'mirror']))
const size = a.wh(part.size, `${name}.size`)(units)
const rec_units = prep.extend({
sx: size[0],
sy: size[1]
}, units)
anchor = anchor_lib.parse(anchor_def, `${name}.anchor`, points)(rec_units)
const corner = a.sane(part.corner || 0, `${name}.corner`, 'number')(rec_units)
const bevel = a.sane(part.bevel || 0, `${name}.bevel`, 'number')(rec_units)
const rect_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean')()
const rect = rectangle(size[0], size[1], corner, bevel, name)
arg = anchor.position(u.deepcopy(rect))
if (rect_mirror) {
const mirror_anchor = u.deepcopy(anchor_def)
a.assert(mirror_anchor.ref, `Field "${name}.anchor.ref" must be speficied if mirroring is required!`)
anchor = anchor_lib.parse(mirror_anchor, `${name}.anchor --> mirror`, points, undefined, undefined, true)(rec_units)
const mirror_rect = m.model.moveRelative(u.deepcopy(rect), [-size[0], 0])
arg = u.union(arg, anchor.position(mirror_rect))
}
break
case 'circle':
a.unexpected(part, name, expected.concat(['anchor', 'radius', 'mirror']))
const radius = a.sane(part.radius, `${name}.radius`, 'number')(units)
const circle_units = prep.extend({
r: radius
}, units)
anchor = anchor_lib.parse(anchor_def, `${name}.anchor`, points)(circle_units)
const circle_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean')()
arg = u.circle(anchor.p, radius)
if (circle_mirror) {
const mirror_anchor = u.deepcopy(anchor_def)
a.assert(mirror_anchor.ref, `Field "${name}.anchor.ref" must be speficied if mirroring is required!`)
anchor = anchor_lib.parse(mirror_anchor, `${name}.anchor --> mirror`, points, undefined, undefined, true)(circle_units)
arg = u.union(arg, u.circle(anchor.p, radius))
}
break
case 'polygon':
a.unexpected(part, name, expected.concat(['points', 'mirror']))
const poly_points = a.sane(part.points, `${name}.points`, 'array')()
const poly_mirror = a.sane(part.mirror || false, `${name.mirror}`, 'boolean')()
const parsed_points = []
const mirror_points = []
let poly_mirror_x = 0
let last_anchor = new Point()
let poly_index = 0
for (const poly_point of poly_points) {
const poly_name = `${name}.points[${++poly_index}]`
if (poly_index == 1 && poly_mirror) {
a.assert(poly_point.ref, `Field "${poly_name}.ref" must be speficied if mirroring is required!`)
const mirrored_ref = anchor_lib.mirror(poly_point.ref, poly_mirror)
a.assert(points[poly_point.ref], `Field "${poly_name}.ref" does not name an existing point!`)
a.assert(points[mirrored_ref], `The mirror of field "${poly_name}.ref" ("${mirrored_ref}") does not name an existing point!`)
poly_mirror_x = (points[poly_point.ref].x + points[mirrored_ref].x) / 2
}
last_anchor = anchor_lib.parse(poly_point, poly_name, points, true, last_anchor)(units)
parsed_points.push(last_anchor.p)
mirror_points.push(last_anchor.clone().mirror(poly_mirror_x).p)
}
arg = u.poly(parsed_points)
if (poly_mirror) {
arg = u.union(arg, u.poly(mirror_points))
}
break
case 'outline':
a.unexpected(part, name, expected.concat(['name', 'fillet']))
a.assert(outlines[part.name], `Field "${name}.name" does not name an existing outline!`)
const fillet = a.sane(part.fillet || 0, `${name}.fillet`, 'number')(units)
arg = u.deepcopy(outlines[part.name])
if (fillet) {
for (const [index, chain] of m.model.findChains(arg).entries()) {
arg.models[`fillet_${index}`] = m.chain.fillet(chain, fillet)
}
}
break
default:
throw new Error(`Field "${name}.type" (${part.type}) does not name a valid outline part type!`)
// `where` is delayed until we have all, potentially what-dependent units
// default where is [0, 0], as per filter parsing
const original_where = part.where // need to save, so the delete's don't get rid of it below
const where = units => filter(original_where, `${name}.where`, points, units, asym)
const original_adjust = part.adjust // same as above
const fillet = a.sane(part.fillet || 0, `${name}.fillet`, 'number')(units)
expand_shorthand(part, `${name}.expand`, units)
const expand = a.sane(part.expand || 0, `${name}.expand`, 'number')(units)
const joints = a.in(a.sane(part.joints || 0, `${name}.joints`, 'number')(units), `${name}.joints`, [0, 1, 2])
const scale = a.sane(part.scale || 1, `${name}.scale`, 'number')(units)
// these keys are then removed, so ops can check their own unexpected keys without interference
delete part.operation
delete part.what
delete part.bound
delete part.asym
delete part.where
delete part.adjust
delete part.fillet
delete part.expand
delete part.joints
delete part.scale
// a prototype "shape" maker (and its units) are computed
const [shape_maker, shape_units] = whats[what](part, name, points, outlines, units)
const adjust = start => anchor(original_adjust || {}, `${name}.adjust`, points, start)(shape_units)
// and then the shape is repeated for all where positions
for (const w of where(shape_units)) {
const point = adjust(w.clone())
let [shape, bbox] = shape_maker(point) // point is passed for mirroring metadata only...
if (bound) {
shape = binding(shape, bbox, point, shape_units)
}
shape = point.position(shape) // ...actual positioning happens here
outlines[outline_name] = operation(outlines[outline_name], shape)
}
result = op(result, arg)
if (scale !== 1) {
outlines[outline_name] = m.model.scale(outlines[outline_name], scale)
}
if (expand) {
outlines[outline_name] = m.model.outline(
outlines[outline_name], Math.abs(expand), joints, (expand < 0), {farPoint: u.farPoint}
)
}
if (fillet) {
for (const [index, chain] of m.model.findChains(outlines[outline_name]).entries()) {
outlines[outline_name].models[`fillet_${part_name}_${index}`] = m.chain.fillet(chain, fillet)
}
}
}
m.model.originate(result)
m.model.simplify(result)
outlines[key] = result
// final adjustments
m.model.originate(outlines[outline_name])
m.model.simplify(outlines[outline_name])
}
return outlines
}
}

View file

@ -1,7 +1,11 @@
const m = require('makerjs')
const yaml = require('js-yaml')
const u = require('./utils')
const a = require('./assert')
const prep = require('./prepare')
const anchor_lib = require('./anchor')
const anchor = require('./anchor').parse
const filter = require('./filter').parse
const kicad_prefix = `
(kicad_pcb (version 20171130) (host pcbnew 5.1.6)
@ -113,7 +117,7 @@ const kicad_netclass = `
)
`
const makerjs2kicad = exports._makerjs2kicad = (model, layer='Edge.Cuts') => {
const makerjs2kicad = exports._makerjs2kicad = (model, layer) => {
const grs = []
const xy = val => `${val[0]} ${-val[1]}`
m.model.walk(model, {
@ -149,84 +153,140 @@ exports.inject_footprint = (name, fp) => {
footprint_types[name] = fp
}
const footprint = exports._footprint = (config, name, points, point, net_indexer, component_indexer, units, extra) => {
const xy_obj = (x, y) => {
return {
x,
y,
str: `${x} ${y}`,
toString: function() { return this.str }
}
}
if (config === false) return ''
const net_obj = (name, index) => {
return {
name,
index,
str: `(net ${index} "${name}")`,
toString: function() { return this.str }
}
}
const footprint = exports._footprint = (points, net_indexer, component_indexer, units, extra) => (config, name, point) => {
// config sanitization
a.unexpected(config, name, ['type', 'anchor', 'nets', 'anchors', 'params'])
const type = a.in(config.type, `${name}.type`, Object.keys(footprint_types))
let anchor = anchor_lib.parse(config.anchor || {}, `${name}.anchor`, points, true, point)(units)
const nets = a.sane(config.nets || {}, `${name}.nets`, 'object')()
const anchors = a.sane(config.anchors || {}, `${name}.anchors`, 'object')()
const params = a.sane(config.params || {}, `${name}.params`, 'object')()
a.unexpected(config, name, ['what', 'params'])
const what = a.in(config.what, `${name}.what`, Object.keys(footprint_types))
const fp = footprint_types[what]
const original_params = config.params || {}
// basic setup
const fp = footprint_types[type]
// param sanitization
// we unset the mirror config, as it would be an unexpected field
let params = u.deepcopy(original_params)
delete params.mirror
// but still override with it when applicable
if (point.meta.mirrored && original_params.mirror !== undefined) {
const mirror_overrides = a.sane(original_params.mirror, `${name}.params.mirror`, 'object')()
params = prep.extend(params, mirror_overrides)
}
a.unexpected(params, `${name}.params`, Object.keys(fp.params))
// parsing parameters
const parsed_params = {}
for (const [param_name, param_def] of Object.entries(fp.params)) {
// 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]
if (value === undefined) {
throw new Error(`Indirection "${name}.params.${param}" --> "${point.meta.name}.${indirect}" to undefined value!`)
// expand param definition shorthand
let parsed_def = param_def
let def_type = a.type(param_def)(units)
if (def_type == 'string') {
parsed_def = {type: 'string', value: param_def}
} else if (def_type == 'number') {
parsed_def = {type: 'number', value: a.mathnum(param_def)(units)}
} else if (def_type == 'boolean') {
parsed_def = {type: 'boolean', value: param_def}
} else if (def_type == 'array') {
parsed_def = {type: 'array', value: param_def}
} else if (def_type == 'object') {
// parsed param definitions also expand to an object
// so to detect whether this is an arbitrary object,
// we first have to make sure it's not an expanded param def
// (this has to be a heuristic, but should be pretty reliable)
const defarr = Object.keys(param_def)
const already_expanded = defarr.length == 2 && defarr.includes('type') && defarr.includes('value')
if (!already_expanded) {
parsed_def = {type: 'object', value: param_def}
}
} else {
parsed_def = {type: 'net', value: undefined}
}
// combine default value with potential user override
let value = params[param_name] !== undefined ? params[param_name] : parsed_def.value
const type = parsed_def.type
// templating support, with conversion back to raw datatypes
const converters = {
string: v => v,
number: v => a.sane(v, `${name}.params.${param_name}`, 'number')(units),
boolean: v => v === 'true',
array: v => yaml.load(v),
object: v => yaml.load(v),
net: v => v,
anchor: v => yaml.load(v)
}
a.in(type, `${name}.params.${param_name}.type`, Object.keys(converters))
if (a.type(value)() == 'string') {
value = u.template(value, point.meta)
value = converters[type](value)
}
// type-specific postprocessing
if (['string', 'number', 'boolean', 'array', 'object'].includes(type)) {
parsed_params[param_name] = value
} else if (type == 'net') {
const net = a.sane(value, `${name}.params.${param_name}`, 'string')(units)
const index = net_indexer(net)
parsed_params[param_name] = net_obj(net, index)
} else { // anchor
let parsed_anchor = anchor(value, `${name}.params.${param_name}`, points, point)(units)
parsed_anchor.y = -parsed_anchor.y // kicad mirror, as per usual
parsed_params[param_name] = parsed_anchor
}
parsed_params.param[param_name] = value
}
// reference
const component_ref = parsed_params.ref = component_indexer(parsed_params.param.class || '_')
const component_ref = parsed_params.ref = component_indexer(parsed_params.designator || '_')
parsed_params.ref_hide = extra.references ? '' : 'hide'
// footprint positioning
parsed_params.at = `(at ${anchor.x} ${-anchor.y} ${anchor.r})`
parsed_params.rot = anchor.r
parsed_params.xy = (x, y) => {
const new_anchor = anchor_lib.parse({
shift: [x, -y]
}, '_internal_footprint_xy', points, true, anchor)(units)
return `${new_anchor.x} ${-new_anchor.y}`
}
parsed_params.x = point.x
parsed_params.y = -point.y
parsed_params.r = point.r
parsed_params.rot = point.r // to be deprecated
parsed_params.xy = `${point.x} ${-point.y}`
parsed_params.at = `(at ${point.x} ${-point.y} ${point.r})`
// 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) {
const indirect = net.substring(1)
net = point.meta[indirect]
net = a.sane(net, `${name}.nets.${net_name} --> ${point.meta.name}.${indirect}`, 'string')()
}
const index = net_indexer(net)
parsed_params.net[net_name] = {
name: net,
index: index,
str: `(net ${index} "${net}")`
}
const internal_xyfunc = (x, y, resist) => {
const sign = resist ? 1 : (point.meta.mirrored ? -1 : 1)
return xy_obj(sign * x, y)
}
parsed_params.isxy = (x, y) => internal_xyfunc(x, y, false)
parsed_params.iaxy = (x, y) => internal_xyfunc(x, y, true)
const external_xyfunc = (x, y, resist) => {
const new_anchor = anchor({
shift: [x, -y],
resist: resist
}, '_internal_footprint_xy', points, point)(units)
return xy_obj(new_anchor.x, -new_anchor.y)
}
parsed_params.esxy = (x, y) => external_xyfunc(x, y, false)
parsed_params.eaxy = (x, y) => external_xyfunc(x, y, true)
// 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}")`
}
}
// parsing anchor-type parameters
parsed_params.anchors = {}
for (const [anchor_name, anchor_config] of Object.entries(prep.extend(fp.anchors || {}, anchors))) {
let parsed_anchor = anchor_lib.parse(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, true, anchor)(units)
parsed_anchor.y = -parsed_anchor.y
parsed_params.anchors[anchor_name] = parsed_anchor
return net_obj(net, index)
}
return fp.body(parsed_params)
@ -273,21 +333,27 @@ exports.parse = (config, points, outlines, units) => {
}
const footprints = []
const footprint_factory = footprint(points, net_indexer, component_indexer, units, {references})
// key-level footprints
for (const [p_name, point] of Object.entries(points)) {
for (const [f_name, f] of Object.entries(point.meta.footprints || {})) {
footprints.push(footprint(f, `${p_name}.footprints.${f_name}`, points, point, net_indexer, component_indexer, units, {references}))
}
}
// global one-off footprints
// generate 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, units, {references}))
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')()
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)
delete f.asym
delete f.where
delete f.adjust
for (const w of where) {
const aw = adjust(w.clone())
footprints.push(footprint_factory(f, name, aw))
}
}
// finalizing nets

View file

@ -24,7 +24,8 @@ module.exports = class Point {
[this.x, this.y] = val
}
shift(s, relative=true) {
shift(s, relative=true, resist=false) {
s[0] *= (!resist && this.meta.mirrored) ? -1 : 1
if (relative) {
s = m.point.rotate(s, this.r)
}
@ -33,8 +34,11 @@ module.exports = class Point {
return this
}
rotate(angle, origin=[0, 0]) {
this.p = m.point.rotate(this.p, angle, origin)
rotate(angle, origin=[0, 0], resist=false) {
angle *= (!resist && this.meta.mirrored) ? -1 : 1
if (origin) {
this.p = m.point.rotate(this.p, angle, origin)
}
this.r += angle
return this
}
@ -58,8 +62,25 @@ module.exports = class Point {
return m.model.moveRelative(m.model.rotate(model, this.r), this.p)
}
unposition(model) {
return m.model.rotate(m.model.moveRelative(model, [-this.x, -this.y]), -this.r)
}
rect(size=14) {
let rect = u.rect(size, size, [-size/2, -size/2], this.meta.mirrored)
let rect = u.rect(size, size, [-size/2, -size/2])
return this.position(rect)
}
angle(other) {
const dx = other.x - this.x
const dy = other.y - this.y
return -Math.atan2(dx, dy) * (180 / Math.PI)
}
equals(other) {
return this.x === other.x
&& this.y === other.y
&& this.r === other.r
&& JSON.stringify(this.meta) === JSON.stringify(other.meta)
}
}

View file

@ -21,10 +21,10 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key,
a.unexpected(zone, `points.zones.${zone_name}`, ['columns', 'rows', 'key'])
// the anchor comes from "above", because it needs other zones too (for references)
const cols = a.sane(zone.columns || {}, `points.zones.${zone_name}.columns`, 'object')()
const cols = zone.columns = a.sane(zone.columns || {}, `points.zones.${zone_name}.columns`, 'object')()
const zone_wide_rows = a.sane(zone.rows || {}, `points.zones.${zone_name}.rows`, 'object')()
for (const [key, val] of Object.entries(zone_wide_rows)) {
zone_wide_rows[key] = a.sane(val || {}, `points.zones.${zone_name}.rows.${key}`, 'object')()
zone_wide_rows[key] = val || {} // no check yet, as it will be extended later
}
const zone_wide_key = a.sane(zone.key || {}, `points.zones.${zone_name}.key`, 'object')()
@ -32,11 +32,14 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key,
const points = {}
const rotations = []
const zone_anchor = anchor.clone()
// transferring the anchor rotation to "real" rotations
rotations.push({
angle: anchor.r,
origin: anchor.p
angle: zone_anchor.r,
origin: zone_anchor.p
})
// and now clear it from the anchor so that we don't apply it twice
zone_anchor.r = 0
// column layout
@ -53,47 +56,15 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key,
a.unexpected(
col,
`points.zones.${zone_name}.columns.${col_name}`,
['stagger', 'spread', 'rotate', 'origin', 'rows', 'row_overrides', 'key']
['rows', 'key']
)
col.stagger = a.sane(
col.stagger || 0,
`points.zones.${zone_name}.columns.${col_name}.stagger`,
'number'
)(units)
col.spread = a.sane(
col.spread !== undefined ? col.spread : (first_col ? 0 : 'u'),
`points.zones.${zone_name}.columns.${col_name}.spread`,
'number'
)(units)
col.rotate = a.sane(
col.rotate || 0,
`points.zones.${zone_name}.columns.${col_name}.rotate`,
'number'
)(units)
col.origin = a.xy(
col.origin || [0, 0],
`points.zones.${zone_name}.columns.${col_name}.origin`
)(units)
let override = false
col.rows = a.sane(
col.rows || {},
`points.zones.${zone_name}.columns.${col_name}.rows`,
'object'
)()
if (col.row_overrides) {
override = true
col.rows = a.sane(
col.row_overrides,
`points.zones.${zone_name}.columns.${col_name}.row_overrides`,
'object'
)()
}
for (const [key, val] of Object.entries(col.rows)) {
col.rows[key] = a.sane(
val || {},
`points.zones.${zone_name}.columns.${col_name}.rows.${key}`,
'object'
)()
col.rows[key] = val || {} // again, no check yet, as it will be extended later
}
col.key = a.sane(
col.key || {},
@ -101,49 +72,33 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key,
'object'
)()
// propagating object key to name field
col.name = col_name
// combining row data from zone-wide defs and col-specific defs
// (while also handling potential overrides)
const actual_rows = override ? Object.keys(col.rows)
: Object.keys(prep.extend(zone_wide_rows, col.rows))
const actual_rows = Object.keys(prep.extend(zone_wide_rows, col.rows))
if (!actual_rows.length) {
actual_rows.push('default')
}
// setting up column-level anchor
anchor.x += col.spread
anchor.y += col.stagger
const col_anchor = anchor.clone()
// clear potential rotations, as they will get re-applied anyway
// and we don't want to apply them twice...
col_anchor.r = 0
// applying col-level rotation (cumulatively, for the next columns as well)
if (col.rotate) {
push_rotation(
rotations,
col.rotate,
col_anchor.clone().shift(col.origin, false).p
)
}
// getting key config through the 5-level extension
const keys = []
const default_key = {
stagger: units.$default_stagger,
spread: units.$default_spread,
splay: units.$default_splay,
origin: [0, 0],
orient: 0,
shift: [0, 0],
rotate: 0,
padding: 'u',
width: 1,
height: 1,
adjust: {},
width: units.$default_width,
height: units.$default_height,
padding: units.$default_padding,
autobind: units.$default_autobind,
skip: false,
asym: 'both'
asym: 'both',
colrow: '{{col.name}}_{{row}}',
name: '{{zone.name}}_{{colrow}}'
}
for (const row of actual_rows) {
const key = prep.extend(
@ -155,32 +110,80 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key,
col.rows[row] || {}
)
key.name = key.name || `${zone_name}_${col_name}_${row}`
key.colrow = `${col_name}_${row}`
key.zone = zone
key.zone.name = zone_name
key.col = col
key.col.name = col_name
key.row = row
key.stagger = a.sane(key.stagger, `${key.name}.stagger`, 'number')(units)
key.spread = a.sane(key.spread, `${key.name}.spread`, 'number')(units)
key.splay = a.sane(key.splay, `${key.name}.splay`, 'number')(units)
key.origin = a.xy(key.origin, `${key.name}.origin`)(units)
key.orient = a.sane(key.orient, `${key.name}.orient`, 'number')(units)
key.shift = a.xy(key.shift, `${key.name}.shift`)(units)
key.rotate = a.sane(key.rotate, `${key.name}.rotate`, 'number')(units)
key.width = a.sane(key.width, `${key.name}.width`, 'number')(units)
key.height = a.sane(key.height, `${key.name}.height`, 'number')(units)
key.padding = a.sane(key.padding, `${key.name}.padding`, 'number')(units)
key.skip = a.sane(key.skip, `${key.name}.skip`, 'boolean')()
key.asym = a.in(key.asym, `${key.name}.asym`, ['left', 'right', 'both'])
key.col = col
key.row = row
key.asym = a.asym(key.asym, `${key.name}.asym`)
// templating support
for (const [k, v] of Object.entries(key)) {
if (a.type(v)(units) == 'string') {
key[k] = u.template(v, key)
}
}
keys.push(key)
}
// setting up column-level anchor
if (!first_col) {
zone_anchor.x += keys[0].spread
}
zone_anchor.y += keys[0].stagger
const col_anchor = zone_anchor.clone()
// applying col-level rotation (cumulatively, for the next columns as well)
if (keys[0].splay) {
push_rotation(
rotations,
keys[0].splay,
col_anchor.clone().shift(keys[0].origin, false).p
)
}
// actually laying out keys
let running_anchor = col_anchor.clone()
for (const r of rotations) {
running_anchor.rotate(r.angle, r.origin)
}
for (const key of keys) {
let point = col_anchor.clone()
for (const r of rotations) {
point.rotate(r.angle, r.origin)
}
// copy the current column anchor
let point = running_anchor.clone()
// apply cumulative per-key adjustments
point.r += key.orient
point.shift(key.shift)
point.r += key.rotate
// commit running anchor
running_anchor = point.clone()
// apply independent adjustments
point = anchor_lib.parse(key.adjust, `${key.name}.adjust`, {}, point)(units)
// save new key
point.meta = key
points[key.name] = point
col_anchor.y += key.padding
// advance the running anchor to the next position
running_anchor.shift([0, key.padding])
}
first_col = false
@ -191,7 +194,7 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key,
const parse_axis = exports._parse_axis = (config, name, points, units) => {
if (!['number', 'undefined'].includes(a.type(config)(units))) {
const mirror_obj = a.sane(config || {}, name, 'object')()
const mirror_obj = a.sane(config, name, 'object')()
const distance = a.sane(mirror_obj.distance || 0, `${name}.distance`, 'number')(units)
delete mirror_obj.distance
let axis = anchor_lib.parse(mirror_obj, name, points)(units).x
@ -201,21 +204,91 @@ const parse_axis = exports._parse_axis = (config, name, points, units) => {
}
const perform_mirror = exports._perform_mirror = (point, axis) => {
if (axis !== undefined) {
point.meta.mirrored = false
if (point.meta.asym == 'left') return ['', null]
const mp = point.clone().mirror(axis)
const mirrored_name = `mirror_${point.meta.name}`
mp.meta = prep.extend(mp.meta, mp.meta.mirror || {})
mp.meta.name = mirrored_name
mp.meta.colrow = `mirror_${mp.meta.colrow}`
mp.meta.mirrored = true
if (point.meta.asym == 'right') {
point.meta.skip = true
}
return [mirrored_name, mp]
point.meta.mirrored = false
if (point.meta.asym == 'source') return ['', null]
const mp = point.clone().mirror(axis)
const mirrored_name = `mirror_${point.meta.name}`
mp.meta = prep.extend(mp.meta, mp.meta.mirror || {})
mp.meta.name = mirrored_name
mp.meta.colrow = `mirror_${mp.meta.colrow}`
mp.meta.mirrored = true
if (point.meta.asym == 'clone') {
point.meta.skip = true
}
return [mirrored_name, mp]
}
const perform_autobind = exports._perform_autobind = (points, units) => {
const bounds = {}
const col_lists = {}
const mirrorzone = p => (p.meta.mirrored ? 'mirror_' : '') + p.meta.zone.name
// round one: get column upper/lower bounds and per-zone column lists
for (const p of Object.values(points)) {
const zone = mirrorzone(p)
const col = p.meta.col.name
if (!bounds[zone]) bounds[zone] = {}
if (!bounds[zone][col]) bounds[zone][col] = {min: Infinity, max: -Infinity}
if (!col_lists[zone]) col_lists[zone] = Object.keys(p.meta.zone.columns)
bounds[zone][col].min = Math.min(bounds[zone][col].min, p.y)
bounds[zone][col].max = Math.max(bounds[zone][col].max, p.y)
}
// round two: apply autobind as appropriate
for (const p of Object.values(points)) {
const autobind = a.sane(p.meta.autobind, `${p.meta.name}.autobind`, 'number')(units)
if (!autobind) continue
const zone = mirrorzone(p)
const col = p.meta.col.name
const col_list = col_lists[zone]
const col_bounds = bounds[zone][col]
// specify default as -1, so we can recognize where it was left undefined even after number-ification
const bind = p.meta.bind = a.trbl(p.meta.bind, `${p.meta.name}.bind`, -1)(units)
// up
if (bind[0] == -1) {
if (p.y < col_bounds.max) bind[0] = autobind
else bind[0] = 0
}
// down
if (bind[2] == -1) {
if (p.y > col_bounds.min) bind[2] = autobind
else bind[2] = 0
}
// left
if (bind[3] == -1) {
bind[3] = 0
const col_index = col_list.indexOf(col)
if (col_index > 0) {
const left = bounds[zone][col_list[col_index - 1]]
if (left && p.y >= left.min && p.y <= left.max) {
bind[3] = autobind
}
}
}
// right
if (bind[1] == -1) {
bind[1] = 0
const col_index = col_list.indexOf(col)
if (col_index < col_list.length - 1) {
const right = bounds[zone][col_list[col_index + 1]]
if (right && p.y >= right.min && p.y <= right.max) {
bind[1] = autobind
}
}
}
}
return ['', null]
}
exports.parse = (config, units) => {
@ -227,15 +300,15 @@ exports.parse = (config, units) => {
const global_rotate = a.sane(config.rotate || 0, 'points.rotate', 'number')(units)
const global_mirror = config.mirror
let points = {}
let mirrored_points = {}
let all_points = {}
// rendering zones
for (let [zone_name, zone] of Object.entries(zones)) {
// zone sanitization
zone = a.sane(zone || {}, `points.zones.${zone_name}`, 'object')()
// extracting keys that are handled here, not at the zone render level
const anchor = anchor_lib.parse(zone.anchor || {}, `points.zones.${zone_name}.anchor`, all_points)(units)
const anchor = anchor_lib.parse(zone.anchor || {}, `points.zones.${zone_name}.anchor`, points)(units)
const rotate = a.sane(zone.rotate || 0, `points.zones.${zone_name}.rotate`, 'number')(units)
const mirror = zone.mirror
delete zone.anchor
@ -243,7 +316,17 @@ exports.parse = (config, units) => {
delete zone.mirror
// creating new points
const new_points = render_zone(zone_name, zone, anchor, global_key, units)
let new_points = render_zone(zone_name, zone, anchor, global_key, units)
// simplifying the names in individual point "zones" and single-key columns
while (Object.keys(new_points).some(k => k.endsWith('_default'))) {
for (const key of Object.keys(new_points).filter(k => k.endsWith('_default'))) {
const new_key = key.slice(0, -8)
new_points[new_key] = new_points[key]
new_points[new_key].meta.name = new_key
delete new_points[key]
}
}
// adjusting new points
for (const [new_name, new_point] of Object.entries(new_points)) {
@ -261,24 +344,21 @@ exports.parse = (config, units) => {
// adding new points so that they can be referenced from now on
points = Object.assign(points, new_points)
all_points = Object.assign(all_points, points)
// per-zone mirroring for the new keys
const axis = parse_axis(mirror, `points.zones.${zone_name}.mirror`, all_points, units)
if (axis) {
const axis = parse_axis(mirror, `points.zones.${zone_name}.mirror`, points, units)
if (axis !== undefined) {
const mirrored_points = {}
for (const new_point of Object.values(new_points)) {
const [mname, mp] = perform_mirror(new_point, axis)
if (mp) {
mirrored_points[mname] = mp
all_points[mname] = mp
}
}
points = Object.assign(points, mirrored_points)
}
}
// merging regular and early-mirrored points
points = Object.assign(points, mirrored_points)
// applying global rotation
for (const point of Object.values(points)) {
if (global_rotate) {
@ -290,15 +370,13 @@ exports.parse = (config, units) => {
const global_axis = parse_axis(global_mirror, `points.mirror`, points, units)
const global_mirrored_points = {}
for (const point of Object.values(points)) {
if (global_axis && point.mirrored === undefined) {
if (global_axis !== undefined && point.meta.mirrored === undefined) {
const [mname, mp] = perform_mirror(point, global_axis)
if (mp) {
global_mirrored_points[mname] = mp
}
}
}
// merging the global-mirrored points as well
points = Object.assign(points, global_mirrored_points)
// removing temporary points
@ -308,17 +386,18 @@ exports.parse = (config, units) => {
filtered[k] = p
}
// apply autobind
perform_autobind(filtered, units)
// done
return filtered
}
exports.visualize = (points, units) => {
const models = {}
const x_unit = units.visual_x || (units.u - 1)
const y_unit = units.visual_y || (units.u - 1)
for (const [pname, p] of Object.entries(points)) {
const w = p.meta.width * x_unit
const h = p.meta.height * y_unit
const w = p.meta.width
const h = p.meta.height
const rect = u.rect(w, h, [-w/2, -h/2])
models[pname] = p.position(rect)
}

View file

@ -42,11 +42,14 @@ const traverse = exports.traverse = (config, root, breadcrumbs, op) => {
}
return result
} else if (a.type(config)() == 'array') {
// needed so that arrays can set output the same way as objects within ops
const dummy = {}
const result = []
let index = 0
for (const val of config) {
breadcrumbs.push(`[${index}]`)
result[index] = traverse(val, root, breadcrumbs, op)
op(dummy, 'dummykey', traverse(val, root, breadcrumbs, op), root, breadcrumbs)
result[index] = dummy.dummykey
breadcrumbs.pop()
index++
}
@ -73,7 +76,7 @@ exports.inherit = config => traverse(config, config, [], (target, key, val, root
candidates = candidates.concat(parents)
list.unshift(other)
}
val = extend.apply(this, list)
val = extend.apply(null, list)
delete val.$extends
}
target[key] = val

View file

@ -5,7 +5,14 @@ const default_units = {
U: 19.05,
u: 19,
cx: 18,
cy: 17
cy: 17,
$default_stagger: 0,
$default_spread: 'u',
$default_splay: 0,
$default_height: 'u-1',
$default_width: 'u-1',
$default_padding: 'u',
$default_autobind: 10
}
exports.parse = (config = {}) => {

View file

@ -18,6 +18,20 @@ const deep = exports.deep = (obj, key, val) => {
return obj
}
exports.template = (str, vals={}) => {
const regex = /\{\{([^}]*)\}\}/g
let res = str
let shift = 0
for (const match of str.matchAll(regex)) {
const replacement = (deep(vals, match[1]) || '') + ''
res = res.substring(0, match.index + shift)
+ replacement
+ res.substring(match.index + shift + match[0].length)
shift += replacement.length - match[0].length
}
return res
}
const eq = exports.eq = (a=[], b=[]) => {
return a[0] === b[0] && a[1] === b[1]
}
@ -54,9 +68,23 @@ exports.poly = (arr) => {
return res
}
const farPoint = [1234.1234, 2143.56789]
exports.bbox = (arr) => {
let minx = Infinity
let miny = Infinity
let maxx = -Infinity
let maxy = -Infinity
for (const p of arr) {
minx = Math.min(minx, p[0])
miny = Math.min(miny, p[1])
maxx = Math.max(maxx, p[0])
maxy = Math.max(maxy, p[1])
}
return {low: [minx, miny], high: [maxx, maxy]}
}
exports.union = (a, b) => {
const farPoint = exports.farPoint = [1234.1234, 2143.56789]
exports.union = exports.add = (a, b) => {
return m.model.combine(a, b, false, true, false, true, {
farPoint
})
@ -80,4 +108,29 @@ exports.stack = (a, b) => {
a, b
}
}
}
const semver = exports.semver = (str, name='') => {
let main = str.split('-')[0]
if (main.startsWith('v')) {
main = main.substring(1)
}
while (main.split('.').length < 3) {
main += '.0'
}
if (/^\d+\.\d+\.\d+$/.test(main)) {
const parts = main.split('.').map(part => parseInt(part, 10))
return {major: parts[0], minor: parts[1], patch: parts[2]}
} else throw new Error(`Invalid semver "${str}" at ${name}!`)
}
const satisfies = exports.satisfies = (current, expected) => {
if (current.major === undefined) current = semver(current)
if (expected.major === undefined) expected = semver(expected)
return current.major === expected.major && (
current.minor > expected.minor || (
current.minor === expected.minor &&
current.patch >= expected.patch
)
)
}

View file

@ -1,16 +0,0 @@
points:
zones:
arst:
columns:
c1:
rows:
r1:
outlines:
exports:
square:
- type: rectangle
size: [5, 5]
cases:
cube:
- name: square
extrude: 5

View file

@ -1,86 +0,0 @@
solid csg.js
facet normal 0 0 -1
outer loop
vertex 0 5 0
vertex 5 5 0
vertex 5 0 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 5 0
vertex 5 0 0
vertex 0 0 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 5 5
vertex 0 0 5
vertex 5 0 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 5 5
vertex 5 0 5
vertex 5 5 5
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 5 0 0
vertex 5 0 5
vertex 0 0 5
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 5 0 0
vertex 0 0 5
vertex 0 0 0
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 5 5 0
vertex 5 5 5
vertex 5 0 5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 5 5 0
vertex 5 0 5
vertex 5 0 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 5 5 5
vertex 5 5 0
vertex 0 5 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 5 5 5
vertex 0 5 0
vertex 0 5 5
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0 5 5
vertex 0 5 0
vertex 0 0 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0 5 5
vertex 0 0 0
vertex 0 0 5
endloop
endfacet
endsolid csg.js

10
test/cases/cube.yaml Normal file
View file

@ -0,0 +1,10 @@
points.zones.matrix: {}
outlines:
square:
- what: rectangle
where: true
size: [5, 5]
cases:
cube:
- name: square
extrude: 5

View file

@ -0,0 +1,36 @@
function square_extrude_5_outline_fn(){
return new CSG.Path2D([[-2.5,-2.5],[2.5,-2.5]]).appendPoint([2.5,2.5]).appendPoint([-2.5,2.5]).appendPoint([-2.5,-2.5]).close().innerToCAG()
.extrude({ offset: [0, 0, 5] });
}
function cube_case_fn() {
// creating part 0 of case cube
let cube__part_0 = square_extrude_5_outline_fn();
// make sure that rotations are relative
let cube__part_0_bounds = cube__part_0.getBounds();
let cube__part_0_x = cube__part_0_bounds[0].x + (cube__part_0_bounds[1].x - cube__part_0_bounds[0].x) / 2
let cube__part_0_y = cube__part_0_bounds[0].y + (cube__part_0_bounds[1].y - cube__part_0_bounds[0].y) / 2
cube__part_0 = translate([-cube__part_0_x, -cube__part_0_y, 0], cube__part_0);
cube__part_0 = rotate([0,0,0], cube__part_0);
cube__part_0 = translate([cube__part_0_x, cube__part_0_y, 0], cube__part_0);
cube__part_0 = translate([0,0,0], cube__part_0);
let result = cube__part_0;
return result;
}
function main() {
return cube_case_fn();
}

View file

@ -0,0 +1,36 @@
points.zones.matrix: {}
outlines:
_square:
- what: rectangle
where: true
size: [8, 8]
_circle:
- what: circle
where: true
radius: 3
cases:
_cube:
- name: _square
extrude: 8
_cylinder_one:
- name: _circle
extrude: 8
_subtract:
target:
name: _cube
what: case
tool:
name: _cylinder_one
what: case
operation: subtract
_cylinder_two:
- name: _circle
extrude: 8
shift: [0,4,4]
rotate: [90,0,0]
_flat_square:
- "_square"
combination:
- "_subtract"
- "~_cylinder_two"
- "+_flat_square"

View file

@ -0,0 +1,211 @@
function _square_extrude_8_outline_fn(){
return new CSG.Path2D([[-4,-4],[4,-4]]).appendPoint([4,4]).appendPoint([-4,4]).appendPoint([-4,-4]).close().innerToCAG()
.extrude({ offset: [0, 0, 8] });
}
function _circle_extrude_8_outline_fn(){
return CAG.circle({"center":[0,0],"radius":3})
.extrude({ offset: [0, 0, 8] });
}
function _square_extrude_1_outline_fn(){
return new CSG.Path2D([[-4,-4],[4,-4]]).appendPoint([4,4]).appendPoint([-4,4]).appendPoint([-4,-4]).close().innerToCAG()
.extrude({ offset: [0, 0, 1] });
}
function _subtract_case_fn() {
// creating part target of case _subtract
let _subtract__part_target = _cube_case_fn();
// make sure that rotations are relative
let _subtract__part_target_bounds = _subtract__part_target.getBounds();
let _subtract__part_target_x = _subtract__part_target_bounds[0].x + (_subtract__part_target_bounds[1].x - _subtract__part_target_bounds[0].x) / 2
let _subtract__part_target_y = _subtract__part_target_bounds[0].y + (_subtract__part_target_bounds[1].y - _subtract__part_target_bounds[0].y) / 2
_subtract__part_target = translate([-_subtract__part_target_x, -_subtract__part_target_y, 0], _subtract__part_target);
_subtract__part_target = rotate([0,0,0], _subtract__part_target);
_subtract__part_target = translate([_subtract__part_target_x, _subtract__part_target_y, 0], _subtract__part_target);
_subtract__part_target = translate([0,0,0], _subtract__part_target);
let result = _subtract__part_target;
// creating part tool of case _subtract
let _subtract__part_tool = _cylinder_one_case_fn();
// make sure that rotations are relative
let _subtract__part_tool_bounds = _subtract__part_tool.getBounds();
let _subtract__part_tool_x = _subtract__part_tool_bounds[0].x + (_subtract__part_tool_bounds[1].x - _subtract__part_tool_bounds[0].x) / 2
let _subtract__part_tool_y = _subtract__part_tool_bounds[0].y + (_subtract__part_tool_bounds[1].y - _subtract__part_tool_bounds[0].y) / 2
_subtract__part_tool = translate([-_subtract__part_tool_x, -_subtract__part_tool_y, 0], _subtract__part_tool);
_subtract__part_tool = rotate([0,0,0], _subtract__part_tool);
_subtract__part_tool = translate([_subtract__part_tool_x, _subtract__part_tool_y, 0], _subtract__part_tool);
_subtract__part_tool = translate([0,0,0], _subtract__part_tool);
result = result.subtract(_subtract__part_tool);
return result;
}
function _cube_case_fn() {
// creating part 0 of case _cube
let _cube__part_0 = _square_extrude_8_outline_fn();
// make sure that rotations are relative
let _cube__part_0_bounds = _cube__part_0.getBounds();
let _cube__part_0_x = _cube__part_0_bounds[0].x + (_cube__part_0_bounds[1].x - _cube__part_0_bounds[0].x) / 2
let _cube__part_0_y = _cube__part_0_bounds[0].y + (_cube__part_0_bounds[1].y - _cube__part_0_bounds[0].y) / 2
_cube__part_0 = translate([-_cube__part_0_x, -_cube__part_0_y, 0], _cube__part_0);
_cube__part_0 = rotate([0,0,0], _cube__part_0);
_cube__part_0 = translate([_cube__part_0_x, _cube__part_0_y, 0], _cube__part_0);
_cube__part_0 = translate([0,0,0], _cube__part_0);
let result = _cube__part_0;
return result;
}
function _cylinder_one_case_fn() {
// creating part 0 of case _cylinder_one
let _cylinder_one__part_0 = _circle_extrude_8_outline_fn();
// make sure that rotations are relative
let _cylinder_one__part_0_bounds = _cylinder_one__part_0.getBounds();
let _cylinder_one__part_0_x = _cylinder_one__part_0_bounds[0].x + (_cylinder_one__part_0_bounds[1].x - _cylinder_one__part_0_bounds[0].x) / 2
let _cylinder_one__part_0_y = _cylinder_one__part_0_bounds[0].y + (_cylinder_one__part_0_bounds[1].y - _cylinder_one__part_0_bounds[0].y) / 2
_cylinder_one__part_0 = translate([-_cylinder_one__part_0_x, -_cylinder_one__part_0_y, 0], _cylinder_one__part_0);
_cylinder_one__part_0 = rotate([0,0,0], _cylinder_one__part_0);
_cylinder_one__part_0 = translate([_cylinder_one__part_0_x, _cylinder_one__part_0_y, 0], _cylinder_one__part_0);
_cylinder_one__part_0 = translate([0,0,0], _cylinder_one__part_0);
let result = _cylinder_one__part_0;
return result;
}
function _cylinder_two_case_fn() {
// creating part 0 of case _cylinder_two
let _cylinder_two__part_0 = _circle_extrude_8_outline_fn();
// make sure that rotations are relative
let _cylinder_two__part_0_bounds = _cylinder_two__part_0.getBounds();
let _cylinder_two__part_0_x = _cylinder_two__part_0_bounds[0].x + (_cylinder_two__part_0_bounds[1].x - _cylinder_two__part_0_bounds[0].x) / 2
let _cylinder_two__part_0_y = _cylinder_two__part_0_bounds[0].y + (_cylinder_two__part_0_bounds[1].y - _cylinder_two__part_0_bounds[0].y) / 2
_cylinder_two__part_0 = translate([-_cylinder_two__part_0_x, -_cylinder_two__part_0_y, 0], _cylinder_two__part_0);
_cylinder_two__part_0 = rotate([90,0,0], _cylinder_two__part_0);
_cylinder_two__part_0 = translate([_cylinder_two__part_0_x, _cylinder_two__part_0_y, 0], _cylinder_two__part_0);
_cylinder_two__part_0 = translate([0,4,4], _cylinder_two__part_0);
let result = _cylinder_two__part_0;
return result;
}
function _flat_square_case_fn() {
// creating part 0 of case _flat_square
let _flat_square__part_0 = _square_extrude_1_outline_fn();
// make sure that rotations are relative
let _flat_square__part_0_bounds = _flat_square__part_0.getBounds();
let _flat_square__part_0_x = _flat_square__part_0_bounds[0].x + (_flat_square__part_0_bounds[1].x - _flat_square__part_0_bounds[0].x) / 2
let _flat_square__part_0_y = _flat_square__part_0_bounds[0].y + (_flat_square__part_0_bounds[1].y - _flat_square__part_0_bounds[0].y) / 2
_flat_square__part_0 = translate([-_flat_square__part_0_x, -_flat_square__part_0_y, 0], _flat_square__part_0);
_flat_square__part_0 = rotate([0,0,0], _flat_square__part_0);
_flat_square__part_0 = translate([_flat_square__part_0_x, _flat_square__part_0_y, 0], _flat_square__part_0);
_flat_square__part_0 = translate([0,0,0], _flat_square__part_0);
let result = _flat_square__part_0;
return result;
}
function combination_case_fn() {
// creating part 0 of case combination
let combination__part_0 = _subtract_case_fn();
// make sure that rotations are relative
let combination__part_0_bounds = combination__part_0.getBounds();
let combination__part_0_x = combination__part_0_bounds[0].x + (combination__part_0_bounds[1].x - combination__part_0_bounds[0].x) / 2
let combination__part_0_y = combination__part_0_bounds[0].y + (combination__part_0_bounds[1].y - combination__part_0_bounds[0].y) / 2
combination__part_0 = translate([-combination__part_0_x, -combination__part_0_y, 0], combination__part_0);
combination__part_0 = rotate([0,0,0], combination__part_0);
combination__part_0 = translate([combination__part_0_x, combination__part_0_y, 0], combination__part_0);
combination__part_0 = translate([0,0,0], combination__part_0);
let result = combination__part_0;
// creating part 1 of case combination
let combination__part_1 = _cylinder_two_case_fn();
// make sure that rotations are relative
let combination__part_1_bounds = combination__part_1.getBounds();
let combination__part_1_x = combination__part_1_bounds[0].x + (combination__part_1_bounds[1].x - combination__part_1_bounds[0].x) / 2
let combination__part_1_y = combination__part_1_bounds[0].y + (combination__part_1_bounds[1].y - combination__part_1_bounds[0].y) / 2
combination__part_1 = translate([-combination__part_1_x, -combination__part_1_y, 0], combination__part_1);
combination__part_1 = rotate([0,0,0], combination__part_1);
combination__part_1 = translate([combination__part_1_x, combination__part_1_y, 0], combination__part_1);
combination__part_1 = translate([0,0,0], combination__part_1);
result = result.intersect(combination__part_1);
// creating part 2 of case combination
let combination__part_2 = _flat_square_case_fn();
// make sure that rotations are relative
let combination__part_2_bounds = combination__part_2.getBounds();
let combination__part_2_x = combination__part_2_bounds[0].x + (combination__part_2_bounds[1].x - combination__part_2_bounds[0].x) / 2
let combination__part_2_y = combination__part_2_bounds[0].y + (combination__part_2_bounds[1].y - combination__part_2_bounds[0].y) / 2
combination__part_2 = translate([-combination__part_2_x, -combination__part_2_y, 0], combination__part_2);
combination__part_2 = rotate([0,0,0], combination__part_2);
combination__part_2 = translate([combination__part_2_x, combination__part_2_y, 0], combination__part_2);
combination__part_2 = translate([0,0,0], combination__part_2);
result = result.union(combination__part_2);
return result;
}
function main() {
return combination_case_fn();
}

View file

@ -0,0 +1 @@
node src/cli.js test/

View file

@ -0,0 +1 @@
Could not read config file "test/"!

View file

@ -1 +1 @@
Error: Input doesn't resolve into an object!
Error: Input doesn't resolve into an object!

View file

@ -5,7 +5,7 @@ Preprocessing input...
Calculating variables...
Parsing points...
Generating outlines...
Extruding cases...
Modeling cases...
Scaffolding PCBs...
Cleaning output folder...
Writing output to disk...

View file

@ -1,4 +1,4 @@
function export_outline_fn(){
function export_extrude_1_outline_fn(){
return new CSG.Path2D([[-9,-9],[9,-9]]).appendPoint([9,9]).appendPoint([-9,9]).appendPoint([-9,-9]).close().innerToCAG()
.extrude({ offset: [0, 0, 1] });
}
@ -10,7 +10,7 @@ function export_outline_fn(){
// creating part 0 of case _export
let _export__part_0 = export_outline_fn();
let _export__part_0 = export_extrude_1_outline_fn();
// make sure that rotations are relative
let _export__part_0_bounds = _export__part_0.getBounds();

View file

@ -1,86 +0,0 @@
solid csg.js
facet normal 0 0 -1
outer loop
vertex -9 9 0
vertex 9 9 0
vertex 9 -9 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex -9 9 0
vertex 9 -9 0
vertex -9 -9 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex -9 9 1
vertex -9 -9 1
vertex 9 -9 1
endloop
endfacet
facet normal 0 0 1
outer loop
vertex -9 9 1
vertex 9 -9 1
vertex 9 9 1
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 9 -9 0
vertex 9 -9 1
vertex -9 -9 1
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 9 -9 0
vertex -9 -9 1
vertex -9 -9 0
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 9 9 0
vertex 9 9 1
vertex 9 -9 1
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 9 9 0
vertex 9 -9 1
vertex 9 -9 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 9 9 1
vertex 9 9 0
vertex -9 9 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 9 9 1
vertex -9 9 0
vertex -9 9 1
endloop
endfacet
facet normal -1 0 0
outer loop
vertex -9 9 1
vertex -9 9 0
vertex -9 -9 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex -9 9 1
vertex -9 -9 0
vertex -9 -9 1
endloop
endfacet
endsolid csg.js

View file

@ -1,4 +1,4 @@
function export_outline_fn(){
function export_extrude_1_outline_fn(){
return new CSG.Path2D([[-9,-9],[9,-9]]).appendPoint([9,9]).appendPoint([-9,9]).appendPoint([-9,-9]).close().innerToCAG()
.extrude({ offset: [0, 0, 1] });
}
@ -10,7 +10,7 @@ function export_outline_fn(){
// creating part 0 of case export
let export__part_0 = export_outline_fn();
let export__part_0 = export_extrude_1_outline_fn();
// make sure that rotations are relative
let export__part_0_bounds = export__part_0.getBounds();

View file

@ -1,86 +0,0 @@
solid csg.js
facet normal 0 0 -1
outer loop
vertex -9 9 0
vertex 9 9 0
vertex 9 -9 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex -9 9 0
vertex 9 -9 0
vertex -9 -9 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex -9 9 1
vertex -9 -9 1
vertex 9 -9 1
endloop
endfacet
facet normal 0 0 1
outer loop
vertex -9 9 1
vertex 9 -9 1
vertex 9 9 1
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 9 -9 0
vertex 9 -9 1
vertex -9 -9 1
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 9 -9 0
vertex -9 -9 1
vertex -9 -9 0
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 9 9 0
vertex 9 9 1
vertex 9 -9 1
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 9 9 0
vertex 9 -9 1
vertex 9 -9 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 9 9 1
vertex 9 9 0
vertex -9 9 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 9 9 1
vertex -9 9 0
vertex -9 9 1
endloop
endfacet
facet normal -1 0 0
outer loop
vertex -9 9 1
vertex -9 9 0
vertex -9 -9 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex -9 9 1
vertex -9 -9 0
vertex -9 -9 1
endloop
endfacet
endsolid csg.js

View file

@ -7,49 +7,39 @@ models:
- 0
- 0
b:
models:
a:
models: {}
paths:
ShapeLine1:
type: line
origin:
- 0
- 0
b:
paths:
ShapeLine1:
type: line
origin:
- -9
- -9
end:
- 9
- -9
ShapeLine2:
type: line
origin:
- 9
- -9
end:
- 9
- 9
ShapeLine3:
type: line
origin:
- 9
- 9
end:
- -9
- 9
ShapeLine4:
type: line
origin:
- -9
- 9
end:
- -9
- -9
- -9
- -9
end:
- 9
- -9
ShapeLine2:
type: line
origin:
- 0
- 0
- 9
- -9
end:
- 9
- 9
ShapeLine3:
type: line
origin:
- 9
- 9
end:
- -9
- 9
ShapeLine4:
type: line
origin:
- -9
- 9
end:
- -9
- -9
origin:
- 0
- 0

View file

@ -7,49 +7,39 @@ models:
- 0
- 0
b:
models:
a:
models: {}
paths:
ShapeLine1:
type: line
origin:
- 0
- 0
b:
paths:
ShapeLine1:
type: line
origin:
- -9
- -9
end:
- 9
- -9
ShapeLine2:
type: line
origin:
- 9
- -9
end:
- 9
- 9
ShapeLine3:
type: line
origin:
- 9
- 9
end:
- -9
- 9
ShapeLine4:
type: line
origin:
- -9
- 9
end:
- -9
- -9
- -9
- -9
end:
- 9
- -9
ShapeLine2:
type: line
origin:
- 0
- 0
- 9
- -9
end:
- 9
- 9
ShapeLine3:
type: line
origin:
- 9
- 9
end:
- -9
- 9
ShapeLine4:
type: line
origin:
- -9
- 9
end:
- -9
- -9
origin:
- 0
- 0

View file

@ -5,8 +5,8 @@
(page A3)
(title_block
(title _export)
(rev v1.0.0)
(company Unknown)
(rev v3.14)
(company MrZealot)
)
(general

View file

@ -5,8 +5,8 @@
(page A3)
(title_block
(title export)
(rev v1.0.0)
(company Unknown)
(rev v3.14)
(company MrZealot)
)
(general

View file

@ -1,7 +1,7 @@
models:
export:
models:
matrix_col_row:
matrix:
paths:
top:
type: line

View file

@ -1,27 +1,42 @@
matrix_col_row:
matrix:
x: 0
'y': 0
r: 0
meta:
stagger: 0
spread: 19
splay: 0
origin:
- 0
- 0
orient: 0
shift:
- 0
- 0
rotate: 0
adjust: {}
width: 18
height: 18
padding: 19
width: 1
height: 1
autobind: 10
skip: false
asym: both
name: matrix_col_row
colrow: col_row
colrow: default_default
name: matrix
zone:
columns:
default:
rows: {}
key: {}
name: default
name: matrix
col:
stagger: 0
spread: 0
rotate: 0
origin:
- 0
- 0
rows: {}
key: {}
name: col
row: row
name: default
row: default
bind:
- 0
- 0
- 0
- 0

View file

@ -2,4 +2,11 @@ U: 19.05
u: 19
cx: 18
cy: 17
$default_stagger: 0
$default_spread: 19
$default_splay: 0
$default_height: 18
$default_width: 18
$default_padding: 19
$default_autobind: 10
a: 47

View file

@ -1,24 +1,22 @@
meta:
author: MrZealot
version: v3.14
units:
a: 28 + u
points:
zones:
matrix:
columns:
col: {}
rows:
row: {}
matrix: null
outlines:
exports:
export:
-
type: keys
side: left
size: 18
_export:
-
type: keys
side: left
size: 18
export:
-
what: rectangle
where: true
size: 18
_export:
-
what: rectangle
where: true
size: 18
cases:
export:
-

View file

@ -1,23 +1,24 @@
meta:
author: MrZealot
version: v3.14
units:
a: 28 + u
points.zones.matrix:
columns.col: {}
rows.row: {}
outlines.exports:
outlines:
export:
- type: 'keys'
side: 'left'
- what: rectangle
where: true
size: 18
_export:
- type: 'keys'
side: 'left'
- what: rectangle
where: true
size: 18
cases:
export:
- name: 'export'
- name: export
extrude: 1
_export:
- name: 'export'
- name: export
extrude: 1
pcbs:
export: {}

1
test/cli/bundle/command Normal file
View file

@ -0,0 +1 @@
node src/cli.js test/fixtures/bundle --clean

14
test/cli/bundle/log Normal file
View file

@ -0,0 +1,14 @@
Ergogen <version> CLI
Analyzing folder...
Interpreting format: YAML
Preprocessing input...
Calculating variables...
Parsing points...
Generating outlines...
Modeling cases...
Scaffolding PCBs...
Cleaning output folder...
Writing output to disk...
Done.

View file

@ -0,0 +1,98 @@
0
SECTION
2
HEADER
9
$INSUNITS
70
4
0
ENDSEC
0
SECTION
2
TABLES
0
TABLE
2
LTYPE
0
LTYPE
72
65
70
64
2
CONTINUOUS
3
______
73
0
40
0
0
ENDTAB
0
TABLE
2
LAYER
0
ENDTAB
0
ENDSEC
0
SECTION
2
ENTITIES
0
LINE
8
0
10
-9
20
-9
11
9
21
-9
0
LINE
8
0
10
9
20
-9
11
9
21
9
0
LINE
8
0
10
9
20
9
11
-9
21
9
0
LINE
8
0
10
-9
20
9
11
-9
21
-9
0
ENDSEC
0
EOF

View file

@ -0,0 +1,122 @@
(kicad_pcb (version 20171130) (host pcbnew 5.1.6)
(page A3)
(title_block
(title pcb)
(rev v1.0.0)
(company Unknown)
)
(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 ""))
)
(net 0 "")
(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 "")
)
(module injected_test_footprint (layer F.Cu) (tedit 5E1ADAC2)
(at 0 0 0)
(fp_text reference "I1" (at 0 0) (layer F.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15))))
)
(gr_line (start -9 9) (end 9 9) (angle 90) (layer Edge.Cuts) (width 0.15))
(gr_line (start 9 9) (end 9 -9) (angle 90) (layer Edge.Cuts) (width 0.15))
(gr_line (start 9 -9) (end -9 -9) (angle 90) (layer Edge.Cuts) (width 0.15))
(gr_line (start -9 -9) (end -9 9) (angle 90) (layer Edge.Cuts) (width 0.15))
)

View file

@ -5,7 +5,7 @@ Preprocessing input...
Calculating variables...
Parsing points...
Generating outlines...
Extruding cases...
Modeling cases...
Scaffolding PCBs...
Writing output to disk...
Done.

View file

@ -5,7 +5,7 @@ Preprocessing input...
Calculating variables...
Parsing points...
Generating outlines...
Extruding cases...
Modeling cases...
Scaffolding PCBs...
Output would be empty, rerunning in debug mode...
Writing output to disk...

View file

@ -3,25 +3,42 @@ matrix_col_row:
'y': 0
r: 0
meta:
stagger: 0
spread: 19
splay: 0
origin:
- 0
- 0
orient: 0
shift:
- 0
- 0
rotate: 0
adjust: {}
width: 18
height: 18
padding: 19
width: 1
height: 1
autobind: 10
skip: false
asym: both
name: matrix_col_row
colrow: col_row
name: matrix_col_row
zone:
columns:
col:
rows: {}
key: {}
name: col
rows:
row: {}
name: matrix
col:
stagger: 0
spread: 0
rotate: 0
origin:
- 0
- 0
rows: {}
key: {}
name: col
row: row
bind:
- 0
- 0
- 0
- 0

View file

@ -2,3 +2,10 @@ U: 19.05
u: 19
cx: 18
cy: 17
$default_stagger: 0
$default_spread: 19
$default_splay: 0
$default_height: 18
$default_width: 18
$default_padding: 19
$default_autobind: 10

View file

@ -1 +1 @@
Usage: ergogen <config_file> [options]
Usage: ergogen <config_file> [options]

View file

@ -1 +1 @@
Could not read config file "nonexistent.yaml": Error: ENOENT: no such file or directory, open 'nonexistent.yaml'
Could not read config file "nonexistent.yaml": File does not exist!

Some files were not shown because too many files have changed in this diff Show more