Compare commits

...

13 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
Benny Megidish a84c8e4d83 fix: designator seperator 2022-06-29 13:00:50 +03:00
Benny Megidish 966f96cf7e fix: metadata tag lower case 2022-06-29 12:55:38 +03:00
Benny Megidish f46f57d36d feat: handle duplicate footprint designators #1 2022-06-28 11:49:11 +03:00
Benny Megidish 5fa84df594 chore: removed JLC ref from plugin name and icon 2022-06-07 12:38:16 +03:00
22 changed files with 568 additions and 194 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"
]
}
+21 -5
View File
@@ -1,4 +1,12 @@
# JLC PCB Plug-in for KiCad
<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,18 +17,26 @@
## Installation
### Official Installation
Open the "Plugin and Content Manager" from the KiCad main menu an install the "JLC Plugin for KiCad" 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".
## 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 '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 to ignore te 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

+9 -3
View File
@@ -1,8 +1,8 @@
{
"$schema": "https://go.kicad.org/pcm/schemas/v1",
"name": "JLC PCB Plugin for KiCad",
"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 component options to in order ignore the footprint from the relevant file.",
"identifier": "com.github.bennymeg.JLC-Plugin-for-KiCad",
"type": "plugin",
"author": {
@@ -21,9 +21,15 @@
"resources": {
"homepage": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
},
"tags": [
"jlc",
"production",
"fabrication",
"toolkit"
],
"versions": [
{
"version": "0.1.0",
"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))
+3
View File
@@ -3,6 +3,7 @@ import pcbnew
baseUrl = 'https://www.jlcpcb.com'
netlistFileName = 'netlist.ipc'
designatorsFileName = 'designators.csv'
placementFileName = 'positions.csv'
bomFileName = 'bom.csv'
gerberArchiveName = 'gerber.zip'
@@ -13,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)
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+10 -8
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,17 +46,16 @@ class KiCadToJLCForm(wx.Frame):
self.m_gaugeStatus.SetValue(status.data)
# Plugin definition
class Plugin(pcbnew.ActionPlugin):
def __init__(self):
self.name = "JLC Plugin for KiCad"
self.name = "Fabrication Toolkit"
self.category = "Manufacturing"
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
+36 -162
View File
@@ -1,209 +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 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)
self.process_manager.generate_gerber(temp_dir)
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()
#generate drill file
# 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())
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())
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()
# 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)
#self.report(87.5)
#self.process_manager.upload_archive(temp_file)
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 #']
for key in keys:
if footprint.HasProperty(key):
return footprint.GetProperty(key)
wx.PostEvent(self.wx, StatusEvent(status))
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB