name: "KiBot PCB Generation" # Controls when the action will run on: push: paths: - '**/*.kicad_sch' - '**/*.kicad_pcb' - '**/*.kicad_pro' - 'kibot.yaml' - '.gitea/workflows/*.yml' pull_request: paths: - '**/*.kicad_sch' - '**/*.kicad_pcb' - '**/*.kicad_pro' - 'kibot.yaml' - '.gitea/workflows/*.yml' repository_dispatch: types: [run_qs] workflow_dispatch: inputs: board_layers: description: 'Number of PCB layers' required: false default: '4' type: choice options: - '2' - '4' - '6' diff_old: description: 'Old revision for diff (e.g., HEAD~1, commit hash, tag)' required: false default: 'HEAD~1' type: string diff_new: description: 'New revision for diff (e.g., HEAD, commit hash, tag)' required: false default: 'HEAD' type: string enable_interactive_diff: description: 'Generate interactive HTML diff' required: false default: false type: boolean # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: generate: runs-on: kicad-kibot-runner steps: - uses: actions/checkout@v3 with: # Fetch full history for diff generation fetch-depth: '0' - name: Find KiCad project id: find_project run: | # Find all .kicad_pcb files in the repository PCB_FILES=$(find . -name "*.kicad_pcb" -type f | grep -v -E '/(backups?|backup|old|archive|deprecated)/' | head -5) if [ -z "$PCB_FILES" ]; then echo "Error: No KiCad PCB files found" exit 1 fi # Use the first PCB file found PCB_FILE=$(echo "$PCB_FILES" | head -1) PROJECT_DIR=$(dirname "$PCB_FILE") PROJECT_NAME=$(basename "$PCB_FILE" .kicad_pcb) echo "Found KiCad project: $PROJECT_NAME" echo "Project directory: $PROJECT_DIR" echo "PCB file: $PCB_FILE" # Find schematic file SCH_FILE=$(find "$PROJECT_DIR" -name "*.kicad_sch" -o -name "*.sch" | head -1) echo "pcb_file=$PCB_FILE" >> $GITHUB_OUTPUT echo "project_dir=$PROJECT_DIR" >> $GITHUB_OUTPUT echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT echo "sch_file=$SCH_FILE" >> $GITHUB_OUTPUT # List all found projects for debugging echo "All PCB files found:" echo "$PCB_FILES" - name: Set layer count id: layers run: | # Use workflow input or default to 4 layers LAYERS="${{ github.event.inputs.board_layers }}" if [ -z "$LAYERS" ]; then LAYERS="4" fi echo "layers=$LAYERS" >> $GITHUB_OUTPUT echo "Using $LAYERS layer configuration" - name: Check for KiBot config id: config_check run: | # Check if kibot.yaml exists in repository root if [ -f "kibot.yaml" ]; then echo "found=true" >> $GITHUB_OUTPUT echo "Using kibot.yaml configuration from repository root" else echo "found=false" >> $GITHUB_OUTPUT echo "No kibot.yaml found in repository root - will use --quick-start mode" fi - name: Generate schematic diff id: generate_diff if: github.event_name == 'push' || github.event_name == 'pull_request' run: | PROJECT_DIR="${{ steps.find_project.outputs.project_dir }}" PROJECT_NAME="${{ steps.find_project.outputs.project_name }}" # Create DIFF output directory mkdir -p "${GITHUB_WORKSPACE}/Generated/DIFF" cd "$PROJECT_DIR" # Get the previous commit hash if [ "${{ github.event_name }}" = "pull_request" ]; then BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" else # For push events, compare with previous commit HEAD_SHA="${{ github.sha }}" BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "") fi if [ -z "$BASE_SHA" ]; then echo "No previous commit found for comparison" echo "has_diff=false" >> $GITHUB_OUTPUT exit 0 fi echo "Comparing commits: $BASE_SHA...$HEAD_SHA" # Find all schematic files SCH_FILES=$(find . -name "*.kicad_sch" -o -name "*.sch" | head -10) if [ -z "$SCH_FILES" ]; then echo "No schematic files found" echo "has_diff=false" >> $GITHUB_OUTPUT exit 0 fi # Generate diff for each schematic DIFF_GENERATED=false for SCH_FILE in $SCH_FILES; do SCH_NAME=$(basename "$SCH_FILE" | sed 's/\.[^.]*$//') # Check if file changed between commits if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -q "$SCH_FILE"; then echo "Generating diff for $SCH_FILE" # Create temp directory for processing TEMP_DIR=$(mktemp -d) # Export old version git show "$BASE_SHA:$SCH_FILE" > "$TEMP_DIR/${SCH_NAME}_old.kicad_sch" 2>/dev/null || continue # Copy current version cp "$SCH_FILE" "$TEMP_DIR/${SCH_NAME}_new.kicad_sch" # Try to use kidiff if available (better visual diff tool for KiCad) if command -v kidiff &> /dev/null; then kidiff -o "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.pdf" \ "$TEMP_DIR/${SCH_NAME}_old.kicad_sch" \ "$TEMP_DIR/${SCH_NAME}_new.kicad_sch" || true fi # Also generate text diff git diff "$BASE_SHA" "$HEAD_SHA" -- "$SCH_FILE" > "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_changes.txt" # Generate a simple HTML diff for easy viewing cat > "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.html" << EOF Schematic Diff: ${SCH_NAME}

Schematic Changes: ${SCH_NAME}

Commit: ${BASE_SHA:0:8} → ${HEAD_SHA:0:8}
        EOF
            
            # Add the diff content with HTML escaping
            git diff "$BASE_SHA" "$HEAD_SHA" -- "$SCH_FILE" | \
              sed 's/&/\&/g; s//\>/g' | \
              sed 's/^+[^+].*/&<\/span>/g' | \
              sed 's/^-[^-].*/&<\/span>/g' >> "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.html"
            
            echo "
" >> "${GITHUB_WORKSPACE}/Generated/DIFF/${SCH_NAME}_diff.html" rm -rf "$TEMP_DIR" DIFF_GENERATED=true fi done # Generate PCB diff if PCB file changed PCB_FILE="${{ steps.find_project.outputs.pcb_file }}" if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -q "$PCB_FILE"; then echo "Generating PCB diff" PCB_NAME=$(basename "$PCB_FILE" | sed 's/\.[^.]*$//') # Generate text diff for PCB git diff "$BASE_SHA" "$HEAD_SHA" -- "$PCB_FILE" > "${GITHUB_WORKSPACE}/Generated/DIFF/${PCB_NAME}_pcb_changes.txt" # Summary of changes git diff --stat "$BASE_SHA" "$HEAD_SHA" -- "$PCB_FILE" > "${GITHUB_WORKSPACE}/Generated/DIFF/${PCB_NAME}_pcb_summary.txt" DIFF_GENERATED=true fi # Create a summary file cat > "${GITHUB_WORKSPACE}/Generated/DIFF/README.md" << EOF # KiCad Project Diff Report **Base Commit:** ${BASE_SHA:0:8} **Head Commit:** ${HEAD_SHA:0:8} **Date:** $(date) ## Changed Files: $(git diff --name-status "$BASE_SHA" "$HEAD_SHA" -- "*.kicad_sch" "*.sch" "*.kicad_pcb" "*.pcb") ## Summary: $(git diff --stat "$BASE_SHA" "$HEAD_SHA" -- "*.kicad_sch" "*.sch" "*.kicad_pcb" "*.pcb") ## Commit Messages: $(git log --oneline "$BASE_SHA".."$HEAD_SHA") EOF if [ "$DIFF_GENERATED" = true ]; then echo "has_diff=true" >> $GITHUB_OUTPUT else echo "No schematic or PCB changes detected" echo "has_diff=false" >> $GITHUB_OUTPUT fi - name: Run ERC if: steps.config_check.outputs.found == 'true' run: | cd "${{ steps.find_project.outputs.project_dir }}" # Clean up any existing output directories rm -rf Fabrication Generated Output if [ -n "${{ steps.find_project.outputs.sch_file }}" ]; then echo "Running ERC on schematic files..." # Use absolute path to config file in repo root # Skip ERC step in preflight to avoid stopping on errors kibot -c "${GITHUB_WORKSPACE}/kibot.yaml" -s run_erc,run_drc -v || true else echo "No schematic files found, skipping ERC" fi continue-on-error: true - name: Run DRC if: steps.config_check.outputs.found == 'true' run: | cd "${{ steps.find_project.outputs.project_dir }}" echo "Running DRC on PCB..." # Use absolute path to config file in repo root # Skip both ERC and DRC in preflight to avoid stopping on errors kibot -c "${GITHUB_WORKSPACE}/kibot.yaml" -s run_erc,run_drc -v || true continue-on-error: true - name: Generate outputs with custom config if: steps.config_check.outputs.found == 'true' run: | cd "${{ steps.find_project.outputs.project_dir }}" echo "Generating outputs for ${{ steps.layers.outputs.layers }} layer board" echo "Project: ${{ steps.find_project.outputs.project_name }}" echo "Using config: ${GITHUB_WORKSPACE}/kibot.yaml" # First, let's check what's in the current directory echo "Current directory contents:" ls -la # Check if Fabrication exists and what it is if [ -e "Fabrication" ]; then echo "Fabrication exists as:" file Fabrication echo "Removing it..." rm -rf Fabrication fi # Create a temporary modified config without the global output setting cp "${GITHUB_WORKSPACE}/kibot.yaml" /tmp/kibot_temp.yaml # Remove or comment out the global output line if it exists sed -i 's/^ output:.*$/ # output: commented out for workflow/' /tmp/kibot_temp.yaml # Update diff revisions if specified if [ -n "${{ github.event.inputs.diff_old }}" ]; then sed -i "s/old: 'HEAD~1'/old: '${{ github.event.inputs.diff_old }}'/" /tmp/kibot_temp.yaml fi if [ -n "${{ github.event.inputs.diff_new }}" ]; then sed -i "s/new: 'HEAD'/new: '${{ github.event.inputs.diff_new }}'/" /tmp/kibot_temp.yaml fi # Use a timestamp-based directory to ensure uniqueness OUTPUT_DIR="kibot_output_$(date +%s)" # Determine which outputs to run EXTRA_OUTPUTS="" if [ "${{ github.event.inputs.enable_interactive_diff }}" = "true" ]; then EXTRA_OUTPUTS="interactive_diff svg_diff" fi # Run KiBot with the modified configuration # Skip ERC/DRC preflight checks to avoid stopping on errors echo "Running KiBot with output directory: $OUTPUT_DIR" # Run all outputs (KiBot will run all by default when no specific outputs are listed) # Only add extra outputs if interactive diff is enabled if [ "${{ github.event.inputs.enable_interactive_diff }}" = "true" ]; then # Run with interactive diff outputs explicitly enabled kibot -c /tmp/kibot_temp.yaml -s run_erc,run_drc -d "$OUTPUT_DIR" -v interactive_diff svg_diff else # Run all default outputs (everything with run_by_default: true) kibot -c /tmp/kibot_temp.yaml -s run_erc,run_drc -d "$OUTPUT_DIR" -v fi # Move outputs to root Generated folder mkdir -p "${GITHUB_WORKSPACE}/Generated" if [ -d "$OUTPUT_DIR" ]; then echo "Copying outputs from $OUTPUT_DIR to Generated/" cp -r "$OUTPUT_DIR"/* "${GITHUB_WORKSPACE}/Generated/" rm -rf "$OUTPUT_DIR" elif [ -d "Fabrication" ]; then echo "KiBot used default Fabrication directory, copying from there" cp -r Fabrication/* "${GITHUB_WORKSPACE}/Generated/" rm -rf Fabrication else echo "Warning: No output directory was created" fi - name: Check downloaded datasheets if: steps.config_check.outputs.found == 'true' continue-on-error: true run: | echo "Checking for downloaded datasheets..." # KiBot should download datasheets to the configured directory # Check if datasheets were downloaded if [ -d "${GITHUB_WORKSPACE}/Generated/Datasheets" ]; then echo "Datasheets folder found in Generated/" # Create README cat > "${GITHUB_WORKSPACE}/Generated/Datasheets/README.md" << EOF # Component Datasheets This folder contains downloaded datasheets for all components in the project. Organized by component type: - **ICs/** - Integrated circuits (U references) - **Capacitors/** - Capacitors (C references) - **Resistors/** - Resistors (R references) - **Transistors/** - Transistors (Q references) - **Diodes/** - Diodes (D references) - **Connectors/** - Connectors (J, P references) - **Inductors/** - Inductors (L references) - And more... Files are named: REFERENCE_VALUE.pdf (e.g., U1_LM7805.pdf) Generated on: $(date) Project: ${{ steps.find_project.outputs.project_name }} EOF echo "Datasheet folder structure:" find "${GITHUB_WORKSPACE}/Generated/Datasheets" -type f -name "*.pdf" | head -20 # Count PDFs PDF_COUNT=$(find "${GITHUB_WORKSPACE}/Generated/Datasheets" -type f -name "*.pdf" | wc -l) echo "Total PDFs downloaded: $PDF_COUNT" else echo "No Datasheets folder found - KiBot may not have downloaded any datasheets" echo "Check that components have valid Datasheet URLs in the schematic" fi - name: Quick Start fallback if: steps.config_check.outputs.found == 'false' run: | cd "${{ steps.find_project.outputs.project_dir }}" echo "Running KiBot in quick-start mode" kibot --quick-start # Move outputs to root Generated folder mkdir -p "${GITHUB_WORKSPACE}/Generated" if [ -d "Generated" ]; then cp -r Generated/* "${GITHUB_WORKSPACE}/Generated/" fi - name: Add board information to outputs run: | echo "Board configuration: ${{ steps.layers.outputs.layers }} layers" > Generated/board_info.txt echo "Project: ${{ steps.find_project.outputs.project_name }}" >> Generated/board_info.txt echo "Project path: ${{ steps.find_project.outputs.project_dir }}" >> Generated/board_info.txt echo "Generated on: $(date)" >> Generated/board_info.txt echo "Commit: ${{ github.sha }}" >> Generated/board_info.txt - name: Fix file permissions run: | chmod -R a+rw Generated || true - name: Retrieve results - All outputs uses: actions/upload-artifact@v3 with: name: Automatic_outputs path: Generated - name: Retrieve results - Gerbers if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: Gerbers-${{ steps.layers.outputs.layers }}layer path: Generated/Gerbers - name: Retrieve results - Assembly if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: Assembly-${{ steps.layers.outputs.layers }}layer path: Generated/Assembly - name: Retrieve results - BOM if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: BOM-${{ steps.layers.outputs.layers }}layer path: Generated/BoM - name: Retrieve results - Datasheets if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: Datasheets path: Generated/Datasheets - name: Retrieve results - 3D Model if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: 3D-Model path: Generated/3D - name: Retrieve results - Fabrication Package if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: Fabrication-Package-${{ steps.layers.outputs.layers }}layer path: Generated/*.zip - name: Retrieve results - JLCPCB Package if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: JLCPCB-${{ steps.layers.outputs.layers }}layer path: Generated/*_JLCPCB_compress.zip - name: Retrieve results - Interactive Viewers if: steps.config_check.outputs.found == 'true' uses: actions/upload-artifact@v3 with: name: Interactive-Viewers path: Generated/Interactive - name: Retrieve results - Diff Report if: steps.generate_diff.outputs.has_diff == 'true' uses: actions/upload-artifact@v3 with: name: Schematic-PCB-Diff path: Generated/DIFF # Deploy to documentation branch deploy: runs-on: kicad-kibot-runner needs: generate steps: - uses: actions/checkout@v4 - name: Retrieve results uses: actions/download-artifact@v3 with: name: Automatic_outputs path: Generated - name: Disable Jekyll # Jekyll will filter the KiRi folders run: | touch Generated/.nojekyll - name: Push to docu branch env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | # Set up git identity git config --global user.email "${{ gitea.actor }}@noreply.localhost" git config --global user.name "${{ gitea.actor }}" # Get the repository URL and add authentication REPO_URL=$(git config --get remote.origin.url) AUTHED_URL=${REPO_URL/https:\/\//https:\/\/${GITEA_TOKEN}@} # Clone the docu branch into a separate folder git clone --depth 1 --branch docu "$AUTHED_URL" docu-branch || \ git clone --depth 1 "$AUTHED_URL" docu-branch cd docu-branch git checkout -B docu # Clean old contents and copy new files rm -rf ./* cp -r ../Generated/* ./ # Commit and push if there are changes git add . if ! git diff --cached --quiet; then git commit -m "chore: update deployed documentation" git push --force "$AUTHED_URL" docu else echo "No changes to deploy" fi