Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a0af1a043 | |||
| d3cb95030a | |||
| 355f3c3ac4 | |||
| ee3e238a3d | |||
| a3fb4259dd | |||
| 872c0d90df | |||
| 891b60ac43 | |||
| 6461a362dd | |||
| 6f1426d972 | |||
| 6567a9da07 | |||
| 57fd582a6d | |||
| 9d18a8bc54 | |||
| df06c3c867 | |||
| 8beb878d32 | |||
| ef138c8feb | |||
| 466c008228 | |||
| c8e3daa5ae | |||
| a9cd57a027 | |||
| 387c5b3931 | |||
| 2d2436993a | |||
| 376d6df61d | |||
| f832e4e007 | |||
| d42611753e | |||
| ac0fec8faf | |||
| 2ac859e7e7 | |||
| 830819b27b | |||
| 9832454472 | |||
| 468640df8a | |||
| db4bf8904a | |||
| 1a9d56d0a9 | |||
| 50ad485758 | |||
| 3954ba9b68 | |||
| 3c1254eb9c | |||
| f10292a797 | |||
| 1d5a1b244e | |||
| c88232dbaf | |||
| 9e78ed2f18 | |||
| 99344155b5 | |||
| 7a7a04a714 | |||
| 4a59fb0672 | |||
| 5afaa579f8 | |||
| a1519203c1 | |||
| 37b039b515 | |||
| db103a5775 | |||
| 8394c85f15 | |||
| b783e7bd3e | |||
| e13276ae62 | |||
| 523a48cdff | |||
| f51c44fb00 | |||
| 817a58e8cc | |||
| 11d48c1370 | |||
| 0db7b02636 | |||
| e90cb33e6a | |||
| 67d725a96f | |||
| c62f1e3c1e | |||
| 68b5eb639a | |||
| 9f0fa6eaee | |||
| 6adb746943 | |||
| 79bd50db9e | |||
| fc12cc94b3 | |||
| 76d8ffbf75 | |||
| 30d1684f76 | |||
| a09b42c20f | |||
| 5417532fed | |||
| dec7a3ca1b | |||
| b8d0073846 | |||
| 599f159806 | |||
| cd38035aff | |||
| ecd98adeff | |||
| d8d81a7fe9 | |||
| d8c9aa775e | |||
| b783b289a8 | |||
| 927128651c | |||
| 91fdcc9fc2 | |||
| 1d1d38b83f | |||
| 49e921d2cf | |||
| 9b1d84265d | |||
| 5a675648bc | |||
| adbd0fdde0 | |||
| 1131b2efa9 | |||
| 0fcaa841a8 | |||
| 9147cacdac | |||
| 17b3c10d16 | |||
| c53997c639 | |||
| 0a8de1183e | |||
| d93476b5a9 | |||
| 1e1b347852 | |||
| 7a67087431 | |||
| adb10d314a | |||
| 5b43ce9047 | |||
| 45cb109668 | |||
| 94bbea2ac4 | |||
| 1205f17d7e | |||
| 1bfcc23256 | |||
| 036754a53b | |||
| c71afbe1b9 | |||
| 1c9e78e869 | |||
| b51024b174 | |||
| 3f19dc2121 | |||
| 6255f69e4a | |||
| a5a31059d0 | |||
| 27ac789281 | |||
| 3a33cfff96 | |||
| 0994f04d2c | |||
| 18b3a8074f | |||
| 05e486d949 | |||
| 34318c7de0 | |||
| 8f062359e4 | |||
| 5b821aebe2 | |||
| a39b65c94c | |||
| a6d1999839 | |||
| c7cc47718d | |||
| b801028d63 | |||
| 959c2110c9 | |||
| 54cdea9c33 | |||
| 08b6895ea3 | |||
| 647d8aaec2 | |||
| a84c8e4d83 | |||
| 966f96cf7e | |||
| f46f57d36d |
@@ -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']
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,19 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredErrors">
|
||||
<list>
|
||||
<option value="N801" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredIdentifiers">
|
||||
<list>
|
||||
<option value="bpy" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/KiCadJLCplugin.iml" filepath="$PROJECT_DIR$/.idea/KiCadJLCplugin.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="81624dc4-41ca-48ac-93b8-92da00936c57" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/plugins/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/plugins/__init__.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/plugins/config.py" beforeDir="false" afterPath="$PROJECT_DIR$/plugins/config.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/plugins/plugin.py" beforeDir="false" afterPath="$PROJECT_DIR$/plugins/plugin.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/plugins/result_event.py" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/plugins/thread.py" beforeDir="false" afterPath="$PROJECT_DIR$/plugins/thread.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectId" id="2C7tSUh2zYdb8tYB6kgwqDOifTc" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="81624dc4-41ca-48ac-93b8-92da00936c57" name="Changes" comment="" />
|
||||
<created>1658165450946</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1658165450946</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Drilland",
|
||||
"EXCELLON",
|
||||
"FPID",
|
||||
"LCSC",
|
||||
"netlist",
|
||||
"pcbnew",
|
||||
"Plotfile",
|
||||
"Protel",
|
||||
"Soldermask"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
- Hildo Guillardi Júnior
|
||||
@@ -1,4 +1,14 @@
|
||||
# JLC PCB Plug-in for KiCad (Fabrication Toolkit)
|
||||
<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** |
|
||||
|:-----:|
|
||||
|
||||
[](https://github.com/sponsors/bennymeg)
|
||||
|
||||
</div>
|
||||
|
||||
## Features
|
||||
1. Generates gerber files in correct format for production
|
||||
@@ -9,18 +19,95 @@
|
||||
## Installation
|
||||
|
||||
### Official Installation
|
||||
Open the "Plugin and Content Manager" from the KiCad main menu an install the "Fabrication Toolkit" 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
|
||||
|
||||
### Component Part Number
|
||||
Add 'MPN' field with the LCSC component part number to the footprint component options.
|
||||
### ① Include Component Part Number in Production Files
|
||||
Add an 'LCSC Part #'* field with the LCSC component part number to the symbol's fields property.
|
||||
|
||||
### Ignore Footprint in Production Files
|
||||
Select 'Exclude from position files' or 'Exclude from BOM' in the footprint component options to ignore te footprint from the relevant file.
|
||||
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/mpn.png?raw=true" height=420>
|
||||
|
||||
#### Primary Fields*:
|
||||
| 'LCSC Part #' | 'JLCPCB Part #' |
|
||||
| --- | --- |
|
||||
|
||||
_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
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 114 KiB |
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="1300"
|
||||
height="136.763"
|
||||
id="svg3509"
|
||||
version="1.1"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
sodipodi:docname="Fabrication Toolkit Logo.svg"
|
||||
inkscape:export-filename="/home/fabrizio/Desktop/kicad_logo2.png"
|
||||
inkscape:export-xdpi="130.52866"
|
||||
inkscape:export-ydpi="130.52866"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs3511">
|
||||
<rect
|
||||
x="979.28571"
|
||||
y="180.33443"
|
||||
width="132.14286"
|
||||
height="193.57143"
|
||||
id="rect2617" />
|
||||
<rect
|
||||
x="590.71429"
|
||||
y="-28.951285"
|
||||
width="167.14286"
|
||||
height="187.85714"
|
||||
id="rect2611" />
|
||||
<rect
|
||||
x="176.42857"
|
||||
y="10.334429"
|
||||
width="1260.7143"
|
||||
height="265.71428"
|
||||
id="rect227" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="687.91388"
|
||||
inkscape:cy="158.08887"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="text225"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1002"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-grids="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-to-guides="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<metadata
|
||||
id="metadata3514">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-40.547253,-883.62329)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#314cb0;fill-opacity:1;fill-rule:nonzero;stroke:#1d2d68;stroke-width:4.85049;stroke-miterlimit:4;stroke-opacity:1"
|
||||
d="m 1056.9425,902.1018 h 114.392 c 0.5127,0 0.9255,0.43179 0.9255,0.96814 v 109.79556 c 0,0.5364 -0.4128,0.9682 -0.9255,0.9682 h -114.392 c -0.5128,0 -0.9255,-0.4318 -0.9255,-0.9682 V 903.06994 c 0,-0.53635 0.4127,-0.96814 0.9255,-0.96814 z"
|
||||
id="rect3438-4-1-6-9-8-4" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3008"
|
||||
style="font-style:normal;font-weight:normal;font-size:120.794px;line-height:122%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 1085.5333,948.38598 16.9276,-21.41018 c 3.4602,-4.36453 5.1903,-7.62815 5.1904,-9.79088 -10e-5,-0.78633 -0.098,-1.41546 -0.2949,-1.8874 h 29.5495 c -2.2413,1.25835 -5.0528,3.91251 -8.4343,7.96246 -0.9044,1.06175 -2.4969,3.00812 -4.7774,5.83914 l -22.177,27.60321 26.4826,36.45038 c 1.6121,2.20197 3.6567,4.67918 6.134,7.43169 0.6684,0.7078 1.6318,1.5532 2.8901,2.5362 h -30.3163 c 0.2358,-0.9044 0.3538,-1.7105 0.3539,-2.4183 -10e-5,-2.16264 -1.4943,-5.28864 -4.4826,-9.37801 l -17.0456,-23.23859 v 23.5335 c 0,5.4656 1.101,9.2994 3.303,11.5014 h -27.7212 c 1.5335,-1.5335 2.5165,-3.28335 2.9491,-5.24939 0.2359,-1.10098 0.3538,-3.16532 0.3538,-6.19303 v -64.9383 c 0,-3.02763 -0.1179,-5.09197 -0.3538,-6.19303 -0.4326,-1.96596 -1.4156,-3.71573 -2.9491,-5.24933 h 27.7212 c -2.202,2.20205 -3.303,6.01617 -3.303,11.44236 v 21.6461" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3010"
|
||||
style="font-style:normal;font-weight:normal;font-size:120.794px;line-height:122%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 1164.7451,938.5951 v 54.14474 c -1e-4,2.83111 0.079,4.67918 0.2359,5.54423 0.3145,1.76943 1.1403,3.38163 2.4772,4.83653 h -25.9517 c 1.3369,-1.4549 2.1626,-3.0671 2.4772,-4.83653 0.1573,-0.86505 0.2359,-2.71312 0.2359,-5.54423 v -43.88201 c 0,-2.83104 -0.079,-4.65946 -0.2359,-5.48526 -0.3146,-1.73005 -1.1207,-3.32254 -2.4182,-4.77747 h 23.1796 m -10.2628,-30.84717 c 3.2636,9e-5 6.0358,1.16006 8.3164,3.47989 2.3199,2.2807 3.4798,5.05281 3.4799,8.31635 -1e-4,3.34235 -1.1404,6.15378 -3.4209,8.43431 -2.2414,2.24136 -5.0331,3.362 -8.3754,3.36193 -3.2636,7e-5 -6.0554,-1.14023 -8.3753,-3.42091 -2.2806,-2.31985 -3.4209,-5.11162 -3.4209,-8.37533 0,-3.34218 1.1206,-6.13396 3.3619,-8.37533 2.2806,-2.28051 5.0921,-3.42082 8.4343,-3.42091" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#314cb0;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1140.5539,906.77745 h 28.3151 v 27.03082 h -28.3151 z"
|
||||
id="rect4352-5-1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ff7700;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1166.4321,902.13073 c 0,6.60244 -5.3681,11.95472 -11.9901,11.95472 -6.622,0 -11.9902,-5.35228 -11.9902,-11.95472 0,-6.60244 5.3682,-11.9548 11.9902,-11.9548 6.622,0 11.9901,5.35236 11.9901,11.9548 z"
|
||||
id="path3552-6-3-7" />
|
||||
<g
|
||||
aria-label="Fabrication Tool t"
|
||||
transform="translate(-30.187122,883.52127)"
|
||||
id="text225"
|
||||
style="font-size:120px;line-height:1.25;white-space:pre;shape-inside:url(#rect227);fill:#1b2b63">
|
||||
<path
|
||||
d="m 234.61133,51.200341 h -29.64844 v 20.625 h 27.24609 V 87.176904 H 204.96289 V 119.81362 H 186.03711 V 35.790185 h 48.57422 z"
|
||||
style="font-weight:bold;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI Bold'"
|
||||
id="path2671" />
|
||||
<path
|
||||
d="m 292.9707,119.81362 h -17.51953 v -8.61328 h -0.23437 q -6.03516,10.07813 -17.8711,10.07813 -8.73047,0 -13.76953,-4.92188 -4.98047,-4.98047 -4.98047,-13.24219 0,-17.460934 20.6836,-20.156246 l 16.28906,-2.167969 q 0,-9.84375 -10.66406,-9.84375 -10.72266,0 -20.39063,6.386719 V 63.387841 q 3.86719,-1.992187 10.54688,-3.515625 6.73828,-1.523437 12.24609,-1.523437 25.66406,0 25.66406,25.605468 z M 275.56836,95.438622 v -4.042968 l -10.89844,1.40625 q -9.02344,1.171875 -9.02344,8.144526 0,3.16407 2.16797,5.21485 2.22657,1.99219 5.97657,1.99219 5.21484,0 8.49609,-3.57422 3.28125,-3.63282 3.28125,-9.140628 z"
|
||||
style="font-weight:bold;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI Bold'"
|
||||
id="path2673" />
|
||||
<path
|
||||
d="m 326.36914,112.84097 h -0.23437 v 6.97265 H 307.61914 V 30.985497 h 18.51563 V 68.83706 h 0.23437 q 6.85547,-10.488281 19.51172,-10.488281 11.60156,0 17.87109,7.96875 6.26953,7.96875 6.26953,21.796875 0,14.999996 -7.32421,24.082026 -7.32422,9.08204 -19.57032,9.08204 -11.07422,0 -16.75781,-8.4375 z M 325.8418,87.528466 v 6.152344 q 0,5.800781 3.33984,9.60937 3.33984,3.8086 8.55469,3.8086 6.32812,0 9.78515,-4.86328 3.51563,-4.921878 3.51563,-13.886721 0,-7.441407 -3.22266,-11.601563 -3.16406,-4.21875 -9.08203,-4.21875 -5.5664,0 -9.25781,4.160156 -3.63281,4.160157 -3.63281,10.839844 z"
|
||||
style="font-weight:bold;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI Bold'"
|
||||
id="path2675" />
|
||||
<path
|
||||
d="m 421.17383,76.512841 q -3.33985,-1.816406 -7.79297,-1.816406 -6.03516,0 -9.43359,4.453125 -3.39844,4.394531 -3.39844,12.011719 V 119.81362 H 382.0332 V 59.813622 h 18.51563 v 11.132813 h 0.23437 q 4.39453,-12.1875 15.82032,-12.1875 2.92968,0 4.57031,0.703125 z"
|
||||
style="font-weight:bold;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI Bold'"
|
||||
id="path2677" />
|
||||
<path
|
||||
d="m 439.63086,50.321435 q -4.6875,0 -7.67578,-2.753906 -2.98828,-2.8125 -2.98828,-6.855469 0,-4.160156 2.98828,-6.796875 2.98828,-2.636719 7.67578,-2.636719 4.74609,0 7.67578,2.636719 2.98828,2.636719 2.98828,6.796875 0,4.21875 -2.98828,6.914062 -2.92969,2.695313 -7.67578,2.695313 z m 9.14062,69.492185 H 430.25586 V 59.813622 h 18.51562 z"
|
||||
style="font-weight:bold;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI Bold'"
|
||||
id="path2679" />
|
||||
<path
|
||||
d="m 509.82617,117.64565 q -6.26953,3.63282 -18.10547,3.63282 -13.82812,0 -22.38281,-8.37891 -8.55469,-8.37891 -8.55469,-21.621094 0,-15.292969 9.14063,-24.082031 9.19922,-8.847656 24.55078,-8.847656 10.60547,0 15.35156,2.8125 v 15.703125 q -5.80078,-4.335938 -12.94922,-4.335938 -7.96875,0 -12.65625,4.6875 -4.6289,4.628906 -4.6289,12.832031 0,7.96875 4.45312,12.539063 4.45313,4.51172 12.2461,4.51172 6.91406,0 13.53515,-4.33594 z"
|
||||
style="font-weight:bold;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI Bold'"
|
||||
id="path2681" />
|
||||
<path
|
||||
d="m 571.87695,119.81362 h -17.51953 v -8.61328 h -0.23437 q -6.03516,10.07813 -17.8711,10.07813 -8.73047,0 -13.76953,-4.92188 -4.98047,-4.98047 -4.98047,-13.24219 0,-17.460934 20.6836,-20.156246 l 16.28906,-2.167969 q 0,-9.84375 -10.66406,-9.84375 -10.72266,0 -20.39063,6.386719 V 63.387841 q 3.86719,-1.992187 10.54688,-3.515625 6.73828,-1.523437 12.24609,-1.523437 25.66406,0 25.66406,25.605468 z M 554.47461,95.438622 v -4.042968 l -10.89844,1.40625 q -9.02344,1.171875 -9.02344,8.144526 0,3.16407 2.16797,5.21485 2.22657,1.99219 5.97657,1.99219 5.21484,0 8.49609,-3.57422 3.28125,-3.63282 3.28125,-9.140628 z"
|
||||
id="path2683" />
|
||||
<path
|
||||
d="m 622.79492,119.1105 q -4.10156,2.16797 -12.36328,2.16797 -19.57031,0 -19.57031,-20.33204 V 73.465966 h -9.72656 V 59.813622 h 9.72656 V 46.864404 l 18.45703,-5.273438 v 18.222656 h 13.47656 v 13.652344 h -13.47656 v 24.257813 q 0,9.375001 7.44141,9.375001 2.92968,0 6.03515,-1.69922 z"
|
||||
id="path2685" />
|
||||
<path
|
||||
d="m 642.59961,50.321435 q -4.6875,0 -7.67578,-2.753906 -2.98828,-2.8125 -2.98828,-6.855469 0,-4.160156 2.98828,-6.796875 2.98828,-2.636719 7.67578,-2.636719 4.74609,0 7.67578,2.636719 2.98828,2.636719 2.98828,6.796875 0,4.21875 -2.98828,6.914062 -2.92969,2.695313 -7.67578,2.695313 z m 9.14062,69.492185 H 633.22461 V 59.813622 h 18.51562 z"
|
||||
id="path2687" />
|
||||
<path
|
||||
d="m 695.91992,121.27847 q -15,0 -23.61328,-8.37891 -8.55469,-8.4375 -8.55469,-22.851563 0,-14.882812 8.90625,-23.261718 8.90625,-8.4375 24.08203,-8.4375 14.94141,0 23.4375,8.4375 8.4961,8.378906 8.4961,22.207031 0,14.94141 -8.78906,23.61328 -8.73047,8.67188 -23.96485,8.67188 z m 0.46875,-48.750004 q -6.5625,0 -10.19531,4.511719 -3.63281,4.511719 -3.63281,12.773437 0,17.285158 13.94531,17.285158 13.30078,0 13.30078,-17.753908 0,-16.816406 -13.41797,-16.816406 z"
|
||||
id="path2689" />
|
||||
<path
|
||||
d="M 798.63477,119.81362 H 780.17773 V 86.473779 q 0,-13.945313 -9.96093,-13.945313 -4.80469,0 -7.91016,3.691406 -3.10547,3.691407 -3.10547,9.375 V 119.81362 H 740.68555 V 59.813622 h 18.51562 v 9.492188 h 0.23438 q 6.62109,-10.957031 19.27734,-10.957031 19.92188,0 19.92188,24.726562 z"
|
||||
id="path2691" />
|
||||
<path
|
||||
d="M 907.20898,51.200341 H 883.24414 V 119.81362 H 864.25977 V 51.200341 H 840.41211 V 35.790185 h 66.79687 z"
|
||||
id="path2693" />
|
||||
<path
|
||||
d="m 934.51367,121.27847 q -15,0 -23.61328,-8.37891 -8.55469,-8.4375 -8.55469,-22.851563 0,-14.882812 8.90625,-23.261718 8.90625,-8.4375 24.08203,-8.4375 14.94141,0 23.4375,8.4375 8.4961,8.378906 8.4961,22.207031 0,14.94141 -8.78906,23.61328 -8.73047,8.67188 -23.96485,8.67188 z m 0.46875,-48.750004 q -6.5625,0 -10.19531,4.511719 -3.63281,4.511719 -3.63281,12.773437 0,17.285158 13.94531,17.285158 13.30078,0 13.30078,-17.753908 0,-16.816406 -13.41797,-16.816406 z"
|
||||
id="path2695" />
|
||||
<path
|
||||
d="m 1007.873,121.27847 q -14.99995,0 -23.61323,-8.37891 -8.55469,-8.4375 -8.55469,-22.851563 0,-14.882812 8.90625,-23.261718 8.90625,-8.4375 24.08207,-8.4375 14.9414,0 23.4375,8.4375 8.4961,8.378906 8.4961,22.207031 0,14.94141 -8.7891,23.61328 -8.7305,8.67188 -23.9649,8.67188 z m 0.4688,-48.750004 q -6.5625,0 -10.19532,4.511719 -3.63281,4.511719 -3.63281,12.773437 0,17.285158 13.94533,17.285158 13.3008,0 13.3008,-17.753908 0,-16.816406 -13.418,-16.816406 z"
|
||||
id="path2697" />
|
||||
<path
|
||||
d="m 1071.1543,119.81362 h -18.5156 V 30.985497 h 18.5156 z"
|
||||
id="path2699" />
|
||||
<path
|
||||
d="m 1255.4316,119.1105 q -4.1015,2.16797 -12.3632,2.16797 -19.5704,0 -19.5704,-20.33204 V 73.465966 h -9.7265 V 59.813622 h 9.7265 V 46.864404 l 18.4571,-5.273438 v 18.222656 h 13.4765 v 13.652344 h -13.4765 v 24.257813 q 0,9.375001 7.4414,9.375001 2.9297,0 6.0351,-1.69922 z"
|
||||
id="path2701" />
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
transform="translate(40.547253,883.62329)"
|
||||
id="text2609"
|
||||
style="fill:black;fill-opacity:1;line-height:1.25;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;white-space:pre;shape-inside:url(#rect2611)" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
transform="translate(40.547253,883.62329)"
|
||||
id="text2615"
|
||||
style="fill:black;fill-opacity:1;line-height:1.25;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;white-space:pre;shape-inside:url(#rect2617)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 248 KiB |
@@ -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",
|
||||
"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": {
|
||||
@@ -21,11 +21,17 @@
|
||||
"resources": {
|
||||
"homepage": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
|
||||
},
|
||||
"tags": [
|
||||
"jlc",
|
||||
"production",
|
||||
"fabrication",
|
||||
"toolkit"
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"status": "testing",
|
||||
"version": "3.1.0",
|
||||
"status": "stable",
|
||||
"kicad_version": "6.00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ try:
|
||||
plugin.register()
|
||||
except Exception as e:
|
||||
import logging
|
||||
root = logging.getLogger()
|
||||
root.debug(repr(e))
|
||||
logger = logging.getLogger()
|
||||
logger.debug(repr(e))
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import pcbnew
|
||||
import pcbnew # type: ignore
|
||||
|
||||
baseUrl = 'https://www.jlcpcb.com'
|
||||
|
||||
netlistFileName = 'netlist.ipc'
|
||||
designatorsFileName = 'designators.csv'
|
||||
placementFileName = 'positions.csv'
|
||||
bomFileName = 'bom.csv'
|
||||
gerberArchiveName = 'gerber.zip'
|
||||
gerberArchiveName = 'gerbers.zip'
|
||||
outputFolder = 'production'
|
||||
|
||||
plotPlan = [
|
||||
@@ -13,11 +14,40 @@ plotPlan = [
|
||||
("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"),
|
||||
("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")
|
||||
]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import wx
|
||||
|
||||
STATUS_EVENT_ID = wx.NewId()
|
||||
|
||||
|
||||
class StatusEvent(wx.PyEvent):
|
||||
def __init__(self, data):
|
||||
wx.PyEvent.__init__(self)
|
||||
self.SetEventType(STATUS_EVENT_ID)
|
||||
self.data = data
|
||||
|
||||
@staticmethod
|
||||
def invoke(window, function):
|
||||
window.Connect(-1, -1, STATUS_EVENT_ID, function)
|
||||
@@ -0,0 +1,2 @@
|
||||
EXCLUDE_DNP_OPT = "EXCLUDE DNP"
|
||||
OUTPUT_NAME_OPT = "OUTPUT NAME"
|
||||
@@ -1,48 +1,85 @@
|
||||
import os
|
||||
import wx
|
||||
import pcbnew
|
||||
import pcbnew # type: ignore
|
||||
|
||||
from .thread import *
|
||||
from .result_event import *
|
||||
from .thread import ProcessThread
|
||||
from .events import StatusEvent
|
||||
from .options import EXCLUDE_DNP_OPT
|
||||
|
||||
|
||||
# WX GUI form that show the plugin progress
|
||||
class KiCadToJLCForm(wx.Frame):
|
||||
def __init__(self):
|
||||
wx.Dialog.__init__(
|
||||
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.app = wx.PySimpleApp()
|
||||
icon = wx.Icon(os.path.join(os.path.dirname(__file__), 'icon.png'))
|
||||
self.SetIcon(icon)
|
||||
self.SetBackgroundColour(wx.LIGHT_GREY)
|
||||
|
||||
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
|
||||
self.SetSizeHints(wx.Size(600, 100), wx.DefaultSize)
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
self.mOptionsLabel = wx.StaticText(self, label='Options:')
|
||||
# self.mOptionsSeparator = wx.StaticLine(self)
|
||||
self.mExcludeDnpCheckbox = wx.CheckBox(self, label='Exclude DNP components')
|
||||
self.mExcludeDnpCheckbox.SetValue(False)
|
||||
|
||||
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)
|
||||
self.mGenerateButton = wx.Button(self, label='Generate', size=wx.Size(600, 60))
|
||||
|
||||
self.SetSizer(bSizer1)
|
||||
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.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.mExcludeDnpCheckbox, 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)
|
||||
|
||||
EVT_RESULT(self, self.updateDisplay)
|
||||
ProcessThread(self)
|
||||
|
||||
def onGenerateButtonClick(self, event):
|
||||
options = dict()
|
||||
options[EXCLUDE_DNP_OPT] = self.mExcludeDnpCheckbox.GetValue()
|
||||
|
||||
self.mExcludeDnpCheckbox.Hide()
|
||||
self.mOptionsLabel.Hide()
|
||||
self.mGenerateButton.Hide()
|
||||
self.mGaugeStatus.Show()
|
||||
|
||||
self.Fit()
|
||||
self.SetTitle('Fabrication Toolkit (Processing...)')
|
||||
|
||||
StatusEvent.invoke(self, self.updateDisplay)
|
||||
ProcessThread(self, options)
|
||||
|
||||
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
|
||||
class Plugin(pcbnew.ActionPlugin):
|
||||
def __init__(self):
|
||||
self.name = "Fabrication Toolkit"
|
||||
@@ -50,10 +87,8 @@ class Plugin(pcbnew.ActionPlugin):
|
||||
self.description = "Toolkit for automating PCB fabrication process with KiCad and JLC PCB"
|
||||
self.pcbnew_icon_support = hasattr(self, "show_toolbar_button")
|
||||
self.show_toolbar_button = True
|
||||
self.icon_file_name = os.path.join(
|
||||
os.path.dirname(__file__), 'icon.png')
|
||||
self.dark_icon_file_name = os.path.join(
|
||||
os.path.dirname(__file__), 'icon.png')
|
||||
self.icon_file_name = os.path.join(os.path.dirname(__file__), 'icon.png')
|
||||
self.dark_icon_file_name = os.path.join(os.path.dirname(__file__), 'icon.png')
|
||||
|
||||
def Run(self):
|
||||
KiCadToJLCForm().Show()
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
# For better annotation.
|
||||
from __future__ import annotations
|
||||
|
||||
# System base libraries
|
||||
import os
|
||||
import csv
|
||||
import math
|
||||
import shutil
|
||||
from collections import defaultdict
|
||||
import re
|
||||
|
||||
# Interaction with KiCad.
|
||||
import pcbnew # type: ignore
|
||||
|
||||
# Application definitions.
|
||||
from .config import *
|
||||
|
||||
|
||||
class ProcessManager:
|
||||
def __init__(self):
|
||||
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 = 50000
|
||||
settings.m_SolderMaskToCopperClearance = 5000
|
||||
settings.m_SolderMaskMinWidth = 0
|
||||
|
||||
plot_controller = pcbnew.PLOT_CONTROLLER(self.board)
|
||||
|
||||
plot_options = plot_controller.GetPlotOptions()
|
||||
plot_options.SetOutputDirectory(temp_dir)
|
||||
plot_options.SetPlotFrameRef(False)
|
||||
plot_options.SetSketchPadLineWidth(pcbnew.FromMM(0.1))
|
||||
plot_options.SetAutoScale(False)
|
||||
plot_options.SetScale(1)
|
||||
plot_options.SetMirror(False)
|
||||
plot_options.SetUseGerberAttributes(True)
|
||||
plot_options.SetUseGerberProtelExtensions(False)
|
||||
plot_options.SetUseAuxOrigin(True)
|
||||
plot_options.SetSubtractMaskFromSilk(True)
|
||||
plot_options.SetDrillMarksType(0) # NO_DRILL_SHAPE
|
||||
|
||||
if hasattr(plot_options, "SetExcludeEdgeLayer"):
|
||||
plot_options.SetExcludeEdgeLayer(True)
|
||||
|
||||
for layer_info in plotPlan:
|
||||
if self.board.IsLayerEnabled(layer_info[1]):
|
||||
plot_controller.SetLayer(layer_info[1])
|
||||
plot_controller.OpenPlotfile(layer_info[0], pcbnew.PLOT_FORMAT_GERBER, layer_info[2])
|
||||
|
||||
if layer_info[1] == pcbnew.Edge_Cuts and hasattr(plot_controller, 'plotLayers'):
|
||||
# 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+
|
||||
seq = pcbnew.LSEQ()
|
||||
seq.push_back(layer_info[1])
|
||||
seq.push_back(pcbnew.User_1)
|
||||
plot_controller.PlotLayers(seq)
|
||||
else:
|
||||
plot_controller.PlotLayer()
|
||||
|
||||
plot_controller.ClosePlot()
|
||||
|
||||
def generate_drills(self, temp_dir):
|
||||
'''Generate the drill file.'''
|
||||
drill_writer = pcbnew.EXCELLON_WRITER(self.board)
|
||||
|
||||
drill_writer.SetOptions(
|
||||
False,
|
||||
True,
|
||||
self.board.GetDesignSettings().GetAuxOrigin(),
|
||||
False)
|
||||
drill_writer.SetFormat(False)
|
||||
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_tables(self, temp_dir, 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())
|
||||
|
||||
# unique designator dictionary
|
||||
footprint_designators = defaultdict(int)
|
||||
for i, footprint in enumerate(footprints):
|
||||
# count unique designators
|
||||
footprint_designators[footprint.GetReference()] += 1
|
||||
bom_designators = footprint_designators.copy()
|
||||
|
||||
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:
|
||||
footprint_name = str(footprint.GetFPID().GetFootprintName())
|
||||
except AttributeError:
|
||||
footprint_name = str(footprint.GetFPID().GetLibItemName())
|
||||
|
||||
layer = {
|
||||
pcbnew.F_Cu: 'top',
|
||||
pcbnew.B_Cu: 'bottom',
|
||||
}.get(footprint.GetLayer())
|
||||
|
||||
# mount_type = {
|
||||
# 0: 'smt',
|
||||
# 1: 'tht',
|
||||
# 2: 'smt'
|
||||
# }.get(footprint.GetAttributes())
|
||||
|
||||
skip_footprint = exclude_dnp and (footprint.HasProperty('dnp') or footprint.GetValue().upper() == 'DNP')
|
||||
|
||||
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_POS_FILES) and not skip_footprint:
|
||||
# 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
|
||||
|
||||
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': designator,
|
||||
'Mid X': mid_x,
|
||||
'Mid Y': mid_y,
|
||||
'Rotation': rotation,
|
||||
'Layer': layer,
|
||||
})
|
||||
|
||||
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM) and not skip_footprint:
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
|
||||
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 ?
|
||||
|
||||
# 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),
|
||||
})
|
||||
|
||||
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())
|
||||
|
||||
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())
|
||||
|
||||
# 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)
|
||||
|
||||
# remove non essential files
|
||||
for item in os.listdir(temp_dir):
|
||||
if not item.endswith(".zip") and not item.endswith(".csv") and not item.endswith(".ipc"):
|
||||
os.remove(os.path.join(temp_dir, item))
|
||||
|
||||
return temp_file
|
||||
|
||||
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']
|
||||
|
||||
if footprint.HasProperty('dnp'):
|
||||
return 'DNP'
|
||||
|
||||
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)
|
||||
@@ -1,14 +0,0 @@
|
||||
import wx
|
||||
|
||||
EVT_RESULT_ID = wx.NewId()
|
||||
|
||||
|
||||
def EVT_RESULT(win, func):
|
||||
win.Connect(-1, -1, EVT_RESULT_ID, func)
|
||||
|
||||
|
||||
class ResultEvent(wx.PyEvent):
|
||||
def __init__(self, data):
|
||||
wx.PyEvent.__init__(self)
|
||||
self.SetEventType(EVT_RESULT_ID)
|
||||
self.data = data
|
||||
@@ -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
|
||||
@@ -1,209 +1,127 @@
|
||||
# original copyright Aisler and licensed under the MIT license.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# from urllib import response
|
||||
# import json
|
||||
# import requests
|
||||
import os
|
||||
import wx
|
||||
import csv
|
||||
import pcbnew # type: ignore
|
||||
import shutil
|
||||
import tempfile
|
||||
import webbrowser
|
||||
import datetime
|
||||
from threading import Thread
|
||||
from .result_event import *
|
||||
from .events import StatusEvent
|
||||
from .process import ProcessManager
|
||||
from .config import *
|
||||
from .options import *
|
||||
|
||||
|
||||
class ProcessThread(Thread):
|
||||
def __init__(self, wxObject):
|
||||
def __init__(self, wx, options):
|
||||
Thread.__init__(self)
|
||||
self.wxObject = wxObject
|
||||
|
||||
self.process_manager = ProcessManager()
|
||||
self.wx = wx
|
||||
self.options = options
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
# initializing
|
||||
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()
|
||||
board = pcbnew.GetBoard()
|
||||
title_block = board.GetTitleBlock()
|
||||
project_directory = os.path.dirname(self.process_manager.board.GetFileName())
|
||||
|
||||
# configure gerber
|
||||
self.report(5)
|
||||
try:
|
||||
# Verify all zones are up-to-date
|
||||
self.progress(10)
|
||||
self.process_manager.update_zone_fills()
|
||||
|
||||
settings = board.GetDesignSettings()
|
||||
settings.m_SolderMaskMargin = 0
|
||||
settings.m_SolderMaskMinWidth = 0
|
||||
# generate gerber
|
||||
self.progress(20)
|
||||
self.process_manager.generate_gerber(temp_dir + "_g")
|
||||
|
||||
pctl = pcbnew.PLOT_CONTROLLER(board)
|
||||
# generate drill file
|
||||
self.progress(30)
|
||||
self.process_manager.generate_drills(temp_dir + "_g")
|
||||
|
||||
popt = pctl.GetPlotOptions()
|
||||
popt.SetOutputDirectory(temp_dir)
|
||||
popt.SetPlotFrameRef(False)
|
||||
popt.SetSketchPadLineWidth(pcbnew.FromMM(0.1))
|
||||
popt.SetAutoScale(False)
|
||||
popt.SetScale(1)
|
||||
popt.SetMirror(False)
|
||||
popt.SetUseGerberAttributes(True)
|
||||
popt.SetExcludeEdgeLayer(True)
|
||||
popt.SetUseGerberProtelExtensions(False)
|
||||
popt.SetUseAuxOrigin(True)
|
||||
popt.SetSubtractMaskFromSilk(False)
|
||||
popt.SetDrillMarksType(0) # NO_DRILL_SHAPE
|
||||
# generate netlist
|
||||
self.progress(40)
|
||||
self.process_manager.generate_netlist(temp_dir)
|
||||
|
||||
# generate gerber
|
||||
self.report(10)
|
||||
for layer_info in plotPlan:
|
||||
if board.IsLayerEnabled(layer_info[1]):
|
||||
pctl.SetLayer(layer_info[1])
|
||||
pctl.OpenPlotfile(
|
||||
layer_info[0],
|
||||
pcbnew.PLOT_FORMAT_GERBER,
|
||||
layer_info[2])
|
||||
pctl.PlotLayer()
|
||||
# generate data tables
|
||||
self.progress(50)
|
||||
self.process_manager.generate_tables(temp_dir, self.options[EXCLUDE_DNP_OPT])
|
||||
|
||||
pctl.ClosePlot()
|
||||
# generate pick and place file
|
||||
self.progress(60)
|
||||
self.process_manager.generate_positions(temp_dir)
|
||||
|
||||
#generate drill file
|
||||
self.report(15)
|
||||
drlwriter = pcbnew.EXCELLON_WRITER(board)
|
||||
# generate BOM file
|
||||
self.progress(70)
|
||||
self.process_manager.generate_bom(temp_dir)
|
||||
|
||||
drlwriter.SetOptions(
|
||||
False,
|
||||
True,
|
||||
board.GetDesignSettings().GetAuxOrigin(),
|
||||
False)
|
||||
drlwriter.SetFormat(False)
|
||||
drlwriter.CreateDrillandMapFilesSet(pctl.GetPlotDirName(), True, False)
|
||||
# generate production archive
|
||||
self.progress(85)
|
||||
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
|
||||
|
||||
# generate netlist
|
||||
self.report(25)
|
||||
netlist_writer = pcbnew.IPC356D_WRITER(board)
|
||||
netlist_writer.Write(os.path.join(temp_dir, netlistFileName))
|
||||
|
||||
# generate pick and place file
|
||||
self.report(40)
|
||||
components = []
|
||||
bom = []
|
||||
if hasattr(board, 'GetModules'):
|
||||
footprints = list(board.GetModules())
|
||||
else:
|
||||
footprints = list(board.GetFootprints())
|
||||
|
||||
for i, footprint in enumerate(footprints):
|
||||
try:
|
||||
footprint_name = str(footprint.GetFPID().GetFootprintName())
|
||||
except AttributeError:
|
||||
footprint_name = str(footprint.GetFPID().GetLibItemName())
|
||||
|
||||
layer = {
|
||||
pcbnew.F_Cu: 'top',
|
||||
pcbnew.B_Cu: 'bottom',
|
||||
}.get(footprint.GetLayer())
|
||||
|
||||
# mount_type = {
|
||||
# 0: 'smt',
|
||||
# 1: 'tht',
|
||||
# 2: 'smt'
|
||||
# }.get(footprint.GetAttributes())
|
||||
|
||||
if not footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_POS_FILES:
|
||||
components.append({
|
||||
'Designator': footprint.GetReference(),
|
||||
'Mid X': (footprint.GetPosition()[0] - board.GetDesignSettings().GetAuxOrigin()[0]) / 1000000.0,
|
||||
'Mid Y': (footprint.GetPosition()[1] - board.GetDesignSettings().GetAuxOrigin()[1]) * -1.0 / 1000000.0,
|
||||
'Rotation': footprint.GetOrientation() / 10.0,
|
||||
'Layer': layer,
|
||||
})
|
||||
|
||||
if not footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM:
|
||||
bom.append({
|
||||
'Designator': footprint.GetReference(),
|
||||
'Footprint': footprint_name,
|
||||
'Quantity': 1,
|
||||
'Value': footprint.GetValue(),
|
||||
# 'Mount': mount_type,
|
||||
'LCSC Part #': self.getMpnFromFootprint(footprint),
|
||||
})
|
||||
|
||||
with open((os.path.join(temp_dir, placementFileName)), 'w', newline='') as outfile:
|
||||
header = True
|
||||
csv_writer = csv.writer(outfile)
|
||||
|
||||
for component in components:
|
||||
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())
|
||||
|
||||
# generate BOM file
|
||||
self.report(60)
|
||||
with open((os.path.join(temp_dir, bomFileName)), 'w', newline='') as outfile:
|
||||
header = True
|
||||
csv_writer = csv.writer(outfile)
|
||||
|
||||
for component in 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())
|
||||
|
||||
|
||||
# generate production archive
|
||||
self.report(75)
|
||||
temp_file = shutil.make_archive(temp_file, 'zip', temp_dir)
|
||||
temp_file = shutil.move(temp_file, temp_dir)
|
||||
|
||||
# remove non essential files
|
||||
for item in os.listdir(temp_dir):
|
||||
if not item.endswith(".zip") and not item.endswith(".csv") and not item.endswith(".ipc"):
|
||||
os.remove(os.path.join(temp_dir, item))
|
||||
|
||||
# upload files
|
||||
self.report(87.5)
|
||||
boardWidth = pcbnew.Iu2Millimeter(board.GetBoardEdgesBoundingBox().GetWidth())
|
||||
boardHeight = pcbnew.Iu2Millimeter(board.GetBoardEdgesBoundingBox().GetHeight())
|
||||
boardLayer = board.GetCopperLayerCount()
|
||||
|
||||
# files = {'upload[file]': open(temp_file, 'rb')}
|
||||
# upload_url = baseUrl + '/Common/KiCadUpFile/'
|
||||
|
||||
# response = requests.post(
|
||||
# upload_url, files=files, data={'boardWidth':boardWidth,'boardHeight':boardHeight,'boardLayer':boardLayer})
|
||||
|
||||
# urls = json.loads(response.content)
|
||||
|
||||
readsofar = 0
|
||||
totalsize = os.path.getsize(temp_file)
|
||||
# progress bar done animation
|
||||
read_so_far = 0
|
||||
total_size = os.path.getsize(temp_file)
|
||||
with open(temp_file, 'rb') as file:
|
||||
while True:
|
||||
data = file.read(10)
|
||||
if not data:
|
||||
break
|
||||
readsofar += len(data)
|
||||
percent = readsofar * 1e2 / totalsize
|
||||
self.report(75 + percent / 8)
|
||||
read_so_far += len(data)
|
||||
percent = read_so_far * 1e2 / total_size
|
||||
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]
|
||||
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))
|
||||
|
||||
webbrowser.open(temp_dir)
|
||||
# webbrowser.open(urls['redirect'])
|
||||
self.report(-1)
|
||||
# copy to & open output dir
|
||||
try:
|
||||
if os.path.exists(output_path):
|
||||
shutil.rmtree(output_path)
|
||||
|
||||
def report(self, status):
|
||||
wx.PostEvent(self.wxObject, ResultEvent(status))
|
||||
|
||||
def getMpnFromFootprint(self, footprint):
|
||||
keys = ['mpn', 'MPN', 'Mpn', 'JLC_MPN', 'LCSC_MPN', 'LCSC Part #']
|
||||
for key in keys:
|
||||
if footprint.HasProperty(key):
|
||||
return footprint.GetProperty(key)
|
||||
|
||||
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.progress(-1)
|
||||
|
||||
def progress(self, percent):
|
||||
wx.PostEvent(self.wx, StatusEvent(percent))
|
||||
|
||||