Compare commits

...

9 Commits

Author SHA1 Message Date
Benny Megidish 4c08e18ef9 feat: upload 2022-07-26 14:21:57 +03:00
Benny Megidish a39b65c94c fix: UnicodeEncodeError during file writing #5 2022-07-21 12:06:02 +03:00
Benny Megidish a6d1999839 fix: webbrowser opening for MacOS #2 #4 2022-07-21 12:03:33 +03:00
Benny Megidish c7cc47718d chore: scafolding + project dir output #3 2022-07-21 12:03:02 +03:00
Benny Megidish b801028d63 Update README.md 2022-07-07 17:40:11 +03:00
Benny Megidish 959c2110c9 chore: added readme logo 2022-07-07 17:30:17 +03:00
Benny Megidish 54cdea9c33 Update README.md 2022-06-30 12:49:54 +03:00
Benny Megidish 08b6895ea3 Update README.md 2022-06-30 12:43:47 +03:00
Benny Megidish 647d8aaec2 feat: added picture assets 2022-06-30 12:34:53 +03:00
20 changed files with 552 additions and 213 deletions
View File
+10
View File
@@ -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>
+19
View File
@@ -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>
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+8
View File
@@ -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>
Generated
+6
View File
@@ -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>
+40
View File
@@ -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>
+13
View File
@@ -0,0 +1,13 @@
{
"cSpell.words": [
"Drilland",
"EXCELLON",
"FPID",
"LCSC",
"netlist",
"pcbnew",
"Plotfile",
"Protel",
"Soldermask"
]
}
+16 -4
View File
@@ -1,4 +1,12 @@
# 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** |
|:-----:|
</div>
## Features
1. Generates gerber files in correct format for production
@@ -9,7 +17,7 @@
## Installation
### Official Installation
Open the "Plugin and Content Manager" from the KiCad main menu an install the "Fabrication Toolkit" plugin from the selection list.
Open the "Plugin and Content Manager" from the KiCad main menu an install the "<ins>Fabrication Toolkit</ins>" plugin from the selection list.
### 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".
@@ -17,14 +25,18 @@ Download the latest ZIP file from https://github.com/bennymeg/JLC-Plugin-for-KiC
## Options
### Include Component Part Number in Production Files
Add an 'MPN'* field with the LCSC component part number to the footprint component options.
Add an 'MPN'* 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'|
| --- | --- | --- | --- | --- | --- | --- | --- |
### Ignore Footprint in Production Files
Select 'Exclude from position files' or 'Exclude from BOM' in the footprint component options in order to ignore the footprint from the relevant file.
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.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/attributes.png?raw=true" height=420>
## Author
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+183
View File
@@ -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

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+1 -1
View File
@@ -29,7 +29,7 @@
],
"versions": [
{
"version": "0.3.2",
"version": "0.4.2",
"status": "testing",
"kicad_version": "6.00"
}
+2 -2
View File
@@ -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))
+2
View File
@@ -14,6 +14,8 @@ 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"),
("F.SilkS", pcbnew.F_SilkS, "Top Silkscreen"),
("B.SilkS", pcbnew.B_SilkS, "Bottom Silkscreen"),
("F.Mask", pcbnew.F_Mask, "Top Soldermask"),
+14
View File
@@ -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)
+9 -7
View File
@@ -1,10 +1,12 @@
import os
import wx
import pcbnew
from .thread import *
from .result_event import *
from .thread import ProcessThread
from .events import StatusEvent
# WX GUI form that show the plugin progress
class KiCadToJLCForm(wx.Frame):
def __init__(self):
wx.Dialog.__init__(
@@ -32,9 +34,10 @@ class KiCadToJLCForm(wx.Frame):
self.Centre(wx.BOTH)
EVT_RESULT(self, self.updateDisplay)
StatusEvent.invoke(self, self.updateDisplay)
ProcessThread(self)
def updateDisplay(self, status):
if status.data == -1:
pcbnew.Refresh()
@@ -43,6 +46,7 @@ class KiCadToJLCForm(wx.Frame):
self.m_gaugeStatus.SetValue(status.data)
# Plugin definition
class Plugin(pcbnew.ActionPlugin):
def __init__(self):
self.name = "Fabrication Toolkit"
@@ -50,10 +54,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()
+188
View File
@@ -0,0 +1,188 @@
import json
import requests
import os
import csv
import shutil
import pcbnew
from collections import defaultdict
from .config import *
class ProcessManager:
def __init__(self):
self.board = pcbnew.GetBoard()
self.bom = []
self.components = []
def generate_gerber(self, temp_dir):
settings = self.board.GetDesignSettings()
settings.m_SolderMaskMargin = 0
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.SetExcludeEdgeLayer(True)
plot_options.SetUseGerberProtelExtensions(False)
plot_options.SetUseAuxOrigin(True)
plot_options.SetSubtractMaskFromSilk(False)
plot_options.SetDrillMarksType(0) # NO_DRILL_SHAPE
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])
plot_controller.PlotLayer()
plot_controller.ClosePlot()
def generate_drills(self, temp_dir):
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):
netlist_writer = pcbnew.IPC356D_WRITER(self.board)
netlist_writer.Write(os.path.join(temp_dir, netlistFileName))
def generate_positions(self, temp_dir):
if hasattr(self.board, 'GetModules'):
footprints = list(self.board.GetModules())
else:
footprints = list(self.board.GetFootprints())
# 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()
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))
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:
# 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
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,
'Layer': layer,
})
if not footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM:
# 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
# todo: merge similar parts into single entry
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),
})
with open((os.path.join(temp_dir, placementFileName)), 'w', newline='', encoding='utf-8') as outfile:
header = True
csv_writer = csv.writer(outfile)
for component in self.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())
def generate_bom(self, temp_dir):
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())
def generate_archive(self, temp_dir, temp_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 upload_archive(self, temp_file):
boardWidth = pcbnew.Iu2Millimeter(self.board.GetBoardEdgesBoundingBox().GetWidth())
boardHeight = pcbnew.Iu2Millimeter(self.board.GetBoardEdgesBoundingBox().GetHeight())
boardLayer = self.board.GetCopperLayerCount()
files = { 'upload[file]': open(temp_file, 'rb') }
data = { 'boardWidth': boardWidth, 'boardHeight': boardHeight, 'boardLayer': boardLayer }
upload_url = baseUrl + '/Common/KiCadUpFile/'
response = requests.post(upload_url, files=files, data=data)
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:
if footprint.HasProperty(key):
return footprint.GetProperty(key)
-14
View File
@@ -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
+35 -185
View File
@@ -1,233 +1,83 @@
# 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 shutil
import tempfile
import webbrowser
from collections import defaultdict
from threading import Thread
from .result_event import *
from .events import StatusEvent
from .process import ProcessManager
from .config import *
class ProcessThread(Thread):
def __init__(self, wxObject):
def __init__(self, wx):
Thread.__init__(self)
self.wxObject = wxObject
self.process_manager = ProcessManager()
self.wx = wx
self.start()
def run(self):
# initializing
self.report(0)
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())
output_path = os.path.join(project_directory, outputFolder)
# configure gerber
# configure and generate gerber
self.report(5)
settings = board.GetDesignSettings()
settings.m_SolderMaskMargin = 0
settings.m_SolderMaskMinWidth = 0
pctl = pcbnew.PLOT_CONTROLLER(board)
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 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()
pctl.ClosePlot()
self.process_manager.generate_gerber(temp_dir)
# generate drill file
self.report(15)
drlwriter = pcbnew.EXCELLON_WRITER(board)
drlwriter.SetOptions(
False,
True,
board.GetDesignSettings().GetAuxOrigin(),
False)
drlwriter.SetFormat(False)
drlwriter.CreateDrillandMapFilesSet(pctl.GetPlotDirName(), True, False)
self.process_manager.generate_drills(temp_dir)
# generate netlist
self.report(25)
netlist_writer = pcbnew.IPC356D_WRITER(board)
netlist_writer.Write(os.path.join(temp_dir, netlistFileName))
self.process_manager.generate_netlist(temp_dir)
# generate pick and place file
self.report(40)
components = []
bom = []
if hasattr(board, 'GetModules'):
footprints = list(board.GetModules())
else:
footprints = list(board.GetFootprints())
# 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()
with open((os.path.join(temp_dir, designatorsFileName)), 'w') 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())
if not footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_POS_FILES:
# 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
components.append({
'Designator': "{}{}{}".format(footprint.GetReference(), "" if unique_id == "" else "_", unique_id),
'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:
# 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
# todo: merge similar parts into single entry
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),
})
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())
self.process_manager.generate_positions(temp_dir)
# 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())
self.process_manager.generate_bom(temp_dir)
# 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))
temp_file = self.process_manager.generate_archive(temp_dir, temp_file)
# upload files
self.report(87.5)
boardWidth = pcbnew.Iu2Millimeter(board.GetBoardEdgesBoundingBox().GetWidth())
boardHeight = pcbnew.Iu2Millimeter(board.GetBoardEdgesBoundingBox().GetHeight())
boardLayer = board.GetCopperLayerCount()
#self.report(87.5)
#self.process_manager.upload_archive(temp_file)
# 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
read_so_far += len(data)
percent = read_so_far * 1e2 / total_size
self.report(75 + percent / 8)
os.rename(temp_file, os.path.join(temp_dir, gerberArchiveName))
webbrowser.open(temp_dir)
# webbrowser.open(urls['redirect'])
try:
if os.path.exists(output_path):
shutil.rmtree(output_path)
shutil.copytree(temp_dir, output_path)
webbrowser.open("file://%s" % (output_path))
except Exception as e:
webbrowser.open("file://%s" % (temp_dir))
self.report(-1)
def report(self, status):
wx.PostEvent(self.wxObject, ResultEvent(status))
def getMpnFromFootprint(self, footprint):
keys = ['mpn', 'Mpn', 'MPN', 'JLC_MPN', 'LCSC_MPN', 'LCSC Part #', 'JLC', 'LCSC']
for key in keys:
if footprint.HasProperty(key):
return footprint.GetProperty(key)
wx.PostEvent(self.wx, StatusEvent(status))