#!/usr/bin/env bash # export_for_msla.sh — KiCad Gerber export + pm4n generation for Anycubic Photon Mono 4 # # Usage: # ./export_for_msla.sh [OPTIONS] # # Options: # --layers LAYER,LAYER,... KiCad layer names to export (default: F.Cu) # --invert LAYER,LAYER,... Layers to invert (comma-separated, e.g. F.Cu,B.Mask) # --mirror LAYER,LAYER,... Layers to mirror (comma-separated, e.g. F.Cu,F.Mask) # --exposure SECONDS Exposure time in seconds (default: 60) # --dummy FILE Dummy .pm4n template (default: Dummy.pm4n beside this script) # --out DIR Output directory (default: ./output) # --dpmm N Render resolution in dots/mm (default: native 58.824) # --pos X,Y Board position on LCD in mm (default: centred) # -h, --help Show this help # # Example: # ./export_for_msla.sh --invert F.Cu,B.Mask --mirror F.Cu,F.Mask panel/Flow_Controller_Panel.kicad_pcb # # Layer name → Gerber filename mapping (KiCad default): # F.Cu → -F_Cu.gbr # B.Cu → -B_Cu.gbr # F.Mask → -F_Mask.gbr # B.Mask → -B_Mask.gbr # F.SilkS → -F_Silkscreen.gbr # (etc.) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PYTHON="${PYTHON:-python3}" CONVERTER="$SCRIPT_DIR/gerber_to_pm4n.py" # ---- defaults ---- LAYERS="F.Cu" INVERT_LAYERS="" MIRROR_LAYERS="" EXPOSURE="60" DUMMY="$SCRIPT_DIR/Dummy.pm4n" OUT_DIR="./output" DPMM="" POS="" # ---- helpers ---- usage() { sed -n '/^# Usage/,/^[^#]/{ /^#/{ s/^# \{0,1\}//; p } }' "$0" exit 0 } contains() { # contains — comma-separated list membership local list="$1" item="$2" echo "$list" | tr ',' '\n' | grep -qx "$item" } layer_to_filename() { # KiCad layer name → Gerber filename stem local layer="$1" echo "$layer" | sed 's/\./_/g' } # ---- parse arguments ---- PCB_FILE="" while [[ $# -gt 0 ]]; do case "$1" in --layers) LAYERS="$2"; shift 2 ;; --invert) INVERT_LAYERS="$2"; shift 2 ;; --mirror) MIRROR_LAYERS="$2"; shift 2 ;; --exposure) EXPOSURE="$2"; shift 2 ;; --dummy) DUMMY="$2"; shift 2 ;; --out) OUT_DIR="$2"; shift 2 ;; --dpmm) DPMM="$2"; shift 2 ;; --pos) POS="$2"; shift 2 ;; -h|--help) usage ;; -*) echo "Unknown option: $1"; exit 1 ;; *) PCB_FILE="$1"; shift ;; esac done if [[ -z "$PCB_FILE" ]]; then echo "ERROR: no .kicad_pcb file specified" echo "Usage: $0 [OPTIONS] " exit 1 fi if [[ ! -f "$PCB_FILE" ]]; then echo "ERROR: file not found: $PCB_FILE" exit 1 fi if [[ ! -f "$DUMMY" ]]; then echo "ERROR: dummy .pm4n not found: $DUMMY" echo "Place Dummy.pm4n next to export.sh, or pass --dummy " exit 1 fi # ---- derive names ---- BOARD_NAME="$(basename "$PCB_FILE" .kicad_pcb)" GERBERS_DIR="$OUT_DIR/gerbers" PM4N_DIR="$OUT_DIR/pm4n" mkdir -p "$GERBERS_DIR" "$PM4N_DIR" # ---- Step 1: export Gerbers via kicad-cli ---- echo "=== Exporting Gerbers from KiCad ===" echo " Board: $PCB_FILE" echo " Layers: $LAYERS" echo " Output: $GERBERS_DIR" echo "" kicad-cli pcb export gerbers \ --output "$GERBERS_DIR" \ --layers "$LAYERS" \ --no-protel-ext \ --subtract-soldermask \ "$PCB_FILE" echo "" # ---- Step 2: convert each layer to .pm4n ---- echo "=== Converting Gerbers to .pm4n ===" IFS=',' read -ra LAYER_LIST <<< "$LAYERS" for LAYER in "${LAYER_LIST[@]}"; do LAYER_STEM="$(layer_to_filename "$LAYER")" GBR_FILE="$GERBERS_DIR/${BOARD_NAME}-${LAYER_STEM}.gbr" if [[ ! -f "$GBR_FILE" ]]; then echo " WARNING: expected Gerber not found: $GBR_FILE — skipping" continue fi OUT_PM4N="$PM4N_DIR/${BOARD_NAME}-${LAYER_STEM}.pm4n" # Build flags FLAGS=() if contains "$INVERT_LAYERS" "$LAYER"; then FLAGS+=(--invert); fi if contains "$MIRROR_LAYERS" "$LAYER"; then FLAGS+=(--mirror); fi [[ -n "$DPMM" ]] && FLAGS+=(--dpmm "$DPMM") [[ -n "$POS" ]] && FLAGS+=(--pos "$POS") echo " Layer: $LAYER" echo " Gerber: $GBR_FILE" echo " pm4n: $OUT_PM4N" echo " Flags: ${FLAGS[*]:-} exposure=${EXPOSURE}s" "$PYTHON" "$CONVERTER" \ "$DUMMY" \ "$GBR_FILE" \ --output "$OUT_PM4N" \ --exposure "$EXPOSURE" \ "${FLAGS[@]}" echo "" done echo "=== Done ===" echo "pm4n files in: $PM4N_DIR"