From 947737683a4d905f32d6fc7bea9c2ae0b17e1249 Mon Sep 17 00:00:00 2001 From: Peter Jaquiery Date: Fri, 7 Feb 2025 13:11:25 +1300 Subject: [PATCH] Add bom.csv part number column name management --- README.md | 23 +++++++++++++++-------- assets/options.png | Bin 18259 -> 19685 bytes plugins/cli.py | 4 +++- plugins/options.py | 3 ++- plugins/plugin.py | 20 ++++++++++++++++---- plugins/process.py | 33 ++++++++++++++++++--------------- plugins/thread.py | 14 ++++++++------ 7 files changed, 62 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 3763425..546d8da 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -JLC PCB Plug-in for KiCad - +
| **JLC PCB Plug-in for KiCad** | @@ -37,7 +37,7 @@ Click on the Fabrication Toolkit +![Option Dialog](assets/options.png) ☑ __Additional layers__: Comma-separated list of additional layers to include in the gerber archive.
☑ __Set User.1 as V-Cut layer__: Merge User.1 layer with the Edge-Cut layer in production.
@@ -45,9 +45,16 @@ Options can be set in the dialog that appears when the plugin is invoked. They a ☑ __Apply automatic translations__: Apply known translation fixes for common components.
☑ __Apply automatic fill for all zones__: Refill all zones before generation production files.
☑ __Exclude DNP components from BOM__: Exclude components the had been set a DNP from th BOM.
+☑ __MPN Column in bom.csv__: Select bom.csv column to use for part numbers.
### ① Include Component Part Number in Production Files -Add an 'LCSC Part #'* field with the LCSC component part number to the symbol's fields property. +Use one of the fields from "Primary Fields" or "Fallback Fields" shown below +for Manufacturer Part Numbers. + +JLCPCB uses the Comment and Footprint columns in the bom.csv to match parts. +Older version of Fabrication Toolkit generated a 'LCSC Part #' column instead. +Use the column name that suits your process to contain the Manufacturer Part +Number or LCSC/JLCPCB component number. @@ -55,13 +62,13 @@ Add an 'LCSC Part #'* field with the LCSC component part number to the symbol's | 'LCSC Part #' | 'LCSC Part' | 'JLCPCB Part #' | 'JLCPCB Part' | | --- | --- | --- | --- | -_The fields will be query in the order denoted above._ +_The fields will be queried in the order denoted above._ #### Fallback Fields*: | 'LCSC' | 'JLC' | 'MPN' | 'Mpn' | 'mpn' | | --- | --- | --- | --- | --- | -_The fields will be query in the order denoted above._ +_The fields will be queried in the order denoted above._ --- @@ -102,7 +109,7 @@ _The fields will be queried in the order denoted above._ --- ### ④ Offset Component Position -The position of components in KiCad Footprints does not always match the orientation in the JLC library because KiCad and JLCPCB used different variation of the same standard. To the exception cases: add an 'FT Position Offset'* field with an comma separated x,y position offset to correct it. +The position of components in KiCad Footprints does not always match the orientation in the JLC library because KiCad and JLCPCB used different variation of the same standard. To the exception cases: add an 'FT Position Offset'* field with an comma separated x,y position offset to correct it. Use following table to quickly find out to which coordinate enter the correction based on JLC arrows clicks - depending on footprint rotation in KiCad PCB Editor status bar: |KiCad footprint deg | x | y| @@ -134,7 +141,7 @@ _The fields will be queried in the order denoted above._ _The fields will be queried in the order denoted above._ ### ⑤ Override Component Origin -The Fabrication Toolkit reports the position of each component based on an automatically selected point of reference. This default behavior can be overridden by adding an 'FT Origin'* field to the component. +The Fabrication Toolkit reports the position of each component based on an automatically selected point of reference. This default behavior can be overridden by adding an 'FT Origin'* field to the component. #### Primary Fields*: | 'FT Origin' | diff --git a/assets/options.png b/assets/options.png index 0fa55ebb21a9d27dc4982f5851142abe1ca9b7f7..7eb078e45d503ac2b06a34461e4bf629a6b99d25 100644 GIT binary patch literal 19685 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%0DgQ$8b(`X3Cg(esu1%jNWyz$NcT&CAuE_#)cjO`H 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 diff --git a/plugins/cli.py b/plugins/cli.py index 3b3dd17..d052818 100644 --- a/plugins/cli.py +++ b/plugins/cli.py @@ -2,7 +2,7 @@ from .thread import ProcessThread from .options import * - + if __name__ == '__main__': parser = ap.ArgumentParser(prog="Fabrication Toolkit", @@ -17,6 +17,7 @@ parser.add_argument("--excludeDNP", "-e", action="store_true", help="Exclude DNP components from BOM") parser.add_argument("--allActiveLayers", "-aaL",action="store_true", help="Export all active layers instead of only commonly used ones") parser.add_argument("--openBrowser", "-b", action="store_true", help="Open webbrowser with directory file overview after generation") + parser.add_argument("--pnColumn", "-c", type=str, help="bom.csv column name for Manufacturer Part Numbers") args = parser.parse_args() options = dict() @@ -27,6 +28,7 @@ options[ALTERNATIVE_EDGE_CUT_OPT] = args.user2AltVCut options[ALL_ACTIVE_LAYERS_OPT] = args.allActiveLayers options[EXTRA_LAYERS] = args.additionalLayers + options[PN_COLUMN] = args.pnColumn openBrowser = args.openBrowser diff --git a/plugins/options.py b/plugins/options.py index 27ae033..9b78b2e 100644 --- a/plugins/options.py +++ b/plugins/options.py @@ -5,4 +5,5 @@ EXTEND_EDGE_CUT_OPT = "EXTEND_EDGE_CUT" ALTERNATIVE_EDGE_CUT_OPT = "ALTERNATIVE_EDGE_CUT" ALL_ACTIVE_LAYERS_OPT = "ALL_ACTIVE_LAYERS" -EXTRA_LAYERS = "EXTRA_LAYERS" \ No newline at end of file +EXTRA_LAYERS = "EXTRA_LAYERS" +PN_COLUMN = "PN_COLUMN" \ No newline at end of file diff --git a/plugins/plugin.py b/plugins/plugin.py index d94d94f..4330304 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -4,7 +4,9 @@ from .thread import ProcessThread from .events import StatusEvent -from .options import AUTO_FILL_OPT, AUTO_TRANSLATE_OPT, EXCLUDE_DNP_OPT, EXTEND_EDGE_CUT_OPT, ALTERNATIVE_EDGE_CUT_OPT, EXTRA_LAYERS, ALL_ACTIVE_LAYERS_OPT +from .options import AUTO_FILL_OPT, AUTO_TRANSLATE_OPT, EXCLUDE_DNP_OPT, \ + EXTEND_EDGE_CUT_OPT, ALTERNATIVE_EDGE_CUT_OPT, EXTRA_LAYERS, \ + ALL_ACTIVE_LAYERS_OPT, PN_COLUMN from .utils import load_user_options, save_user_options, get_layer_names @@ -19,7 +21,7 @@ def __init__(self): pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_DIALOG_STYLE) - + # self.app = wx.PySimpleApp() icon = wx.Icon(os.path.join(os.path.dirname(__file__), 'icon.png')) self.SetIcon(icon) @@ -34,7 +36,8 @@ def __init__(self): ALTERNATIVE_EDGE_CUT_OPT: False, AUTO_TRANSLATE_OPT: True, AUTO_FILL_OPT: True, - EXCLUDE_DNP_OPT: False + EXCLUDE_DNP_OPT: False, + PN_COLUMN: "LCSC Part #" }) self.mOptionsLabel = wx.StaticText(self, label='Options:') @@ -58,6 +61,10 @@ def __init__(self): self.mAutomaticFillCheckbox.SetValue(userOptions[AUTO_FILL_OPT]) self.mExcludeDnpCheckbox = wx.CheckBox(self, label='Exclude DNP components from BOM') self.mExcludeDnpCheckbox.SetValue(userOptions[EXCLUDE_DNP_OPT]) + self.mPNColStaticText = wx.StaticText(self, label="MPN column in bom.csv:") + self.mPNColumnListbox = wx.ListBox(self, style=wx.LB_SINGLE, choices=["LCSC Part #", "Comment"]) + index = self.mPNColumnListbox.FindString(userOptions[PN_COLUMN]) + self.mPNColumnListbox.SetSelection(index) self.mGaugeStatus = wx.Gauge( self, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size(600, 20), wx.GA_HORIZONTAL) @@ -78,6 +85,8 @@ def __init__(self): boxSizer.Add(self.mAutomaticTranslationCheckbox, 0, wx.ALL, 5) boxSizer.Add(self.mAutomaticFillCheckbox, 0, wx.ALL, 5) boxSizer.Add(self.mExcludeDnpCheckbox, 0, wx.ALL, 5) + boxSizer.Add(self.mPNColStaticText, 0, wx.ALL | wx.ALIGN_LEFT, 5) + boxSizer.Add(self.mPNColumnListbox, 0, wx.ALL | wx.ALIGN_LEFT | wx.EXPAND, 5) boxSizer.Add(self.mGaugeStatus, 0, wx.ALL, 5) boxSizer.Add(self.mGenerateButton, 0, wx.ALL, 5) @@ -107,7 +116,8 @@ def onGenerateButtonClick(self, event): options[AUTO_TRANSLATE_OPT] = self.mAutomaticTranslationCheckbox.GetValue() options[AUTO_FILL_OPT] = self.mAutomaticFillCheckbox.GetValue() options[EXCLUDE_DNP_OPT] = self.mExcludeDnpCheckbox.GetValue() - + index = self.mPNColumnListbox.GetSelection() + options[PN_COLUMN] = self.mPNColumnListbox.GetString(index) save_user_options(options) self.mOptionsLabel.Hide() @@ -118,6 +128,8 @@ def onGenerateButtonClick(self, event): self.mAutomaticTranslationCheckbox.Hide() self.mAutomaticFillCheckbox.Hide() self.mExcludeDnpCheckbox.Hide() + self.mPNColStaticText.Hide(); + self.mPNColumnListbox.Hide() self.mGenerateButton.Hide() self.mGaugeStatus.Show() diff --git a/plugins/process.py b/plugins/process.py index 91dfb7d..25546d5 100644 --- a/plugins/process.py +++ b/plugins/process.py @@ -67,7 +67,7 @@ def generate_gerber(self, temp_dir, extra_layers, extend_edge_cuts, alternative_ plot_options.SetSubtractMaskFromSilk(True) plot_options.SetUseGerberX2format(False) plot_options.SetDrillMarksType(0) # NO_DRILL_SHAPE - + if hasattr(plot_options, "SetExcludeEdgeLayer"): plot_options.SetExcludeEdgeLayer(True) @@ -117,7 +117,7 @@ def generate_netlist(self, temp_dir): netlist_writer = pcbnew.IPC356D_WRITER(self.board) netlist_writer.Write(os.path.join(temp_dir, netlistFileName)) - def _get_footprint_position(self, footprint): + def _get_footprint_position(self, footprint): """Calculate position based on center of pads / bounding box.""" origin_type = self._get_origin_from_footprint(footprint) @@ -133,10 +133,10 @@ def _get_footprint_position(self, footprint): position = bbox.GetCenter() else: position = footprint.GetPosition() # if we have no pads we fallback to anchor - + return position - def generate_tables(self, temp_dir, auto_translate, exclude_dnp): + def generate_tables(self, temp_dir, auto_translate, exclude_dnp, pnColumn): '''Generate the data tables.''' if hasattr(self.board, 'GetModules'): footprints = list(self.board.GetModules()) @@ -158,6 +158,8 @@ def generate_tables(self, temp_dir, auto_translate, exclude_dnp): for key, value in footprint_designators.items(): f.write('%s:%s\n' % (key, value)) + part_column_name = pnColumn + for i, footprint in enumerate(footprints): try: footprint_name = str(footprint.GetFPID().GetFootprintName()) @@ -172,8 +174,8 @@ def generate_tables(self, temp_dir, auto_translate, exclude_dnp): # 2: 'unspecified' # }.get(footprint.GetAttributes()) - is_dnp = (footprint_has_field(footprint, 'dnp') - or (footprint.GetValue().upper() == 'DNP') + is_dnp = (footprint_has_field(footprint, 'dnp') + or (footprint.GetValue().upper() == 'DNP') or getattr(footprint, 'IsDNP', bool)()) skip_dnp = exclude_dnp and is_dnp @@ -236,7 +238,7 @@ def generate_tables(self, temp_dir, auto_translate, exclude_dnp): for component in self.bom: same_footprint = component['Footprint'] == self._normalize_footprint_name(footprint_name) same_value = component['Value'].upper() == footprint.GetValue().upper() - same_lcsc = component['LCSC Part #'] == self._get_mpn_from_footprint(footprint) + same_lcsc = component[part_column_name] == self._get_mpn_from_footprint(footprint) under_limit = component['Quantity'] < bomRowLimit if same_footprint and same_value and same_lcsc and under_limit: @@ -247,14 +249,15 @@ def generate_tables(self, temp_dir, auto_translate, exclude_dnp): # add component to BOM if insert: - self.bom.append({ + fields = { 'Designator': "{}{}{}".format(footprint.GetReference().upper(), "" if unique_id == "" else "_", unique_id), 'Footprint': self._normalize_footprint_name(footprint_name), 'Quantity': 1, 'Value': footprint.GetValue(), # 'Mount': mount_type, - 'LCSC Part #': self._get_mpn_from_footprint(footprint), - }) + part_column_name: self._get_mpn_from_footprint(footprint), + } + self.bom.append(fields) def generate_positions(self, temp_dir): '''Generate the position file.''' @@ -294,7 +297,7 @@ def generate_archive(self, temp_dir, temp_file): os.remove(os.path.join(temp_dir, item)) return temp_file - + """ Private """ def __read_rotation_db(self, filename: str = os.path.join(os.path.dirname(__file__), 'transformations.csv')) -> dict[str, float]: @@ -352,7 +355,7 @@ def __read_rotation_db(self, filename: str = os.path.join(os.path.dirname(__file db[rowNum]['name'] = row['footprint'] db[rowNum]['rotation'] = rotation db[rowNum]['x'] = delta_x - db[rowNum]['y'] = delta_y + db[rowNum]['y'] = delta_y return db @@ -405,7 +408,7 @@ def _get_position_offset_from_db(self, footprint: str) -> Tuple[float, float]: return (0.0, 0.0) def _get_mpn_from_footprint(self, footprint) -> str: - ''''Get the MPN/LCSC stock code from standard symbol fields.''' + '''Get the MPN/LCSC stock code from standard symbol fields.''' keys = ['LCSC Part #', 'LCSC Part', 'JLCPCB Part #', 'JLCPCB Part'] fallback_keys = ['LCSC', 'JLC', 'MPN', 'Mpn', 'mpn'] @@ -479,7 +482,7 @@ def _get_position_offset_from_footprint(self, footprint) -> Tuple[float, float]: return (float(offset[0]), float(offset[1])) except Exception as e: raise RuntimeError("Position offset of {} is not a valid pair of numbers".format(footprint.GetReference())) - + def _get_origin_from_footprint(self, footprint) -> float: '''Get the origin from standard symbol fields.''' keys = ['FT Origin'] @@ -490,7 +493,7 @@ def _get_origin_from_footprint(self, footprint) -> float: # determine origin type by package type if attributes & pcbnew.FP_SMD: origin_type = 'Anchor' - else: + else: origin_type = 'Center' for key in keys + fallback_keys: diff --git a/plugins/thread.py b/plugins/thread.py index 14769d1..1b3d6e4 100644 --- a/plugins/thread.py +++ b/plugins/thread.py @@ -18,11 +18,11 @@ class ProcessThread(Thread): def __init__(self, wx, options, cli = None, openBrowser = True): Thread.__init__(self) - # prevent use of cli and grapgical mode at the same time + # prevent use of cli and graphical mode at the same time if (wx is None and cli is None) or (wx is not None and cli is not None): logging.error("Specify either graphical or cli use!") return - + if cli is not None: try: self.board = pcbnew.LoadBoard(cli) @@ -31,7 +31,7 @@ def __init__(self, wx, options, cli = None, openBrowser = True): return else: self.board = None - + self.process_manager = ProcessManager(self.board) self.wx = wx self.cli = cli @@ -71,7 +71,9 @@ def run(self): # generate data tables self.progress(50) - self.process_manager.generate_tables(temp_dir, self.options[AUTO_TRANSLATE_OPT], self.options[EXCLUDE_DNP_OPT]) + self.process_manager.generate_tables( + temp_dir, self.options[AUTO_TRANSLATE_OPT], self.options[EXCLUDE_DNP_OPT], + self.options[PN_COLUMN]) # generate pick and place file self.progress(60) @@ -126,7 +128,7 @@ def run(self): output_path = os.path.join(project_directory, outputFolder) if not os.path.exists(output_path): os.makedirs(output_path) - + # rename gerber archive gerberArchiveName = ProcessManager.normalize_filename("_".join(("{} {}".format(title or filename, revision or '').strip() + '.zip').split())) os.rename(temp_file, os.path.join(temp_dir, gerberArchiveName)) @@ -146,7 +148,7 @@ def run(self): if self.openBrowser: webbrowser.open("file://%s" % (temp_dir)) - if self.wx is None: + if self.wx is None: self.progress(100) else: self.progress(-1)