Compare commits

..

200 Commits

Author SHA1 Message Date
L'électron rare 2402288a2b feat: ajout d'un fichier README_FR.md et mise à jour du README.md avec un lien de parrainage 2026-03-26 07:57:20 +01:00
Benny Megidish 51fe5fc3f4 Update README.md 2025-12-14 19:48:09 +02:00
Benny Megidish 8c4c341b10 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2025-12-14 18:49:22 +02:00
Benny Megidish 605756ddb1 chore: invert backup flag + fix checkbox ui bug 2025-12-14 18:44:56 +02:00
Lukas Lang 7ac0af4ccf fix: crash when trying to rename non-existent designators/BOM/positions files (#225) 2025-12-05 13:50:10 +02:00
Benny Megidish 8c3a4ba784 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2025-10-30 22:00:18 +02:00
Benny Megidish 3f9d135e66 chore: bump version (5.2.0) 2025-10-30 22:00:11 +02:00
Neil Enns 1e25598c4e fix: noBackup CLI option (#222) 2025-10-14 21:00:33 +03:00
Benny Megidish 6393fd89d8 feat: restore design setting after plotting. #208 2025-10-13 10:37:16 +03:00
Benny Megidish 87fe0fc675 chore: rewording 2025-10-13 10:22:18 +03:00
Neil Enns a20484abc5 feat: add support for a --noBackup flag and UI checkbox & dev containers (#221)
* Add vscode devcontainer

* Add test files to gitignore

* Add --noBackup flag

* Update installer
2025-10-13 09:18:09 +03:00
Peter Jaquiery 3381050309 feat: skip empty MPNs, update image asset (#191)
* Correct PNG for Symbol Properties dialog to show "FT Rotation Offset" rather than "JLCPCB Rotation Offset" to match readme and code behaviour.

* Continue field search in MPN handling if selected field is empty.

---------

Co-authored-by: Benny Megidish <bennymegk@gmail.com>
2025-10-03 14:14:27 +03:00
WilheJo 6a2804ef19 feat: rename unzipped files if archive gets a user defined name (#210) 2025-10-03 13:59:00 +03:00
wenzellabs d047824964 feat: allow 'Part Number' as BOM keyword, add checkbox whether browser opens (#213)
* allow 'Part Number' as BOM keyword

Signed-off-by: matthias wenzel <wenzel@wenzellabs.de>

* add a checkbox 'Open browser after generation'

Signed-off-by: matthias wenzel <wenzel@wenzellabs.de>

* document 'Part Number' as BOM field

Signed-off-by: matthias wenzel <wenzel@wenzellabs.de>

---------

Signed-off-by: matthias wenzel <wenzel@wenzellabs.de>
2025-10-03 13:48:27 +03:00
Benny Megidish da738d6423 chore: update README.md 2025-09-18 12:50:00 +03:00
Antonín Skala 2894567147 feat: add nonInteractive mode (#212) 2025-08-02 21:04:04 +03:00
Benny Megidish e2afaec677 chore: bump version 2025-04-30 19:35:08 +03:00
Guy Naor 1d441283f6 feat: added support for footprint rotation and position regex to also check the library nickname (#203)
This is because of the way Ultra Librarian setup the names with colon
2025-04-30 14:06:19 +03:00
Benny Megidish 08774e06ed chore: archive name PR cleanup #202, close #188 2025-03-25 18:59:00 +02:00
Lukas Lang 3778c9a1fa feat: add ability to customize archive name (#202)
* Add ability to customize archive name

* Remove incorrect autocomplete for ARCHIVE_NAME text box

---------

Co-authored-by: Lukas Lang <langl@phys.ethz.ch>
2025-03-25 18:47:04 +02:00
Lukas Lang 9922748010 fix: footprints rotations that are not a multiple of 90° (#198) 2025-03-20 11:56:20 +02:00
Randall Scharpf b98c02c743 feat: add common abbreviations for part number (pn, p/n, part no.) (#201) 2025-03-20 11:53:00 +02:00
Benny Megidish 89d587b1cd chore: initial v10 support #200 2025-03-18 09:01:17 +02:00
Nanashi db3878b7ad fix: add support for the new EasyEDA2Kicad fields format (#185)
* update part number checks to work with new easyedatokicad format
* bump version
2025-01-12 21:15:19 +02:00
Benny Megidish 5880f10ea7 chore: update bug report template 2024-12-19 16:14:29 +02:00
Benny Megidish f20134e34d fix: metadata single version 2024-12-07 21:42:41 +02:00
Benny Megidish 588625cab0 feat: added v5.0.0 2024-12-05 20:58:23 +02:00
Benny Megidish 466a2acc53 chore: clean up readme #179 2024-12-05 20:38:01 +02:00
Benny Megidish f623083701 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-12-05 20:31:19 +02:00
Viermusketiere ff77eca2bc feat: added CLI Interface (#179)
* Added CLI Interface

* Added .pyc to gitignore for CLI usecases

* Moved cli progress bar generation to utils

* Included ALL_ACTIVE_LAYERS_OPT

* Added documentation to README for cli usage
2024-12-05 20:30:58 +02:00
Benny Megidish c4f876c991 feat: plot using custom layers name, closes #178 2024-12-05 20:23:53 +02:00
Benny Megidish 5f0dc0cb22 fix: layer override out of range, close #171 2024-12-05 18:13:07 +02:00
Benny Megidish 762794e025 feat: modified default field keys **BREAKING** 2024-12-05 11:06:50 +02:00
Benny Megidish 9c4d801d8c feat: cleanup origin field method 2024-12-05 10:58:38 +02:00
Benny Megidish cf61d8c717 chore: typos 2024-12-05 10:13:11 +02:00
Benny Megidish 34bcb62852 chore: clean up #181 2024-12-05 10:12:56 +02:00
Benny Megidish a2341cfd36 chore: remove ds store from PR 2024-12-05 09:54:13 +02:00
Benny Megidish 635f46fae4 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-12-05 09:52:15 +02:00
Benny Megidish fba8f8bab2 chore: minor 2024-12-05 09:52:10 +02:00
grodnay 03dae2cb63 fix: Trough Hole & "unspecified" position to center of pads (#181)
* Fixed Trough Hole position to center of pads, and "unspecifiled" to centor of bounding box.

* "unspecified" changed to center of pads.  "Origin" field added.
2024-12-05 09:49:34 +02:00
Benny Megidish efaf3d7bc6 chore: added missing new line 2024-11-24 20:52:40 +02:00
Adrian Studer e1378eea49 feat: add option to enable/disable plotting of all active layers (#176)
* Add option to enable/disable export of all active layers. Disabling that option will only export commonly used fabrication layers. Addresses issue #172

* move definition of standard layers to config.py
2024-11-21 14:57:43 +02:00
Dewalddp be93f52882 feat: apply delta x and delta y from transformations.csv (#177) 2024-11-19 14:04:16 +02:00
Romain ea200f23dd feat: close the dialog when pressing the ESC key (#180) 2024-11-19 13:38:18 +02:00
Benny Megidish 5c5b59d520 chore: bump version 2024-10-14 22:25:48 +03:00
XiNGRZ 2a5e4a9aca Feat: Optionally use User.2 layer for alternative outline (#170)
* feat: optionally use User.2 layer for alternative outline #169

* chore: fix table of layer override fields
2024-10-14 22:02:02 +03:00
Lukas Lang 79da28d1bb Fix: Capitalize reference designators in generated files (#174) 2024-10-14 21:59:32 +03:00
Benny Megidish 545fc80d3b chore: readme 2024-07-07 21:15:43 +03:00
Benny Megidish 8ac70a9c18 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-07-07 21:12:14 +03:00
Benny Megidish 4cb3006fa8 feat: added options to readme #162 2024-07-07 21:12:09 +03:00
dzid26 94bc00fa2b feat: allow footprints with no pads (#166) 2024-07-03 21:43:07 +03:00
Lukas Lang 678032f041 chore: Update JLCPCB position alignment step size (#163) 2024-05-22 16:27:00 +03:00
Benny Megidish 006072f3b1 chore: added issue templates 2024-04-29 22:38:34 +03:00
Benny Megidish a3081153e6 chore: bump version 2024-04-25 12:21:20 +03:00
Benny Megidish 28ea32fedf chore: cleanup footprint position 2024-04-25 12:20:37 +03:00
Benny Megidish 05e10eba00 fix: dnp attribute invocation, closes #148 2024-04-25 12:08:28 +03:00
Benny Megidish 31690863e5 chore: reorder UI components and declerations 2024-04-21 12:11:55 +03:00
Benny Megidish da76ad54bc chore: bump version 2024-04-21 12:00:30 +03:00
Benny Megidish e19c0e411b feat: added extend edge cuts layer option #149 2024-04-21 00:31:03 +03:00
Benny Megidish 87df32ac65 chore: rename options vars 2024-04-21 00:19:10 +03:00
Benny Megidish 702e7e6ee9 feat: amended exclude DNP from BOM only option #159 #148 2024-04-20 21:26:14 +03:00
Benny Megidish f78ad8c141 feat: added auto fill option #151 2024-04-20 20:22:47 +03:00
Benny Megidish 8984740e74 chore: bump version 2024-04-16 17:16:46 +03:00
Alexander a43b555c90 feat: dynamic layers (#152)
Makes the additional layers text autocomplete and the gerber generator use the list of currently active layers

> * Add: utils to get dynamic layers
> 
> * Change: Use KiCad standard name for layers
> 
> * Add: description to new util funcs
> 
> * Fix: only include active layers in layer util funcs
> 
> * Fix: infinite loop
> 
> * Change: use util layer functions in processmanager
> 
> * Remove: hardcoded layers
> 
> * Change: use new util layer functions in dialog
> 
> * Change: include comma-separated hint in layer control
2024-04-09 19:53:27 +03:00
Arx ab0b336ed9 adjusting plot options and drill options to more closely match JLCPCB recommendations (#141)
Co-authored-by: Juraj Durajka <arx@macbook>
2024-03-14 12:58:12 +02:00
dzid26 88ae87bf48 fix: footprints without pads fix
* Skip components without pads (e.g. graphics)
- skip_footprint condition refactor.

* Unspecified footprint bounding box comments
2024-03-12 22:51:49 +02:00
Hannes Rüger 1192d6cc11 feat: remember settings (#136)
* fix #134: remember settings

* chore: move filename to variable in config file

* chore: rename config file

* feat: add new option

* fix: surround user options saving with try catch

* docs: add info about options to readme
2024-03-12 22:49:33 +02:00
Benny Megidish 266eb3bd31 feat: auto traslate as an option (#135) 2024-03-07 14:37:50 +02:00
Benny Megidish a3aea835b1 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-03-02 21:46:53 +02:00
Benny Megidish c9e3ef16c0 chore: bump version 2024-03-02 21:46:46 +02:00
Benny Megidish 0917e9f199 Adds KiCad 8.99 compatibility patch (#132) 2024-03-01 10:38:01 +02:00
Benny Megidish bce98f72c3 chore: bump version 2024-02-29 15:42:26 +02:00
Benny Megidish 84e933b566 fix: extra layers filter 2024-02-28 07:53:29 +02:00
Benny Megidish 3714af0afb Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-02-27 22:42:03 +02:00
Benny Megidish b80a14dfbb feat: extra layers support (#113) 2024-02-27 22:41:54 +02:00
Maksim Salau bfa87db797 ifix: Use IsDNP() to get the DNP attribute in KiCAD v8 (#128)
`footprint_has_field(footprint, 'dnp')` returns `False` for DNP
components in KiCAD v8.

In KiCAD v8 the DNP flag is available via either the IsDNP() getter or
the FP_DNP attribute.

The added `getattr()` call turns into `footprint.IsDNP()` in KiCAD v8 or
`bool()` (which evaluates into `False`) in KiCAD v7 and earlier.
2024-02-27 22:16:20 +02:00
Benny Megidish c1322b705f chore: v4 2024-02-27 12:18:05 +02:00
Benny Megidish 371611e6f4 fix: translation footprint name 2024-02-27 12:16:17 +02:00
Benny Megidish 038cd1ddbe feat: get trasformations from db (#96) 2024-02-27 11:44:55 +02:00
Benny Megidish e2e10578cf chore: updated env settings 2024-02-27 11:35:25 +02:00
Chris Whiteford eeca1704e2 fix: case of PlotLayers attribute check (#120)
PlotLayers has a capital P
(https://gitlab.com/kicad/code/kicad/-/blob/7.0/pcbnew/plotcontroller.h#L98)

hasattr(and internally getattr) is case sensitive.  This resulted in the User.1 layer not being included for v-cuts even if it existed in the project
2024-02-23 11:13:31 +02:00
dzid26 6729b564db feat: Calculate centroid based on footprint type (#117) 2024-02-21 11:27:52 +02:00
Benny Megidish 9e4e497899 chore: Update README.md 2024-02-21 00:07:51 +02:00
dzid26 f92c64e7dc fix: consistent top/bottom rotation (#111) 2024-02-15 16:54:56 +02:00
dzid26 13fc67bbd7 feat: Don't change output folder and store backups as zip (#110)
* store backups as zip instead of new directories

* Make sure all temp folders are removed
2024-02-15 16:46:38 +02:00
dzid26 8dc0e8a440 Adds KiCad 7.99 compatibility (#109)
`HasProperty()` no longer exists on footprints
and is replaced by `HasFieldByName()`.

Fixes #84, #97

Check kicad api version check

Co-authored-by: Wolfgang Jung <w.jung@polyas.de>
2024-02-15 16:37:41 +02:00
Arkwolf e9f4b5b29b feat: Add Top and Bottom layer override on footprints (#104)
* working changes

* add pad back

* remove confusing break continue
2023-11-16 18:16:00 +02:00
Benny Megidish 3cdcc31e63 fix: update max BOM row limit to 200 #95 #102 (#105) 2023-11-16 18:08:03 +02:00
Benny Megidish 8db1a4d446 chore: update BOM max row size to 256 #95 2023-10-14 19:09:47 +03:00
Benny Megidish 2b8c898d22 feat: stop looping the bom once the part inserted #95 2023-10-08 20:38:50 +03:00
Mats Engstrom bad1d2d1a3 Add limit of parts in BOM rows (#95)
* Add limit of parts in BOM rows

The number of components that are merged into the same row in the BOM file is limited. If there are too many you'll get an "Internal Server Error" when JLCPCB is processing it. 

Their support told me 20 parts per row is the limit but I've tested with 50 and it works, so I split the difference and set it to 30 here. I guess the 20 was only a suggestion :)

* Use variable instead of magic number for BOM-merge limit

* Use configure value for setting limit of merged parts in BOM
2023-10-08 20:25:03 +03:00
Benny Megidish 8a0af1a043 chore: rename to exclude DNP #54 2023-09-26 23:03:33 +03:00
Benny Megidish d3cb95030a feat: ignore dnp if value == DNP and option is set
#54
2023-09-26 22:44:15 +03:00
Benny Megidish 355f3c3ac4 chore: clean up GUI #54 2023-09-21 18:21:08 +03:00
Benny Megidish ee3e238a3d chore: lint fix 2023-09-21 16:58:45 +03:00
Benny Megidish a3fb4259dd Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2023-09-21 12:04:09 +03:00
Benny Megidish 872c0d90df feat: added GUI options support #54 2023-09-21 12:04:04 +03:00
Benny Megidish 891b60ac43 Merge pull request #90 from bennymeg/bennymeg-patch-3
fix: PlotLayers attribute missing #87
2023-09-12 20:21:35 +03:00
Benny Megidish 6461a362dd fix: PlotLayers attribute missing #87 2023-09-12 20:21:23 +03:00
Benny Megidish 6f1426d972 fix: add support to v7 #81 2023-08-19 20:36:34 +03:00
Benny Megidish 6567a9da07 fix: solder mask margin in nanometers #81 2023-08-19 20:25:52 +03:00
Benny Megidish 57fd582a6d fix: normalized generated filename #85 2023-08-18 15:13:39 +03:00
Benny Megidish 9d18a8bc54 Merge pull request #82 - verify-zone-fills
Verify zone fills
2023-08-09 11:19:28 +03:00
John Obaterspok df06c3c867 We need to rebuild the connectivity db after zones has been refilled 2023-08-04 12:44:03 +02:00
John Obaterspok 8beb878d32 Make sure all zones are updated before starting an export 2023-08-04 12:23:50 +02:00
Benny Megidish ef138c8feb chore: bump versio to v3 2023-07-06 14:22:53 +03:00
Benny Megidish 466c008228 chore: bump version 2023-07-06 14:11:32 +03:00
Benny Megidish c8e3daa5ae Merge pull request #75 from bennymeg/bennymeg-patch-2
chore: update readme v-cut layer #71
2023-07-06 14:09:16 +03:00
Benny Megidish a9cd57a027 chore: update readme v-cut layer #71 2023-07-06 14:08:47 +03:00
Benny Megidish 387c5b3931 Merge pull request #71
Update process.py for V Cuts
2023-07-06 13:59:37 +03:00
Benny Megidish 2d2436993a Merge pull request #70
Bottom side rotates still not quite right
2023-07-06 13:44:16 +03:00
RevK 376d6df61d Update process.py for V Cuts
Adds User_1 layer to Edge_Cuts file.
It seems JLCPCB want V cuts in the file as Edge Cuts.
So simply make User_1 a V Cuts layer.

Alternative would be for config for plotPlan to take array of layers to include, not just one later, but my python is not that good.
2023-06-26 18:16:43 +01:00
RevK f832e4e007 Bottom side rotates still not quite right
Inverting around 360 degrees looked fine for angles, even 45 degrees and 10 degrees and so on, but turns out it actually needs inverting around 180 degrees else your diodes are all backwards. Arrrg.
2023-06-21 11:13:11 +01:00
Benny Megidish d42611753e Merge pull request #67 from Fisheiyy/naming-rework
Output folder name rework & Minor correction
2023-06-14 10:06:55 +03:00
Fisheiyy ac0fec8faf Update thread.py 2023-06-13 20:35:21 -05:00
Fisheiyy 2ac859e7e7 exclude .csv & .ipc) from gerber archive 2023-06-13 02:33:24 -05:00
Fisheiyy 830819b27b Only delete temp_dir if files successfully copied to output_path 2023-06-12 19:42:26 -05:00
Benny Megidish 9832454472 Merge pull request #66 from Fisheiyy/master
Add Support for all 32 possible Copper Layers
2023-06-12 14:10:04 +03:00
Fisheiyy 468640df8a Output Folder Name Rework & Minor Correction 2023-06-12 05:59:32 -05:00
Fisheiyy db4bf8904a Fix Error (over 32 layer max) 2023-06-11 03:44:44 -05:00
Fisheiyy 1a9d56d0a9 Add support for 32 Layers (Max Amount) 2023-06-10 23:10:22 -05:00
Benny Megidish 50ad485758 Merge pull request #62
Explain the need to update PCB after adding rotation/position offset
2023-05-30 12:28:37 +03:00
Benny Megidish 3954ba9b68 Update README.md #57 2023-05-30 12:25:12 +03:00
Tom Preston-Werner 3c1254eb9c Explain the need to update PCB after adding rotation/position offset
I was having the same problem as presented in #41 and realized that I needed to update the PCB after entering a rotation offset in the Schematic Editor. This PR adds documentation to point this out and help others avoid the same frustration.
2023-05-19 13:29:51 -07:00
Benny Megidish f10292a797 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2023-05-18 20:06:52 +03:00
Benny Megidish 1d5a1b244e feat: orginize gerber archive #15 #61 2023-05-18 20:06:47 +03:00
Benny Megidish c88232dbaf Merge pull request #60 from bennymeg/bennymeg-patch-1
Update README.md
2023-05-14 22:35:00 +03:00
Benny Megidish 9e78ed2f18 Update README.md 2023-05-14 22:34:34 +03:00
Benny Megidish 99344155b5 Merge pull request #59
Fix rotation offset field name
2023-05-14 22:23:20 +03:00
Marcus T 7a7a04a714 Fix rotation offset field name
The name of the field doesn't match the value the code actually looks for.  The name of the field is correct elsewhere in the document.
2023-05-12 13:04:12 -04:00
Benny Megidish 4a59fb0672 feat: added support for 8 layers PCBs, closes #56 2023-05-11 10:05:40 +03:00
Benny Megidish 5afaa579f8 Merge pull request #52
Correct rotation for bottom layer
2023-04-30 21:36:42 +03:00
Benny Megidish a1519203c1 Merge pull request #55 from tobsec/patch-1
Fix wrong position key in README.md
2023-04-30 21:35:10 +03:00
tobsec 37b039b515 Update README.md 2023-04-28 12:14:28 +02:00
Benny Megidish db103a5775 fix: metadata version 2023-04-28 13:02:40 +03:00
RevK 8394c85f15 Correct rotation for bottom layer
Fix #51
2023-04-13 11:03:02 +01:00
Benny Megidish b783e7bd3e feat: set DNP as MPN #37 2023-03-25 20:46:48 +03:00
Benny Megidish e13276ae62 chore: refactor 2023-03-23 20:17:57 +02:00
Benny Megidish 523a48cdff Merge pull request #46
Don't merge similar components with different LCSC part numbers
2023-03-23 20:16:44 +02:00
Benny Megidish f51c44fb00 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2023-03-23 20:15:30 +02:00
Benny Megidish 817a58e8cc chore: misc 2023-03-23 20:15:22 +02:00
Benny Megidish 11d48c1370 feat: add user comments to gerber, closes #16 2023-03-23 20:13:51 +02:00
Alexis Lockwood 0db7b02636 Don't merge similar components with different LCSC part numbers 2023-03-23 10:15:12 -06:00
Benny Megidish e90cb33e6a Merge pull request #44
Change encoding of output files to UTF-8 with BOM
2023-03-23 13:23:02 +02:00
Koki Mizumoto 67d725a96f Update process.py 2023-03-21 22:46:50 +09:00
Benny Megidish c62f1e3c1e fix: solder mask margin as per JLCPCB guidelines #34 2023-03-17 16:02:53 +02:00
Benny Megidish 68b5eb639a chore: removed unwanted footprint key 2023-03-14 11:48:19 +02:00
Benny Megidish 9f0fa6eaee Merge pull request #42
Add new symbol field support for easyeda2kicad.py
2023-03-14 11:40:41 +02:00
Tyler Crumpton 6adb746943 Add new symbol field support for easyeda2kicad.py 2023-03-13 22:42:09 -04:00
Benny Megidish 79bd50db9e chore: bump version 2023-02-22 12:28:05 +02:00
Benny Megidish fc12cc94b3 Update README.md 2023-02-14 18:57:47 +02:00
Benny Megidish 76d8ffbf75 Create CONTRIBUTERS.md 2023-02-14 18:57:25 +02:00
Benny Megidish 30d1684f76 fix: merge similar parts into single entry
merge PR
2023-02-14 18:55:10 +02:00
Ionel a09b42c20f fix merge similar parts into single entry 2023-02-14 18:03:16 +02:00
Ionel 5417532fed fix merge similar parts into single entry 2023-02-14 17:52:31 +02:00
Benny Megidish dec7a3ca1b Update README.md 2023-02-01 00:21:06 +02:00
Benny Megidish b8d0073846 Update README.md 2023-01-31 10:58:18 +02:00
Benny Megidish 599f159806 Update README.md
fix manager image ratio
2023-01-29 20:19:59 +02:00
Benny Megidish cd38035aff chore: added installation manager image 2023-01-29 20:17:00 +02:00
Benny Megidish ecd98adeff feat: normalize footprint name as per #27 2023-01-06 16:04:27 +02:00
Benny Megidish d8d81a7fe9 Update README.md 2023-01-06 00:57:12 +02:00
Benny Megidish d8c9aa775e Update README.md 2023-01-06 00:54:44 +02:00
Benny Megidish b783b289a8 chore: updated images 2023-01-06 00:50:16 +02:00
Benny Megidish 927128651c chore: bump version 2023-01-06 00:20:47 +02:00
Benny Megidish 91fdcc9fc2 Create FUNDING.yml 2023-01-06 00:19:37 +02:00
Benny Megidish 1d1d38b83f refactor: rename 2023-01-06 00:16:53 +02:00
Benny Megidish 49e921d2cf chore: updated readme 2023-01-06 00:07:51 +02:00
Benny Megidish 9b1d84265d Merge pull request #25
Add ability to set a position offset
2023-01-05 23:53:32 +02:00
Benny Megidish 5a675648bc Merge pull request #24
Add rotation DB
2023-01-05 23:51:08 +02:00
Benny Megidish adbd0fdde0 Merge pull request #31 closes #29
Substract maks from silk
2023-01-04 18:15:31 +02:00
kiu 1131b2efa9 Substract maks from silk
See https://github.com/bennymeg/JLC-Plugin-for-KiCad/issues/29
2022-12-30 18:00:42 +01:00
harbaum 0fcaa841a8 Take rotation into account for position offset 2022-11-03 13:17:58 +01:00
Till Harbaum 9147cacdac Add ability to set a position offset 2022-11-01 13:23:19 +01:00
Hildo Guillardi Júnior 17b3c10d16 Add rotation DB 2022-10-21 23:17:14 -03:00
Benny Megidish c53997c639 Merge pull request #22
Skip CSVs when there are no components or designators
2022-10-20 11:20:39 +03:00
Benny Megidish 0a8de1183e Merge pull request #21
Rotation offset field
2022-10-20 11:20:17 +03:00
Carlos Garcia Saura d93476b5a9 Skip CSVs when there are no components or designators 2022-10-19 18:51:53 +02:00
Anton Schirg 1e1b347852 feat: show errors in a message box
before, the progress window would stay on screen if the thread aborts
due to an error
2022-10-17 22:20:00 +02:00
Anton Schirg 7a67087431 feat: rotation offset field to align KiCad and JLC footprints 2022-10-17 22:19:56 +02:00
Benny Megidish adb10d314a chore: minor code ordering 2022-09-28 17:26:51 +03:00
Benny Megidish 5b43ce9047 Merge pull request #19
Fixes for KiCad 7 nightly compatibility
2022-09-28 17:24:35 +03:00
miceuz 45cb109668 Return back KiCad 6 code with conditionals for v7 compatibility 2022-09-28 17:20:10 +03:00
Benny Megidish 94bbea2ac4 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2022-09-28 16:42:20 +03:00
Benny Megidish 1205f17d7e fix: status gauge value most be an integer #18 2022-09-28 16:42:15 +03:00
miceuz 1bfcc23256 Fixes for KiCad 7 nightly compatibility
* edge layers are not excluded in v7
* use orientation as degrees
2022-09-25 20:53:19 +03:00
Benny Megidish 036754a53b chore: updated readme, added usage, closes #12 2022-09-13 10:28:43 +03:00
Benny Megidish c71afbe1b9 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2022-08-11 11:03:10 +03:00
Benny Megidish 1c9e78e869 feat: reordered the distributer part number as per #9 2022-08-11 11:03:06 +03:00
Benny Megidish b51024b174 Merge pull request #8 from butter-robotics/master
Update README.md
2022-08-04 23:24:52 +03:00
Benny Megidish 3f19dc2121 Update README.md
chore: fix readme typo
2022-08-04 23:22:59 +03:00
Benny Megidish 6255f69e4a fix: position file header 2022-08-02 15:13:07 +03:00
Benny Megidish a5a31059d0 feat: v1.1.0 - merge bom components 2022-08-02 10:28:09 +03:00
Benny Megidish 27ac789281 Merge pull request #7
merge similar parts into single entry, sort footprint
2022-08-01 21:18:12 +03:00
Ionel 3a33cfff96 handled duplicates the footprints designators (penalized PCB) 2022-08-01 09:28:08 +03:00
Ionel 0994f04d2c merge similar parts into single entry, sort footprint 2022-07-29 11:31:35 +03:00
Ionel 18b3a8074f merge similar parts into single entry, sort footprint 2022-07-29 11:10:22 +03:00
Benny Megidish 05e486d949 Update README.md 2022-07-27 21:34:42 +03:00
Benny Megidish 34318c7de0 feat: updated readme 2022-07-27 21:32:49 +03:00
Benny Megidish 8f062359e4 feat: updated version to v1 - stable 2022-07-27 21:07:22 +03:00
Benny Megidish 5b821aebe2 chore: removed all JLCPCB direct integration code 2022-07-26 14:26:56 +03:00
26 changed files with 1603 additions and 179 deletions
+17
View File
@@ -0,0 +1,17 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "Python 3",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/python:1": {},
"ghcr.io/stuartleeks/dev-container-features/shell-history:0": {},
"ghcr.io/neilenns/devcontainer-features/oh-my-zsh": {
"disableAutoUpdate": false,
"disableUpdatePrompt": false,
"stripWorkspacesFromPrompt": true,
"theme": "agnoster"
}
},
"postCreateCommand": ".devcontainer/install-kicad.sh"
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
set -e
echo "Installing prerequisites..."
sudo apt update > /dev/null 2>&1 && sudo apt install -y software-properties-common > /dev/null 2>&1
echo "Installing KiCad (takes a few minutes)..."
sudo add-apt-repository -y ppa:kicad/kicad-9.0-releases > /dev/null 2>&1
sudo apt update > /dev/null 2>&1
sudo apt install -y --no-install-recommends kicad kicad-symbols kicad-footprints > /dev/null 2>&1
# Link KiCad plugin to workspace path
echo "Linking KiCad plugin..."
mkdir -p "$HOME/.local/share/kicad/9.0/3rdparty/plugins"
ln -s /workspaces/Fabrication-Toolkit/plugins "$HOME/.local/share/kicad/9.0/3rdparty/plugins/com_github_bennymeg_JLC-Plugin-for-KiCad"
mkdir -p "$HOME/.local/share/kicad/9.0/3rdparty/resources"
ln -s /workspaces/Fabrication-Toolkit/resources "$HOME/.local/share/kicad/9.0/3rdparty/resources/com_github_bennymeg_JLC-Plugin-for-KiCad"
# Add plugin to PYTHONPATH
echo "Adding plugin to PYTHONPATH..."
echo 'export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/kicad/9.0/3rdparty/plugins"' >> "$HOME/.zshrc"
+13
View File
@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: bennymeg
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']
+79
View File
@@ -0,0 +1,79 @@
name: Bug Report
description: Report an Fabrication Toolkit bug
title: "[Bug]: "
labels: "bug"
body:
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I have searched the [issue tracker](https://www.github.com/bennymeg/Fabrication-Toolkit/issues) for a bug report that matches the one I want to file, without success.
required: true
- type: input
attributes:
label: Fabrication Toolkit Version
description: |
What version of Fabrication Toolkit are you using?
Note: Please only report issues for currently supported versions of Fabrication Toolkit.
validations:
required: true
- type: input
attributes:
label: KiCad Version
description: |
What version of KiCad are you using?
Note: Please only report issues for currently supported versions of KiCad.
validations:
required: true
- type: dropdown
attributes:
label: What operating system are you using?
options:
- Windows
- macOS
- Ubuntu
- Other Linux
- Other (specify below)
validations:
required: true
- type: input
attributes:
label: Operating System Version
description: What operating system version are you using? On Windows, click Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a.
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
validations:
required: false
- type: dropdown
attributes:
label: What arch are you using?
options:
- x64
- ia32
- arm64 (including Apple Silicon)
- Other (specify below)
validations:
required: false
- type: input
attributes:
label: Last Known Working Fabrication Toolkit version
description: What is the last version of Fabrication Toolkit this worked in, if applicable?
placeholder: 4.0.0
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: A clear description of what actually happens.
validations:
required: true
- type: textarea
attributes:
label: Additional Information
description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here.
+4
View File
@@ -0,0 +1,4 @@
contact_links:
- name: Github Sponsor
url: https://github.com/sponsors/bennymeg
about: Help support Fabrication Toolkit by sponsoring us
@@ -0,0 +1,36 @@
name: Feature Request
description: Suggest an idea for Fabrication Toolkit
title: "[Feature Request]: "
labels: "enhancement"
body:
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I have searched the [issue tracker](https://www.github.com/bennymeg/Fabrication-Toolkit/issues) for a feature request that matches the one I want to file, without success.
required: true
- type: textarea
attributes:
label: Problem Description
description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
validations:
required: true
- type: textarea
attributes:
label: Proposed Solution
description: Describe the solution you'd like in a clear and concise manner.
validations:
required: true
- type: textarea
attributes:
label: Alternatives Considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Additional Information
description: Add any other context about the problem here.
validations:
required: false
+4 -1
View File
@@ -1,2 +1,5 @@
.venv
dist/
dist/
.DS_Store
*.pyc
Test/
+30 -1
View File
@@ -1,13 +1,42 @@
{
"cSpell.words": [
"bennymeg",
"Colour",
"csvfile",
"Drilland",
"Elec",
"EXCELLON",
"FPID",
"gerbers",
"HTSSOP",
"JLCPCB",
"kicad",
"LCSC",
"LQFP",
"LSEQ",
"Megidish",
"MSOP",
"netlist",
"panelization",
"pcbnew",
"Plotfile",
"Protel",
"Soldermask"
"rcos",
"restkey",
"restval",
"rsin",
"SOIC",
"Soldermask",
"SPST",
"SSOP",
"TDSON",
"TQFP",
"TSOT",
"TSSOP",
"UDFN",
"USON",
"venv",
"VSON",
"VSSOP"
]
}
+1
View File
@@ -0,0 +1 @@
- Hildo Guillardi Júnior
+193 -11
View File
@@ -1,11 +1,14 @@
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/logo.svg?raw=true"
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/logo.svg?raw=true"
style="display:block margin-left: auto; margin-right: auto;" alt="JLC PCB Plug-in for KiCad">
<div align="center">
| **JLC PCB Plug-in for KiCad** |
|:-----:|
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/bennymeg)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/bennymeg/fabrication-toolkit/total)
</div>
## Features
@@ -17,27 +20,206 @@
## Installation
### Official Installation
Open the "Plugin and Content Manager" from the KiCad main menu an install the "<ins>Fabrication Toolkit</ins>" plugin from the selection list.
Fabrication Toolkit is distributed with the official releases of KiCad 6+. Open the "Plugin and Content Manager" from the KiCad main menu and install the "<ins>Fabrication Toolkit</ins>" plugin from the selection list.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/manager.png?raw=true" height=275>
### Manual installation
Download the latest ZIP file from https://github.com/bennymeg/JLC-Plugin-for-KiCad. Open the "Plugin and Content Manager" from the KiCads main window and install the ZIP file via "Install from File".
Download the [latest release](https://github.com/bennymeg/JLC-Plugin-for-KiCad/releases) ZIP file. Open the "Plugin and Content Manager" from the KiCads main window and install the ZIP file via "Install from File".
## Usage
Click on the Fabrication Toolkit <img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/resources/icon.png?raw=true" style="margin-bottom: 8px;" alt="Logo" height=24> button on the top tool box inside KiCad pcb editor (pcbnew), choose the required options and click on 'Generate'.
> [!IMPORTANT]
> Ensure your board is synchronized before invoking this addon [**F8**].
## Options
### Include Component Part Number in Production Files
Add an 'MPN'* field with the LCSC component part number to the symbol's fields property.
Options can be set in the dialog that appears when the plugin is invoked. They are saved in a file called `fabrication-toolkit-options.json` in the project directory so that they are remembered between invocations of the plugin.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/options.png?raw=true" height=275>
__Archive name__: Name of the archive file to be generated. Can include text variables, such as e.g. `${TITLE}_${REVISION}`</br>
__Additional layers__: Comma-separated list of additional layers to include in the gerber archive.</br>
__Plot all active layers__: Whether to include all layers, instead of just the layers required by JLCPCB.</br>
__Set User.1 as V-Cut layer__: Merge User.1 layer with the Edge-Cut layer in production.</br>
__Use User.2 for an alternative Edge-Cut layer__: Use the User.2 instead of the Edge-Cut layer for the board outline in production.
This is useful if you need process edges or panelization during production but still want to keep the individual outline for prototyping, 3D model exports, or similar purposes.</br>
__Apply automatic translations__: Apply known translation fixes for common components.</br>
__Apply automatic fill for all zones__: Refill all zones before generation production files.</br>
__Exclude DNP components from BOM__: Exclude components the had been set a DNP from th BOM.</br>
__Generate Backups__: Generate Backup zip for the production files.</br>
## Attributes
> [!NOTE]
> All the attributes are entered in the Schematic Editor, make sure to update your PCB [**F8**] with the changes made to the schematic before generating a new set of production files, otherwise your changes won't be reflected.
### ① Include Component Part Number in Production Files
Add an 'LCSC Part #'* field with the LCSC component part number to the symbol's fields property.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/mpn.png?raw=true" height=420>
#### Similar Fields*:
|'mpn' | 'Mpn' | 'MPN' | 'JLC_MPN' | 'LCSC_MPN' | 'LCSC Part #' | 'JLC' | 'LCSC'|
| --- | --- | --- | --- | --- | --- | --- | --- |
#### Primary Fields*:
| 'LCSC Part #' | 'LCSC Part' | 'LCSC PN' | 'LCSC P/N' | 'LCSC Part No.' | 'LCSC Part Number' | 'JLCPCB Part #' | 'JLCPCB Part' | 'JLCPCB PN' | 'JLCPCB P/N' | 'JLCPCB Part No.' | 'JLCPCB Part Number' |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
### Ignore Footprint in Production Files
Select 'Exclude from position files' or 'Exclude from BOM' in the symbol's attributes property in order to ignore the footprint from the relevant file.
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'LCSC' | 'JLC' | 'MPN' | 'Mpn' | 'mpn' |
| --- | --- | --- | --- | --- |
_The fields will be queried in the order denoted above._
Empty fields are skipped so, for example, a 'MPN' field with a value will be used if
all earlier fields are missing or empty.
---
### ② Ignore Footprint in Production Files
Select 'Exclude from board' or 'Exclude from BOM' in the symbol's attributes property in order to ignore the footprint from the relevant file.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/attributes.png?raw=true" height=420>
Select 'Exclude from position files' or 'Exclude from BOM' in the footprint's fabrication attributes property in order to ignore the footprint from the relevant file.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/fabrication.png?raw=true" height=505>
---
### ③ Offset Component Rotation
The rotation of components in KiCad Footprints does not always match the orientation in the JLC library because KiCad and JLC PCB used different variation of the same standard. Most of the rotations may be corrected by the `rotations.cf` definitions. To the exception cases: add an 'FT Rotation Offset'* field - with positive values indicating counter-clockwise orientation offset in degrees.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/rotation-jlc.png?raw=true" height=164>
If the JLC preview shows a footprint like this, enter a rotation offset of -90 to rotate pin 1 to the lower right corner.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/rotation.png?raw=true" height=420>
Use following table to quickly find out to which coordinate enter the correction based on JLC arrows clicks - depending on footprint rotation in KiCad PCB Editor status bar:
|KiCad footprint deg | x | y|
|----|----|----|
|0 deg, Front | right arrow | up arrow |
|0 deg, Back | left arrow | down arrow |
|180 deg, Front | left arrow | down arrow |
|180 deg, Back | right arrow | up arrow |
|90 deg, Front or Back | up arrow | left arrow |
|-90 deg, Front or Back | down arrow | right arrow |
> [!TIP]
> Single arrow press in JLC is 0.0635mm (= 1/400in) shift.
#### Primary Fields*:
| 'FT Rotation Offset' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Rotation Offset' | 'RotOffset' |
| --- | --- |
_The fields will be queried in the order denoted above._
---
### ④ Offset Component Position
The position of components in KiCad Footprints does not always match the orientation in the JLC library because KiCad and JLCPCB used different variation of the same standard. To the exception cases: add an 'FT Position Offset'* field with an comma separated x,y position offset to correct it.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/position.png?raw=true" height=420>
> [!TIP]
> Single arrow press in JLC equals to 0.0635mm (= 1/400in) shift.
#### Primary Fields*:
| 'FT Position Offset' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Position Offset' | 'PosOffset' |
| --- | --- |
_The fields will be queried in the order denoted above._
### ⑤ Override Component Origin
The Fabrication Toolkit reports the position of each component based on an automatically selected point of reference. This default behavior can be overridden by adding an 'FT Origin'* field to the component.
#### Primary Fields*:
| 'FT Origin' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Origin' |
| --- |
The **Origin** field supports the following values:
- `Anchor` - Uses the footprint's anchor point, which can be modified in KiCad's footprint editor.
- `Center` - Uses the center of the bounding box formed by the footprint's pads.
### ⑥ Override Component Layer
Some footprints may have their components defined on the opposite layer to there actual footprints. In these instances you can override mount side by adding an 'FT Layer Override'* field to the component.
The **Layer Override** field supports the following values:
- `top`, `t` - Override footprint layer to the top.
- `bottom`, `b` - Override footprint layer to the bottom.
#### Primary Fields*:
| 'FT Layer Override' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Layer Override' | 'LayerOverride' |
| --- | --- |
_The fields will be queried in the order denoted above._
## CLI
The plugin can also be used via the linux command line. This can be particularly useful if you need to embed the plugin into an automation pipeline or environment.
The plugin can be called with the command below:
```
python3 -m plugins.cli -p /myProject/myBoard.kicad_pcb
```
All the options from the GUI are also available via the cli interface:
```
python3 -m plugins.cli -h
usage: Fabrication Toolkit [-h] --path PATH [--additionalLayers LAYERS] [--user1VCut] [--user2AltVCut]
[--autoTranslate] [--autoFill] [--excludeDNP] [--allActiveLayers] [--archiveName NAME]
[--openBrowser] [--noBackup]
Generates JLCPCB production files from a KiCAD board file
options:
-h, --help show this help message and exit
--path PATH, -p PATH Path to KiCAD board file
--additionalLayers LAYERS, -aL LAYERS
Additional layers(comma-separated)
--user1VCut, -u1 Set User.1 as V-Cut layer
--user2AltVCut, -u2 Use User.2 for alternative Edge-Cut layer
--autoTranslate, -t Apply automatic position/rotation translations
--autoFill, -f Apply automatic fill for all zones
--excludeDNP, -e Exclude DNP components from BOM
--allActiveLayers, -aaL
Export all active layers instead of only commonly used ones
--archiveName NAME, -aN NAME
Name of the generated archives
--openBrowser, -b Open web browser with directory file overview after generation
--noBackup, -nB Do not create a backup of the project before generation
```
## Author
Benny Megidish
<iframe src="https://github.com/sponsors/electron-rare/card" title="Sponsor electron-rare" height="225" width="600" style="border: 0;"></iframe>
+225
View File
@@ -0,0 +1,225 @@
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/logo.svg?raw=true"
style="display:block margin-left: auto; margin-right: auto;" alt="JLC PCB Plug-in for KiCad">
<div align="center">
| **JLC PCB Plug-in for KiCad** |
|:-----:|
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/bennymeg)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/bennymeg/fabrication-toolkit/total)
</div>
## Features
1. Generates gerber files in correct format for production
2. Generates BOM file in correct format for production
3. Generates Pick and Place file in correct format for assembly
4. Generates IPC netlist file
## Installation
### Official Installation
Fabrication Toolkit is distributed with the official releases of KiCad 6+. Open the "Plugin and Content Manager" from the KiCad main menu and install the "<ins>Fabrication Toolkit</ins>" plugin from the selection list.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/manager.png?raw=true" height=275>
### Manual installation
Download the [latest release](https://github.com/bennymeg/JLC-Plugin-for-KiCad/releases) ZIP file. Open the "Plugin and Content Manager" from the KiCads main window and install the ZIP file via "Install from File".
## Usage
Click on the Fabrication Toolkit <img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/resources/icon.png?raw=true" style="margin-bottom: 8px;" alt="Logo" height=24> button on the top tool box inside KiCad pcb editor (pcbnew), choose the required options and click on 'Generate'.
> [!IMPORTANT]
> Ensure your board is synchronized before invoking this addon [**F8**].
## Options
Options can be set in the dialog that appears when the plugin is invoked. They are saved in a file called `fabrication-toolkit-options.json` in the project directory so that they are remembered between invocations of the plugin.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/options.png?raw=true" height=275>
__Archive name__: Name of the archive file to be generated. Can include text variables, such as e.g. `${TITLE}_${REVISION}`</br>
__Additional layers__: Comma-separated list of additional layers to include in the gerber archive.</br>
__Plot all active layers__: Whether to include all layers, instead of just the layers required by JLCPCB.</br>
__Set User.1 as V-Cut layer__: Merge User.1 layer with the Edge-Cut layer in production.</br>
__Use User.2 for an alternative Edge-Cut layer__: Use the User.2 instead of the Edge-Cut layer for the board outline in production.
This is useful if you need process edges or panelization during production but still want to keep the individual outline for prototyping, 3D model exports, or similar purposes.</br>
__Apply automatic translations__: Apply known translation fixes for common components.</br>
__Apply automatic fill for all zones__: Refill all zones before generation production files.</br>
__Exclude DNP components from BOM__: Exclude components the had been set a DNP from th BOM.</br>
__Generate Backups__: Generate Backup zip for the production files.</br>
## Attributes
> [!NOTE]
> All the attributes are entered in the Schematic Editor, make sure to update your PCB [**F8**] with the changes made to the schematic before generating a new set of production files, otherwise your changes won't be reflected.
### ① Include Component Part Number in Production Files
Add an 'LCSC Part #'* field with the LCSC component part number to the symbol's fields property.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/mpn.png?raw=true" height=420>
#### Primary Fields*:
| 'LCSC Part #' | 'LCSC Part' | 'LCSC PN' | 'LCSC P/N' | 'LCSC Part No.' | 'LCSC Part Number' | 'JLCPCB Part #' | 'JLCPCB Part' | 'JLCPCB PN' | 'JLCPCB P/N' | 'JLCPCB Part No.' | 'JLCPCB Part Number' |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'LCSC' | 'JLC' | 'MPN' | 'Mpn' | 'mpn' |
| --- | --- | --- | --- | --- |
_The fields will be queried in the order denoted above._
Empty fields are skipped so, for example, a 'MPN' field with a value will be used if
all earlier fields are missing or empty.
---
### ② Ignore Footprint in Production Files
Select 'Exclude from board' or 'Exclude from BOM' in the symbol's attributes property in order to ignore the footprint from the relevant file.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/attributes.png?raw=true" height=420>
Select 'Exclude from position files' or 'Exclude from BOM' in the footprint's fabrication attributes property in order to ignore the footprint from the relevant file.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/fabrication.png?raw=true" height=505>
---
### ③ Offset Component Rotation
The rotation of components in KiCad Footprints does not always match the orientation in the JLC library because KiCad and JLC PCB used different variation of the same standard. Most of the rotations may be corrected by the `rotations.cf` definitions. To the exception cases: add an 'FT Rotation Offset'* field - with positive values indicating counter-clockwise orientation offset in degrees.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/rotation-jlc.png?raw=true" height=164>
If the JLC preview shows a footprint like this, enter a rotation offset of -90 to rotate pin 1 to the lower right corner.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/rotation.png?raw=true" height=420>
Use following table to quickly find out to which coordinate enter the correction based on JLC arrows clicks - depending on footprint rotation in KiCad PCB Editor status bar:
|KiCad footprint deg | x | y|
|----|----|----|
|0 deg, Front | right arrow | up arrow |
|0 deg, Back | left arrow | down arrow |
|180 deg, Front | left arrow | down arrow |
|180 deg, Back | right arrow | up arrow |
|90 deg, Front or Back | up arrow | left arrow |
|-90 deg, Front or Back | down arrow | right arrow |
> [!TIP]
> Single arrow press in JLC is 0.0635mm (= 1/400in) shift.
#### Primary Fields*:
| 'FT Rotation Offset' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Rotation Offset' | 'RotOffset' |
| --- | --- |
_The fields will be queried in the order denoted above._
---
### ④ Offset Component Position
The position of components in KiCad Footprints does not always match the orientation in the JLC library because KiCad and JLCPCB used different variation of the same standard. To the exception cases: add an 'FT Position Offset'* field with an comma separated x,y position offset to correct it.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/position.png?raw=true" height=420>
> [!TIP]
> Single arrow press in JLC equals to 0.0635mm (= 1/400in) shift.
#### Primary Fields*:
| 'FT Position Offset' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Position Offset' | 'PosOffset' |
| --- | --- |
_The fields will be queried in the order denoted above._
### ⑤ Override Component Origin
The Fabrication Toolkit reports the position of each component based on an automatically selected point of reference. This default behavior can be overridden by adding an 'FT Origin'* field to the component.
#### Primary Fields*:
| 'FT Origin' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Origin' |
| --- |
The **Origin** field supports the following values:
- `Anchor` - Uses the footprint's anchor point, which can be modified in KiCad's footprint editor.
- `Center` - Uses the center of the bounding box formed by the footprint's pads.
### ⑥ Override Component Layer
Some footprints may have their components defined on the opposite layer to there actual footprints. In these instances you can override mount side by adding an 'FT Layer Override'* field to the component.
The **Layer Override** field supports the following values:
- `top`, `t` - Override footprint layer to the top.
- `bottom`, `b` - Override footprint layer to the bottom.
#### Primary Fields*:
| 'FT Layer Override' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'Layer Override' | 'LayerOverride' |
| --- | --- |
_The fields will be queried in the order denoted above._
## CLI
The plugin can also be used via the linux command line. This can be particularly useful if you need to embed the plugin into an automation pipeline or environment.
The plugin can be called with the command below:
```
python3 -m plugins.cli -p /myProject/myBoard.kicad_pcb
```
All the options from the GUI are also available via the cli interface:
```
python3 -m plugins.cli -h
usage: Fabrication Toolkit [-h] --path PATH [--additionalLayers LAYERS] [--user1VCut] [--user2AltVCut]
[--autoTranslate] [--autoFill] [--excludeDNP] [--allActiveLayers] [--archiveName NAME]
[--openBrowser] [--noBackup]
Generates JLCPCB production files from a KiCAD board file
options:
-h, --help show this help message and exit
--path PATH, -p PATH Path to KiCAD board file
--additionalLayers LAYERS, -aL LAYERS
Additional layers(comma-separated)
--user1VCut, -u1 Set User.1 as V-Cut layer
--user2AltVCut, -u2 Use User.2 for alternative Edge-Cut layer
--autoTranslate, -t Apply automatic position/rotation translations
--autoFill, -f Apply automatic fill for all zones
--excludeDNP, -e Exclude DNP components from BOM
--allActiveLayers, -aaL
Export all active layers instead of only commonly used ones
--archiveName NAME, -aN NAME
Name of the generated archives
--openBrowser, -b Open web browser with directory file overview after generation
--noBackup, -nB Do not create a backup of the project before generation
```
## Author
Benny Megidish
<iframe src="https://github.com/sponsors/electron-rare/card" title="Sponsor electron-rare" height="225" width="600" style="border: 0;"></iframe>
Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

+8 -7
View File
@@ -2,24 +2,25 @@
"$schema": "https://go.kicad.org/pcm/schemas/v1",
"name": "Fabrication Toolkit",
"description": "JLC PCB fabrication toolkit",
"description_full": "Toolkit for automating PCB fabrication process with KiCad and JLC PCB. \n\n OPTIONS: \n\u2022 Include Component Part Number in Production Files \n Add an 'MPN' field with the LCSC component part number to the footprint component options. \n\n\u2022 Ignore Footprint in Production Files \n Select 'Exclude from position files' or 'Exclude from BOM' in the footprint component options to in order ignore the footprint from the relevant file.",
"description_full": "Toolkit for automating PCB fabrication process with KiCad and JLC PCB. \n\n Features: \n\u2022 Generates gerber files in correct format for production. \n\u2022 Generates BOM file in correct format for production. \n\u2022 Generates Pick and Place file in correct format for assembly. \n\u2022 Automatic and manual component translations. \n\u2022 Many more unique additional features. \n\u2665 https://github.com/sponsors/bennymeg",
"identifier": "com.github.bennymeg.JLC-Plugin-for-KiCad",
"type": "plugin",
"author": {
"name": "Benny Megidish",
"contact": {
"web": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
"web": "https://github.com/bennymeg/Fabrication-Toolkit"
}
},
"maintainer": {
"name": "Benny Megidish",
"contact": {
"web": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
"web": "https://github.com/bennymeg/Fabrication-Toolkit"
}
},
"license": "Apache-2.0",
"resources": {
"homepage": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
"homepage": "https://github.com/bennymeg/Fabrication-Toolkit",
"Sponsor": "https://github.com/sponsors/bennymeg"
},
"tags": [
"jlc",
@@ -29,9 +30,9 @@
],
"versions": [
{
"version": "0.4.2",
"status": "testing",
"version": "5.2.0",
"status": "stable",
"kicad_version": "6.00"
}
]
}
}
+42
View File
@@ -0,0 +1,42 @@
import argparse as ap
from .thread import ProcessThread
from .options import *
if __name__ == '__main__':
parser = ap.ArgumentParser(prog="Fabrication Toolkit",
description="Generates JLCPCB production files from a KiCAD board file")
parser.add_argument("--path", "-p", type=str, help="Path to KiCAD board file", required=True)
parser.add_argument("--additionalLayers", "-aL", type=str, help="Additional layers(comma-separated)", metavar="LAYERS")
parser.add_argument("--user1VCut", "-u1", action="store_true", help="Set User.1 as V-Cut layer")
parser.add_argument("--user2AltVCut", "-u2", action="store_true", help="Set User.2 as alternative Edge-Cut layer")
parser.add_argument("--autoTranslate", "-t", action="store_true", help="Apply automatic position/rotation translations")
parser.add_argument("--autoFill", "-f", action="store_true", help="Apply automatic fill for all zones")
parser.add_argument("--excludeDNP", "-e", action="store_true", help="Exclude DNP components from BOM")
parser.add_argument("--allActiveLayers", "-aaL",action="store_true", help="Export all active layers instead of only commonly used ones")
parser.add_argument("--archiveName", "-aN", type=str, help="Name of the generated archives", metavar="NAME")
parser.add_argument("--openBrowser", "-b", action="store_true", help="Open webbrowser with directory file overview after generation")
parser.add_argument("--nonInteractive", "-nI" ,action="store_true", help="Run in non-Interactive mode. Useful in CI/CD environment.")
parser.add_argument("--noBackup", "-nB", action="store_true", help="Do not create backup files")
args = parser.parse_args()
options = dict()
options[AUTO_TRANSLATE_OPT] = args.autoTranslate
options[AUTO_FILL_OPT] = args.autoFill
options[EXCLUDE_DNP_OPT] = args.excludeDNP
options[EXTEND_EDGE_CUT_OPT] = args.user1VCut
options[ALTERNATIVE_EDGE_CUT_OPT] = args.user2AltVCut
options[ALL_ACTIVE_LAYERS_OPT] = args.allActiveLayers
options[ARCHIVE_NAME] = args.archiveName
options[EXTRA_LAYERS] = args.additionalLayers
options[BACKUP_OPT] = not args.noBackup
openBrowser = args.openBrowser
nonInteractive = args.nonInteractive
path = args.path
ProcessThread(wx=None, cli=path, options=options, openBrowser=openBrowser, nonInteractive=nonInteractive)
+17 -17
View File
@@ -1,4 +1,4 @@
import pcbnew
import pcbnew # type: ignore
baseUrl = 'https://www.jlcpcb.com'
@@ -6,21 +6,21 @@ netlistFileName = 'netlist.ipc'
designatorsFileName = 'designators.csv'
placementFileName = 'positions.csv'
bomFileName = 'bom.csv'
gerberArchiveName = 'gerber.zip'
gerberArchiveName = 'gerbers.zip'
outputFolder = 'production'
bomRowLimit = 200
plotPlan = [
("F.Cu", pcbnew.F_Cu, "Top Layer"),
("B.Cu", pcbnew.B_Cu, "Bottom Layer"),
("In1.Cu", pcbnew.In1_Cu, "Internal plane 1"),
("In2.Cu", pcbnew.In2_Cu, "Internal plane 2"),
("In3.Cu", pcbnew.In3_Cu, "Internal plane 3"),
("In4.Cu", pcbnew.In4_Cu, "Internal plane 4"),
("F.SilkS", pcbnew.F_SilkS, "Top Silkscreen"),
("B.SilkS", pcbnew.B_SilkS, "Bottom Silkscreen"),
("F.Mask", pcbnew.F_Mask, "Top Soldermask"),
("B.Mask", pcbnew.B_Mask, "Bottom Soldermask"),
("F.Paste", pcbnew.F_Paste, "Top Paste (Stencil)"),
("B.Paste", pcbnew.B_Paste, "Bottom Paste (Stencil)"),
("Edge.Cuts", pcbnew.Edge_Cuts, "Board Outline")
]
optionsFileName = 'fabrication-toolkit-options.json'
standardLayers = [ pcbnew.F_Cu, pcbnew.B_Cu,
pcbnew.In1_Cu, pcbnew.In2_Cu, pcbnew.In3_Cu, pcbnew.In4_Cu, pcbnew.In5_Cu,
pcbnew.In6_Cu, pcbnew.In7_Cu, pcbnew.In8_Cu, pcbnew.In9_Cu, pcbnew.In10_Cu,
pcbnew.In11_Cu, pcbnew.In12_Cu, pcbnew.In13_Cu, pcbnew.In14_Cu, pcbnew.In15_Cu,
pcbnew.In16_Cu, pcbnew.In17_Cu, pcbnew.In18_Cu, pcbnew.In19_Cu, pcbnew.In20_Cu,
pcbnew.In21_Cu, pcbnew.In22_Cu, pcbnew.In23_Cu, pcbnew.In24_Cu, pcbnew.In25_Cu,
pcbnew.In26_Cu, pcbnew.In27_Cu, pcbnew.In28_Cu, pcbnew.In29_Cu, pcbnew.In30_Cu,
pcbnew.F_SilkS, pcbnew.B_SilkS,
pcbnew.F_Mask, pcbnew.B_Mask,
pcbnew.F_Paste, pcbnew.B_Paste,
pcbnew.Edge_Cuts
]
+11
View File
@@ -0,0 +1,11 @@
AUTO_TRANSLATE_OPT = "AUTO TRANSLATE"
AUTO_FILL_OPT = "AUTO FILL"
EXCLUDE_DNP_OPT = "EXCLUDE DNP"
OUTPUT_NAME_OPT = "OUTPUT NAME"
EXTEND_EDGE_CUT_OPT = "EXTEND_EDGE_CUT"
ALTERNATIVE_EDGE_CUT_OPT = "ALTERNATIVE_EDGE_CUT"
ALL_ACTIVE_LAYERS_OPT = "ALL_ACTIVE_LAYERS"
OPEN_BROWSER_OPT = "OPEN BROWSER"
ARCHIVE_NAME = "ARCHIVE_NAME"
EXTRA_LAYERS = "EXTRA_LAYERS"
BACKUP_OPT = "BACKUP_OPT"
+123 -14
View File
@@ -1,9 +1,11 @@
import os
import wx
import pcbnew
import pcbnew # type: ignore
from .thread import ProcessThread
from .events import StatusEvent
from .options import AUTO_FILL_OPT, AUTO_TRANSLATE_OPT, EXCLUDE_DNP_OPT, EXTEND_EDGE_CUT_OPT, ALTERNATIVE_EDGE_CUT_OPT, EXTRA_LAYERS, ALL_ACTIVE_LAYERS_OPT, ARCHIVE_NAME, OPEN_BROWSER_OPT, BACKUP_OPT
from .utils import load_user_options, save_user_options, get_layer_names
# WX GUI form that show the plugin progress
@@ -13,37 +15,144 @@ class KiCadToJLCForm(wx.Frame):
self,
None,
id=wx.ID_ANY,
title=u"Processing...",
title=u"Fabrication Toolkit",
pos=wx.DefaultPosition,
size=wx.DefaultSize,
style=wx.DEFAULT_DIALOG_STYLE)
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
# self.app = wx.PySimpleApp()
icon = wx.Icon(os.path.join(os.path.dirname(__file__), 'icon.png'))
self.SetIcon(icon)
self.SetBackgroundColour(wx.LIGHT_GREY)
bSizer1 = wx.BoxSizer(wx.VERTICAL)
self.SetSizeHints(wx.Size(600, 100), wx.DefaultSize)
self.m_gaugeStatus = wx.Gauge(
self, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size(
300, 20), wx.GA_HORIZONTAL)
self.m_gaugeStatus.SetValue(0)
bSizer1.Add(self.m_gaugeStatus, 0, wx.ALL, 5)
userOptions = load_user_options({
EXTRA_LAYERS: "",
ALL_ACTIVE_LAYERS_OPT: False,
ARCHIVE_NAME: "",
EXTEND_EDGE_CUT_OPT: False,
ALTERNATIVE_EDGE_CUT_OPT: False,
AUTO_TRANSLATE_OPT: True,
AUTO_FILL_OPT: True,
EXCLUDE_DNP_OPT: False,
OPEN_BROWSER_OPT: True,
BACKUP_OPT: True,
})
self.SetSizer(bSizer1)
self.mOptionsLabel = wx.StaticText(self, label='Options:')
# self.mOptionsSeparator = wx.StaticLine(self)
layers = get_layer_names(pcbnew.GetBoard())
self.mAdditionalLayersControl = wx.TextCtrl(self, size=wx.Size(600, 50))
self.mAdditionalLayersControl.Hint = "Additional layers (comma-separated) [optional]"
self.mAdditionalLayersControl.AutoComplete(layers)
self.mAdditionalLayersControl.Enable()
self.mAdditionalLayersControl.SetValue(userOptions[EXTRA_LAYERS])
self.mArchiveNameControl = wx.TextCtrl(self, size=wx.Size(600, 50))
self.mArchiveNameControl.Hint = "Archive name (e.g. ${TITLE}_${REVISION}) [optional]"
self.mArchiveNameControl.Enable()
self.mArchiveNameControl.SetValue(userOptions[ARCHIVE_NAME])
self.mAllActiveLayersCheckbox = wx.CheckBox(self, label='Plot all active layers')
self.mAllActiveLayersCheckbox.SetValue(userOptions[ALL_ACTIVE_LAYERS_OPT])
self.mExtendEdgeCutsCheckbox = wx.CheckBox(self, label='Set User.1 as V-Cut layer')
self.mExtendEdgeCutsCheckbox.SetValue(userOptions[EXTEND_EDGE_CUT_OPT])
self.mAlternativeEdgeCutsCheckbox = wx.CheckBox(self, label='Set User.2 as alternative Edge-Cut layer')
self.mAlternativeEdgeCutsCheckbox.SetValue(userOptions[ALTERNATIVE_EDGE_CUT_OPT])
self.mAutomaticTranslationCheckbox = wx.CheckBox(self, label='Apply automatic component translations')
self.mAutomaticTranslationCheckbox.SetValue(userOptions[AUTO_TRANSLATE_OPT])
self.mAutomaticFillCheckbox = wx.CheckBox(self, label='Apply automatic fill for all zones')
self.mAutomaticFillCheckbox.SetValue(userOptions[AUTO_FILL_OPT])
self.mExcludeDnpCheckbox = wx.CheckBox(self, label='Exclude DNP components from BOM')
self.mExcludeDnpCheckbox.SetValue(userOptions[EXCLUDE_DNP_OPT])
self.mOpenBrowserCheckbox = wx.CheckBox(self, label='Open browser after generation')
self.mOpenBrowserCheckbox.SetValue(userOptions[OPEN_BROWSER_OPT])
self.mBackupCheckbox = wx.CheckBox(self, label='Generate backup files')
self.mBackupCheckbox.SetValue(userOptions[BACKUP_OPT])
self.mGaugeStatus = wx.Gauge(
self, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size(600, 20), wx.GA_HORIZONTAL)
self.mGaugeStatus.SetValue(0)
self.mGaugeStatus.Hide()
self.mGenerateButton = wx.Button(self, label='Generate', size=wx.Size(600, 60))
self.mGenerateButton.Bind(wx.EVT_BUTTON, self.onGenerateButtonClick)
boxSizer = wx.BoxSizer(wx.VERTICAL)
boxSizer.Add(self.mOptionsLabel, 0, wx.ALL, 5)
# boxSizer.Add(self.mOptionsSeparator, 0, wx.ALL, 5)
boxSizer.Add(self.mArchiveNameControl, 0, wx.ALL, 5)
boxSizer.Add(self.mAdditionalLayersControl, 0, wx.ALL, 5)
boxSizer.Add(self.mAllActiveLayersCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mExtendEdgeCutsCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mAlternativeEdgeCutsCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mAutomaticTranslationCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mAutomaticFillCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mExcludeDnpCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mOpenBrowserCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mBackupCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mGaugeStatus, 0, wx.ALL, 5)
boxSizer.Add(self.mGenerateButton, 0, wx.ALL, 5)
self.SetSizer(boxSizer)
self.Layout()
bSizer1.Fit(self)
boxSizer.Fit(self)
self.Centre(wx.BOTH)
StatusEvent.invoke(self, self.updateDisplay)
ProcessThread(self)
# Bind the ESC key event to a handler
self.Bind(wx.EVT_CHAR_HOOK, self.onKey)
# Close the dialog when pressing the ESC key
def onKey(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE:
self.Close(True)
else:
event.Skip()
def onGenerateButtonClick(self, event):
options = dict()
options[ARCHIVE_NAME] = self.mArchiveNameControl.GetValue()
options[EXTRA_LAYERS] = self.mAdditionalLayersControl.GetValue()
options[ALL_ACTIVE_LAYERS_OPT] = self.mAllActiveLayersCheckbox.GetValue()
options[EXTEND_EDGE_CUT_OPT] = self.mExtendEdgeCutsCheckbox.GetValue()
options[ALTERNATIVE_EDGE_CUT_OPT] = self.mAlternativeEdgeCutsCheckbox.GetValue()
options[AUTO_TRANSLATE_OPT] = self.mAutomaticTranslationCheckbox.GetValue()
options[AUTO_FILL_OPT] = self.mAutomaticFillCheckbox.GetValue()
options[EXCLUDE_DNP_OPT] = self.mExcludeDnpCheckbox.GetValue()
options[OPEN_BROWSER_OPT] = self.mOpenBrowserCheckbox.GetValue()
options[BACKUP_OPT] = self.mBackupCheckbox.GetValue()
save_user_options(options)
self.mOptionsLabel.Hide()
self.mArchiveNameControl.Hide()
self.mAdditionalLayersControl.Hide()
self.mAllActiveLayersCheckbox.Hide()
self.mExtendEdgeCutsCheckbox.Hide()
self.mAlternativeEdgeCutsCheckbox.Hide()
self.mAutomaticTranslationCheckbox.Hide()
self.mAutomaticFillCheckbox.Hide()
self.mExcludeDnpCheckbox.Hide()
self.mOpenBrowserCheckbox.Hide()
self.mBackupCheckbox.Hide()
self.mGenerateButton.Hide()
self.mGaugeStatus.Show()
self.Fit()
self.SetTitle('Fabrication Toolkit (Processing...)')
StatusEvent.invoke(self, self.updateDisplay)
ProcessThread(self, options, openBrowser=options[OPEN_BROWSER_OPT])
def updateDisplay(self, status):
if status.data == -1:
self.SetTitle('Fabrication Toolkit (Done!)')
pcbnew.Refresh()
self.Destroy()
else:
self.m_gaugeStatus.SetValue(status.data)
self.mGaugeStatus.SetValue(int(status.data))
# Plugin definition
+466 -91
View File
@@ -1,23 +1,56 @@
# from urllib import response
# import json
# import requests
# For better annotation.
from __future__ import annotations
# System base libraries
import os
import re
import csv
import math
import shutil
import pcbnew
from collections import defaultdict
from typing import Tuple
# Interaction with KiCad.
import pcbnew # type: ignore
from .utils import footprint_has_field, footprint_get_field, get_plot_plan
# Application definitions.
from .config import *
class ProcessManager:
def __init__(self):
self.board = pcbnew.GetBoard()
def __init__(self, board = None):
# if no board is already loaded by cli mode getBoard from kicad environment
if board is None:
self.board = pcbnew.GetBoard()
else:
self.board = board
self.bom = []
self.components = []
self.__rotation_db = self.__read_rotation_db()
def generate_gerber(self, temp_dir):
@staticmethod
def normalize_filename(filename):
return re.sub(r'[^\w\s\.\-]', '', filename)
def update_zone_fills(self):
'''Verify all zones have up-to-date fills.'''
filler = pcbnew.ZONE_FILLER(self.board)
zones = self.board.Zones()
# Fill returns true/false if a refill was made
# We cant use aCheck = True as that would require a rollback on the commit object if
# user decided to not perform the zone fill and the commit object is not exposed to python API
filler.Fill(zones, False)
# Finally rebuild the connectivity db
self.board.BuildConnectivity()
def generate_gerber(self, temp_dir, extra_layers, extend_edge_cuts, alternative_edge_cuts, all_active_layers):
'''Generate the Gerber files.'''
original_settings = self.board.GetDesignSettings()
settings = self.board.GetDesignSettings()
settings.m_SolderMaskMargin = 0
settings.m_SolderMaskMargin = 50000
settings.m_SolderMaskToCopperClearance = 5000
settings.m_SolderMaskMinWidth = 0
plot_controller = pcbnew.PLOT_CONTROLLER(self.board)
@@ -30,54 +63,126 @@ class ProcessManager:
plot_options.SetScale(1)
plot_options.SetMirror(False)
plot_options.SetUseGerberAttributes(True)
plot_options.SetExcludeEdgeLayer(True)
plot_options.SetUseGerberProtelExtensions(False)
plot_options.SetUseGerberProtelExtensions(True)
plot_options.SetUseAuxOrigin(True)
plot_options.SetSubtractMaskFromSilk(False)
plot_options.SetSubtractMaskFromSilk(True)
plot_options.SetUseGerberX2format(False)
plot_options.SetDrillMarksType(0) # NO_DRILL_SHAPE
for layer_info in plotPlan:
if self.board.IsLayerEnabled(layer_info[1]):
if hasattr(plot_options, "SetExcludeEdgeLayer"):
plot_options.SetExcludeEdgeLayer(True)
if extra_layers is not None:
extra_layers = [element.strip() for element in extra_layers.strip().split(',') if element.strip()]
else:
extra_layers = []
for layer_info in get_plot_plan(self.board):
if (self.board.IsLayerEnabled(layer_info[1]) and (all_active_layers or layer_info[1] in standardLayers)) or layer_info[0] in extra_layers:
plot_controller.SetLayer(layer_info[1])
plot_controller.OpenPlotfile(
layer_info[0],
pcbnew.PLOT_FORMAT_GERBER,
layer_info[2])
plot_controller.PlotLayer()
plot_controller.OpenPlotfile(layer_info[2], pcbnew.PLOT_FORMAT_GERBER, layer_info[2])
if layer_info[1] == pcbnew.Edge_Cuts and hasattr(plot_controller, 'PlotLayers') and (extend_edge_cuts or alternative_edge_cuts):
seq = pcbnew.LSEQ()
# uses User_2 layer for alternative Edge_Cuts layer
if alternative_edge_cuts:
seq.push_back(pcbnew.User_2)
else:
seq.push_back(layer_info[1])
# includes User_1 layer with Edge_Cuts layer to allow V Cuts to be defined as User_1 layer
# available for KiCad 7.0.1+
if extend_edge_cuts:
seq.push_back(layer_info[1])
seq.push_back(pcbnew.User_1)
plot_controller.PlotLayers(seq)
else:
plot_controller.PlotLayer()
plot_controller.ClosePlot()
settings = original_settings
def generate_drills(self, temp_dir):
'''Generate the drill file.'''
drill_writer = pcbnew.EXCELLON_WRITER(self.board)
drill_writer.SetOptions(
False,
True,
False,
self.board.GetDesignSettings().GetAuxOrigin(),
False)
drill_writer.SetFormat(False)
drill_writer.CreateDrillandMapFilesSet(temp_dir, True, False)
drill_writer.SetFormat(True)
drill_writer.SetMapFileFormat(pcbnew.PLOT_FORMAT_GERBER)
drill_writer.CreateDrillandMapFilesSet(temp_dir, True, True)
def generate_netlist(self, temp_dir):
'''Generate the connection netlist.'''
netlist_writer = pcbnew.IPC356D_WRITER(self.board)
netlist_writer.Write(os.path.join(temp_dir, netlistFileName))
def generate_positions(self, temp_dir):
def _get_footprint_rotation(self, footprint):
return footprint.GetOrientation().AsDegrees() if hasattr(footprint.GetOrientation(), 'AsDegrees') else footprint.GetOrientation() / 10.0
def _get_footprint_position(self, footprint):
"""Calculate position based on center of pads / bounding box."""
origin_type = self._get_origin_from_footprint(footprint)
footprint_rotation = self._get_footprint_rotation(footprint)
footprint_rotated = footprint_rotation % 90 != 0
# if the footprint is not rotated by a multiple of 90 degrees, the bounding boxes will be off, so we create a temporary copy that is rotated to 0
if footprint_rotated:
footprint = footprint.Duplicate()
footprint.SetOrientationDegrees(0)
if origin_type == 'Anchor':
position = footprint.GetPosition()
else: # if type_origin == 'Center' or anything else
pads = footprint.Pads()
if len(pads) > 0:
# get bounding box based on pads only to ignore non-copper layers, e.g. silkscreen
bbox = pads[0].GetBoundingBox() # start with small bounding box
for pad in pads:
bbox.Merge(pad.GetBoundingBox()) # expand bounding box
position = bbox.GetCenter()
else:
position = footprint.GetPosition() # if we have no pads we fallback to anchor
if footprint_rotated:
# now we determine the offset of the "true" position relative to the "KiCAD" position & apply the footprints rotation
raw_pos = footprint.GetPosition()
relative_position = (position[0] - raw_pos[0], position[1] - raw_pos[1])
rsin = math.sin(footprint_rotation / 180 * math.pi)
rcos = math.cos(footprint_rotation / 180 * math.pi)
relative_position = ( relative_position[0] * rcos + relative_position[1] * rsin, -relative_position[0] * rsin + relative_position[1] * rcos )
position = (raw_pos[0] + relative_position[0], raw_pos[1] + relative_position[1])
return position
def generate_tables(self, temp_dir, auto_translate, exclude_dnp):
'''Generate the data tables.'''
if hasattr(self.board, 'GetModules'):
footprints = list(self.board.GetModules())
else:
footprints = list(self.board.GetFootprints())
# sort footprint after designator
footprints.sort(key=lambda x: x.GetReference().upper())
# unique designator dictionary
footprint_designators = defaultdict(int)
for i, footprint in enumerate(footprints):
# count unique designators
footprint_designators[footprint.GetReference()] += 1
footprint_designators[footprint.GetReference().upper()] += 1
bom_designators = footprint_designators.copy()
with open((os.path.join(temp_dir, designatorsFileName)), 'w', encoding='utf-8') as f:
for key, value in footprint_designators.items():
f.write('%s:%s\n' % (key, value))
if len(footprint_designators.items()) > 0:
with open((os.path.join(temp_dir, designatorsFileName)), 'w', encoding='utf-8-sig') as f:
for key, value in footprint_designators.items():
f.write('%s:%s\n' % (key, value))
for i, footprint in enumerate(footprints):
try:
@@ -85,81 +190,134 @@ class ProcessManager:
except AttributeError:
footprint_name = str(footprint.GetFPID().GetLibItemName())
layer = {
pcbnew.F_Cu: 'top',
pcbnew.B_Cu: 'bottom',
}.get(footprint.GetLayer())
# Get the library nickname when available
lib_nickname = None
try:
lib_nickname = str(footprint.GetFPID().GetLibNickname())
except AttributeError:
pass
layer = self._get_layer_override_from_footprint(footprint)
# mount_type = {
# 0: 'smt',
# 1: 'tht',
# 2: 'smt'
# 2: 'unspecified'
# }.get(footprint.GetAttributes())
if not footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_POS_FILES:
is_dnp = (footprint_has_field(footprint, 'dnp')
or (footprint.GetValue().upper() == 'DNP')
or getattr(footprint, 'IsDNP', bool)())
skip_dnp = exclude_dnp and is_dnp
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_POS_FILES) and not is_dnp:
# append unique ID if duplicate footprint designator
unique_id = ""
if footprint_designators[footprint.GetReference()] > 1:
unique_id = str(footprint_designators[footprint.GetReference()])
footprint_designators[footprint.GetReference()] -= 1
if footprint_designators[footprint.GetReference().upper()] > 1:
unique_id = str(footprint_designators[footprint.GetReference().upper()])
footprint_designators[footprint.GetReference().upper()] -= 1
designator = "{}{}{}".format(footprint.GetReference().upper(), "" if unique_id == "" else "_", unique_id)
position = self._get_footprint_position(footprint)
mid_x = (position[0] - self.board.GetDesignSettings().GetAuxOrigin()[0]) / 1000000.0
mid_y = (position[1] - self.board.GetDesignSettings().GetAuxOrigin()[1]) * -1.0 / 1000000.0
rotation = self._get_footprint_rotation(footprint)
rotation_offset_db = self._get_rotation_from_db(footprint_name, lib_nickname) # Try with lib_nickname if available
rotation_offset_manual = self._get_rotation_offset_from_footprint(footprint)
# position offset needs to take rotation into account
pos_offset = self._get_position_offset_from_footprint(footprint)
if auto_translate:
pos_offset_db = self._get_position_offset_from_db(footprint_name, lib_nickname) # Try with lib_nickname if available
pos_offset = (pos_offset[0] + pos_offset_db[0], pos_offset[1] + pos_offset_db[1])
rsin = math.sin(rotation / 180 * math.pi)
rcos = math.cos(rotation / 180 * math.pi)
if layer == 'bottom':
pos_offset = ( pos_offset[0] * rcos + pos_offset[1] * rsin, pos_offset[0] * rsin - pos_offset[1] * rcos )
else:
pos_offset = ( pos_offset[0] * rcos - pos_offset[1] * rsin, pos_offset[0] * rsin + pos_offset[1] * rcos )
mid_x, mid_y = tuple(map(sum,zip((mid_x, mid_y), pos_offset)))
# JLC expect 'Rotation' to be 'as viewed from above component', so bottom needs inverting, and ends up 180 degrees out as well
if layer == 'bottom':
rotation = (180.0 - rotation)
if auto_translate:
rotation += rotation_offset_db
rotation = (rotation + rotation_offset_manual) % 360.0
self.components.append({
'Designator': "{}{}{}".format(footprint.GetReference(), "" if unique_id == "" else "_", unique_id),
'Mid X': (footprint.GetPosition()[0] - self.board.GetDesignSettings().GetAuxOrigin()[0]) / 1000000.0,
'Mid Y': (footprint.GetPosition()[1] - self.board.GetDesignSettings().GetAuxOrigin()[1]) * -1.0 / 1000000.0,
'Rotation': footprint.GetOrientation() / 10.0,
'Designator': designator,
'Mid X': mid_x,
'Mid Y': mid_y,
'Rotation': rotation,
'Layer': layer,
})
if not footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM:
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM) and not skip_dnp:
# append unique ID if we are dealing with duplicate bom designator
unique_id = ""
if bom_designators[footprint.GetReference()] > 1:
unique_id = str(bom_designators[footprint.GetReference()])
bom_designators[footprint.GetReference()] -= 1
if bom_designators[footprint.GetReference().upper()] > 1:
unique_id = str(bom_designators[footprint.GetReference().upper()])
bom_designators[footprint.GetReference().upper()] -= 1
# todo: merge similar parts into single entry
# merge similar parts into single entry
insert = True
for component in self.bom:
same_footprint = component['Footprint'] == self._normalize_footprint_name(footprint_name)
same_value = component['Value'].upper() == footprint.GetValue().upper()
same_lcsc = component['LCSC Part #'] == self._get_mpn_from_footprint(footprint)
under_limit = component['Quantity'] < bomRowLimit
self.bom.append({
'Designator': "{}{}{}".format(footprint.GetReference(), "" if unique_id == "" else "_", unique_id),
'Footprint': footprint_name,
'Quantity': 1,
'Value': footprint.GetValue(),
# 'Mount': mount_type,
'LCSC Part #': self._getMpnFromFootprint(footprint),
})
if same_footprint and same_value and same_lcsc and under_limit:
component['Designator'] += ", " + "{}{}{}".format(footprint.GetReference().upper(), "" if unique_id == "" else "_", unique_id)
component['Quantity'] += 1
insert = False
break
with open((os.path.join(temp_dir, placementFileName)), 'w', newline='', encoding='utf-8') as outfile:
header = True
csv_writer = csv.writer(outfile)
# add component to BOM
if insert:
self.bom.append({
'Designator': "{}{}{}".format(footprint.GetReference().upper(), "" if unique_id == "" else "_", unique_id),
'Footprint': self._normalize_footprint_name(footprint_name),
'Quantity': 1,
'Value': footprint.GetValue(),
# 'Mount': mount_type,
'LCSC Part #': self._get_mpn_from_footprint(footprint),
})
for component in self.components:
if header:
# writing headers of CSV file
csv_writer.writerow(component.keys())
header = False
def generate_positions(self, temp_dir):
'''Generate the position file.'''
if len(self.components) > 0:
with open((os.path.join(temp_dir, placementFileName)), 'w', newline='', encoding='utf-8-sig') as outfile:
csv_writer = csv.writer(outfile)
# writing headers of CSV file
csv_writer.writerow(self.components[0].keys())
# writing data of CSV file
if ('**' not in component['Designator']):
csv_writer.writerow(component.values())
for component in self.components:
# writing data of CSV file
if ('**' not in component['Designator']):
csv_writer.writerow(component.values())
def generate_bom(self, temp_dir):
'''Generate the bom file.'''
if len(self.bom) > 0:
with open((os.path.join(temp_dir, bomFileName)), 'w', newline='', encoding='utf-8-sig') as outfile:
csv_writer = csv.writer(outfile)
# writing headers of CSV file
csv_writer.writerow(self.bom[0].keys())
with open((os.path.join(temp_dir, bomFileName)), 'w', newline='', encoding='utf-8') as outfile:
header = True
csv_writer = csv.writer(outfile)
for component in self.bom:
if header:
# writing headers of CSV file
csv_writer.writerow(component.keys())
header = False
# writing data of CSV file
if ('**' not in component['Designator']):
csv_writer.writerow(component.values())
# Output all of the component information
for component in self.bom:
# writing data of CSV file
if ('**' not in component['Designator']):
csv_writer.writerow(component.values())
def generate_archive(self, temp_dir, temp_file):
'''Generate the archive file.'''
temp_file = shutil.make_archive(temp_file, 'zip', temp_dir)
temp_file = shutil.move(temp_file, temp_dir)
@@ -170,20 +328,237 @@ class ProcessManager:
return temp_file
def upload_archive(self, temp_file):
boardWidth = pcbnew.Iu2Millimeter(self.board.GetBoardEdgesBoundingBox().GetWidth())
boardHeight = pcbnew.Iu2Millimeter(self.board.GetBoardEdgesBoundingBox().GetHeight())
boardLayer = self.board.GetCopperLayerCount()
""" Private """
files = { 'upload[file]': open(temp_file, 'rb') }
upload_url = baseUrl + '/Common/KiCadUpFile/'
def __read_rotation_db(self, filename: str = os.path.join(os.path.dirname(__file__), 'transformations.csv')) -> dict[str, float]:
'''Read the rotations.cf config file so we know what rotations
to apply later.
'''
db = {}
# response = requests.post(
# upload_url, files=files, data={ 'boardWidth': boardWidth, 'boardHeight': boardHeight, 'boardLayer': boardLayer })
# urls = json.loads(response.content)
with open(filename, newline='') as csvfile:
csvDialect = csv.Sniffer().sniff(csvfile.read(1024))
csvfile.seek(0)
csvData = csv.DictReader(csvfile, fieldnames=["footprint", "rotation", "x", "y"],
restkey="extra", restval="0", dialect=csvDialect)
def _getMpnFromFootprint(self, footprint):
keys = ['mpn', 'Mpn', 'MPN', 'JLC_MPN', 'LCSC_MPN', 'LCSC Part #', 'JLC', 'LCSC']
for key in keys:
if footprint.HasProperty(key):
return footprint.GetProperty(key)
rowNum = 0
for row in csvData:
rowNum = rowNum + 1
# First row is header row, skip.
if rowNum == 1:
continue
# If there was too many fields, throw an exception.
if len(row) > 4:
raise RuntimeError("{}: Too many fields found in row {}: {}".format(filename, rowNum, row))
# See if the values we expect to be floating point numbers
# can be converted to floating point, if not throw an exception.
if row['rotation'] == "":
rotation = 0.0
else:
try:
rotation = float(row['rotation'])
except ValueError:
raise RuntimeError("{}: Non-numeric rotation value found in row {}".format(filename, rowNum))
if row['x'] == "":
delta_x = 0.0
else:
try:
delta_x = float(row['x'])
except ValueError:
raise RuntimeError("{}: Non-numeric translation value found in row {}".format(filename, rowNum))
if row['y'] == "":
delta_y = 0.0
else:
try:
delta_y = float(row['y'])
except ValueError:
raise RuntimeError("{}: Non-numeric translation value found in row {}".format(filename, rowNum))
# Add the entry to the database in the format we expect.
db[rowNum] = {}
db[rowNum]['name'] = row['footprint']
db[rowNum]['rotation'] = rotation
db[rowNum]['x'] = delta_x
db[rowNum]['y'] = delta_y
return db
def _get_rotation_from_db(self, footprint: str, lib_nickname: str = None) -> float:
'''Get the rotation to be added from the database file.
Args:
footprint: The footprint name
lib_nickname: The library nickname, if available
'''
# First try with the standard approach for backward compatibility
for entry in self.__rotation_db.items():
# If the expression in the DB contains a :, search for it literally.
if (re.search(':', entry[1]['name'])):
if (re.search(entry[1]['name'], footprint)):
return float(entry[1]['rotation'])
# There is no : in the expression, so only search the right side of the :
else:
footprint_segments = footprint.split(':')
# Only one means there was no :, just check the short.
if (len(footprint_segments) == 1):
check = footprint_segments[0]
# More means there was a :, check the right side.
else:
check = footprint_segments[1]
if (re.search(entry[1]['name'], check)):
return float(entry[1]['rotation'])
# If no match found and we have a library nickname, try matching against that
if lib_nickname:
for entry in self.__rotation_db.items():
if (re.search(entry[1]['name'], lib_nickname)):
return float(entry[1]['rotation'])
# Not found, no rotation.
return 0.0
def _get_position_offset_from_db(self, footprint: str, lib_nickname: str = None) -> Tuple[float, float]:
'''Get the position offset to be added from the database file.
Args:
footprint: The footprint name
lib_nickname: The library nickname, if available
'''
# First try with the standard approach for backward compatibility
for entry in self.__rotation_db.items():
# If the expression in the DB contains a :, search for it literally.
if (re.search(':', entry[1]['name'])):
if (re.search(entry[1]['name'], footprint)):
return ( float(entry[1]['x']), float(entry[1]['y']) )
# There is no : in the expression, so only search the right side of the :
else:
footprint_segments = footprint.split(':')
# Only one means there was no :, just check the short.
if (len(footprint_segments) == 1):
check = footprint_segments[0]
# More means there was a :, check the right side.
else:
check = footprint_segments[1]
if (re.search(entry[1]['name'], check)):
return (float(entry[1]['x']), float(entry[1]['y']))
# If no match found and we have a library nickname, try matching against that
if lib_nickname:
for entry in self.__rotation_db.items():
if (re.search(entry[1]['name'], lib_nickname)):
return (float(entry[1]['x']), float(entry[1]['y']))
# Not found, no delta.
return (0.0, 0.0)
def _get_mpn_from_footprint(self, footprint) -> str:
''''Get the MPN/LCSC stock code from standard symbol fields.'''
supplier_names = ['LCSC', 'JLCPCB']
pn_abbrevs = ['Part #', 'Part', 'PN', 'P/N', 'Part No.', 'Part Number']
keys = [(sn + " " + abr) for sn in supplier_names for abr in pn_abbrevs]
fallback_keys = ['LCSC', 'JLC', 'MPN', 'Mpn', 'mpn']
if footprint_has_field(footprint, 'dnp'):
return 'DNP'
for key in keys + fallback_keys:
if footprint_has_field(footprint, key) and '' != footprint_get_field(footprint, key):
return footprint_get_field(footprint, key)
def _get_layer_override_from_footprint(self, footprint) -> str:
'''Get the layer override from standard symbol fields.'''
keys = ['FT Layer Override']
fallback_keys = ['Layer Override', 'LayerOverride']
layer = {
pcbnew.F_Cu: 'top',
pcbnew.B_Cu: 'bottom',
}.get(footprint.GetLayer())
for key in keys + fallback_keys:
if footprint_has_field(footprint, key):
temp_layer = footprint_get_field(footprint, key)
if len(temp_layer) > 0:
if (temp_layer[0] == 'b' or temp_layer[0] == 'B'):
layer = "bottom"
break
elif (temp_layer[0] == 't' or temp_layer[0] == 'T'):
layer = "top"
break
return layer
def _get_rotation_offset_from_footprint(self, footprint) -> float:
'''Get the rotation offset from standard symbol fields.'''
keys = ['FT Rotation Offset']
fallback_keys = ['Rotation Offset', 'RotOffset']
offset = ""
for key in keys + fallback_keys:
if footprint_has_field(footprint, key):
offset = footprint_get_field(footprint, key)
break
if offset is None or offset == "":
return 0.0
else:
try:
return float(offset)
except ValueError:
raise RuntimeError("Rotation offset of {} is not a valid number".format(footprint.GetReference()))
def _get_position_offset_from_footprint(self, footprint) -> Tuple[float, float]:
'''Get the position offset from standard symbol fields.'''
keys = ['FT Position Offset']
fallback_keys = ['Position Offset', 'PosOffset']
offset = ""
for key in keys + fallback_keys:
if footprint_has_field(footprint, key):
offset = footprint_get_field(footprint, key)
break
if offset == "":
return (0.0, 0.0)
else:
try:
offset = offset.split(",")
return (float(offset[0]), float(offset[1]))
except Exception:
raise RuntimeError("Position offset of {} is not a valid pair of numbers".format(footprint.GetReference()))
def _get_origin_from_footprint(self, footprint) -> float:
'''Get the origin from standard symbol fields.'''
keys = ['FT Origin']
fallback_keys = ['Origin']
attributes = footprint.GetAttributes()
# determine origin type by package type
if attributes & pcbnew.FP_SMD:
origin_type = 'Anchor'
else:
origin_type = 'Center'
for key in keys + fallback_keys:
if footprint_has_field(footprint, key):
origin_type_override = str(footprint_get_field(footprint, key)).strip().capitalize()
if origin_type_override in ['Anchor', 'Center']:
origin_type = origin_type_override
break
return origin_type
def _normalize_footprint_name(self, footprint) -> str:
# replace footprint names of resistors, capacitors, inductors, diodes, LEDs, fuses etc, with the footprint size only
pattern = re.compile(r'^(\w*_SMD:)?\w{1,4}_(\d+)_\d+Metric.*$')
return pattern.sub(r'\2', footprint)
+152 -37
View File
@@ -1,58 +1,124 @@
import os
import wx
import pcbnew # type: ignore
import shutil
import tempfile
import webbrowser
import datetime
import logging
from threading import Thread
from .events import StatusEvent
from .process import ProcessManager
from .config import *
from .options import *
from .utils import print_cli_progress_bar
class ProcessThread(Thread):
def __init__(self, wx):
def __init__(self, wx, options, cli = None, openBrowser = True, nonInteractive = False):
Thread.__init__(self)
self.process_manager = ProcessManager()
# prevent use of cli and graphical mode at the same time
if (wx is None and cli is None) or (wx is not None and cli is not None):
logging.error("Specify either graphical or cli use!")
return
if cli is not None:
try:
self.board = pcbnew.LoadBoard(cli)
except Exception as e:
logging.error("Fabrication Toolkit - Error" + str(e))
return
else:
self.board = None
self.process_manager = ProcessManager(self.board)
self.wx = wx
self.cli = cli
self.options = options
self.openBrowser = openBrowser
self.nonInteractive = nonInteractive
self.start()
def expandTextVariables(self, string):
titleBlock = pcbnew.GetBoard().GetTitleBlock()
titleBlockVars = {
"ISSUE_DATE": titleBlock.GetDate(),
"CURRENT_DATE": datetime.datetime.now().strftime('%Y-%m-%d'),
"REVISION": titleBlock.GetRevision(),
"TITLE": titleBlock.GetTitle(),
"COMPANY": titleBlock.GetCompany(),
}
for comment_index in range(9):
titleBlockVars[f"COMMENT{comment_index + 1}"] = titleBlock.GetComment(comment_index)
for var, val in titleBlockVars.items():
string = string.replace(f"${{{var}}}", val)
if (hasattr(self.process_manager.board, "GetProject") and hasattr(pcbnew, "ExpandTextVars")):
project = self.process_manager.board.GetProject()
string = pcbnew.ExpandTextVars(string, project)
return string
def run(self):
# initializing
self.report(0)
self.progress(0)
temp_dir = tempfile.mkdtemp()
temp_dir_gerber = temp_dir + "_g"
os.makedirs(temp_dir_gerber)
_, temp_file = tempfile.mkstemp()
project_directory = os.path.dirname(self.process_manager.board.GetFileName())
output_path = os.path.join(project_directory, outputFolder)
# configure and generate gerber
self.report(5)
self.process_manager.generate_gerber(temp_dir)
try:
# Verify all zones are up-to-date
self.progress(10)
if (self.options[AUTO_FILL_OPT]):
self.process_manager.update_zone_fills()
# generate drill file
self.report(15)
self.process_manager.generate_drills(temp_dir)
# generate netlist
self.report(25)
self.process_manager.generate_netlist(temp_dir)
# generate gerber
self.progress(20)
self.process_manager.generate_gerber(temp_dir_gerber, self.options[EXTRA_LAYERS], self.options[EXTEND_EDGE_CUT_OPT],
self.options[ALTERNATIVE_EDGE_CUT_OPT], self.options[ALL_ACTIVE_LAYERS_OPT])
# generate pick and place file
self.report(40)
self.process_manager.generate_positions(temp_dir)
# generate drill file
self.progress(30)
self.process_manager.generate_drills(temp_dir_gerber)
# generate BOM file
self.report(60)
self.process_manager.generate_bom(temp_dir)
# generate netlist
self.progress(40)
self.process_manager.generate_netlist(temp_dir)
# generate production archive
self.report(75)
temp_file = self.process_manager.generate_archive(temp_dir, temp_file)
# generate data tables
self.progress(50)
self.process_manager.generate_tables(temp_dir, self.options[AUTO_TRANSLATE_OPT], self.options[EXCLUDE_DNP_OPT])
# upload files
#self.report(87.5)
#self.process_manager.upload_archive(temp_file)
# generate pick and place file
self.progress(60)
self.process_manager.generate_positions(temp_dir)
# generate BOM file
self.progress(70)
self.process_manager.generate_bom(temp_dir)
# generate production archive
self.progress(85)
temp_file = self.process_manager.generate_archive(temp_dir_gerber, temp_file)
shutil.move(temp_file, temp_dir)
shutil.rmtree(temp_dir_gerber)
temp_file = os.path.join(temp_dir, os.path.basename(temp_file))
except Exception as e:
if self.wx is None:
logging.error("Fabrication Toolkit - Error" + str(e))
else:
wx.MessageBox(str(e), "Fabrication Toolkit - Error", wx.OK | wx.ICON_ERROR)
self.progress(-1)
return
# progress bar done animation
read_so_far = 0
@@ -64,20 +130,69 @@ class ProcessThread(Thread):
break
read_so_far += len(data)
percent = read_so_far * 1e2 / total_size
self.report(75 + percent / 8)
self.progress(85 + percent / 8)
# generate gerber name
title_block = self.process_manager.board.GetTitleBlock()
title = title_block.GetTitle()
revision = title_block.GetRevision()
company = title_block.GetCompany()
file_date = title_block.GetDate()
if (hasattr(self.process_manager.board, "GetProject") and hasattr(pcbnew, "ExpandTextVars")):
project = self.process_manager.board.GetProject()
title = pcbnew.ExpandTextVars(title, project)
revision = pcbnew.ExpandTextVars(revision, project)
company = pcbnew.ExpandTextVars(company, project)
file_date = pcbnew.ExpandTextVars(file_date, project)
# make output dir
filename = os.path.splitext(os.path.basename(self.process_manager.board.GetFileName()))[0]
output_path = os.path.join(project_directory, outputFolder)
if not os.path.exists(output_path):
os.makedirs(output_path)
# rename gerber archive
if self.options[ARCHIVE_NAME]:
baseName = self.expandTextVariables(self.options[ARCHIVE_NAME])
else:
baseName = "{} {}".format(title or filename, revision or '')
gerberArchiveName = ProcessManager.normalize_filename("_".join((baseName.strip() + '.zip').split()))
os.rename(temp_file, os.path.join(temp_dir, gerberArchiveName))
if self.options[ARCHIVE_NAME]:
if os.path.exists(os.path.join(temp_dir, designatorsFileName)):
os.rename(os.path.join(temp_dir, designatorsFileName), os.path.join(temp_dir, ProcessManager.normalize_filename("_".join((baseName.strip() + '_designators.csv').split()))))
if os.path.exists(os.path.join(temp_dir, placementFileName)):
os.rename(os.path.join(temp_dir, placementFileName), os.path.join(temp_dir, ProcessManager.normalize_filename("_".join((baseName.strip() + '_positions.csv').split()))))
if os.path.exists(os.path.join(temp_dir, bomFileName)):
os.rename(os.path.join(temp_dir, bomFileName), os.path.join(temp_dir, ProcessManager.normalize_filename("_".join((baseName.strip() + '_bom.csv').split()))))
# Make a backup as long as the BACKUP_OPT flag is set.
if not self.options[BACKUP_OPT]:
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S')
backup_name = ProcessManager.normalize_filename("_".join(("{} {}".format(baseName, timestamp).strip()).split()))
shutil.make_archive(os.path.join(output_path, 'backups', backup_name), 'zip', temp_dir)
# copy to & open output dir
try:
if os.path.exists(output_path):
shutil.rmtree(output_path)
shutil.copytree(temp_dir, output_path, dirs_exist_ok=True)
if self.openBrowser:
webbrowser.open("file://%s" % (output_path))
shutil.rmtree(temp_dir)
except Exception:
if self.openBrowser:
webbrowser.open("file://%s" % (temp_dir))
shutil.copytree(temp_dir, output_path)
webbrowser.open("file://%s" % (output_path))
except Exception as e:
webbrowser.open("file://%s" % (temp_dir))
if self.wx is None:
self.progress(100)
else:
self.progress(-1)
self.report(-1)
def report(self, status):
wx.PostEvent(self.wx, StatusEvent(status))
def progress(self, percent):
if self.wx is None:
if not self.nonInteractive:
print_cli_progress_bar(percent, prefix = 'Progress:', suffix = 'Complete', length = 50)
else:
wx.PostEvent(self.wx, StatusEvent(percent))
+52
View File
@@ -0,0 +1,52 @@
"Regex To Match","Rotation","Delta X","Delta Y"
"^Bosch_LGA-",90,0,0
"^CP_EIA-",180,0,0
"^CP_Elec_",180,0,0
"^C_Elec_",180,0,0
"^DFN-",270,0,0
"^DFN-",270,0,0
"^D_SOT-23",180,0,0
"^HTSSOP-",270,0,0
"^HTSSOP-",270,0,0
"^HTSSOP-",270,0,0
"^JST_GH_SM",180,0,0
"^JST_PH_S",180,0,0
"^LQFP-",270,0,0
"^MSOP-",270,0,0
"^PowerPAK_SO-8_Single",270,0,0
"^QFN-",90,0,0
"^R_Array_Concave_",90,0,0
"^R_Array_Convex_",90,0,0
"^SC-74-6",180,0,0
"^SOIC-",270,0,0
"^SOIC-16_",270,0,0
"^SOIC-8_",270,0,0
"^SOIC127P798X216-8N",-90,0,0
"^SOP-(?!18_)",270,0,0
"^SOP-(?!18_)",270,0,0
"^SOP-18_",0,0,0
"^SOP-18_",0,0,0
"^SOP-4_",0,0,0
"^SOP-4_",0,0,0
"^SOT-143",180,0,0
"^SOT-223",180,0,0
"^SOT-23",180,0,0
"^SOT-353",180,0,0
"^SOT-363",180,0,0
"^SOT-89",180,0,0
"^SSOP-",270,0,0
"^SW_SPST_B3",90,0,0
"^TDSON-8-1",270,0,0
"^TO-277",90,0,0
"^TQFP-",270,0,0
"^TSOT-23",180,0,0
"^TSSOP-",270,0,0
"^UDFN-10",270,0,0
"^USON-10",270,0,0
"^VSON-8_",270,0,0
"^VSSOP-10_-",270,0,0
"^VSSOP-10_-",270,0,0
"^VSSOP-8_",180,0,0
"^VSSOP-8_",270,0,0
"^VSSOP-8_3.0x3.0mm_P0.65mm",270,0,0
"^qfn-",90,0,0
1 Regex To Match Rotation Delta X Delta Y
2 ^Bosch_LGA- 90 0 0
3 ^CP_EIA- 180 0 0
4 ^CP_Elec_ 180 0 0
5 ^C_Elec_ 180 0 0
6 ^DFN- 270 0 0
7 ^DFN- 270 0 0
8 ^D_SOT-23 180 0 0
9 ^HTSSOP- 270 0 0
10 ^HTSSOP- 270 0 0
11 ^HTSSOP- 270 0 0
12 ^JST_GH_SM 180 0 0
13 ^JST_PH_S 180 0 0
14 ^LQFP- 270 0 0
15 ^MSOP- 270 0 0
16 ^PowerPAK_SO-8_Single 270 0 0
17 ^QFN- 90 0 0
18 ^R_Array_Concave_ 90 0 0
19 ^R_Array_Convex_ 90 0 0
20 ^SC-74-6 180 0 0
21 ^SOIC- 270 0 0
22 ^SOIC-16_ 270 0 0
23 ^SOIC-8_ 270 0 0
24 ^SOIC127P798X216-8N -90 0 0
25 ^SOP-(?!18_) 270 0 0
26 ^SOP-(?!18_) 270 0 0
27 ^SOP-18_ 0 0 0
28 ^SOP-18_ 0 0 0
29 ^SOP-4_ 0 0 0
30 ^SOP-4_ 0 0 0
31 ^SOT-143 180 0 0
32 ^SOT-223 180 0 0
33 ^SOT-23 180 0 0
34 ^SOT-353 180 0 0
35 ^SOT-363 180 0 0
36 ^SOT-89 180 0 0
37 ^SSOP- 270 0 0
38 ^SW_SPST_B3 90 0 0
39 ^TDSON-8-1 270 0 0
40 ^TO-277 90 0 0
41 ^TQFP- 270 0 0
42 ^TSOT-23 180 0 0
43 ^TSSOP- 270 0 0
44 ^UDFN-10 270 0 0
45 ^USON-10 270 0 0
46 ^VSON-8_ 270 0 0
47 ^VSSOP-10_- 270 0 0
48 ^VSSOP-10_- 270 0 0
49 ^VSSOP-8_ 180 0 0
50 ^VSSOP-8_ 270 0 0
51 ^VSSOP-8_3.0x3.0mm_P0.65mm 270 0 0
52 ^qfn- 90 0 0
+108
View File
@@ -0,0 +1,108 @@
import pcbnew # type: ignore
import os
import json
from .config import optionsFileName
import wx
def get_version():
return float('.'.join(pcbnew.GetBuildVersion().split(".")[0:2])) # e.g GetBuildVersion(): e.g. '7.99.0-3969-gc5ac2337e4'
def is_v10(version = get_version()):
return version >= 9.99 and version < 10.99
def is_v9(version = get_version()):
return version >= 8.99 and version < 9.99
def is_v8(version = get_version()):
return version >= 7.99 and version < 8.99
def is_v7(version = get_version()):
return version >= 6.99 and version < 7.99
def is_v6(version = get_version()):
return version >= 5.99 and version < 6.99
def footprint_has_field(footprint, field_name):
version = get_version()
if is_v10(version):
return footprint.HasField(field_name)
elif is_v8(version) or is_v9(version):
return footprint.HasFieldByName(field_name)
else:
return footprint.HasProperty(field_name)
def footprint_get_field(footprint, field_name):
version = get_version()
if is_v10(version):
return footprint.GetField(field_name).GetText()
elif is_v8(version) or is_v9(version):
return footprint.GetFieldByName(field_name).GetText()
else:
return footprint.GetProperty(field_name)
def get_user_options_file_path():
boardFilePath = pcbnew.GetBoard().GetFileName()
return os.path.join(os.path.dirname(boardFilePath), optionsFileName)
def load_user_options(default_options):
try:
with open(get_user_options_file_path(), 'r') as f:
user_options = json.load(f)
except:
user_options = default_options
# merge the user options with the default options
options = default_options.copy()
options.update(user_options)
return options
def save_user_options(options):
try:
with open(get_user_options_file_path(), 'w') as f:
json.dump(options, f)
except:
wx.MessageBox("Error saving user options", "Error", wx.OK | wx.ICON_ERROR)
def get_plot_plan(board, active_only=True):
"""Returns `(KiCad standard name, layer id, custom user name)` of all (active) layers of the given board."""
layers = []
i = pcbnew.PCBNEW_LAYER_ID_START - 1
while i < pcbnew.PCBNEW_LAYER_ID_START + pcbnew.PCB_LAYER_ID_COUNT - 1:
i += 1
if active_only and not board.IsLayerEnabled(i):
continue
layer_std_name = pcbnew.BOARD.GetStandardLayerName(i)
layer_name = pcbnew.BOARD.GetLayerName(board, i)
layers.append((layer_std_name, i, layer_name))
return layers
def get_layer_names(board, active_only=True):
"""Returns a list of (active) layer names of the current board"""
plotPlan = get_plot_plan(board, active_only)
return [layer_info[0] for layer_info in plotPlan]
def print_cli_progress_bar(percent, prefix = '', suffix = '', decimals = 1, length = 100, fill = '', printEnd = "\r"):
"""
Call in a loop to create terminal progress bar string
@params:
percent - Required : current percentage (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
printEnd - Optional : end character (e.g. "\r", "\r\n") (Str)
"""
if percent == -1:
percent = 0
filledLength = int(length * (percent / 100 ))
bar = fill * filledLength + '-' * (length - filledLength)
percent2dec = "%.2f" % percent
print(f'\r{prefix} |{bar}| {percent2dec}% {suffix}', end = printEnd)
# Print New Line on Complete
if percent == 100:
print()