diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a36b744..5bf839b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,60 +8,100 @@ workflow: stages: - run_erc - run_drc - - gen_mech - - mech_out - gen_fab - - fab_out image: - name: ghcr.io/inti-cmnb/kicad6_auto:1.3.0 + name: ghcr.io/inti-cmnb/kicad7_auto:latest .main_rules: rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"' - if: $CI_COMMIT_BRANCH == "main" + - if: $GITLAB_CI == 'false' .dev_rules: rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "dev"' - if: $CI_COMMIT_BRANCH == "dev" - + +.commands: + get_files: + - | + if find **/*$SEARCH ; then + FILES=$(find **/*$SEARCH) + else + FILES='' + fi + get_dirs: + - | + if find **/*$SEARCH ; then + FILES=$(find **/*$SEARCH) + DIRS=$(dirname $FILES) + else + FILES='' + DIRS='' + fi + - '' + sch_from_pro: + - 'SCHEMS=$(for f in $FILES ; do echo "${f%.*}.kicad_sch"; done)' + - 'SCHEMS=$(for f in $SCHEMS ; do echo "${f##**/}"; done)' + get_names: + - 'NAMES=$(for f in $FILES ; do echo "${**/f%$SEARCH}"; done)' + dir_arr: + - !reference [.commands, get_dirs] + - END=$(wc -w <<< $DIRS) + - dir_arr=($DIRS) + kibot: + - 'SEARCH=".kicad_pro"' + - !reference [.commands, dir_arr] + - !reference [.commands, sch_from_pro] + - sch_arr=($SCHEMS) + - | + for i in $(seq 1 $END) + do + echo ${dir_arr[i-1]} + cd $CI_PROJECT_DIR/${dir_arr[i-1]} + kibot -e ${sch_arr[i-1]} -c $CI_PROJECT_DIR/default.kibot.yaml -d $CI_PROJECT_DIR/Fabrication/${dir_arr[i-1]} -s $SUFFIX + done + + panel: + - 'SEARCH="boards.json"' + - !reference [.commands, get_dirs] + - | + for d in $DIRS + do + echo $d + cd $CI_PROJECT_DIR/$d + JSON=$(find panel_*.json ) + NAME=$(echo "${JSON%.json}") + mkdir panel + kikit -p $JSON boards.json panel/$NAME.kicad_pcb + touch panel/$NAME.kicad_sch + done + + neo: + - 'SEARCH=".kicad_pro"' + - !reference [.commands, get_dirs] + - | + for d in $DIRS + do + python3 scripts/neo.py $d + done + .template: rules: - !reference [.main_rules, rules] - variables: - COMMAND: kibot -e "$SCHEM" -c "$CI_PROJECT_DIR/default.kibot.yaml" -d "$CI_PROJECT_DIR/Fabrication/$DIR" -s + vars: SUFFIX: "" script: - - cd "$DIR" - - echo $COMMAND $SUFFIX | bash + - !reference [.commands, kibot] dependencies: [] - parallel: - matrix: - -.tempout: - extends: .template - artifacts: - when: always - expire_in: 2 mins - paths: - - Fabrication/ .temprc: extends: .template rules: - !reference [.main_rules, rules] - !reference [.dev_rules, rules] - -.tempcoll: - rules: - - !reference [.main_rules, rules] - script: - - ls Fabrication/ - artifacts: - when: always - paths: - - Fabrication/ erc: extends: .temprc @@ -75,46 +115,38 @@ drc: variables: SUFFIX: update_xml,run_erc -i -cad_outputs: - extends: .tempout +outputs_dev: + rules: + - !reference [.dev_rules, rules] + stage: gen_fab + artifacts: + when: always + paths: + - Fabrication/ + variables: + SUFF_SCH: run_drc,run_erc print_sch step + SUFF_PCB: all print_front + script: + - SUFFIX=$SUFF_SCH + - !reference [.commands, kibot] + - SUFFIX=$SUFF_PCB + - !reference [.commands, kibot] + +outputs_all: + extends: outputs_dev rules: - !reference [.main_rules, rules] - - !reference [.dev_rules, rules] - stage: gen_mech - variables: - SUFFIX: run_drc step - -mech_outputs: - extends: .tempcoll - rules: - - !reference [.main_rules, rules] - - !reference [.dev_rules, rules] - stage: mech_out - dependencies: - - cad_outputs - -sch_outputs: - extends: .tempout - stage: gen_fab variables: - SUFFIX: run_drc,run_erc print_sch - -pcb_outputs: - extends: .tempout - stage: gen_fab - variables: - SUFFIX: all print_front JLCPCB_fab assembly step - -bom: - extends: .tempout - stage: gen_fab - variables: - SUFFIX: all bom - -fab_outputs: - extends: .tempcoll - stage: fab_out - dependencies: - - sch_outputs - - pcb_outputs - - bom + SUFF_SCH: run_drc,run_erc print_sch step bom + SUFF_PCB: all print_front gerbers drill position + script: + - SUFFIX=$SUFF_SCH + - !reference [.commands, kibot] + - cd $CI_PROJECT_DIR + - !reference [.commands, panel] + - SUFFIX=$SUFF_PCB + - cd $CI_PROJECT_DIR + - !reference [.commands, kibot] + - cd $CI_PROJECT_DIR + - ls Fabrication/interface + - !reference [.commands, neo] diff --git a/default.kibot.yaml b/default.kibot.yaml index 3618322..3228736 100644 --- a/default.kibot.yaml +++ b/default.kibot.yaml @@ -60,6 +60,16 @@ outputs: download: true kicad_3d_url: https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/ + - name: 'position' + comment: "Pick and place" + type: position + options: + output: '%f_cpl.%x' + format: CSV + units: millimeters + separate_files_for_front_and_back: false + only_smd: true + - name: 'assembly' comment: "Pick and place file, JLC style" type: position diff --git a/scripts/neo.py b/scripts/neo.py new file mode 100644 index 0000000..c31ddf4 --- /dev/null +++ b/scripts/neo.py @@ -0,0 +1,156 @@ +import csv +import sys +from copy import deepcopy +from glob import glob +from kikit.panelize import Origin,getOriginCoord +from kikit.common import findBoardBoundingBox +from pcbnewTransition.pcbnew import LoadBoard + +YY1_SEP = ["","","","","","","","","","","","","",""] +YY1_HEADER = ["NEODEN","YY1","P&P FILE","","","","","","","","","","",""] +YY1_PANEL = ["PanelizedPCB","UnitLength",0,"UnitWidth",0,"Rows",1,"Columns",1,] +YY1_NOZZLE_DEF = ["NozzleChange","OFF","BeforeComponent",1,"Head1","Drop","Station1","PickUp","Station1",] +YY1_COMP_HEADER = ["Designator","Comment","Footprint","Mid X(mm)","Mid Y(mm) ","Rotation","Head ","FeederNo","Mount Speed(%)","Pick Height(mm)","Place Height(mm)","Mode", "Skip"] + +YY1_EXTRA_IND = YY1_COMP_HEADER.index("Head ") +YY1_FEEDER_IND = YY1_COMP_HEADER.index("FeederNo") - YY1_EXTRA_IND + +YY1_DEF_EXTRA = [0, 0, 50, 0.0, 0.0, 1, 0] + +KI_POS_SUFFIX = "-all-pos" + + +pcb_file = glob("*.kicad_pcb") +board = LoadBoard(pcb_file[0]) +sourceArea = findBoardBoundingBox(board) +bottomOrig = [(a - b)/10**6 for a,b in zip(*[list(getOriginCoord(o, sourceArea)) for o in [Origin.BottomRight, Origin.BottomLeft]])] +print("bottom Orig: ", bottomOrig) + + +csv_files = glob("*.csv") +input = next(filter(lambda f: KI_POS_SUFFIX in f, csv_files)) +output = input.split(KI_POS_SUFFIX)[0] + "-neo-pos.csv" +try: + feed_opt = next(filter(lambda f: "feed" in f, csv_files)) +except: + feed_opt = None + +rows_in = [] +with open(input, newline='') as csvfile: + spamreader = csv.reader(csvfile, delimiter=',', quotechar='|') + for row in spamreader: + # Round floats to 2 places + for n, r in enumerate(row): + r = r.strip('"') + row[n] = r + try: + r = round(float(r),2) + row[n] = "{:.2f}".format(r) + except: + continue + rows_in.append(row) + +in_header = rows_in[0] +posx = in_header.index("PosX") +posy = in_header.index("PosY") +desig = in_header.index("Ref") +comm = in_header.index("Val") +foot = in_header.index("Package") +rot = in_header.index("Rot") + +out_inds = [desig, comm, foot, posx, posy, rot] + +def pos_diff(pos1, pos2): + return sum([(pos1[i] - pos2[i])**2 for i in range(2)])**1/2 + +# Fiducial +fids = [x for x in filter(lambda x: "Fiducial" in x, rows_in)] +for f in fids: + rows_in.remove(f) + +# Components +comps = rows_in[1:] +comp_feed = {} +feeder = 0 +# Load in optional file +if feed_opt is not None: + # Read in feeder options for components + with open(feed_opt, newline='') as csvfile: + spamreader = csv.reader(csvfile, delimiter=',', quotechar='|') + for row in spamreader: + comp_feed[row[0]] = row[1:] + f = int(row[YY1_FEEDER_IND]) + if f > feeder: + feeder = f + +def comp_name(row): + return row[foot] + row[comm] + +# Add all component types +for comp in comps: + name = comp_name(comp) + if name not in comp_feed: + entry = deepcopy(YY1_DEF_EXTRA) + entry[YY1_FEEDER_IND] = feeder + comp_feed[name] = entry + feeder += 1 + +def comp_format(filt, offset): + filt_rows = filter(filt, rows_in[1:]) + out = [] + for r in filt_rows: + t = [] + for i in out_inds: + t.append(r[i]) + t.extend(comp_feed[comp_name(r)]) + # Cannot go below 0, so just use abs to take care of sign + for n,p in enumerate([out_inds.index(n) for n in [posx, posy]]): + t[p] = "{:.2f}".format(abs(float(t[p]) - offset[n])) + rotation = float(t[rot]) + rotation = rotation + offset[2] + if rotation > 180.0: + rotation = -1 * (360.0 - rotation) + t[rot] = "{:.2f}".format(rotation) + out.append(t) + return out + +# Top and bottom +origins = {"top": [0.0,0.0], "bottom": list(bottomOrig)} +print("origins:", origins) + +for tag, origin in origins.items(): + rotation = 180.0 * (tag != "top") + print("rot: ", rotation) + # Find fiducial furthest away from origin + fid_pos = deepcopy(origin) + fid_dist = 0.0 + for f in fids: + pos = [float(f[posx]), float(f[posy])] + d = pos_diff(pos, origin) + if d > fid_dist : + fid_pos = pos + fid_dist = d + + + print("Fid pos: ", fid_pos) + + # component formatting + comp = comp_format(lambda x: tag in x, origin + [rotation]) + + # Format output + rows_out = [YY1_HEADER, YY1_SEP, YY1_PANEL, YY1_SEP] + # y is still same dir, but x gets flipped, so the x calc is opposite sign + rows_out.append(["Fiducial","1-X", "{:.2f}".format(origin[0] - fid_pos[0]) ,"1-Y","{:.2f}".format(fid_pos[1] - origin[1]) ,"OverallOffsetX",0,"OverallOffsetY",0,]) + rows_out.append(YY1_SEP) + for _ in range(4): + rows_out.append(YY1_NOZZLE_DEF) + rows_out.append(YY1_SEP) + rows_out.append(YY1_COMP_HEADER) + rows_out.extend(comp) + + # Write output + with open(output.split(".csv")[0] + "_" + tag + ".csv", 'w', newline='') as csvfile: + spamwriter = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + for row in rows_out: + spamwriter.writerow(row)