Skip to content

Instantly share code, notes, and snippets.

@dzindra
Created March 5, 2021 07:52
Show Gist options
  • Save dzindra/dde9ede4fea0c1488ab9435aa2ec3d2e to your computer and use it in GitHub Desktop.
Save dzindra/dde9ede4fea0c1488ab9435aa2ec3d2e to your computer and use it in GitHub Desktop.
JLCPCB BOM and CPL generator for Eagle
#usage "<b>Data generation for JLCPCB Assembly service</b>\n"
"<p>"
"Generates files for smds on the top and bottom layers "
"wich can be used with mounting machines. "
"The x and y coordinates (units: mm) of the SMD elements are calculated "
"as mean of maximum and mimimum value of the smds origin points. "
"The calculated value does not necessarily fit with the origin "
"point of the part in the layout."
"All SMD elements populated in currently set assembly variant are considered."
"</p>"
"<p>"
"Additional part attributes that are checked"
"</p><ul>"
"<li><b>JLC_ROT</b> - number of degreed to add to existing part rotation. Positive or negative integer.</li>"
"<li><b>JLC_VALUE</b> - override default part value. String.</li>"
"<li><b>JLC_PACKAGE</b> - override default part package. String.</li>"
"<li><b>JLC_MOUNT</b> - <i>NO</i> - do not mount part even if it is SMD. <i>FORCE</i> - mount part event if it is not and SMD part (useful for THT parts).</li>"
"</ul>"
"<p>"
"<author>Author: support@cadsoft.de</author><br>"
"<author>Author: D Roosendaal</author><br>"
"<author>Author: jindra@thecave.cz</author><br>"
// THIS PROGRAM IS PROVIDED AS IS AND WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED
// 2019-09-20: Modified by D. Roosendaal to output a JLCPCB compatible CSV and BOM!
// UNVERIFIED as of yet. Use at your own risk.
// 2021-03-05: Modified by J. Dolezy to support additional attributes that affect the output files.
// Additional part attributes that are checked
// JLC_ROT - number of degreed to add to existing part rotation. Positive or negative integer.
// JLC_VALUE - override default part value. String.
// JLC_PACKAGE - override default part package. String.
// JLC_MOUNT - "NO" - do not mount part even if it is SMD. "FORCE" - mount part event if it is not and SMD part (useful for THT parts).
//
// *** YOU CAN POPULATE THIS LIST TO CORRECT ROTATION, AND TO [option] ADD SMT ORDER CODE
//
// Value, package, additional rotation (pos or neg), optional JLC SMT order code to populate the BOM
string Modify[] = { "BC847B;SOT23-BEC;-90",
"BC857B;SOT23-BEC;-90",
"12.288MHz;ABM3B;-90",
"BAV99;SOT-23;90",
"1117-3.3;SOT223;-90",
"BS170;SOT-23;90",
"BSS84;SOT23;-90",
"8V2;SOT23;-90",
"S9012;SOT23-BEC;-90",
"47uF;CT3216;180",
"100uF;CT3528;180" };
//,
//"SS14;DO214AC;-180" };
// At this moment, JLCPCB seems to have a bug which stops properly reading of parts if one or more components have the optional order code.
// This may or may not be useful to you in the future.... your decision.
//"ADAU1701;LQFP-48;;C26394"};
if (board) board(B) {
int NumParts=0;
int i, j;
string PartName[], PartValue[], PartPackage[];
int PartSorted[];
string List;
string Code;
// Get filename
string fileName = dlgFileSave("Save File", filesetext(B.name, "CPL.csv"), "*.csv");
if (fileName == "") exit(0);
output(fileName) {
printf("Designator,Mid X,Mid Y,Layer,Rotation\n");
B.elements(E) if (E.populate) {
int wasSmd,
xmax =-2147483648,
xmin = 2147483647,
ymax = xmax,
ymin = xmin;
wasSmd = 0;
E.package.contacts(C) {
if ((C.smd && C.smd.layer == 1) || (E.attribute["JLC_MOUNT"]=="FORCE")) {
wasSmd = 1;
if (C.x > xmax) xmax = C.x;
if (C.y > ymax) ymax = C.y;
if (C.x < xmin) xmin = C.x;
if (C.y < ymin) ymin = C.y;
// If MOUNT attribute is NO, don't mount it...
if ((E.attribute["JLC_MOUNT"]=="NO")) wasSmd=0;
}
}
if (wasSmd) {
i=360;
// Look for the part&package in the Modify list, and apply rotation if applicable
if (E.package.name==lookup(Modify, E.value, 1, ';')) {
i+=strtol(lookup(Modify, E.value, 2, ';'));
} else if (E.attribute["JLC_ROT"]) {
i+=strtol(E.attribute["JLC_ROT"]);
}
i+=E.angle;
i%=360;
//Designator,Mid X,Mid Y,Layer,Rotation
printf("%s,%5.3fmm,%5.3fmm,Top,%d\n",E.name, u2mm((xmin + xmax)/2), u2mm((ymin + ymax)/2),i);
// Get part details to create the BOM, only populate parts that have a value.
if(E.value) {
PartSorted[NumParts]=0;
PartName[NumParts]=E.name;
PartValue[NumParts]= E.attribute["JLC_VALUE"] == "" ? E.value : E.attribute["JLC_VALUE"];
PartPackage[NumParts++]= E.attribute["JLC_PACKAGE"] == "" ? E.package.name : E.attribute["JLC_PACKAGE"];
}
}
}
B.elements(E) if (E.populate) {
int wasSmd,
xmax =-2147483648,
xmin = 2147483647,
ymax = xmax,
ymin = xmin;
wasSmd = 0;
E.package.contacts(C) {
if ((C.smd && C.smd.layer == 16) || (E.attribute["JLC_MOUNT"]=="FORCE")) {
wasSmd = 1;
if (C.x > xmax) xmax = C.x;
if (C.y > ymax) ymax = C.y;
if (C.x < xmin) xmin = C.x;
if (C.y < ymin) ymin = C.y;
// If MOUNT attribute is NO, don't mount it...
if ((E.attribute["JLC_MOUNT"]=="NO")) wasSmd=0;
}
}
if (wasSmd) {
i=360;
// Look for the part&package in the Modify list, and apply rotation if applicable
if (E.package.name==lookup(Modify, E.value, 1, ';')) {
i+=strtol(lookup(Modify, E.value, 2, ';'));
} else if (E.attribute["JLC_ROT"]) {
i+=strtol(E.attribute["JLC_ROT"]);
}
i+=E.angle;
i%=360;
//Designator,Mid X,Mid Y,Layer,Rotation
printf("%s,%5.3fmm,%5.3fmm,Bottom,%d\n",E.name, u2mm((xmin + xmax)/2), u2mm((ymin + ymax)/2),i);
// Get part details to create the BOM, only populate parts that have a value.
if(E.value) {
PartSorted[NumParts]=0;
PartName[NumParts]=E.name;
PartValue[NumParts]= E.attribute["JLC_VALUE"] == "" ? E.value : E.attribute["JLC_VALUE"];
PartPackage[NumParts++]= E.attribute["JLC_PACKAGE"] == "" ? E.package.name : E.attribute["JLC_PACKAGE"];
}
}
}
}
// Get filename for the BOM
string fileNameBOM = dlgFileSave("Save File", filesetext(B.name, "BOM.csv"), "*.csv");
if (fileNameBOM == "") exit(0);
output(fileNameBOM) {
// Comment,Designator,Footprint - Comment being the value
List+="Comment,Designator,Footprint\n";
for(i=0; i<NumParts;i++) {
if(!PartSorted[i]) {
List+="\""+PartValue[i]+"\",\""+PartName[i];
for(j=i+1; j<NumParts;j++) {
// Find other parts with the same value AND package
if(!PartSorted[j] && PartValue[i]==PartValue[j] && PartPackage[i]==PartPackage[j]) {
// Found!
PartSorted[j]=1;
List+=","+PartName[j];
}
}
// Look for the part&package in the Modify list, and append order Code, if it exists
if (PartPackage[i]==lookup(Modify, PartValue[i], 1, ';')) {
if(lookup(Modify, PartValue[i], 3, ';'))
Code=","+lookup(Modify, PartValue[i], 3, ';');
}
else
Code="";
List+="\",\""+PartPackage[i]+Code+"\"\n";
}
}
printf ("%s", List);
}
}
else {
dlgMessageBox("\n Start this ULP in a Board \n");
exit (0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment