my-kicad-lib/scripts/generate_jlcpcb_symbols.sh

554 lines
20 KiB
Bash

#!/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