Files
2025-08-28 03:07:33 +07:00

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)