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.
← Commit history
Publish 1.0.0
9 files changed
+432
SKILL.mdadded+91@@ -0,0 +1,91 @@+---+name: fusion-jlcpcb-export+description: Export the full JLCPCB manufacturing package (gerber ZIP + BOM with LCSC part numbers + CPL pick-and-place) from an open Fusion 360 Electronics board, via the Adom Desktop Fusion bridge. Use on "export for jlcpcb", "jlc bom and cpl", "export jlcpcb files", "files to get a jlcpcb quote", "export the bom with part numbers", "jlcpcb assembly files".+---++# Fusion JLCPCB Export++Produce everything JLCPCB needs to quote and assemble a board, the **gerber ZIP**, the+**BOM** (with the **LCSC part number** per component), and the **CPL** (pick-and-place),+from a Fusion 360 Electronics design, all by asking the AI.++This skill ships its own EAGLE ULP and runs it through the Fusion bridge's generic+`fusion_electron_run` verb. It does not modify the bridge. It is the working example of+**extending the Fusion bridge independently** (no fork, no pull request): bring a ULP,+run it through `electron_run`.++++## Prerequisite++- The Fusion bridge is connected (`adom-desktop ping`) and the board is open in Fusion's+ **2D PCB Editor**. If it is on the 3D view or schematic, switch first:+ `adom-desktop fusion_show_2d_board '{}'`.+- This skill is installed, so its ULP is at `~/.claude/skills/fusion-jlcpcb-export/ulp/jlcpcb_smta_exporter.ulp`.++## The export, step by step++### 1. Gerbers + drill (the fab files)++```bash+adom-desktop fusion_export_gerbers '{"outputDir":"C:/tmp/jlc"}'+# -> <board>_gerbers.zip : copper layers, soldermask, silkscreen, paste, outline, Excellon drill.+```++The bridge auto-detects 2 vs 4 layers and runs the matching bundled JLCPCB CAM job.++### 2. BOM + CPL (the assembly files, with the LCSC part numbers)++Send this skill's ULP to the machine, then run it through `electron_run`, passing the+**output directory** so it writes where you want:++```bash+# a) copy the ULP to the user's machine (once per session is fine)+adom-desktop send_files '{"filePaths":["~/.claude/skills/fusion-jlcpcb-export/ulp/jlcpcb_smta_exporter.ulp"],"dest":"C:/tmp/jlc"}'++# b) run it. Pass ONLY the output directory, the ULP builds <board>_bom.csv / _cpl.csv / _info.txt there.+adom-desktop fusion_electron_run '{"command":"set confirm yes;RUN '\''C:/tmp/jlc/jlcpcb_smta_exporter.ulp'\'' '\''C:/tmp/jlc'\''"}'++# c) pull the results back+adom-desktop pull_file '{"filePaths":["C:/tmp/jlc/<board>_bom.csv","C:/tmp/jlc/<board>_cpl.csv"]}'+```++**The BOM** is JLCPCB-formatted, `Comment,Designator,Footprint,LCSC Part #`:++++**The CPL** is JLCPCB pick-and-place, `Designator,Mid X,Mid Y,Layer,Rotation`:++++### 3. The complete package++Zip the gerber ZIP + the BOM + the CPL. That is exactly what you upload to+**jlcpcb.com/smt-assembly** to get a quote. The AI can stop here, the user uploads the+zip; this skill produces the files, it does not place the order.++## Why the LCSC numbers are already there (no lookup)++The part number is **not** looked up after the fact. A good engineer embeds the+distributor part number on the component **in the library, at part-selection time**, as an+**attribute**. Open any component's Properties in the 2D board and you will see an+`Attributes` section with `LCSC` (e.g. `C2861705`), alongside `DESC` and `JLC_ROTATION`.+That attribute rides through the schematic and board, and the ULP reads it per part.++The same mechanism is general: a component can equally carry `MOUSER_PN`, `DIGIKEY_PN`,+`MPN`, or `MANUFACTURER` attributes. Set the attributes for whatever distributor you order+from, and the BOM carries them. Connectors / jumpers / through-hole parts that are not+SMT-assembled correctly come out with a blank LCSC.++## How this skill extends the bridge (the pattern to copy)++The Fusion bridge exposes **primitives**, not every workflow. `fusion_electron_run` runs any+EAGLE command, including `RUN <ulp>`. So anyone can add a capability **without touching the+bridge**: ship a ULP in your own skill, `send_files` it to the machine, and run it through+`electron_run`. That is what this skill does with `jlcpcb_smta_exporter.ulp` (OXullo+Intersecans' open-source JLCPCB generator, MIT). Copy this structure to contribute your own.++## Credits++`jlcpcb_smta_exporter.ulp` (c) OXullo Intersecans, MIT. Generates BOM/CPL for the JLCPCB+SMT/TH dual-side assembly service.
docs/shot.pngadded⋯ 1 unchanged line ⋯
install.shadded+9@@ -0,0 +1,9 @@+#!/bin/bash+set -e+PKG_DIR="$(cd "$(dirname "$0")" && pwd)"+DEST="$HOME/.claude/skills/fusion-jlcpcb-export"+mkdir -p "$DEST/ulp"+cp "$PKG_DIR/SKILL.md" "$DEST/SKILL.md"+cp -r "$PKG_DIR/screenshots" "$DEST/" 2>/dev/null || true+cp "$PKG_DIR/ulp/jlcpcb_smta_exporter.ulp" "$DEST/ulp/jlcpcb_smta_exporter.ulp"+echo "Installed fusion-jlcpcb-export skill + ULP."
package.jsonadded+31@@ -0,0 +1,31 @@+{+ "schema_version": 1,+ "slug": "fusion-jlcpcb-export",+ "type": "skill",+ "title": "Fusion JLCPCB Export",+ "brief": "Export the complete JLCPCB manufacturing package (gerbers + BOM with LCSC + CPL) from a Fusion board, by asking your AI.",+ "version": "1.0.0",+ "description": "Export everything JLCPCB needs to quote and assemble a board, gerber ZIP plus the BOM (with the LCSC part numbers) and the CPL pick-and-place, straight from a Fusion 360 Electronics design via the Adom Desktop Fusion bridge. Ships its own EAGLE ULP and runs it through the bridge's generic electron_run, a working example of extending the Fusion bridge independently (no fork, no pull request).",+ "org": "adom",+ "dependencies": {},+ "tags": ["fusion360", "jlcpcb", "lcsc", "bom", "cpl", "gerber", "manufacturing", "pcb"],+ "discovery_triggers": [+ "export for jlcpcb",+ "jlcpcb bom cpl",+ "jlc bom and cpl",+ "export jlcpcb files",+ "jlcpcb assembly files",+ "gerber bom cpl",+ "lcsc bom",+ "pcb manufacturing files",+ "files to get a jlcpcb quote",+ "jlcpcb smt assembly export",+ "export the bom with part numbers"+ ],+ "scripts": { "install": "./install.sh", "uninstall": "./uninstall.sh" },+ "hero": {+ "headline": "Fusion to JLCPCB, in one ask",+ "subhead": "Gerbers + BOM (with LCSC) + CPL, ready to quote",+ "screenshot": "docs/shot.png"+ }+}
screenshots/board.pngadded⋯ 1 unchanged line ⋯
screenshots/bom.pngadded⋯ 1 unchanged line ⋯
screenshots/cpl.pngadded⋯ 1 unchanged line ⋯
ulp/jlcpcb_smta_exporter.ulpadded+297@@ -0,0 +1,297 @@+/*++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);+}
uninstall.shadded+4@@ -0,0 +1,4 @@+#!/bin/bash+set -e+rm -rf "$HOME/.claude/skills/fusion-jlcpcb-export"+echo "Removed fusion-jlcpcb-export skill."