From 248c5e517e57af8a486eb1d4ae0d90c399d7ae2a Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 10:47:02 +0200 Subject: [PATCH 01/23] Add dict_factory helper --- helpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/helpers.py b/helpers.py index cb1896f..7e2d992 100644 --- a/helpers.py +++ b/helpers.py @@ -128,6 +128,14 @@ def alphanum_key(key): return -1 if natorder.index(a) == 0 else 1 +def dict_factory(cursor, row) -> dict: + """Row factory that returns a dict.""" + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + + def get_lcsc_value(fp): """Get the first lcsc number (C123456 for example) from the properties of the footprint.""" # KiCad 7.99 From ab4813c926c0af8f1003f4bf77f75fb147f1c3a0 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 10:47:46 +0200 Subject: [PATCH 02/23] Let read_all return a dict instead of a list --- store.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/store.py b/store.py index e52ed2a..ee7bf61 100644 --- a/store.py +++ b/store.py @@ -8,6 +8,7 @@ import sqlite3 from .helpers import ( + dict_factory, get_exclude_from_bom, get_exclude_from_pos, get_lcsc_value, @@ -85,13 +86,11 @@ def read_all(self): """Read all parts from the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con: con.create_collation("naturalsort", natural_sort_collation) + con.row_factory = dict_factory with con as cur: - return [ - list(part) - for part in cur.execute( - f"SELECT * FROM part_info ORDER BY {self.order_by} COLLATE naturalsort {self.order_dir}" - ).fetchall() - ] + return cur.execute( + f"SELECT * FROM part_info ORDER BY {self.order_by} COLLATE naturalsort {self.order_dir}" + ).fetchall() def read_bom_parts(self): """Read all parts that should be included in the BOM.""" From 76dcc1159d665de828d63ba7a59590d40a386c4e Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 10:50:00 +0200 Subject: [PATCH 03/23] Use dict in populate_footprint_list for better readability --- mainwindow.py | 57 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/mainwindow.py b/mainwindow.py index d9328bc..cbb3642 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -590,24 +590,23 @@ def populate_footprint_list(self, *_): numbers = [] parts = [] for part in self.store.read_all(): - fp = self.pcbnew.GetBoard().FindFootprintByReference(part[0]) - if part[3] and part[3] not in numbers: - numbers.append(part[3]) - part.insert(4, "") - part[5] = str(part[5]) + fp = self.pcbnew.GetBoard().FindFootprintByReference(part["reference"]) + if part["lcsc"] and part["lcsc"] not in numbers: + numbers.append(part["lcsc"]) + part["stock"] = str(part["stock"]) # don't show the part if hide BOM is set - if self.hide_bom_parts and part[6]: + if self.hide_bom_parts and part["exclude_from_bom"]: continue # don't show the part if hide POS is set - if self.hide_pos_parts and part[7]: + if self.hide_pos_parts and part["exclude_from_pos"]: continue # decide which icon to use - part[6] = icons.get(part[6], icons.get(0)) - part[7] = icons.get(part[7], icons.get(0)) - part.insert(8, "") - side = "Top" if fp.GetLayer() == 0 else "Bot" - part.insert(9, side) - part.insert(10, "") + part["exclude_from_bom"] = icons.get(part["exclude_from_bom"], icons.get(0)) + part["exclude_from_pos"] = icons.get(part["exclude_from_pos"], icons.get(0)) + part["side"] = "Top" if fp.GetLayer() == 0 else "Bot" + part["rotation"] = "" + part["type"] = "" + part["side"] = "" parts.append(part) details = self.library.get_part_details(numbers) corrections = self.library.get_all_correction_data() @@ -615,26 +614,40 @@ def populate_footprint_list(self, *_): for part in parts: detail = list( filter( - lambda x, lcsc=part[3]: x[0] == lcsc, + lambda x, lcsc=part["lcsc"]: x[0] == lcsc, details, ) ) if detail: - part[4] = detail[0][2] - part[5] = detail[0][1] + part["type"] = detail[0][2] + part["stock"] = detail[0][1] # First check if the part name mathes for regex, correction in corrections: - if re.search(regex, str(part[1])): - part[8] = str(correction) + if re.search(regex, str(part["reference"])): + part["rotation"] = str(correction) break # If there was no match for the part name, check if the package matches - if part[8] == "": + if part["rotation"] == "": for regex, correction in corrections: - if re.search(regex, str(part[2])): - part[8] = str(correction) + if re.search(regex, str(part["footprint"])): + part["rotation"] = str(correction) break - self.footprint_list.AppendItem(part) + self.footprint_list.AppendItem( + [ + part["reference"], + part["value"], + part["footprint"], + part["lcsc"], + part["type"], + part["stock"], + part["exclude_from_bom"], + part["exclude_from_pos"], + part["rotation"], + part["side"], + "", + ] + ) def OnSortFootprintList(self, e): """Set order_by to the clicked column and trigger list refresh.""" From 1d82f69d8278d794d7ced6fcfaacd489267cf675 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:28:53 +0200 Subject: [PATCH 04/23] Improve get_part to use dict_factory for better readability --- mainwindow.py | 2 +- store.py | 65 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/mainwindow.py b/mainwindow.py index cbb3642..8835ce3 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -842,7 +842,7 @@ def select_alike(self, *_): for r in range(self.footprint_list.GetItemCount()): value = self.footprint_list.GetValue(r, 1) fp = self.footprint_list.GetValue(r, 2) - if part[1] == value and part[2] == fp: + if part["value"] == value and part["footprint"] == fp: self.footprint_list.SelectRow(r) def get_part_details(self, *_): diff --git a/store.py b/store.py index ee7bf61..88eeadc 100644 --- a/store.py +++ b/store.py @@ -135,9 +135,10 @@ def update_part(self, part): cur.commit() - def get_part(self, ref): + def get_part(self, ref: str) -> dict: """Get a part from the database by its reference.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: + con.row_factory = dict_factory return cur.execute( "SELECT * FROM part_info WHERE reference=?", (ref,) ).fetchone() @@ -183,57 +184,77 @@ def set_lcsc(self, ref, value): def update_from_board(self): """Read all footprints from the board and insert them into the database if they do not exist.""" for fp in get_valid_footprints(self.board): - part = [ - fp.GetReference(), - fp.GetValue(), - str(fp.GetFPID().GetLibItemName()), - get_lcsc_value(fp), - get_exclude_from_bom(fp), - get_exclude_from_pos(fp), - ] - dbpart = self.get_part(part[0]) + board_part = { + "reference": fp.GetReference(), + "value": fp.GetValue(), + "footprint": str(fp.GetFPID().GetLibItemName()), + "lcsc": get_lcsc_value(fp), + "exclude_from_bom": get_exclude_from_bom(fp), + "exclude_from_pos": get_exclude_from_pos(fp), + } + db_part = self.get_part(board_part["reference"]) # if part is not in the database yet, create it - if not dbpart: + if not db_part: self.logger.debug( "Part %s does not exist in the database and will be created from the board.", - part[0], + board_part["reference"], ) +<<<<<<< HEAD self.create_part(part) elif ( part[0:3] == list(dbpart[0:3]) and part[4:] == [bool(x) for x in dbpart[5:]] ): # if the board part matches the dbpart except for the LCSC and the stock value, +======= + self.create_part(board_part) + # if the board part matches the db_part except for the LCSC and the stock value + elif [ + board_part["reference"], + board_part["value"], + board_part["footprint"], + ] == [ + db_part["reference"], + db_part["value"], + db_part["footprint"], + ] and [ + board_part["exclude_from_bom"], + board_part["exclude_from_pos"], + ] == [ + bool(db_part["exclude_from_bom"]), + bool(db_part["exclude_from_pos"]), + ]: +>>>>>>> 2621281 (Improve get_part to use dict_factory for better readability) # if part in the database, has no lcsc value the board part has a lcsc value, update including lcsc - if dbpart and not dbpart[3] and part[3]: + if db_part and not db_part["lcsc"] and board_part["lcsc"]: self.logger.debug( "Part %s is already in the database but without lcsc value, so the value supplied from the board will be set.", - part[0], + board_part["reference"], ) - self.update_part(part) + self.update_part(board_part) # if part in the database, has a lcsc value - elif dbpart and dbpart[3] and part[3]: + elif db_part and db_part["lcsc"] and board_part["lcsc"]: # update lcsc value as well if setting is accordingly if not self.parent.settings.get("general", {}).get( "lcsc_priority", True ): self.logger.debug( "Part %s is already in the database and has a lcsc value, the value supplied from the board will be ignored.", - part[0], + board_part["reference"], ) - part.pop(3) + board_part["lcsc"] = None else: self.logger.debug( "Part %s is already in the database and has a lcsc value, the value supplied from the board will overwrite that in the database.", - part[0], + board_part["reference"], ) - self.update_part(part) + self.update_part(board_part) else: # If something changed, we overwrite the part and dump the lcsc value or use the one supplied by the board self.logger.debug( "Part %s is already in the database but value, footprint, bom or pos values changed in the board file, part will be updated, lcsc overwritten/cleared.", - part[0], + board_part["reference"], ) - self.update_part(part) + self.update_part(board_part) self.import_legacy_assignments() self.clean_database() From 8f373d4517fbe58d739be959f4eaa0e38bace235 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:32:05 +0200 Subject: [PATCH 05/23] Use dict instead of list in update_part --- store.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/store.py b/store.py index 88eeadc..1b7cac5 100644 --- a/store.py +++ b/store.py @@ -122,17 +122,17 @@ def create_part(self, part): def update_part(self, part): """Update a part in the database, overwrite lcsc if supplied.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: - if len(part) == 6: - cur.execute( - "UPDATE part_info set value = ?, footprint = ?, lcsc = ?, exclude_from_bom = ?, exclude_from_pos = ? WHERE reference = ?", - part[1:] + part[0:1], - ) - else: - cur.execute( - "UPDATE part_info set value = ?, footprint = ?, exclude_from_bom = ?, exclude_from_pos = ? WHERE reference = ?", - part[1:] + part[0:1], - ) - + cur.execute( + "UPDATE part_info set value = ?, footprint = ?, lcsc = ?, exclude_from_bom = ?, exclude_from_pos = ? WHERE reference = ?", + ( + part["value"], + part["footprint"], + part["lcsc"], + part["exclude_from_bom"], + part["exclude_from_pos"], + part["reference"], + ), + ) cur.commit() def get_part(self, ref: str) -> dict: From e39bdabba70199ad2ac7f670ffcfc09f93b020b6 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:35:24 +0200 Subject: [PATCH 06/23] Improve readability even more --- store.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/store.py b/store.py index 1b7cac5..c746757 100644 --- a/store.py +++ b/store.py @@ -212,14 +212,12 @@ def update_from_board(self): board_part["reference"], board_part["value"], board_part["footprint"], + board_part["exclude_from_bom"], + board_part["exclude_from_pos"], ] == [ db_part["reference"], db_part["value"], db_part["footprint"], - ] and [ - board_part["exclude_from_bom"], - board_part["exclude_from_pos"], - ] == [ bool(db_part["exclude_from_bom"]), bool(db_part["exclude_from_pos"]), ]: From ebc624ac310f02885ae48508c5c59e53c6e81670 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:40:56 +0200 Subject: [PATCH 07/23] Imporved readability by using named style query --- store.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/store.py b/store.py index c746757..ad3f0c4 100644 --- a/store.py +++ b/store.py @@ -123,15 +123,8 @@ def update_part(self, part): """Update a part in the database, overwrite lcsc if supplied.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( - "UPDATE part_info set value = ?, footprint = ?, lcsc = ?, exclude_from_bom = ?, exclude_from_pos = ? WHERE reference = ?", - ( - part["value"], - part["footprint"], - part["lcsc"], - part["exclude_from_bom"], - part["exclude_from_pos"], - part["reference"], - ), + "UPDATE part_info set value = :value, footprint = :footprint, lcsc = :lcsc, exclude_from_bom = :exclude_from_bom, exclude_from_pos = :exclude_from_pos WHERE reference = :reference", + part, ) cur.commit() From 72dbeef0176197023e2dd7515b475ced68c51a65 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:45:39 +0200 Subject: [PATCH 08/23] Improved context manager of read_all --- store.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/store.py b/store.py index ad3f0c4..12d5536 100644 --- a/store.py +++ b/store.py @@ -82,15 +82,14 @@ def create_db(self): ) cur.commit() - def read_all(self): + def read_all(self) -> dict: """Read all parts from the database.""" - with contextlib.closing(sqlite3.connect(self.dbfile)) as con: + with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: con.create_collation("naturalsort", natural_sort_collation) con.row_factory = dict_factory - with con as cur: - return cur.execute( - f"SELECT * FROM part_info ORDER BY {self.order_by} COLLATE naturalsort {self.order_dir}" - ).fetchall() + return cur.execute( + f"SELECT * FROM part_info ORDER BY {self.order_by} COLLATE naturalsort {self.order_dir}" + ).fetchall() def read_bom_parts(self): """Read all parts that should be included in the BOM.""" From 0df9fb0e053913ad924986f023c926228ae96ab9 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:46:11 +0200 Subject: [PATCH 09/23] Use named style in create_part for better readability --- store.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store.py b/store.py index 12d5536..1452a76 100644 --- a/store.py +++ b/store.py @@ -112,10 +112,10 @@ def read_pos_parts(self): query = "SELECT reference, value, footprint, lcsc FROM part_info WHERE exclude_from_pos = '0' ORDER BY reference COLLATE naturalsort ASC" return [list(part) for part in cur.execute(query).fetchall()] - def create_part(self, part): + def create_part(self, part: dict): """Create a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: - cur.execute("INSERT INTO part_info VALUES (?,?,?,?,'',?,?)", part) + cur.execute("INSERT INTO part_info VALUES (:reference, :value, :footprint, :lcsc, '', :exclude_from_bom, :exclude_from_pos)", part) cur.commit() def update_part(self, part): From 20a9f41a5c59d13af43491f191af55ec31a5c86c Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:49:19 +0200 Subject: [PATCH 10/23] Add type hints --- store.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/store.py b/store.py index 1452a76..bb9c47d 100644 --- a/store.py +++ b/store.py @@ -41,7 +41,7 @@ def setup(self): Path(self.datadir).mkdir(parents=True, exist_ok=True) self.create_db() - def set_order_by(self, n): + def set_order_by(self, n: int): """Set which value we want to order by when getting data from the database.""" if n > 7: return @@ -118,7 +118,7 @@ def create_part(self, part: dict): cur.execute("INSERT INTO part_info VALUES (:reference, :value, :footprint, :lcsc, '', :exclude_from_bom, :exclude_from_pos)", part) cur.commit() - def update_part(self, part): + def update_part(self, part: dict): """Update a part in the database, overwrite lcsc if supplied.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( @@ -135,13 +135,13 @@ def get_part(self, ref: str) -> dict: "SELECT * FROM part_info WHERE reference=?", (ref,) ).fetchone() - def delete_part(self, ref): + def delete_part(self, ref: str): """Delete a part from the database by its reference.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute("DELETE FROM part_info WHERE reference=?", (ref,)) cur.commit() - def set_stock(self, ref, stock): + def set_stock(self, ref: str, stock: int): """Set the stock value for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( @@ -149,7 +149,7 @@ def set_stock(self, ref, stock): ) cur.commit() - def set_bom(self, ref, state): + def set_bom(self, ref: str, state: int): """Change the BOM attribute for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( @@ -157,7 +157,7 @@ def set_bom(self, ref, state): ) cur.commit() - def set_pos(self, ref, state): + def set_pos(self, ref: str, state: int): """Change the BOM attribute for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( @@ -165,7 +165,7 @@ def set_pos(self, ref, state): ) cur.commit() - def set_lcsc(self, ref, value): + def set_lcsc(self, ref: str, value: str): """Change the BOM attribute for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( From 78e4d96bb8e5bdb0cb76b501d67009fe52f4b33e Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:49:50 +0200 Subject: [PATCH 11/23] Remove unused methods --- store.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/store.py b/store.py index bb9c47d..4b4fd45 100644 --- a/store.py +++ b/store.py @@ -91,26 +91,6 @@ def read_all(self) -> dict: f"SELECT * FROM part_info ORDER BY {self.order_by} COLLATE naturalsort {self.order_dir}" ).fetchall() - def read_bom_parts(self): - """Read all parts that should be included in the BOM.""" - with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: - # Query all parts that are supposed to be in the BOM an have an lcsc number, group the references together - subquery = "SELECT value, reference, footprint, lcsc FROM part_info WHERE exclude_from_bom = '0' AND lcsc != '' ORDER BY lcsc, reference" - query = f"SELECT value, GROUP_CONCAT(reference) AS refs, footprint, lcsc FROM ({subquery}) GROUP BY lcsc" - a = [list(part) for part in cur.execute(query).fetchall()] - # Query all parts that are supposed to be in the BOM but have no lcsc number - query = "SELECT value, reference, footprint, lcsc FROM part_info WHERE exclude_from_bom = '0' AND lcsc = ''" - b = [list(part) for part in cur.execute(query).fetchall()] - return a + b - - def read_pos_parts(self): - """Read all parts that should be included in the POS.""" - with contextlib.closing(sqlite3.connect(self.dbfile)) as con: - con.create_collation("naturalsort", natural_sort_collation) - with con as cur: - # Query all parts that are supposed to be in the POS - query = "SELECT reference, value, footprint, lcsc FROM part_info WHERE exclude_from_pos = '0' ORDER BY reference COLLATE naturalsort ASC" - return [list(part) for part in cur.execute(query).fetchall()] def create_part(self, part: dict): """Create a part in the database.""" From 0dbfa8437508cfb94694d9761810844c1c780f01 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:51:48 +0200 Subject: [PATCH 12/23] Remove one more unused method --- store.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/store.py b/store.py index 4b4fd45..4217b08 100644 --- a/store.py +++ b/store.py @@ -115,11 +115,6 @@ def get_part(self, ref: str) -> dict: "SELECT * FROM part_info WHERE reference=?", (ref,) ).fetchone() - def delete_part(self, ref: str): - """Delete a part from the database by its reference.""" - with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: - cur.execute("DELETE FROM part_info WHERE reference=?", (ref,)) - cur.commit() def set_stock(self, ref: str, stock: int): """Set the stock value for a part in the database.""" From 8cb2834ba413edefb46d6532a5d5a0642da93d52 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:57:33 +0200 Subject: [PATCH 13/23] Use named style in get_part --- store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store.py b/store.py index 4217b08..3ab72e3 100644 --- a/store.py +++ b/store.py @@ -112,7 +112,7 @@ def get_part(self, ref: str) -> dict: with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: con.row_factory = dict_factory return cur.execute( - "SELECT * FROM part_info WHERE reference=?", (ref,) + "SELECT * FROM part_info WHERE reference = :reference", {"reference": ref} ).fetchone() From a902d0ee02b64df5f755eecaf24689b4df27baec Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 13:59:53 +0200 Subject: [PATCH 14/23] Use named style in set_stock --- mainwindow.py | 2 +- store.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mainwindow.py b/mainwindow.py index 8835ce3..3c4bc01 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -554,7 +554,7 @@ def assign_parts(self, e): """Assign a selected LCSC number to parts.""" for reference in e.references: self.store.set_lcsc(reference, e.lcsc) - self.store.set_stock(reference, e.stock) + self.store.set_stock(reference, int(e.stock)) self.populate_footprint_list() def display_message(self, e): diff --git a/store.py b/store.py index 3ab72e3..d6bc290 100644 --- a/store.py +++ b/store.py @@ -119,8 +119,9 @@ def get_part(self, ref: str) -> dict: def set_stock(self, ref: str, stock: int): """Set the stock value for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: + self.logger.debug(ref) cur.execute( - f"UPDATE part_info SET stock = '{int(stock)}' WHERE reference = '{ref}'" + "UPDATE part_info SET stock = :stock WHERE reference = :reference", {"reference": ref, "stock": stock} ) cur.commit() From 99125b1548599bb0eb53d809dff60b47e15e6ca4 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 14:03:52 +0200 Subject: [PATCH 15/23] Use named style in set_bom --- mainwindow.py | 4 ++-- store.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mainwindow.py b/mainwindow.py index 3c4bc01..f4573d1 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -779,7 +779,7 @@ def toggle_bom_pos(self, *_): fp = board.FindFootprintByReference(ref) bom = toggle_exclude_from_bom(fp) pos = toggle_exclude_from_pos(fp) - self.store.set_bom(ref, bom) + self.store.set_bom(ref, int(bom)) self.store.set_pos(ref, pos) self.footprint_list.SetValue( GetListIcon(bom, self.scale_factor), row, Column.BOM @@ -798,7 +798,7 @@ def toggle_bom(self, *_): board = self.pcbnew.GetBoard() fp = board.FindFootprintByReference(ref) bom = toggle_exclude_from_bom(fp) - self.store.set_bom(ref, bom) + self.store.set_bom(ref, int(bom)) self.footprint_list.SetValue( GetListIcon(bom, self.scale_factor), row, Column.BOM ) diff --git a/store.py b/store.py index d6bc290..1d5f768 100644 --- a/store.py +++ b/store.py @@ -129,7 +129,7 @@ def set_bom(self, ref: str, state: int): """Change the BOM attribute for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( - f"UPDATE part_info SET exclude_from_bom = '{int(state)}' WHERE reference = '{ref}'" + "UPDATE part_info SET exclude_from_bom = :state WHERE reference = :reference", {"reference": ref, "state": state} ) cur.commit() From 1fddc875d26cf4ac9eca5755e850a99fd4f83add Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 14:05:46 +0200 Subject: [PATCH 16/23] Use named style in set_pos --- mainwindow.py | 4 ++-- store.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mainwindow.py b/mainwindow.py index f4573d1..f95b91c 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -780,7 +780,7 @@ def toggle_bom_pos(self, *_): bom = toggle_exclude_from_bom(fp) pos = toggle_exclude_from_pos(fp) self.store.set_bom(ref, int(bom)) - self.store.set_pos(ref, pos) + self.store.set_pos(ref, int(pos)) self.footprint_list.SetValue( GetListIcon(bom, self.scale_factor), row, Column.BOM ) @@ -813,7 +813,7 @@ def toggle_pos(self, *_): board = self.pcbnew.GetBoard() fp = board.FindFootprintByReference(ref) pos = toggle_exclude_from_pos(fp) - self.store.set_pos(ref, pos) + self.store.set_pos(ref, int(pos)) self.footprint_list.SetValue( GetListIcon(pos, self.scale_factor), row, Column.POS ) diff --git a/store.py b/store.py index 1d5f768..0961e22 100644 --- a/store.py +++ b/store.py @@ -137,7 +137,7 @@ def set_pos(self, ref: str, state: int): """Change the BOM attribute for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( - f"UPDATE part_info SET exclude_from_pos = '{int(state)}' WHERE reference = '{ref}'" + "UPDATE part_info SET exclude_from_pos = :state WHERE reference = :reference", {"reference": ref, "state": state} ) cur.commit() @@ -243,8 +243,8 @@ def import_legacy_assignments(self): ) for row in csvreader: self.set_lcsc(row["reference"], row["lcsc"]) - self.set_bom(row["reference"], row["bom"]) - self.set_pos(row["reference"], row["pos"]) + self.set_bom(row["reference"], int(row["bom"])) + self.set_pos(row["reference"], int(row["pos"])) self.logger.debug( "Update %s from legacy 'part_assignments.csv'", row["reference"] ) From 374c562277983dfb0f3c951b2ba32abe13b6ca72 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 14:06:58 +0200 Subject: [PATCH 17/23] Use named style in set_lcsc --- store.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store.py b/store.py index 0961e22..6894f0c 100644 --- a/store.py +++ b/store.py @@ -141,11 +141,11 @@ def set_pos(self, ref: str, state: int): ) cur.commit() - def set_lcsc(self, ref: str, value: str): + def set_lcsc(self, ref: str, lcsc: str): """Change the BOM attribute for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: cur.execute( - f"UPDATE part_info SET lcsc = '{value}' WHERE reference = '{ref}'" + "UPDATE part_info SET lcsc = :lcsc WHERE reference = :reference", {"reference": ref, "lcsc": lcsc} ) cur.commit() From d64bd2359c83d0863f2be8694b440158cc9e3328 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 21 Jun 2024 14:09:43 +0200 Subject: [PATCH 18/23] Remove debugging message --- store.py | 1 - 1 file changed, 1 deletion(-) diff --git a/store.py b/store.py index 6894f0c..d148409 100644 --- a/store.py +++ b/store.py @@ -119,7 +119,6 @@ def get_part(self, ref: str) -> dict: def set_stock(self, ref: str, stock: int): """Set the stock value for a part in the database.""" with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: - self.logger.debug(ref) cur.execute( "UPDATE part_info SET stock = :stock WHERE reference = :reference", {"reference": ref, "stock": stock} ) From 06b59d00cecfa4390b07d47bd1ce6eaa5f79f8a0 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 19 Jul 2024 15:54:37 +0200 Subject: [PATCH 19/23] Use dict in generate_cpl --- fabrication.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fabrication.py b/fabrication.py index a2fe62b..74b030d 100644 --- a/fabrication.py +++ b/fabrication.py @@ -273,9 +273,9 @@ def generate_cpl(self): part = self.parent.store.get_part(fp.GetReference()) if not part: # No matching part in the database, continue continue - if part[6] == 1: # Exclude from POS + if part["exclude_from_pos"] == 1: continue - if not add_without_lcsc and not part[3]: + if not add_without_lcsc and not part["lcsc"]: continue try: # Kicad <= 8.0 position = self.get_position(fp) - aux_orgin @@ -285,9 +285,9 @@ def generate_cpl(self): position = VECTOR2I(x1 - x2, y1 - y2) writer.writerow( [ - part[0], - part[1], - part[2], + part["reference"], + part["value"], + part["footprint"], ToMM(position.x), ToMM(position.y) * -1, self.fix_rotation(fp), From 0861e85171f5216a0d1547574371b7b6b583a96a Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 19 Jul 2024 16:12:34 +0200 Subject: [PATCH 20/23] Restore method, make use of dict_factory --- store.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/store.py b/store.py index d148409..257069b 100644 --- a/store.py +++ b/store.py @@ -91,6 +91,18 @@ def read_all(self) -> dict: f"SELECT * FROM part_info ORDER BY {self.order_by} COLLATE naturalsort {self.order_dir}" ).fetchall() + def read_bom_parts(self) -> dict: + """Read all parts that should be included in the BOM.""" + with contextlib.closing(sqlite3.connect(self.dbfile)) as con, con as cur: + con.row_factory = dict_factory + # Query all parts that are supposed to be in the BOM an have an lcsc number, group the references together + subquery = "SELECT value, reference, footprint, lcsc FROM part_info WHERE exclude_from_bom = '0' AND lcsc != '' ORDER BY lcsc, reference" + query = f"SELECT value, GROUP_CONCAT(reference) AS refs, footprint, lcsc FROM ({subquery}) GROUP BY lcsc" + a = cur.execute(query).fetchall() + # Query all parts that are supposed to be in the BOM but have no lcsc number + query = "SELECT value, reference, footprint, lcsc FROM part_info WHERE exclude_from_bom = '0' AND lcsc = ''" + b = cur.execute(query).fetchall() + return a + b def create_part(self, part: dict): """Create a part in the database.""" From b20b82518441d697c8c2ff1ac665861f546c9596 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 19 Jul 2024 16:13:05 +0200 Subject: [PATCH 21/23] use dict in generate_bom --- fabrication.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fabrication.py b/fabrication.py index 74b030d..ce6b092 100644 --- a/fabrication.py +++ b/fabrication.py @@ -310,19 +310,27 @@ def generate_bom(self): writer = csv.writer(csvfile, delimiter=",") writer.writerow(["Comment", "Designator", "Footprint", "LCSC"]) for part in self.parent.store.read_bom_parts(): - components = part[1].split(",") + components = part["refs"].split(",") for component in components: for fp in self.board.Footprints(): if fp.GetReference() == component and fp.IsDNP(): components.remove(component) - part[1] = ",".join(components) + part["refs"] = ",".join(components) self.logger.info( "Component %s has 'Do not placed' enabled: removing from BOM", component, ) - if not add_without_lcsc and not part[3]: + if not add_without_lcsc and not part["lcsc"]: continue - writer.writerow(part) + self.logger.debug(part) + writer.writerow( + [ + part["value"], + part["refs"], + part["footprint"], + part["lcsc"] + ] + ) self.logger.info( "Finished generating BOM file %s", os.path.join(self.outputdir, bomname) ) From e409c162f4340a1e2e29014251a8c7f337cdadfd Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 19 Jul 2024 16:17:24 +0200 Subject: [PATCH 22/23] Fix merge fuckup --- store.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/store.py b/store.py index 257069b..cad8d88 100644 --- a/store.py +++ b/store.py @@ -178,13 +178,6 @@ def update_from_board(self): "Part %s does not exist in the database and will be created from the board.", board_part["reference"], ) -<<<<<<< HEAD - self.create_part(part) - elif ( - part[0:3] == list(dbpart[0:3]) - and part[4:] == [bool(x) for x in dbpart[5:]] - ): # if the board part matches the dbpart except for the LCSC and the stock value, -======= self.create_part(board_part) # if the board part matches the db_part except for the LCSC and the stock value elif [ @@ -200,7 +193,6 @@ def update_from_board(self): bool(db_part["exclude_from_bom"]), bool(db_part["exclude_from_pos"]), ]: ->>>>>>> 2621281 (Improve get_part to use dict_factory for better readability) # if part in the database, has no lcsc value the board part has a lcsc value, update including lcsc if db_part and not db_part["lcsc"] and board_part["lcsc"]: self.logger.debug( From cd001afd7aa0be189ddc5c79118aaefb462ad859 Mon Sep 17 00:00:00 2001 From: bouni Date: Fri, 19 Jul 2024 16:18:11 +0200 Subject: [PATCH 23/23] Remove debug message --- fabrication.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fabrication.py b/fabrication.py index ce6b092..9d4f3af 100644 --- a/fabrication.py +++ b/fabrication.py @@ -322,7 +322,6 @@ def generate_bom(self): ) if not add_without_lcsc and not part["lcsc"]: continue - self.logger.debug(part) writer.writerow( [ part["value"],