#!/bin/bash # KiCad Symbol Generator for JLCPCB Basic Parts # Generates KiCad symbol libraries with Fabrication Toolkit compatible properties # Compatible with bennymeg/JLC-Plugin-for-KiCad set -euo pipefail # Default values CSV_PATH="" OUTPUT_PATH="" CREATE_SYMBOLS=true GENERATE_REPORT=true DRY_RUN=false # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' WHITE='\033[1;37m' NC='\033[0m' # No Color # Function to print colored output print_color() { local color=$1 local message=$2 echo -e "${color}${message}${NC}" } # Function to show usage usage() { cat << EOF Usage: $0 -c CSV_PATH -o OUTPUT_PATH [options] KiCad Symbol Generator for JLCPCB Basic Parts Required arguments: -c, --csv-path PATH Path to JLCPCB_Basic_Parts.csv file -o, --output-path PATH Output directory for generated files Optional arguments: -s, --skip-symbols Skip symbol generation -r, --skip-report Skip report generation -d, --dry-run Show what would be generated without creating files -h, --help Show this help message Examples: $0 -c ./docs/JLCPCB_Basic_Parts.csv -o . $0 -c ./docs/JLCPCB_Basic_Parts.csv -o . --dry-run EOF } # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -c|--csv-path) CSV_PATH="$2" shift 2 ;; -o|--output-path) OUTPUT_PATH="$2" shift 2 ;; -s|--skip-symbols) CREATE_SYMBOLS=false shift ;; -r|--skip-report) GENERATE_REPORT=false shift ;; -d|--dry-run) DRY_RUN=true shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown option $1" usage exit 1 ;; esac done # Validate required arguments if [[ -z "$CSV_PATH" || -z "$OUTPUT_PATH" ]]; then echo "Error: Both CSV path and output path are required" usage exit 1 fi # Validate input file if [[ ! -f "$CSV_PATH" ]]; then print_color $RED "Error: CSV file not found: $CSV_PATH" exit 1 fi print_color $GREEN "KiCad JLCPCB Basic Parts Symbol Generator" print_color $GREEN "=========================================" print_color $YELLOW "Loading JLCPCB Basic Parts database: $CSV_PATH" # Check if we have required tools if ! command -v awk &> /dev/null; then print_color $RED "Error: awk is required but not installed" exit 1 fi # Read and parse CSV file if [[ ! -r "$CSV_PATH" ]]; then print_color $RED "Error: Cannot read CSV file: $CSV_PATH" exit 1 fi # Count components by type (skip comments and empty lines) RESISTOR_COUNT=$(awk -F',' 'NR>1 && $1=="R" {count++} END {print count+0}' "$CSV_PATH") CAPACITOR_COUNT=$(awk -F',' 'NR>1 && $1=="C" {count++} END {print count+0}' "$CSV_PATH") INDUCTOR_COUNT=$(awk -F',' 'NR>1 && $1=="L" {count++} END {print count+0}' "$CSV_PATH") IC_COUNT=$(awk -F',' 'NR>1 && $1=="IC" {count++} END {print count+0}' "$CSV_PATH") LED_COUNT=$(awk -F',' 'NR>1 && $1=="LED" {count++} END {print count+0}' "$CSV_PATH") DIODE_COUNT=$(awk -F',' 'NR>1 && $1=="D" {count++} END {print count+0}' "$CSV_PATH") CRYSTAL_COUNT=$(awk -F',' 'NR>1 && $1=="XTAL" {count++} END {print count+0}' "$CSV_PATH") TOTAL_COUNT=$(awk -F',' 'NR>1 && $1!~/^#/ && $1!="" {count++} END {print count+0}' "$CSV_PATH") print_color $GREEN "Successfully loaded $TOTAL_COUNT parts" print_color $CYAN "Component distribution:" print_color $WHITE " Resistors: $RESISTOR_COUNT" print_color $WHITE " Capacitors: $CAPACITOR_COUNT" print_color $WHITE " Inductors: $INDUCTOR_COUNT" print_color $WHITE " ICs: $IC_COUNT" print_color $WHITE " LEDs: $LED_COUNT" print_color $WHITE " Diodes: $DIODE_COUNT" print_color $WHITE " Crystals: $CRYSTAL_COUNT" # Function to get KiCad footprint based on component type and package get_kicad_footprint() { local component=$1 local package=$2 case $component in "R") case $package in "0402") echo "Resistor_SMD:R_0402_1005Metric" ;; "0603") echo "Resistor_SMD:R_0603_1608Metric" ;; "0805") echo "Resistor_SMD:R_0805_2012Metric" ;; "1206") echo "Resistor_SMD:R_1206_3216Metric" ;; *) echo "Resistor_SMD:R_$package" ;; esac ;; "C") case $package in "0402") echo "Capacitor_SMD:C_0402_1005Metric" ;; "0603") echo "Capacitor_SMD:C_0603_1608Metric" ;; "0805") echo "Capacitor_SMD:C_0805_2012Metric" ;; "1206") echo "Capacitor_SMD:C_1206_3216Metric" ;; *) echo "Capacitor_SMD:C_$package" ;; esac ;; "L") case $package in "0603") echo "Inductor_SMD:L_0603_1608Metric" ;; "0805") echo "Inductor_SMD:L_0805_2012Metric" ;; "1206") echo "Inductor_SMD:L_1206_3216Metric" ;; *) echo "Inductor_SMD:L_$package" ;; esac ;; "IC") case $package in "SOIC-8") echo "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm" ;; "SOIC-14") echo "Package_SO:SOIC-14_3.9x8.7mm_P1.27mm" ;; "SOIC-16") echo "Package_SO:SOIC-16_3.9x9.9mm_P1.27mm" ;; "SOIC-18") echo "Package_SO:SOIC-18W_7.5x11.6mm_P1.27mm" ;; "LQFP-48") echo "Package_QFP:LQFP-48_7x7mm_P0.5mm" ;; "TSSOP-20") echo "Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm" ;; "SOT-223") echo "Package_TO_SOT_SMD:SOT-223-3_TabPin2" ;; "SSOP-28") echo "Package_SO:SSOP-28_5.3x10.2mm_P0.65mm" ;; "MODULE") echo "RF_Module:ESP32-WROOM-32" ;; *) echo "Package_SO:$package" ;; esac ;; "LED") case $package in "0603") echo "LED_SMD:LED_0603_1608Metric" ;; "0805") echo "LED_SMD:LED_0805_2012Metric" ;; *) echo "LED_SMD:LED_$package" ;; esac ;; "D") case $package in "SOD-123") echo "Diode_SMD:D_SOD-123" ;; "SOD-123FL") echo "Diode_SMD:D_SOD-123F" ;; *) echo "Diode_SMD:D_$package" ;; esac ;; "XTAL") case $package in "HC-49S") echo "Crystal:Crystal_HC49-SD_SMD" ;; "3215") echo "Crystal:Crystal_SMD_3215-2Pin_3.2x1.5mm" ;; *) echo "Crystal:Crystal_$package" ;; esac ;; *) echo "Unknown:$package" ;; esac } # Function to generate symbol graphics based on component type get_symbol_graphics() { local component=$1 local symbol_name=$2 case $component in "R") cat << EOF (symbol "${symbol_name}_0_1" (rectangle (start -1.016 -2.54) (end 1.016 2.54) (stroke (width 0.254) (type default)) (fill (type none)))) (symbol "${symbol_name}_1_1" (pin passive line (at 0 3.81 270) (length 1.27) (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin passive line (at 0 -3.81 90) (length 1.27) (name "~" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))) EOF ;; "C") cat << EOF (symbol "${symbol_name}_0_1" (polyline (pts (xy -2.032 -0.762) (xy 2.032 -0.762)) (stroke (width 0.508) (type default)) (fill (type none))) (polyline (pts (xy -2.032 0.762) (xy 2.032 0.762)) (stroke (width 0.508) (type default)) (fill (type none)))) (symbol "${symbol_name}_1_1" (pin passive line (at 0 2.54 270) (length 1.778) (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin passive line (at 0 -2.54 90) (length 1.778) (name "~" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))) EOF ;; "L") cat << EOF (symbol "${symbol_name}_0_1" (arc (start 0 -2.54) (mid 0.635 -1.905) (end 0 -1.27) (stroke (width 0) (type default)) (fill (type none))) (arc (start 0 -1.27) (mid 0.635 -0.635) (end 0 0) (stroke (width 0) (type default)) (fill (type none))) (arc (start 0 0) (mid 0.635 0.635) (end 0 1.27) (stroke (width 0) (type default)) (fill (type none))) (arc (start 0 1.27) (mid 0.635 1.905) (end 0 2.54) (stroke (width 0) (type default)) (fill (type none)))) (symbol "${symbol_name}_1_1" (pin passive line (at 0 3.81 270) (length 1.27) (name "1" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin passive line (at 0 -3.81 90) (length 1.27) (name "2" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))) EOF ;; "LED") cat << EOF (symbol "${symbol_name}_0_1" (polyline (pts (xy -1.27 -1.27) (xy -1.27 1.27)) (stroke (width 0.254) (type default)) (fill (type none))) (polyline (pts (xy -1.27 0) (xy 1.27 0)) (stroke (width 0) (type default)) (fill (type none))) (polyline (pts (xy 1.27 -1.27) (xy 1.27 1.27) (xy -1.27 0) (xy 1.27 -1.27)) (stroke (width 0.254) (type default)) (fill (type none)))) (symbol "${symbol_name}_1_1" (pin passive line (at -3.81 0 0) (length 2.54) (name "K" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin passive line (at 3.81 0 180) (length 2.54) (name "A" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))) EOF ;; *) # Generic two-pin component for ICs, diodes, crystals cat << EOF (symbol "${symbol_name}_0_1" (rectangle (start -7.62 5.08) (end 7.62 -5.08) (stroke (width 0.254) (type default)) (fill (type background)))) (symbol "${symbol_name}_1_1" (pin input line (at -10.16 0 0) (length 2.54) (name "VCC" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin input line (at 10.16 0 180) (length 2.54) (name "GND" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))) EOF ;; esac } # Function to generate a symbol template generate_symbol() { local component=$1 local value=$2 local package=$3 local lcsc=$4 local manufacturer=$5 local mfg_part=$6 local description=$7 local rotation=$8 local ref_prefix=$9 # Clean up special characters for KiCad compatibility local symbol_name=$(echo "${value}_${package}_${lcsc}" | sed 's/ยต/u/g') local footprint=$(get_kicad_footprint "$component" "$package") local datasheet="https://lcsc.com/product-detail/${lcsc}.html" cat << EOF (symbol "$symbol_name" (in_bom yes) (on_board yes) (property "Reference" "$ref_prefix" (at 0 2.54 0) (effects (font (size 1.27 1.27)))) (property "Value" "$value" (at 0 -2.54 0) (effects (font (size 1.27 1.27)))) (property "Footprint" "$footprint" (at 0 -5.08 0) (effects (font (size 1.27 1.27)) hide)) (property "Datasheet" "$datasheet" (at 0 -7.62 0) (effects (font (size 1.27 1.27)) hide)) (property "Description" "$description" (at 0 -10.16 0) (effects (font (size 1.27 1.27)) hide)) (property "LCSC Part #" "$lcsc" (at 0 -12.70 0) (effects (font (size 1.27 1.27)) hide)) (property "Manufacturer" "$manufacturer" (at 0 -15.24 0) (effects (font (size 1.27 1.27)) hide)) (property "MFG Part #" "$mfg_part" (at 0 -17.78 0) (effects (font (size 1.27 1.27)) hide)) (property "Package" "$package" (at 0 -20.32 0) (effects (font (size 1.27 1.27)) hide)) (property "FT Rotation Offset" "$rotation" (at 0 -22.86 0) (effects (font (size 1.27 1.27)) hide)) $(get_symbol_graphics "$component" "$symbol_name") ) EOF } # Generate symbol libraries if [[ "$CREATE_SYMBOLS" == true ]]; then print_color $YELLOW "Generating KiCad symbol libraries..." if [[ "$DRY_RUN" == false ]]; then # Create output directory mkdir -p "$OUTPUT_PATH/symbols" fi # Generate Standard Passives library if [[ $((RESISTOR_COUNT + CAPACITOR_COUNT + INDUCTOR_COUNT)) -gt 0 ]]; then print_color $CYAN " Creating Standard_Passives.kicad_sym..." local passives_file="$OUTPUT_PATH/symbols/Standard_Passives.kicad_sym" if [[ "$DRY_RUN" == false ]]; then # Write header cat > "$passives_file" << EOF (kicad_symbol_lib (version 20211014) (generator "JLCPCB-Generator") (symbol_lib_info (name "Standard_Passives") (description "JLCPCB Basic Parts - Passives with Fabrication Toolkit properties")) EOF # Process resistors, capacitors, and inductors awk -F',' ' NR>1 && ($1=="R" || $1=="C" || $1=="L") && $1!~/^#/ { # Escape quotes in description gsub(/"/, "\\\"", $7) ref = ($1=="R") ? "R" : ($1=="C") ? "C" : "L" print $1 "," $2 "," $3 "," $4 "," $5 "," $6 "," $7 "," $8 "," ref }' "$CSV_PATH" | while IFS=',' read -r component value package lcsc manufacturer mfg_part description rotation ref_prefix; do generate_symbol "$component" "$value" "$package" "$lcsc" "$manufacturer" "$mfg_part" "$description" "$rotation" "$ref_prefix" >> "$passives_file" done # Write footer echo ")" >> "$passives_file" print_color $GREEN " Created: $passives_file" else print_color $BLUE " Would create: $passives_file" fi fi # Generate Standard ICs library if [[ $IC_COUNT -gt 0 ]]; then print_color $CYAN " Creating Standard_ICs.kicad_sym..." local ics_file="$OUTPUT_PATH/symbols/Standard_ICs.kicad_sym" if [[ "$DRY_RUN" == false ]]; then # Write header cat > "$ics_file" << EOF (kicad_symbol_lib (version 20211014) (generator "JLCPCB-Generator") (symbol_lib_info (name "Standard_ICs") (description "JLCPCB Basic Parts - ICs with Fabrication Toolkit properties")) EOF # Process ICs awk -F',' ' NR>1 && $1=="IC" && $1!~/^#/ { # Escape quotes in description gsub(/"/, "\\\"", $7) print $1 "," $2 "," $3 "," $4 "," $5 "," $6 "," $7 "," $8 ",U" }' "$CSV_PATH" | while IFS=',' read -r component value package lcsc manufacturer mfg_part description rotation ref_prefix; do generate_symbol "$component" "$value" "$package" "$lcsc" "$manufacturer" "$mfg_part" "$description" "$rotation" "$ref_prefix" >> "$ics_file" done # Write footer echo ")" >> "$ics_file" print_color $GREEN " Created: $ics_file" else print_color $BLUE " Would create: $ics_file" fi fi # Generate Standard Components library (LEDs, Diodes, Crystals) if [[ $((LED_COUNT + DIODE_COUNT + CRYSTAL_COUNT)) -gt 0 ]]; then print_color $CYAN " Creating Standard_Components.kicad_sym..." local components_file="$OUTPUT_PATH/symbols/Standard_Components.kicad_sym" if [[ "$DRY_RUN" == false ]]; then # Write header cat > "$components_file" << EOF (kicad_symbol_lib (version 20211014) (generator "JLCPCB-Generator") (symbol_lib_info (name "Standard_Components") (description "JLCPCB Basic Parts - LEDs, Diodes, Crystals with Fabrication Toolkit properties")) EOF # Process LEDs, Diodes, Crystals awk -F',' ' NR>1 && ($1=="LED" || $1=="D" || $1=="XTAL") && $1!~/^#/ { # Escape quotes in description gsub(/"/, "\\\"", $7) ref = ($1=="LED" || $1=="D") ? "D" : "Y" print $1 "," $2 "," $3 "," $4 "," $5 "," $6 "," $7 "," $8 "," ref }' "$CSV_PATH" | while IFS=',' read -r component value package lcsc manufacturer mfg_part description rotation ref_prefix; do generate_symbol "$component" "$value" "$package" "$lcsc" "$manufacturer" "$mfg_part" "$description" "$rotation" "$ref_prefix" >> "$components_file" done # Write footer echo ")" >> "$components_file" print_color $GREEN " Created: $components_file" else print_color $BLUE " Would create: $components_file" fi fi fi # Generate BOM template if [[ "$GENERATE_REPORT" == true ]]; then print_color $YELLOW "Generating JLCPCB BOM template..." local bom_file="$OUTPUT_PATH/docs/JLCPCB_BOM_Template.csv" if [[ "$DRY_RUN" == false ]]; then mkdir -p "$OUTPUT_PATH/docs" # Write BOM header cat > "$bom_file" << EOF # JLCPCB Assembly BOM Template # Generated from JLCPCB Basic Parts Database # Compatible with JLC Plugin for KiCad (bennymeg) Comment,Designator,Footprint,LCSC Part #,Manufacturer,MFG Part # EOF # Process all parts for BOM awk -F',' ' NR>1 && $1!~/^#/ && $1!="" { footprint_full = "" component = $1 package = $3 # Get footprint name (remove library prefix) if (component == "R") { if (package == "0603") footprint_full = "R_0603_1608Metric" else if (package == "0805") footprint_full = "R_0805_2012Metric" else footprint_full = "R_" package } else if (component == "C") { if (package == "0603") footprint_full = "C_0603_1608Metric" else if (package == "0805") footprint_full = "C_0805_2012Metric" else footprint_full = "C_" package } else if (component == "L") { if (package == "0603") footprint_full = "L_0603_1608Metric" else if (package == "0805") footprint_full = "L_0805_2012Metric" else footprint_full = "L_" package } else if (component == "LED") { if (package == "0603") footprint_full = "LED_0603_1608Metric" else footprint_full = "LED_" package } else { footprint_full = package } print $2 ",REF**," footprint_full "," $4 "," $5 "," $6 }' "$CSV_PATH" >> "$bom_file" print_color $GREEN " Created: $bom_file" else print_color $BLUE " Would create: $bom_file" fi fi # Generate statistics print_color $GREEN "\n$(printf '=%.0s' {1..50})" print_color $GREEN "GENERATION SUMMARY" print_color $GREEN "$(printf '=%.0s' {1..50})" print_color $CYAN "Components by package:" awk -F',' 'NR>1 && $1!~/^#/ && $1!="" {packages[$3]++} END {for (p in packages) printf " %-10s: %d parts\n", p, packages[p]}' "$CSV_PATH" | sort -k3 -nr print_color $CYAN "\nComponents by manufacturer:" awk -F',' 'NR>1 && $1!~/^#/ && $1!="" {mfgs[$5]++} END {for (m in mfgs) printf " %-20s: %d parts\n", m, mfgs[m]}' "$CSV_PATH" | sort -k3 -nr # Calculate total cost TOTAL_COST=$(awk -F',' ' NR>1 && $1!~/^#/ && $1!="" && $10 ~ /^[0-9]+\.?[0-9]*$/ { total += $10 } END { printf "%.2f", total }' "$CSV_PATH") print_color $CYAN "\nCost analysis:" print_color $WHITE " Total cost for complete basic parts kit (1k each): \$$TOTAL_COST" if [[ "$DRY_RUN" == true ]]; then print_color $YELLOW "\n[DRY RUN] No files were created" else print_color $GREEN "\nGeneration completed successfully!" fi