skill
Fusion JLCPCB Export
Public Made by Adomby adom
Export the complete JLCPCB manufacturing package (gerbers + BOM with LCSC + CPL) from a Fusion board, by asking your AI.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
/*
Copyright 2019 OXullo Intersecans <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Note: the following eagle-bundled ULPs have been used as a guidance:
// * mountsmd.ulp
// * bom.ulp
// * cmd-change-swap-layer.ulp
// * centroid-screamingcircuits-smd.ulp
#usage "<b>JLCPCB BOM/CPL files generator</b>\n"
"<p>"
"Generates BOM and CPL files for JLCPCB SMT/TH dual side assembly service"
"https://jlcpcb.com/smt-assembly"
"<p>"
"Run the ULP from the board editor"
"<p>"
"<author>Author: OXullo Intersecans [email protected]</author>"
// Command line arguments
// Note: 'argv' and 'argc' are built-in variables in Eagle ULP
// argc is automatically set to the number of command line arguments
// argv contains the command line arguments as an indexed array (argv[0], argv[1], etc.)
// Parse argument list
status("JLCPCB ULP: Starting execution");
string arg_count_str;
sprintf(arg_count_str, "%d", argc);
status("JLCPCB ULP: Received " + arg_count_str + " command line arguments");
int layer_id_map[] = { 1, 16 };
UL_ELEMENT selected_elements[];
string replace_commas(string s)
{
while (strstr(s, ",") >= 0) { // convert commas to spaces
string substitution_string = " ";
sprintf(s, "%s%s%s", strsub(s, 0, strstr(s, ",")), substitution_string, strsub(s, strstr(s, ",")+1));
}
return s;
}
// Dumbest implementation of fmod
real angle_mod(real angle)
{
while (angle < 0) {
angle += 360;
}
while (angle >= 360) {
angle -= 360;
}
return angle;
}
// Borrowed from e-brueckenklemmenverwaltung.ulp
string strip(string s)
{
int i, n;
string t;
while (s && isspace(s[0])) {
s = strsub(s, 1);
}
while (s && isspace(s[strlen(s) - 1])) {
s = strsub(s, 0, strlen(s) - 1);
}
for (i = 0; i < strlen(s); i++) {
if (!isspace(s[i])) {
t[n++] = s[i];
} else {
if ((strlen(s) > i+1) && isspace(s[i+1]));
else t[n++] = s[i];
}
}
s = t;
return s;
}
string get_lcsc_part(UL_ELEMENT E)
{
E.attributes(A) {
if (A.name == "LCSC") {
return replace_commas(A.value);
}
}
return "";
}
int is_dnp(UL_ELEMENT E)
{
E.attributes(A) {
if (A.name == "DNP") {
return 1;
}
}
return 0;
}
if (board) board(B) {
status("JLCPCB ULP: Board found: " + B.name);
string output_dir = "";
string bom_filename = "";
string cpl_filename = "";
string info_filename = "";
// Check if arguments were provided (should be output_dir, bom_file, cpl_file, info_file)
if (argc >= 4) {
// Note: argv[0] is the ULP filename, argv[1] is the output directory, argv[2] is bom filename, argv[3] is cpl filename, and argv[4] is info filename
output_dir = argv[1];
bom_filename = argv[2];
cpl_filename = argv[3];
info_filename = argv[4];
status("JLCPCB ULP: Using paths from command line:");
status("JLCPCB ULP: Output directory: " + output_dir);
status("JLCPCB ULP: BOM file: " + bom_filename);
status("JLCPCB ULP: CPL file: " + cpl_filename);
status("JLCPCB ULP: Info file: " + info_filename);
}
else if (argc > 1) {
// Backward compatibility - just output directory was provided
output_dir = argv[1];
status("JLCPCB ULP: Using output directory from command line: " + output_dir);
// Generate filenames based on board name
string base_path = (output_dir + "/" +
strsub(filename(B.name), 0, strlen(filename(B.name)) - 4));
cpl_filename = base_path + "_cpl.csv";
bom_filename = base_path + "_bom.csv";
info_filename = base_path + "_info.txt";
}
else {
// Fallback to asking user for the directory if not provided via command line
output_dir = dlgDirectory("Export files to", filedir(B.name));
if (output_dir == "") {
status("JLCPCB ULP: User cancelled directory selection");
exit(1);
}
status("JLCPCB ULP: Using output directory from dialog: " + output_dir);
// Generate filenames based on board name
string base_path = (output_dir + "/" +
strsub(filename(B.name), 0, strlen(filename(B.name)) - 4));
cpl_filename = base_path + "_cpl.csv";
bom_filename = base_path + "_bom.csv";
info_filename = base_path + "_info.txt";
}
// Ensure we have a valid output directory
if (output_dir == "") {
status("JLCPCB ULP: ERROR - Empty output directory");
exit(1);
}
status("JLCPCB ULP: Using output directory: " + output_dir);
int element_count = 0;
int jlc_rot_count = 0;
string jlc_rot_elements = "";
int dnp_count = 0;
string dnp_elements = "";
status("JLCPCB ULP: Gathering components");
// Gather the components that will appear in BOM/CPL
B.elements(E) if (E.populate && !is_dnp(E)) {
E.package.contacts(C) {
if (C.smd || C.pad) {
selected_elements[element_count++] = E;
break;
}
}
}
string element_count_str;
sprintf(element_count_str, "%d", element_count);
status("JLCPCB ULP: Found " + element_count_str + " components to include");
// Count DNP elements separately
B.elements(E) if (E.populate && is_dnp(E)) {
dnp_count++;
dnp_elements += E.name + " ";
}
string dnp_count_str;
sprintf(dnp_count_str, "%d", dnp_count);
status("JLCPCB ULP: Found " + dnp_count_str + " DNP components");
status("JLCPCB ULP: Generating files:");
status("JLCPCB ULP: CPL file: " + cpl_filename);
status("JLCPCB ULP: BOM file: " + bom_filename);
status("JLCPCB ULP: Info file: " + info_filename);
output(cpl_filename) {
status("JLCPCB ULP: Writing CPL file");
printf("Designator,Mid X,Mid Y,Layer,Rotation\n");
for (int i = 0 ; i < element_count ; ++i) {
UL_ELEMENT E = selected_elements[i];
real angle = E.angle;
string layer = "Top";
if (E.mirror) {
layer = "Bottom";
}
E.attributes(A) { // manually rotate the part
if (A.name == "JLC_ROT") {
// Found a component with JLC_ROT
jlc_rot_count++;
jlc_rot_elements += E.name + " ";
// Note: supporting only integer offsets
angle = angle + strtol(A.value);
}
}
printf("%s,%5.2f,%5.2f,%s,%.1f\n",
E.name, u2mm(E.x), u2mm(E.y),
layer,
angle_mod(angle));
}
status("JLCPCB ULP: CPL file written successfully");
}
output(bom_filename) {
status("JLCPCB ULP: Writing BOM file");
int i;
int indexes[];
numeric string values[];
numeric string footprints[];
for (i=0 ; i < element_count ; ++i) {
indexes[i] = i;
values[i] = selected_elements[i].value;
footprints[i] = selected_elements[i].footprint.name;
}
sort(element_count, indexes, values, footprints);
// Header
printf("Comment,Designator,Footprint,LCSC Part #\n");
string designators;
for (i = 0 ; i < element_count ; ++i) {
UL_ELEMENT E = selected_elements[indexes[i]];
string lcsc_part = get_lcsc_part(E);
if (strip(E.value) == "") {
// Elements with no values aren't aggregated
printf("%s,%s,%s,%s\n", "", replace_commas(E.name), replace_commas(E.footprint.name), lcsc_part);
designators = "";
} else if (i < element_count - 1
&& E.value == selected_elements[indexes[i + 1]].value
&& E.footprint.name == selected_elements[indexes[i + 1]].footprint.name) {
// Split elements that have different value or package
designators += " " + replace_commas(E.name);
} else {
designators += " " + replace_commas(E.name);
printf("%s,%s,%s,%s\n", replace_commas(E.value), strip(designators), replace_commas(E.footprint.name), lcsc_part);
designators = "";
}
}
status("JLCPCB ULP: BOM file written successfully");
}
// Write additional info to separate file
output(info_filename) {
status("JLCPCB ULP: Writing info file");
printf("JLC_ROT_COUNT=%d\n", jlc_rot_count);
printf("JLC_ROT_ELEMENTS=%s\n", jlc_rot_elements);
printf("DNP_COUNT=%d\n", dnp_count);
printf("DNP_ELEMENTS=%s\n", dnp_elements);
status("JLCPCB ULP: Info file written successfully");
}
status("JLCPCB ULP: All files generated successfully in: " + output_dir);
// No user prompt, just log success
} else {
status("JLCPCB ULP: ERROR - No board found. This ULP must be run from a Board.");
exit(1);
}