From a5735ccff239687cc8a84daddc18aa6800e60115 Mon Sep 17 00:00:00 2001 From: MitjaNemec Date: Tue, 14 May 2024 21:38:51 +0200 Subject: [PATCH] sort footprint text items, refactor some parts fixes #72 --- replicate_layout.py | 122 ++++++++++++++++++++------------------- test_replicate_layout.py | 30 +++++++++- version.txt | 2 +- 3 files changed, 92 insertions(+), 62 deletions(-) diff --git a/replicate_layout.py b/replicate_layout.py index 08218b9..3c95500 100644 --- a/replicate_layout.py +++ b/replicate_layout.py @@ -192,21 +192,31 @@ def parse_schematic_files(self, filename, dict_of_sheets): with open(filename, encoding='utf-8') as f: contents = f.read() - indexes = [] - level = [] + brace_indexes = [] + quote_indexes = [] + brace_level = [] sheet_definitions = [] new_lines = [] - lvl = 0 + brace_lvl = 0 + in_quote = 0 # get the nesting levels at index + # TODO this will fail if there is a single bracket in a sheetname for idx in range(len(contents) - 20): + if contents[idx] == "\"": + quote_indexes.append(idx) + if in_quote: + in_quote = False + else: + in_quote = True + # TODO might want to ignore braces within quotes (in_quote is True) if contents[idx] == "(": - lvl = lvl + 1 - level.append(lvl) - indexes.append(idx) + brace_lvl = brace_lvl + 1 + brace_level.append(brace_lvl) + brace_indexes.append(idx) if contents[idx] == ")": - lvl = lvl - 1 - level.append(lvl) - indexes.append(idx) + brace_lvl = brace_lvl - 1 + brace_level.append(brace_lvl) + brace_indexes.append(idx) if contents[idx] == "\n": new_lines.append(idx) a = contents[idx:idx + 20] @@ -216,7 +226,7 @@ def parse_schematic_files(self, filename, dict_of_sheets): start_idx = sheet_definitions end_idx = sheet_definitions[1:] end_idx.append(len(contents)) - braces = list(zip(indexes, level)) + braces = list(zip(brace_indexes, brace_level)) # parse individual sheet definitions (if any) for start, end in zip(start_idx, end_idx): def next_bigger(l, v): @@ -683,29 +693,7 @@ def get_footprint_text_items(footprint): def get_sheet_anchor_footprint(self, sheet): # get all footprints on this sheet sheet_footprints = self.get_footprints_on_sheet(sheet) - # get anchor footprint - list_of_possible_anchor_footprints = [] - for fp in sheet_footprints: - if fp.fp_id == self.src_anchor_fp.fp_id: - list_of_possible_anchor_footprints.append(fp) - - # if there is only one - if len(list_of_possible_anchor_footprints) == 1: - sheet_anchor_fp = list_of_possible_anchor_footprints[0] - # if there are more then one, we're dealing with multiple hierarchy - # the correct one is the one who's path is the best match to the sheet path - else: - list_of_matches = [] - for fp in list_of_possible_anchor_footprints: - index = list_of_possible_anchor_footprints.index(fp) - matches = 0 - for item in self.src_anchor_fp.sheet_id: - if item in fp.sheet_id: - matches = matches + 1 - list_of_matches.append((index, matches)) - # select the one with most matches - index, _ = max(list_of_matches, key=lambda x: x[1]) - sheet_anchor_fp = list_of_possible_anchor_footprints[index] + sheet_anchor_fp = self.match_fp_in_list(self.src_anchor_fp, sheet_footprints) return sheet_anchor_fp def get_net_pairs(self, sheet): @@ -840,6 +828,35 @@ def find_match_level(netname_a, netname_b): good_match_count = good_match_count + match_ratio return good_match_count / len_nets_2 + @staticmethod + def match_fp_in_list(footprint, fp_list): + # find proper match in source footprints + list_of_possible_dst_footprints = [] + for d_fp in fp_list: + if d_fp.fp_id == footprint.fp_id: + list_of_possible_dst_footprints.append(d_fp) + + # if there is more than one possible anchor, select the correct one + if len(list_of_possible_dst_footprints) == 1: + dst_fp = list_of_possible_dst_footprints[0] + else: + list_of_matches = [] + for fp in list_of_possible_dst_footprints: + index = list_of_possible_dst_footprints.index(fp) + matches = 0 + for item in footprint.sheet_id: + if item in fp.sheet_id: + matches = matches + 1 + list_of_matches.append((index, matches)) + # check if list is empty, if it is, then it is highly likely that schematics and pcb are not in sync + if not list_of_matches: + raise LookupError("Can not find destination footprint for source footprint: " + repr(src_fp.ref) + + "\n" + "Most likely, schematics and PCB are not in sync") + # select the one with most matches + index, _ = max(list_of_matches, key=lambda item: item[1]) + dst_fp = list_of_possible_dst_footprints[index] + return dst_fp + def replicate_footprints(self, settings): logger.info("Replicating footprints") nr_sheets = len(self.dst_sheets) @@ -870,30 +887,7 @@ def replicate_footprints(self, settings): self.update_progress(self.stage, progress, None) # find proper match in source footprints - list_of_possible_dst_footprints = [] - for d_fp in dst_footprints: - if d_fp.fp_id == src_fp.fp_id: - list_of_possible_dst_footprints.append(d_fp) - - # if there is more than one possible anchor, select the correct one - if len(list_of_possible_dst_footprints) == 1: - dst_fp = list_of_possible_dst_footprints[0] - else: - list_of_matches = [] - for fp in list_of_possible_dst_footprints: - index = list_of_possible_dst_footprints.index(fp) - matches = 0 - for item in src_fp.sheet_id: - if item in fp.sheet_id: - matches = matches + 1 - list_of_matches.append((index, matches)) - # check if list is empty, if it is, then it is highly likely that schematics and pcb are not in sync - if not list_of_matches: - raise LookupError("Can not find destination footprint for source footprint: " + repr(src_fp.ref) - + "\n" + "Most likely, schematics and PCB are not in sync") - # select the one with most matches - index, _ = max(list_of_matches, key=lambda item: item[1]) - dst_fp = list_of_possible_dst_footprints[index] + dst_fp = self.match_fp_in_list(src_fp, dst_footprints) # skip locked footprints if dst_fp.fp.IsLocked() is True and self.replicate_locked_footprints is False: @@ -960,8 +954,15 @@ def replicate_footprints(self, settings): # replicate also text layout - also for anchor footprint. I am counting that the user is lazy and will # just position the destination anchors and will not edit them # get footprint text - src_fp_text_items = self.get_footprint_text_items(src_fp) - dst_fp_text_items = self.get_footprint_text_items(dst_fp) + src_text_items = self.get_footprint_text_items(src_fp) + dst_text_items = self.get_footprint_text_items(dst_fp) + + # sort text items, as by default the order is random + src_fp_text_items = sorted(src_text_items, + key=lambda element: (element.GetLayer(), element.GetText())) + dst_fp_text_items = sorted(dst_text_items, + key=lambda element: (element.GetLayer(), element.GetText())) + # check if both footprints (source and the one for replication) have the same number of text items if len(src_fp_text_items) != len(dst_fp_text_items): raise LookupError( @@ -981,6 +982,9 @@ def replicate_footprints(self, settings): delta_angle = dst_fp_orientation - src_fp_orientation dst_text = dst_fp_text_items[txt_index] + logger.info("Src text UUid:" + src_text.m_Uuid.AsString()) + logger.info("Dst text UUid:" + dst_text.m_Uuid.AsString()) + dst_text.SetLayer(src_text.GetLayer()) # set text parameters dst_text.SetAttributes(src_text.GetAttributes()) diff --git a/test_replicate_layout.py b/test_replicate_layout.py index 9067cb0..3b8c537 100644 --- a/test_replicate_layout.py +++ b/test_replicate_layout.py @@ -80,14 +80,40 @@ def test_file(in_filename, test_filename, src_anchor_fp_reference, level, sheets print(f"Make sure that you check the connectivity around:\n" + report_string) print("comparing boards") - return compare_boards(out_filename, test_filename) + #return compare_boards(out_filename, test_filename) +@unittest.skip +class TestBrackets(unittest.TestCase): + def setUp(self): + os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "brackets")) + + def test_inner(self): + logger.info("Testing multiple hierarchy - inner levels") + input_filename = 'replicate_layout_test_project.kicad_pcb' + test_filename = input_filename.split('.')[0] + "_ref_inner" + ".kicad_pcb" + err = test_file(input_filename, test_filename, 'U701', level=1, sheets=(1, 3, 7), + containing=False, remove=False, by_group=True) + self.assertEqual(err, 0, "inner levels failed") + +class TestFpText(unittest.TestCase): + def setUp(self): + os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "bug-demo")) + + def test(self): + logger.info("Testin fp text replication") + input_filename = 'controller-led-matrix.kicad_pcb' + test_filename = input_filename.split('.')[0] + "_ref_inner" + ".kicad_pcb" + err = test_file(input_filename, test_filename, 'U1', level=0, sheets=(0, 1), + containing=False, remove=False, by_group=False) + self.assertEqual(err, 0, "inner levels failed") + + +@unittest.skip class TestOfficial(unittest.TestCase): def setUp(self): os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "replicate_layout_test_project")) - @unittest.skip def test_inner(self): logger.info("Testing multiple hierarchy - inner levels") input_filename = 'replicate_layout_test_project.kicad_pcb' diff --git a/version.txt b/version.txt index d9c62ed..282895a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.2 \ No newline at end of file +3.0.3 \ No newline at end of file