320 lines
11 KiB
Python
320 lines
11 KiB
Python
#https://opensource.org/licenses/MIT
|
|
|
|
import pcbnew # type: ignore
|
|
import os
|
|
import csv
|
|
|
|
from itertools import groupby
|
|
|
|
from .config import *
|
|
from .utils import *
|
|
|
|
class PCBWayProcess:
|
|
def __init__(self):
|
|
self.board = pcbnew.GetBoard()
|
|
self.pctl = pcbnew.PLOT_CONTROLLER(self.board)
|
|
self.bom = []
|
|
self.components = []
|
|
|
|
def get_name(self):
|
|
return self.board.GetFileName()
|
|
|
|
def get_basedir(self):
|
|
return os.path.dirname(self.board.GetFileName())
|
|
|
|
def get_basename(self):
|
|
return os.path.basename(self.board.GetFileName())
|
|
|
|
def get_gerber_file(self, temp_dir):
|
|
settings = self.board.GetDesignSettings()
|
|
settings.m_SolderMaskMargin = 0
|
|
settings.m_SolderMaskMinWidth = 0
|
|
|
|
#pctl = pcbnew.PLOT_CONTROLLER(self.board)
|
|
|
|
popt = self.pctl.GetPlotOptions()
|
|
popt.SetOutputDirectory(temp_dir)
|
|
popt.SetPlotFrameRef(False)
|
|
popt.SetSketchPadLineWidth(pcbnew.FromMM(0.1))
|
|
popt.SetAutoScale(False)
|
|
popt.SetScale(1)
|
|
popt.SetMirror(False)
|
|
popt.SetUseGerberAttributes(True)
|
|
if hasattr(popt, "SetExcludeEdgeLayer"):
|
|
popt.SetExcludeEdgeLayer(True)
|
|
popt.SetUseGerberProtelExtensions(False)
|
|
popt.SetUseAuxOrigin(True)
|
|
popt.SetSubtractMaskFromSilk(True)
|
|
popt.SetDrillMarksType(0) # NO_DRILL_SHAPE
|
|
|
|
for layer_info in plotPlan:
|
|
if self.board.IsLayerEnabled(layer_info[1]):
|
|
self.pctl.SetLayer(layer_info[1])
|
|
self.pctl.OpenPlotfile(
|
|
layer_info[0],
|
|
pcbnew.PLOT_FORMAT_GERBER,
|
|
layer_info[2])
|
|
self.pctl.PlotLayer()
|
|
|
|
self.pctl.ClosePlot()
|
|
|
|
def get_netlist_file(self, temp_dir):
|
|
drlwriter = pcbnew.EXCELLON_WRITER(self.board)
|
|
|
|
drlwriter.SetOptions(
|
|
False,
|
|
True,
|
|
self.board.GetDesignSettings().GetAuxOrigin(),
|
|
False)
|
|
drlwriter.SetFormat(False)
|
|
drlwriter.CreateDrillandMapFilesSet(self.pctl.GetPlotDirName(), True, False)
|
|
|
|
netlist_writer = pcbnew.IPC356D_WRITER(self.board)
|
|
netlist_writer.Write(os.path.join(temp_dir, self.get_basename() + "_" + netlistFilename))
|
|
|
|
def get_components_file(self, temp_dir):
|
|
if hasattr(self.board, 'GetModules'):
|
|
footprints = list(self.board.GetModules())
|
|
else:
|
|
footprints = list(self.board.GetFootprints())
|
|
|
|
footprints.sort(key=lambda x: x.GetReference())
|
|
|
|
mpn_keys = get_mpn_keys()
|
|
pack_keys = get_pack_keys()
|
|
no_show_keys = [
|
|
'ki_fp_filters',
|
|
'DNP',
|
|
'Reference',
|
|
'Value',
|
|
'Datasheet',
|
|
'Footprint',
|
|
]
|
|
ignore_ext_keys = mpn_keys + pack_keys + no_show_keys
|
|
|
|
greater_v8 = is_greater_v8()
|
|
fp_datas = []
|
|
for i, f in enumerate(footprints):
|
|
try:
|
|
footprint_name = str(f.GetFPID().GetFootprintName())
|
|
except AttributeError:
|
|
footprint_name = str(f.GetFPID().GetLibItemName())
|
|
|
|
layer = {
|
|
pcbnew.F_Cu: 'top',
|
|
pcbnew.B_Cu: 'bottom',
|
|
}.get(f.GetLayer())
|
|
|
|
f_attrs = f.GetAttributes()
|
|
parsed_attrs = self.parse_attrs(f_attrs)
|
|
|
|
mount_type = 'smt' if parsed_attrs['smd'] else 'tht'
|
|
not_in_bom = parsed_attrs['not_in_bom']
|
|
not_in_pos = parsed_attrs['not_in_pos']
|
|
|
|
if not_in_bom and not_in_pos:
|
|
continue
|
|
|
|
rotation = f.GetOrientation().AsDegrees() if hasattr(f.GetOrientation(), 'AsDegrees') else f.GetOrientation() / 10.0
|
|
|
|
pos_x = (f.GetPosition()[0] - self.board.GetDesignSettings().GetAuxOrigin()[0]) / 1000000.0
|
|
pos_y = (f.GetPosition()[1] - self.board.GetDesignSettings().GetAuxOrigin()[1]) * -1.0 / 1000000.0
|
|
|
|
designator = f.GetReference()
|
|
value = f.GetValue()
|
|
mpn = get_mpn_from_footprint(f)
|
|
pack = get_pack_from_footprint(f)
|
|
is_dnp = get_is_dnp_from_footprint(f) if greater_v8 else False
|
|
|
|
if not footprint_name:
|
|
footprint_name = ''
|
|
|
|
if not pack:
|
|
pack = ''
|
|
|
|
if not mpn:
|
|
mpn = ''
|
|
|
|
if not_in_pos == False:
|
|
self.components.append({
|
|
'pos_x': pos_x,
|
|
'pos_y': pos_y,
|
|
'rotation': rotation,
|
|
'side': layer,
|
|
'designator': designator,
|
|
'mpn': mpn,
|
|
'pack': pack,
|
|
'footprint': footprint_name,
|
|
'value': value,
|
|
'mount_type': mount_type,
|
|
})
|
|
|
|
if not_in_bom:
|
|
continue
|
|
|
|
fp_item_fields = {
|
|
'designator': designator,
|
|
'value': value,
|
|
'footprint': footprint_name,
|
|
'pack': pack,
|
|
'mpn': mpn,
|
|
'DNP': 'Yes' if is_dnp else '',
|
|
'Mount_Type': mount_type,
|
|
}
|
|
|
|
if greater_v8:
|
|
footprint_fields = f.GetFieldsText()
|
|
if footprint_fields:
|
|
for k, v in footprint_fields.items():
|
|
if k.upper() == 'DNP' or k.upper() == 'MOUNT_TYPE':
|
|
k = 'Custom_' + k
|
|
if not v or k in ignore_ext_keys:
|
|
continue
|
|
fp_item_fields[k] = v
|
|
|
|
fp_datas.append(fp_item_fields)
|
|
|
|
fp_data_group = {}
|
|
for item in fp_datas:
|
|
designator = item['designator']
|
|
value = item['value']
|
|
footprint = item['footprint']
|
|
pack = item['pack']
|
|
mpn = item['mpn']
|
|
is_dnp = item['DNP']
|
|
|
|
index = value + '_' + footprint + '_' + pack + '_' + mpn
|
|
if is_dnp:
|
|
index = designator + '_' + index
|
|
|
|
if index in fp_data_group:
|
|
fp_data_group[index].append(item)
|
|
else:
|
|
fp_data_group[index] = [ item ]
|
|
|
|
fixed_columns = [
|
|
'designator',
|
|
'quantity',
|
|
'value',
|
|
'footprint',
|
|
'pack',
|
|
'mpn',
|
|
]
|
|
|
|
all_columns = [
|
|
'Designator',
|
|
'Quantity',
|
|
'Value',
|
|
'Footprint',
|
|
'Package',
|
|
'MPN',
|
|
]
|
|
rows = []
|
|
for _key, items in fp_data_group.items():
|
|
first_item = items[0]
|
|
|
|
row_datas = {}
|
|
row_columns = []
|
|
designators = []
|
|
for item in items:
|
|
designator = item['designator']
|
|
designators.append(designator)
|
|
|
|
for item_key, item_value in item.items():
|
|
if item_key in fixed_columns:
|
|
continue
|
|
if item_key not in all_columns:
|
|
all_columns.append(item_key)
|
|
if item_key not in row_columns:
|
|
row_columns.append(item_key)
|
|
if item_key not in row_datas:
|
|
row_datas[item_key] = []
|
|
row_datas[item_key].append({
|
|
'key': designator,
|
|
'value': item_value,
|
|
})
|
|
|
|
for k in row_columns:
|
|
row_data = row_datas[k]
|
|
row_data_groupby = {val: list(group) for val, group in groupby(row_data, key=lambda x: x['value'])}
|
|
item_text = ''
|
|
if len(row_data_groupby) > 1:
|
|
item_text = '; '.join(['[' + ','.join([g['key'] for g in group]) + ']'+ group_key for group_key, group in row_data_groupby.items()])
|
|
else:
|
|
item_text = row_data[0]['value']
|
|
first_item[k] = item_text
|
|
|
|
designator_count = len(designators)
|
|
designator = ', '.join(designators)
|
|
|
|
|
|
row = {
|
|
'Designator': designator,
|
|
'Quantity': designator_count,
|
|
'Value': first_item['value'],
|
|
'Footprint': first_item['footprint'],
|
|
'Package': first_item['pack'],
|
|
'MPN': first_item['mpn'],
|
|
}
|
|
|
|
for k in row_columns:
|
|
if k in first_item:
|
|
row[k] = first_item[k]
|
|
|
|
rows.append(row)
|
|
|
|
for row in rows:
|
|
newRow = {}
|
|
for k in all_columns:
|
|
if k in row:
|
|
newRow[k] = row[k]
|
|
else:
|
|
newRow[k] = ''
|
|
if not greater_v8:
|
|
del newRow['DNP']
|
|
self.bom.append(newRow)
|
|
|
|
if len(self.components) > 0:
|
|
with open((os.path.join(temp_dir, self.get_basename() + "_" + positionsFilename)), 'w', newline='', encoding='utf-8-sig') as outfile:
|
|
csvobj = csv.writer(outfile)
|
|
csvobj.writerow(self.components[0].keys())
|
|
|
|
for component in self.components:
|
|
if ('**' not in component['designator']):
|
|
csvobj.writerow(component.values())
|
|
|
|
if len(self.bom) > 0:
|
|
with open((os.path.join(temp_dir, self.get_basename() + "_" + bomFilename)), 'w', newline='', encoding='utf-8-sig') as outfile:
|
|
csvobj = csv.writer(outfile)
|
|
csvobj.writerow(self.bom[0].keys())
|
|
|
|
for component in self.bom:
|
|
if ('**' not in component['Designator']):
|
|
csvobj.writerow(component.values())
|
|
|
|
|
|
def get_gerber_parameter(self):
|
|
boardWidth = pcbnew.ToMM(self.board.GetBoardEdgesBoundingBox().GetWidth())
|
|
boardHeight = pcbnew.ToMM(self.board.GetBoardEdgesBoundingBox().GetHeight())
|
|
|
|
if hasattr(self.board, 'GetCopperLayerCount'):
|
|
boardLayer = self.board.GetCopperLayerCount()
|
|
|
|
return {
|
|
'boardWidth':boardWidth,
|
|
'boardHeight':boardHeight,
|
|
'boardLayer':boardLayer,
|
|
}
|
|
|
|
def parse_attrs(self, attrs):
|
|
return {} if not isinstance(attrs, int) else {
|
|
'tht': self.parse_attr_flag(attrs, pcbnew.FP_THROUGH_HOLE),
|
|
'smd': self.parse_attr_flag(attrs, pcbnew.FP_SMD),
|
|
'not_in_pos': self.parse_attr_flag(attrs, pcbnew.FP_EXCLUDE_FROM_POS_FILES),
|
|
'not_in_bom': self.parse_attr_flag(attrs, pcbnew.FP_EXCLUDE_FROM_BOM),
|
|
'not_in_plan': self.parse_attr_flag(attrs, pcbnew.FP_BOARD_ONLY)
|
|
}
|
|
|
|
def parse_attr_flag(self, attr, mask):
|
|
return mask == (attr & mask)
|