Variants: able to use kibot variants, separate variants for: mech, sch, and pcb outputs. This because a BOM variant does not affect the PCB, but it might affect the step file.
168 lines
5.4 KiB
Python
168 lines
5.4 KiB
Python
import csv
|
|
import os
|
|
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 = "_cpl"
|
|
|
|
pcb_dir = sys.argv[1]
|
|
|
|
pcb_file = glob(pcb_dir + "/*.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)
|
|
|
|
|
|
|
|
output_dir = deepcopy(pcb_dir)
|
|
csv_files = glob(output_dir + "/*.csv")
|
|
print("CSV Files: ", csv_files)
|
|
if len(csv_files) < 1:
|
|
output_dir = os.path.join(os.environ.get('CI_PROJECT_DIR', ''), '') + "Fabrication/" + output_dir
|
|
csv_files = glob(output_dir + "/*.csv")
|
|
|
|
input = next(filter(lambda f: KI_POS_SUFFIX in f, csv_files))
|
|
output = output_dir + "/" + input.split("/")[-1].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 = 1
|
|
# 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]
|
|
# Wrap the phase [-180, 180]
|
|
rotation = (rotation + 180.0) % (2 * 180.0) - 180.0
|
|
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(abs(fid_pos[0] - origin[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)
|
|
|
|
|