Compare commits

...

101 Commits

Author SHA1 Message Date
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
14 changed files with 465 additions and 106 deletions
+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']
+1
View File
@@ -0,0 +1 @@
- Hildo Guillardi Júnior
+80 -9
View File
@@ -6,6 +6,8 @@
| **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)
</div>
## Features
@@ -17,27 +19,96 @@
## 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="magin-bottom: 8px;" alt="Logo" height=24> button on the top tool box inside KiCad pcb editor (pcbnew).
⊛ Ensure your board is syncronized before invoking this feature.
⊛ The `User_1` layer in internally defined as a **V-Cuts** layer, please avoid using it for anything else. <span style="text-color: light-grey !important;">_(since v3.0.0)_.</span>
## Options
### Include Component Part Number in Production Files
Add an 'MPN'* field with the LCSC component part number to the symbol's fields property.
### 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 #' | 'JLCPCB Part #' |
| --- | --- |
### 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 query in the order denoted above._
#### Fallback Fields*:
| 'JLC Part' | 'LCSC Part' | 'LCSC' | 'JLC' | 'MPN' | 'Mpn' | 'mpn' |
| --- | --- | --- | --- | --- | --- | --- |
_The fields will be query in the order denoted above._
---
### ② 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 'JLCPCB Rotation Offset' field with an counter-clockwise orientation offset in degrees to correct this.
<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>
As the rotation offset is entered in the Schematic Editor, make sure to update your PCB with the changes made to the schematic before generating a new set of production files, otherwise your changes won't be reflected.
#### Primary Fields*:
| 'JLCPCB Rotation Offset' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'JlcRotOffset' | 'JLCRotOffset' |
| --- | --- |
_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 JLCPB used different variation of the same standard. To the exception cases: add an 'JLCPCB Position Offset' field with an comma seperated 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>
As the position offset is entered in the Schematic Editor, make sure to update your PCB with the changes made to the schematic before generating a new set of production files, otherwise your changes won't be reflected.
#### Primary Fields*:
| 'JLCPCB Position Offset' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'JlcPosOffset' | 'JLCPosOffset' |
| --- | --- |
_The fields will be queried in the order denoted above._
## Author
Benny Megidish
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: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

+4 -4
View File
@@ -2,7 +2,7 @@
"$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 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 properties fabrication attributes in order to ignore the footprint from the relevant file. \n\n\u2022 Rotate components \n Add an 'JLCPCB Rotation Offset' field with the additional counter-clockwise rotation in degrees to correct a mismatch between KiCad and JLC footprint orientation.",
"identifier": "com.github.bennymeg.JLC-Plugin-for-KiCad",
"type": "plugin",
"author": {
@@ -29,9 +29,9 @@
],
"versions": [
{
"version": "0.4.2",
"status": "testing",
"version": "3.0.2",
"status": "stable",
"kicad_version": "6.00"
}
]
}
}
+29 -2
View File
@@ -6,7 +6,7 @@ netlistFileName = 'netlist.ipc'
designatorsFileName = 'designators.csv'
placementFileName = 'positions.csv'
bomFileName = 'bom.csv'
gerberArchiveName = 'gerber.zip'
gerberArchiveName = 'gerbers.zip'
outputFolder = 'production'
plotPlan = [
@@ -16,11 +16,38 @@ plotPlan = [
("In2.Cu", pcbnew.In2_Cu, "Internal plane 2"),
("In3.Cu", pcbnew.In3_Cu, "Internal plane 3"),
("In4.Cu", pcbnew.In4_Cu, "Internal plane 4"),
("In5.Cu", pcbnew.In5_Cu, "Internal plane 5"),
("In6.Cu", pcbnew.In6_Cu, "Internal plane 6"),
("In7.Cu", pcbnew.In7_Cu, "Internal plane 7"),
("In8.Cu", pcbnew.In8_Cu, "Internal plane 8"),
("In9.Cu", pcbnew.In9_Cu, "Internal plane 9"),
("In10.Cu", pcbnew.In10_Cu, "Internal plane 10"),
("In11.Cu", pcbnew.In11_Cu, "Internal plane 11"),
("In12.Cu", pcbnew.In12_Cu, "Internal plane 12"),
("In13.Cu", pcbnew.In13_Cu, "Internal plane 13"),
("In14.Cu", pcbnew.In14_Cu, "Internal plane 14"),
("In15.Cu", pcbnew.In15_Cu, "Internal plane 15"),
("In16.Cu", pcbnew.In16_Cu, "Internal plane 16"),
("In17.Cu", pcbnew.In17_Cu, "Internal plane 17"),
("In18.Cu", pcbnew.In18_Cu, "Internal plane 18"),
("In19.Cu", pcbnew.In19_Cu, "Internal plane 19"),
("In20.Cu", pcbnew.In20_Cu, "Internal plane 20"),
("In21.Cu", pcbnew.In21_Cu, "Internal plane 21"),
("In22.Cu", pcbnew.In22_Cu, "Internal plane 22"),
("In23.Cu", pcbnew.In23_Cu, "Internal plane 23"),
("In24.Cu", pcbnew.In24_Cu, "Internal plane 24"),
("In25.Cu", pcbnew.In25_Cu, "Internal plane 25"),
("In26.Cu", pcbnew.In26_Cu, "Internal plane 26"),
("In27.Cu", pcbnew.In27_Cu, "Internal plane 27"),
("In28.Cu", pcbnew.In28_Cu, "Internal plane 28"),
("In29.Cu", pcbnew.In29_Cu, "Internal plane 29"),
("In30.Cu", pcbnew.In30_Cu, "Internal plane 30"),
("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")
("Edge.Cuts", pcbnew.Edge_Cuts, "Board Outline"),
("User.Comments", pcbnew.Cmts_User, "User Comments")
]
+1 -1
View File
@@ -43,7 +43,7 @@ class KiCadToJLCForm(wx.Frame):
pcbnew.Refresh()
self.Destroy()
else:
self.m_gaugeStatus.SetValue(status.data)
self.m_gaugeStatus.SetValue(int(status.data))
# Plugin definition
+209 -61
View File
@@ -1,11 +1,18 @@
# from urllib import response
# import json
# import requests
# For better annotation.
from __future__ import annotations
# System base libraries
import os
import csv
import math
import shutil
import pcbnew
from collections import defaultdict
import re
# Interaction with KiCad.
import pcbnew
# Application definitions.
from .config import *
@@ -14,10 +21,69 @@ class ProcessManager:
self.board = pcbnew.GetBoard()
self.bom = []
self.components = []
self.__rotation_db = self.__read_rotation_db()
@staticmethod
def normalize_filename(filename):
return re.sub(r'[^\w\s\.\-]', '', filename)
@staticmethod
def __read_rotation_db(filename: str = os.path.join(os.path.dirname(__file__), 'rotations.cf')) -> dict[str, float]:
'''Read the rotations.cf config file so we know what rotations
to apply later.
'''
db = {}
with open(filename, 'r') as fh:
for line in fh:
line = line.rstrip()
line = re.sub('#.*$', '', line) # remove anything after a comment
line = re.sub('\s*$', '', line) # remove all trailing space
if (line == ""):
continue
match = re.match('^([^\s]+)\s+(\d+)$', line)
if match:
db.update({ match.group(1): int(match.group(2)) })
return db
def _get_rotation_from_db(self, footprint: str) -> float:
'''Get the rotation to be added from the database file.'''
# Look for regular expression math of the footprint name and not its root library.
fpshort = footprint.split(':')[-1]
for expression, delta in self.db.items():
fp = fpshort
if (re.search(':', expression)):
fp = footprint
if(re.search(expression, fp)):
return delta
return 0.0
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):
'''Generate the Gerber files.'''
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,12 +96,16 @@ class ProcessManager:
plot_options.SetScale(1)
plot_options.SetMirror(False)
plot_options.SetUseGerberAttributes(True)
plot_options.SetExcludeEdgeLayer(True)
plot_options.SetUseGerberProtelExtensions(False)
plot_options.SetUseAuxOrigin(True)
plot_options.SetSubtractMaskFromSilk(False)
plot_options.SetSubtractMaskFromSilk(True)
plot_options.SetDrillMarksType(0) # NO_DRILL_SHAPE
if hasattr(plot_options, "SetExcludeEdgeLayer"):
plot_options.SetExcludeEdgeLayer(True)
# This includes User_1 with Edge_Cuts to allow V Cuts to be defined as User_1 layer and added so JLC accept it
# It seems making a separate file just confuses them and they want it in the Edge Cuts file, but that would upset KiCad
for layer_info in plotPlan:
if self.board.IsLayerEnabled(layer_info[1]):
plot_controller.SetLayer(layer_info[1])
@@ -43,11 +113,16 @@ class ProcessManager:
layer_info[0],
pcbnew.PLOT_FORMAT_GERBER,
layer_info[2])
plot_controller.PlotLayer()
seq = pcbnew.LSEQ()
seq.push_back(layer_info[1])
if layer_info[1] == pcbnew.Edge_Cuts :
seq.push_back(pcbnew.User_1)
plot_controller.PlotLayers(seq)
plot_controller.ClosePlot()
def generate_drills(self, temp_dir):
'''Generate the drill file.'''
drill_writer = pcbnew.EXCELLON_WRITER(self.board)
drill_writer.SetOptions(
@@ -59,15 +134,20 @@ class ProcessManager:
drill_writer.CreateDrillandMapFilesSet(temp_dir, True, False)
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):
'''Generate the position files.'''
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())
# unique designator dictionary
footprint_designators = defaultdict(int)
for i, footprint in enumerate(footprints):
@@ -75,9 +155,10 @@ class ProcessManager:
footprint_designators[footprint.GetReference()] += 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:
@@ -103,11 +184,31 @@ class ProcessManager:
unique_id = str(footprint_designators[footprint.GetReference()])
footprint_designators[footprint.GetReference()] -= 1
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().AsDegrees() if hasattr(footprint.GetOrientation(), 'AsDegrees') else footprint.GetOrientation() / 10.0
# Get the rotation offset to be added to the actual rotation prioritizing the explicated by the
# designer at the standards symbol fields. If not specified use the internal database.
rotation_offset = self._get_rotation_offset_from_footprint(footprint) #or self._get_rotation_from_db(footprint)
rotation = (rotation + rotation_offset) % 360.0
# position offset needs to take rotation into account
pos_offset = self._get_position_offset_from_footprint(footprint)
rsin = math.sin(rotation / 180 * math.pi)
rcos = math.cos(rotation / 180 * math.pi)
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 = (540.0 - rotation) % 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,
})
@@ -118,48 +219,56 @@ class ProcessManager:
unique_id = str(bom_designators[footprint.GetReference()])
bom_designators[footprint.GetReference()] -= 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)
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:
component['Designator'] += ", " + "{}{}{}".format(footprint.GetReference(), "" 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(), "" 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
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):
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 files.'''
temp_file = shutil.make_archive(temp_file, 'zip', temp_dir)
temp_file = shutil.move(temp_file, temp_dir)
@@ -170,20 +279,59 @@ 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()
def _get_mpn_from_footprint(self, footprint):
''''Get the MPN/LCSC stock code from standard symbol fields.'''
keys = ['LCSC Part #', 'JLCPCB Part #']
fallback_keys = ['LCSC Part', 'JLC Part', 'LCSC', 'JLC', 'MPN', 'Mpn', 'mpn']
files = { 'upload[file]': open(temp_file, 'rb') }
upload_url = baseUrl + '/Common/KiCadUpFile/'
if footprint.HasProperty('dnp'):
return 'DNP'
# response = requests.post(
# upload_url, files=files, data={ 'boardWidth': boardWidth, 'boardHeight': boardHeight, 'boardLayer': boardLayer })
# urls = json.loads(response.content)
def _getMpnFromFootprint(self, footprint):
keys = ['mpn', 'Mpn', 'MPN', 'JLC_MPN', 'LCSC_MPN', 'LCSC Part #', 'JLC', 'LCSC']
for key in keys:
for key in keys + fallback_keys:
if footprint.HasProperty(key):
return footprint.GetProperty(key)
def _get_rotation_offset_from_footprint(self, footprint) -> float:
'''Get the rotation from standard symbol fields.'''
keys = ['JLCPCB Rotation Offset']
fallback_keys = ['JlcRotOffset', 'JLCRotOffset']
offset = None
for key in keys + fallback_keys:
if footprint.HasProperty(key):
offset = footprint.GetProperty(key)
break
if offset is None or offset == "":
return 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):
keys = ['JLCPCB Position Offset']
fallback_keys = ['JlcPosOffset', 'JLCPosOffset']
offset = None
for key in keys + fallback_keys:
if footprint.HasProperty(key):
offset = footprint.GetProperty(key)
break
if offset is None or offset == "":
return (0, 0)
else:
try:
return ( float(offset.split(",")[0]), float(offset.split(",")[1]) )
except ValueError:
raise RuntimeError("Position offset of {} is not a valid pair of numbers".format(footprint.GetReference()))
def _normalize_footprint_name(self, footprint):
# 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)
+61
View File
@@ -0,0 +1,61 @@
#
# Rotations for jlcpcba
#
# This defines additional rotations we need to apply to ensure the
# footprints are correctly orientated to work with the JLCPCB PBCA
# service
#
# These are regular expressions that are matched by default against
# the short footprint name (without the library name), however if there
# is a colon in the regex then the full name (with the library name) is
# used.
#
# The matches are checked against one by one, and the first match
# is used
#
# The regex and the rotation value is separated by any amount of
# whitespace, blank lines and comments are ignored
#
#
# Normal short-name matches
#
^SOT-223 180
^SOT-23 180
^D_SOT-23 180
^TSOT-23 180
^SOT-353 180
^QFN- 90
^qfn- 90
^LQFP- 270
^TQFP- 270
^MSOP- 270
^TSSOP- 270
^DFN- 270
^SOIC-8_ 270
^SOIC-16_ 270
^VSSOP-10_- 270
#
# Polarised caps are 180 out
#
^CP_Elec_ 180
^C_Elec_ 180
^CP_EIA- 180
#
# Long name matches
#
#^Lees_Footprints: 270
^LED_WS2812B_PLCC4 180
# More rotation from https://github.com/matthewlai/JLCKicadTools/tree/master/jlc_kicad_tools
^R_Array_Convex_ 90
^R_Array_Concave_ 90
^SOP-4_ 0
^SOP-(?!18_) 270
^SOP-18_ 0
^VSSOP-8_ 270
^Bosch_LGA- 90
^PowerPAK_SO-8_Single 270
^HTSSOP- 270
+67 -29
View File
@@ -1,8 +1,10 @@
import os
import wx
import pcbnew
import shutil
import tempfile
import webbrowser
import datetime
from threading import Thread
from .events import StatusEvent
from .process import ProcessManager
@@ -19,40 +21,52 @@ class ProcessThread(Thread):
def run(self):
# initializing
self.report(0)
self.progress(0)
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S')
temp_dir = tempfile.mkdtemp() + timestamp
os.makedirs(temp_dir)
os.makedirs(temp_dir + "_g")
temp_dir = tempfile.mkdtemp()
_, 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)
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 + "_g")
# 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 + "_g")
# 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 pick and place file
self.progress(50)
self.process_manager.generate_positions(temp_dir)
# upload files
#self.report(87.5)
#self.process_manager.upload_archive(temp_file)
# generate BOM file
self.progress(60)
self.process_manager.generate_bom(temp_dir)
# generate production archive
self.progress(75)
temp_file = self.process_manager.generate_archive(temp_dir + "_g", temp_file)
shutil.move(temp_file, temp_dir)
shutil.rmtree(temp_dir + "_g")
temp_file = os.path.join(temp_dir, os.path.basename(temp_file))
except Exception as e:
wx.MessageBox(str(e), "Error", wx.OK | wx.ICON_ERROR)
self.progress(-1)
return
# progress bar done animation
read_so_far = 0
@@ -64,20 +78,44 @@ class ProcessThread(Thread):
break
read_so_far += len(data)
percent = read_so_far * 1e2 / total_size
self.report(75 + percent / 8)
self.progress(75 + 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]
name = ProcessManager.normalize_filename("_".join(("{} {} {}".format(title or filename, revision or '', timestamp).strip()).split()))
output_path = os.path.join(project_directory, outputFolder, name)
os.makedirs(output_path)
# rename gerber archive
gerberArchiveName = ProcessManager.normalize_filename("_".join(("{} {}".format(title or filename, revision or '').strip() + '.zip').split()))
os.rename(temp_file, os.path.join(temp_dir, gerberArchiveName))
# copy to & open output dir
try:
if os.path.exists(output_path):
shutil.rmtree(output_path)
shutil.copytree(temp_dir, output_path)
webbrowser.open("file://%s" % (output_path))
shutil.rmtree(temp_dir)
except Exception as e:
webbrowser.open("file://%s" % (temp_dir))
self.report(-1)
self.progress(-1)
def report(self, status):
wx.PostEvent(self.wx, StatusEvent(status))
def progress(self, percent):
wx.PostEvent(self.wx, StatusEvent(percent))