From 389e48035d559cf66865c1fdd7a11df253a5f3c0 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 19 Jul 2020 22:57:05 +0100 Subject: [PATCH 01/30] Add bom ignore option for cables and connectors --- src/wireviz/DataClasses.py | 2 ++ src/wireviz/Harness.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 3ad84d21..6e50d787 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -65,6 +65,7 @@ class Connector: hide_disconnected_pins: bool = False autogenerate: bool = False loops: List[Any] = field(default_factory=list) + ignore_in_bom: bool = False def __post_init__(self): @@ -139,6 +140,7 @@ class Cable: color_code: Optional[str] = None show_name: bool = True show_wirecount: bool = True + ignore_in_bom: bool = False def __post_init__(self): diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 8b401c21..158c602b 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -336,7 +336,7 @@ def bom(self): bom_extra = [] # connectors connector_group = lambda c: (c.type, c.subtype, c.pincount, c.manufacturer, c.mpn, c.pn) - for group in Counter([connector_group(v) for v in self.connectors.values()]): + for group in Counter([connector_group(v) for v in self.connectors.values() if v.ignore_in_bom is not True]): items = {k: v for k, v in self.connectors.items() if connector_group(v) == group} shared = next(iter(items.values())) designators = list(items.keys()) @@ -355,7 +355,7 @@ def bom(self): # TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name? # The category needs to be included in cable_group to keep the bundles excluded. cable_group = lambda c: (c.category, c.type, c.gauge, c.gauge_unit, c.wirecount, c.shield, c.manufacturer, c.mpn, c.pn) - for group in Counter([cable_group(v) for v in self.cables.values() if v.category != 'bundle']): + for group in Counter([cable_group(v) for v in self.cables.values() if v.category != 'bundle' and v.ignore_in_bom is not True]): items = {k: v for k, v in self.cables.items() if cable_group(v) == group} shared = next(iter(items.values())) designators = list(items.keys()) From 84c9db6a8a9795ed20bdbc410e63fc06e5b192a3 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Mon, 20 Jul 2020 02:13:44 +0100 Subject: [PATCH 02/30] Add connector aditional bom components --- src/wireviz/DataClasses.py | 1 + src/wireviz/Harness.py | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 6e50d787..b0540741 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -66,6 +66,7 @@ class Connector: autogenerate: bool = False loops: List[Any] = field(default_factory=list) ignore_in_bom: bool = False + additional_components: List[Any] = None def __post_init__(self): diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 158c602b..be37d2f7 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -332,6 +332,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True def bom(self): bom = [] bom_connectors = [] + bom_connectors_extra = [] bom_cables = [] bom_extra = [] # connectors @@ -351,6 +352,50 @@ def bom(self): bom_connectors.append(item) bom_connectors = sorted(bom_connectors, key=lambda k: k['item']) # https://stackoverflow.com/a/73050 bom.extend(bom_connectors) + + connectors_extra = [] + for connector in self.connectors.values(): + if connector.additional_components: + for part in connector.additional_components: + print(part) + if 'qty' in part: + if isinstance(part['qty'], int) or isinstance(part['qty'], float): + qty = part['qty'] + else: # check for special quantities + if part['qty'] == 'pincount': + qty = connector.pincount + elif part['qty'] == 'connectioncount': + qty = connector.pincount + else: + raise ValueError('invalid aty parameter') + else: + qty = 1 + connectors_extra.append( + { + 'type': part['type'] if 'type' in part else None, + 'qty': qty, + 'unit': part['unit'] if 'unit' in part else None, + 'manufacturer': part['manufacturer'] if 'manufacturer' in part else None, + 'mpn': part['mpn'] if 'mpn' in part else None, + 'pn': part['pn'] if 'pn' in part else None, + 'designator': connector.name + } + ) + connector_extra_group = lambda ce: (ce['type'], ce['qty'], ce['unit'], ce['manufacturer'], ce['mpn'], ce['pn']) + for group in Counter([connector_extra_group(v) for v in connectors_extra]): + items = [v for v in connectors_extra if connector_extra_group(v) == group] + shared = items[0] + designators = [i['designator'] for i in items] + designators = list(dict.fromkeys(designators)) # remove duplicates + designators.sort() + total_qty = sum(i['qty'] for i in items) + + item = {'item': shared['type'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, + 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} + bom_connectors_extra.append(item) + bom_connectors_extra = sorted(bom_connectors_extra, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) + bom.extend(bom_connectors_extra) + # cables # TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name? # The category needs to be included in cable_group to keep the bundles excluded. From 064fbc617d34fdc94533eb456c0cc64e50745bf2 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Mon, 20 Jul 2020 20:33:36 +0100 Subject: [PATCH 03/30] Add support for connectioncount as a qty option --- src/wireviz/Harness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index be37d2f7..9efefe69 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -365,7 +365,7 @@ def bom(self): if part['qty'] == 'pincount': qty = connector.pincount elif part['qty'] == 'connectioncount': - qty = connector.pincount + qty = sum(1 for value in connector.visible_pins.values() if value is True) else: raise ValueError('invalid aty parameter') else: From 6680e5597bbd2edf3c3740611deebfbad3685cba Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Tue, 21 Jul 2020 22:59:56 +0100 Subject: [PATCH 04/30] Clean up construction of connectors_extra list --- src/wireviz/Harness.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 9efefe69..161017e8 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -357,7 +357,6 @@ def bom(self): for connector in self.connectors.values(): if connector.additional_components: for part in connector.additional_components: - print(part) if 'qty' in part: if isinstance(part['qty'], int) or isinstance(part['qty'], float): qty = part['qty'] @@ -372,12 +371,12 @@ def bom(self): qty = 1 connectors_extra.append( { - 'type': part['type'] if 'type' in part else None, + 'type': part.get('type', None), 'qty': qty, - 'unit': part['unit'] if 'unit' in part else None, - 'manufacturer': part['manufacturer'] if 'manufacturer' in part else None, - 'mpn': part['mpn'] if 'mpn' in part else None, - 'pn': part['pn'] if 'pn' in part else None, + 'unit': part.get('unit', None), + 'manufacturer': part.get('manufacturer', None), + 'mpn': part.get('mpn', None), + 'pn': part.get('pn', None), 'designator': connector.name } ) From 9a1bc20ace8d0d181ef1ea4656a392ab55542942 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Tue, 21 Jul 2020 23:00:25 +0100 Subject: [PATCH 05/30] Add extra components to connector nodes in graph --- src/wireviz/Harness.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 161017e8..1a0e2a4e 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -99,8 +99,29 @@ def create_graph(self) -> Graph: connector.color, html_colorbar(connector.color)], '' if connector.style != 'simple' else None, [html_image(connector.image)], - [html_caption(connector.image)], - [html_line_breaks(connector.notes)]] + [html_caption(connector.image)]] + if connector.additional_components is not None: + rows.append(["Additional components"]) + for extra in connector.additional_components: + if 'qty' in extra: + if isinstance(extra['qty'], int) or isinstance(extra['qty'], float): + qty = extra['qty'] + else: # check for special quantities + if extra['qty'] == 'pincount': + qty = connector.pincount + elif extra['qty'] == 'connectioncount': + qty = sum(1 for value in connector.visible_pins.values() if value is True) + else: + raise ValueError('invalid aty parameter') + else: + qty = 1 + rows.append([extra["type"], qty]) + rows.append([extra["manufacturer"], + f'MPN: {extra["manufacturer_part_number"]}' if "manufacturer_part_number" in extra else None, + f'IPN: {extra["internal_part_number"]}' if "internal_part_number" in extra else None],) + rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, + html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) + rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) if connector.style != 'simple': From 1a8942abaa7809ff42fbaed86d42545337e1aded Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Wed, 22 Jul 2020 00:02:43 +0100 Subject: [PATCH 06/30] Add support for cables to have extra components --- src/wireviz/DataClasses.py | 1 + src/wireviz/Harness.py | 109 ++++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index b0540741..51a4a99c 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -142,6 +142,7 @@ class Cable: show_name: bool = True show_wirecount: bool = True ignore_in_bom: bool = False + additional_components: List[Any] = None def __post_init__(self): diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 1a0e2a4e..1a02538a 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -102,25 +102,22 @@ def create_graph(self) -> Graph: [html_caption(connector.image)]] if connector.additional_components is not None: rows.append(["Additional components"]) - for extra in connector.additional_components: - if 'qty' in extra: - if isinstance(extra['qty'], int) or isinstance(extra['qty'], float): - qty = extra['qty'] - else: # check for special quantities - if extra['qty'] == 'pincount': - qty = connector.pincount - elif extra['qty'] == 'connectioncount': - qty = sum(1 for value in connector.visible_pins.values() if value is True) - else: - raise ValueError('invalid aty parameter') - else: - qty = 1 - rows.append([extra["type"], qty]) - rows.append([extra["manufacturer"], - f'MPN: {extra["manufacturer_part_number"]}' if "manufacturer_part_number" in extra else None, - f'IPN: {extra["internal_part_number"]}' if "internal_part_number" in extra else None],) - rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, - html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) + for extra in connector.additional_components: + if 'qty' in extra: + if isinstance(extra['qty'], int) or isinstance(extra['qty'], float): + qty = extra['qty'] + else: # check for special quantities + if extra['qty'] == 'pincount': + qty = connector.pincount + elif extra['qty'] == 'connectioncount': + qty = sum(1 for value in connector.visible_pins.values() if value is True) + else: + raise ValueError('invalid aty parameter') + else: + qty = 1 + rows.append([extra["type"], qty]) + rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, + html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -193,8 +190,31 @@ def create_graph(self) -> Graph: cable.color, html_colorbar(cable.color)], '', [html_image(cable.image)], - [html_caption(cable.image)], - [html_line_breaks(cable.notes)]] + [html_caption(cable.image)]] + + if cable.additional_components is not None: + rows.append(["Additional components"]) + for extra in cable.additional_components: + if 'qty' in extra: + if isinstance(extra['qty'], int) or isinstance(extra['qty'], float): + qty = extra['qty'] + else: # check for special quantities + if extra['qty'] == 'wirecount': + qty = cable.wirecount + elif extra['qty'] == 'terminations': + qty = len(cable.connections) + elif extra['qty'] == 'length': + qty = cable.length + elif extra['qty'] == 'total_length': + qty = cable.length * cable.wirecount + else: + raise ValueError('invalid aty parameter {}'.format(extra["qty"])) + else: + qty = 1 + rows.append([extra["type"], qty]) + rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, + html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) + rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) wirehtml = [] @@ -355,6 +375,7 @@ def bom(self): bom_connectors = [] bom_connectors_extra = [] bom_cables = [] + bom_cables_extra = [] bom_extra = [] # connectors connector_group = lambda c: (c.type, c.subtype, c.pincount, c.manufacturer, c.mpn, c.pn) @@ -463,6 +484,52 @@ def bom(self): bom_cables = sorted(bom_cables, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) bom.extend(bom_cables) + cables_extra = [] + for cable in self.cables.values(): + if cable.additional_components: + for part in cable.additional_components: + if 'qty' in part: + if isinstance(part['qty'], int) or isinstance(part['qty'], float): + qty = part['qty'] + else: # check for special quantities + if part['qty'] == 'wirecount': + qty = cable.wirecount + elif part['qty'] == 'terminations': + qty = len(cable.connections) + elif part['qty'] == 'length': + qty = cable.length + elif part['qty'] == 'total_length': + qty = cable.length * cable.wirecount + else: + raise ValueError('invalid aty parameter') + else: + qty = 1 + cables_extra.append( + { + 'type': part.get('type', None), + 'qty': qty, + 'unit': part.get('unit', None), + 'manufacturer': part.get('manufacturer', None), + 'mpn': part.get('mpn', None), + 'pn': part.get('pn', None), + 'designator': connector.name + } + ) + cables_extra_group = lambda ce: (ce['type'], ce['qty'], ce['unit'], ce['manufacturer'], ce['mpn'], ce['pn']) + for group in Counter([connector_extra_group(v) for v in cables_extra]): + items = [v for v in cables_extra if connector_extra_group(v) == group] + shared = items[0] + designators = [i['designator'] for i in items] + designators = list(dict.fromkeys(designators)) # remove duplicates + designators.sort() + total_qty = sum(i['qty'] for i in items) + + item = {'item': shared['type'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, + 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} + bom_cables_extra.append(item) + bom_cables_extra = sorted(bom_cables_extra, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) + bom.extend(bom_cables_extra) + for item in self.additional_bom_items: name = item['description'] if item.get('description', None) else '' if isinstance(item.get('designators', None), List): From 94d6fc9015bc0ba7bcb4c8d67088c55ad9854840 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Fri, 18 Sep 2020 00:02:20 +0100 Subject: [PATCH 07/30] Add units to graph output --- src/wireviz/Harness.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 1a02538a..39efd06b 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -112,10 +112,10 @@ def create_graph(self) -> Graph: elif extra['qty'] == 'connectioncount': qty = sum(1 for value in connector.visible_pins.values() if value is True) else: - raise ValueError('invalid aty parameter') + raise ValueError('invalid qty parameter {}'.format(extra["qty"])) else: qty = 1 - rows.append([extra["type"], qty]) + rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) rows.append([html_line_breaks(connector.notes)]) @@ -208,10 +208,10 @@ def create_graph(self) -> Graph: elif extra['qty'] == 'total_length': qty = cable.length * cable.wirecount else: - raise ValueError('invalid aty parameter {}'.format(extra["qty"])) + raise ValueError('invalid qty parameter {}'.format(extra["qty"])) else: qty = 1 - rows.append([extra["type"], qty]) + rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) rows.append([html_line_breaks(cable.notes)]) From bb37b37d2865ce80ceede490d4d53861c5e1f125 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Fri, 18 Sep 2020 00:37:08 +0100 Subject: [PATCH 08/30] Split qty into multiple fields There is now a numberic field 'qty' and a multiplier field 'qty_multiplier' --- src/wireviz/Harness.py | 96 ++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 56 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 39efd06b..efd25c40 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -103,18 +103,14 @@ def create_graph(self) -> Graph: if connector.additional_components is not None: rows.append(["Additional components"]) for extra in connector.additional_components: - if 'qty' in extra: - if isinstance(extra['qty'], int) or isinstance(extra['qty'], float): - qty = extra['qty'] - else: # check for special quantities - if extra['qty'] == 'pincount': - qty = connector.pincount - elif extra['qty'] == 'connectioncount': - qty = sum(1 for value in connector.visible_pins.values() if value is True) - else: - raise ValueError('invalid qty parameter {}'.format(extra["qty"])) - else: - qty = 1 + qty = extra.get('qty', 1) + if 'qty_multiplier' in extra: + if extra['qty_multiplier'] == 'pincount': + qty = connector.pincount + elif extra['qty_multiplier'] == 'populated': + qty = sum(1 for value in connector.visible_pins.values() if value is True) + else: + raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier'"])) rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) @@ -195,22 +191,18 @@ def create_graph(self) -> Graph: if cable.additional_components is not None: rows.append(["Additional components"]) for extra in cable.additional_components: - if 'qty' in extra: - if isinstance(extra['qty'], int) or isinstance(extra['qty'], float): - qty = extra['qty'] - else: # check for special quantities - if extra['qty'] == 'wirecount': - qty = cable.wirecount - elif extra['qty'] == 'terminations': - qty = len(cable.connections) - elif extra['qty'] == 'length': - qty = cable.length - elif extra['qty'] == 'total_length': - qty = cable.length * cable.wirecount - else: - raise ValueError('invalid qty parameter {}'.format(extra["qty"])) - else: - qty = 1 + qty = extra.get('qty', 1) + if 'qty_multiplier' in extra: + if extra['qty_multiplier'] == 'wirecount': + qty *= cable.wirecount + elif extra['qty_multiplier'] == 'terminations': + qty *= len(cable.connections) + elif extra['qty_multiplier'] == 'length': + qty *= cable.length + elif extra['qty_multiplier'] == 'total_length': + qty *= cable.length * cable.wirecount + else: + raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) @@ -399,18 +391,14 @@ def bom(self): for connector in self.connectors.values(): if connector.additional_components: for part in connector.additional_components: - if 'qty' in part: - if isinstance(part['qty'], int) or isinstance(part['qty'], float): - qty = part['qty'] - else: # check for special quantities - if part['qty'] == 'pincount': - qty = connector.pincount - elif part['qty'] == 'connectioncount': - qty = sum(1 for value in connector.visible_pins.values() if value is True) - else: - raise ValueError('invalid aty parameter') - else: - qty = 1 + qty = part.get('qty', 1) + if 'qty_multiplier' in part: + if part['qty_multiplier'] == 'pincount': + qty = connector.pincount + elif part['qty_multiplier'] == 'populated': + qty = sum(1 for value in connector.visible_pins.values() if value is True) + else: + raise ValueError('invalid qty parameter {}'.format(part["qty_multiplier'"])) connectors_extra.append( { 'type': part.get('type', None), @@ -488,22 +476,18 @@ def bom(self): for cable in self.cables.values(): if cable.additional_components: for part in cable.additional_components: - if 'qty' in part: - if isinstance(part['qty'], int) or isinstance(part['qty'], float): - qty = part['qty'] - else: # check for special quantities - if part['qty'] == 'wirecount': - qty = cable.wirecount - elif part['qty'] == 'terminations': - qty = len(cable.connections) - elif part['qty'] == 'length': - qty = cable.length - elif part['qty'] == 'total_length': - qty = cable.length * cable.wirecount - else: - raise ValueError('invalid aty parameter') - else: - qty = 1 + qty = part.get('qty', 1) + if 'qty_multiplier' in part: + if part['qty_multiplier'] == 'wirecount': + qty *= cable.wirecount + elif part['qty_multiplier'] == 'terminations': + qty *= len(cable.connections) + elif part['qty_multiplier'] == 'length': + qty *= cable.length + elif part['qty_multiplier'] == 'total_length': + qty *= cable.length * cable.wirecount + else: + raise ValueError('invalid qty parameter {}'.format(part["qty_multiplier"])) cables_extra.append( { 'type': part.get('type', None), From 668ee0aa9e9e368c51b431520cfedd7a83a4ce36 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 20 Sep 2020 00:31:38 +0100 Subject: [PATCH 09/30] Fix wrong designation in bom for cable additions --- src/wireviz/Harness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index efd25c40..800646ad 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -496,7 +496,7 @@ def bom(self): 'manufacturer': part.get('manufacturer', None), 'mpn': part.get('mpn', None), 'pn': part.get('pn', None), - 'designator': connector.name + 'designator': cable.name } ) cables_extra_group = lambda ce: (ce['type'], ce['qty'], ce['unit'], ce['manufacturer'], ce['mpn'], ce['pn']) From e852df5fd0d13c258986b885b9935171d44e4b80 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 20 Sep 2020 00:39:59 +0100 Subject: [PATCH 10/30] Fix bad check for pn being set --- src/wireviz/Harness.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 800646ad..e578973a 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -112,7 +112,7 @@ def create_graph(self) -> Graph: else: raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier'"])) rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) - rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, + rows.append([f'P/N: {extra["pn"]}' if "pn" in extra else None, html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -204,7 +204,7 @@ def create_graph(self) -> Graph: else: raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) - rows.append([f'P/N: {extra["pn"]}' if extra["pn"] else None, + rows.append([f'P/N: {extra["pn"]}' if "pn" in extra else None, html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) From 22e6fb5c3541952a39ed6101cf80fa729b3a0da6 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 20 Sep 2020 01:31:15 +0100 Subject: [PATCH 11/30] Fix conector additions to multiply quantity rather than replace it --- src/wireviz/Harness.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index e578973a..70cf561e 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -106,9 +106,9 @@ def create_graph(self) -> Graph: qty = extra.get('qty', 1) if 'qty_multiplier' in extra: if extra['qty_multiplier'] == 'pincount': - qty = connector.pincount + qty *= connector.pincount elif extra['qty_multiplier'] == 'populated': - qty = sum(1 for value in connector.visible_pins.values() if value is True) + qty *= sum(1 for value in connector.visible_pins.values() if value is True) else: raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier'"])) rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) From 983c322bf8354c3f4ad591c7115f0fc0b24de0bb Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 20 Sep 2020 01:50:47 +0100 Subject: [PATCH 12/30] Add error handling for missing type in additional components. --- src/wireviz/DataClasses.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 51a4a99c..b024e18e 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -116,6 +116,10 @@ def __post_init__(self): if len(loop) != 2: raise Exception('Loops must be between exactly two pins!') + for additional_component in self.additional_components: + if 'type' not in additional_component: + raise Exception('Additional components must have a type specified') + def activate_pin(self, pin): self.visible_pins[pin] = True @@ -200,6 +204,10 @@ def __post_init__(self): else: raise Exception('lists of part data are only supported for bundles') + for additional_component in self.additional_components: + if 'type' not in additional_component: + raise Exception('Additional components must have a type specified') + def connect(self, from_name, from_pin, via_pin, to_name, to_pin): from_pin = int2tuple(from_pin) From 0e929fe85a319af4d0f7a8fbbdd661931e8a86cd Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 20 Sep 2020 01:52:20 +0100 Subject: [PATCH 13/30] Reformat aditional component rendering in conector and cable nodes --- src/wireviz/Harness.py | 12 ++++-------- src/wireviz/wv_helper.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 70cf561e..62dcdc00 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -8,7 +8,7 @@ from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, \ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write, \ - html_colorbar, html_image, html_caption, manufacturer_info_field + html_colorbar, html_image, html_caption, manufacturer_info_field, component_table_entry from collections import Counter from typing import List from pathlib import Path @@ -110,10 +110,8 @@ def create_graph(self) -> Graph: elif extra['qty_multiplier'] == 'populated': qty *= sum(1 for value in connector.visible_pins.values() if value is True) else: - raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier'"])) - rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) - rows.append([f'P/N: {extra["pn"]}' if "pn" in extra else None, - html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) + raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) + rows.append(html_line_breaks(component_table_entry(extra.get["type"], qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -203,9 +201,7 @@ def create_graph(self) -> Graph: qty *= cable.length * cable.wirecount else: raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) - rows.append([extra["type"], f'{qty} {extra.get("unit", "")}'.strip()]) - rows.append([f'P/N: {extra["pn"]}' if "pn" in extra else None, - html_line_breaks(manufacturer_info_field(extra.get("manufacturer", None), extra.get("mpn", None)))]) + rows.append(html_line_breaks(component_table_entry(extra["type"], qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 8385f7e9..cf02d5a6 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -181,3 +181,22 @@ def manufacturer_info_field(manufacturer, mpn): return f'{manufacturer if manufacturer else "MPN"}{": " + str(mpn) if mpn else ""}' else: return None + +def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn=None): + output = f'{qty}' + if unit: + output += f' {unit}' + output += f' x {type}' + # print an extra line with part and manufacturer information if provided + manufacturer_str = manufacturer_info_field(manufacturer, mpn) + if pn or manufacturer_str: + output += '
' + if pn: + output += f'P/N: {pn}' + if manufacturer_str: + output += ', ' + if manufacturer_str: + output += manufacturer_str + # format the above output as left aligned text in a single visable cell + output = f'
{output}
' + return output From 038938cc278fff3bff919501b6d902e4e6dfc97e Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 20 Sep 2020 01:57:33 +0100 Subject: [PATCH 14/30] Fix bugs from the the error handling additions. --- src/wireviz/DataClasses.py | 14 ++++++++------ src/wireviz/Harness.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index b024e18e..bb1c2e17 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -116,9 +116,10 @@ def __post_init__(self): if len(loop) != 2: raise Exception('Loops must be between exactly two pins!') - for additional_component in self.additional_components: - if 'type' not in additional_component: - raise Exception('Additional components must have a type specified') + if self.additional_components: + for additional_component in self.additional_components: + if 'type' not in additional_component: + raise Exception('Additional components must have a type specified') def activate_pin(self, pin): self.visible_pins[pin] = True @@ -204,9 +205,10 @@ def __post_init__(self): else: raise Exception('lists of part data are only supported for bundles') - for additional_component in self.additional_components: - if 'type' not in additional_component: - raise Exception('Additional components must have a type specified') + if self.additional_components: + for additional_component in self.additional_components: + if 'type' not in additional_component: + raise Exception('Additional components must have a type specified') def connect(self, from_name, from_pin, via_pin, to_name, to_pin): diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 62dcdc00..bf907ea7 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -111,7 +111,7 @@ def create_graph(self) -> Graph: qty *= sum(1 for value in connector.visible_pins.values() if value is True) else: raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) - rows.append(html_line_breaks(component_table_entry(extra.get["type"], qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) + rows.append(html_line_breaks(component_table_entry(extra["type"], qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) From 3e1e9b7e6d8299b729893973750cb6dd384fb24a Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sun, 20 Sep 2020 02:21:04 +0100 Subject: [PATCH 15/30] remove bom designators for connector extras with show_name=false This matches the behaviour of the connectors themselves --- src/wireviz/Harness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index bf907ea7..db080e6e 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -403,7 +403,7 @@ def bom(self): 'manufacturer': part.get('manufacturer', None), 'mpn': part.get('mpn', None), 'pn': part.get('pn', None), - 'designator': connector.name + 'designator': connector.name if connector.show_name else '' } ) connector_extra_group = lambda ce: (ce['type'], ce['qty'], ce['unit'], ce['manufacturer'], ce['mpn'], ce['pn']) From c6c54b0c4e93b5dd2d2fcbb3d4519c6122bbd947 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sat, 26 Sep 2020 14:32:03 +0100 Subject: [PATCH 16/30] Add Bom referenced parts lists for aditional components Bom generation has been refactored to add an ID field and remove duplicates. This is used to lookup adidiontal components and a lookup reference and short description is now included in the graph rather than the full details. --- src/wireviz/Harness.py | 124 ++++++++++++++++++++------------------- src/wireviz/wv_helper.py | 6 ++ 2 files changed, 70 insertions(+), 60 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index db080e6e..cec804fe 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -8,7 +8,8 @@ from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, \ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write, \ - html_colorbar, html_image, html_caption, manufacturer_info_field, component_table_entry + html_colorbar, html_image, html_caption, manufacturer_info_field, \ + component_table_entry, extra_component_long_name from collections import Counter from typing import List from pathlib import Path @@ -19,8 +20,10 @@ class Harness: def __init__(self): self.color_mode = 'SHORT' + self.mini_bom_mode = True self.connectors = {} self.cables = {} + self._bom = [] # Internal Cache for generated bom self.additional_bom_items = [] def add_connector(self, name: str, *args, **kwargs) -> None: @@ -111,7 +114,11 @@ def create_graph(self) -> Graph: qty *= sum(1 for value in connector.visible_pins.values() if value is True) else: raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) - rows.append(html_line_breaks(component_table_entry(extra["type"], qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) + if(self.mini_bom_mode): + id = self.get_bom_index(extra_component_long_name(extra["type"], extra.get("subtype", None)), extra.get("unit", None), extra.get("manufacturer", None), extra.get("mpn", None), extra.get("pn", None)) + rows.append(html_line_breaks(component_table_entry(f'{id} ({extra["type"].capitalize()})', qty, extra.get("unit", None), None, None, None))) + else: + rows.append(html_line_breaks(extra_component_long_name(extra["type"], extra.get("subtype", None)), qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None))) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -201,7 +208,11 @@ def create_graph(self) -> Graph: qty *= cable.length * cable.wirecount else: raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) - rows.append(html_line_breaks(component_table_entry(extra["type"], qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) + if(self.mini_bom_mode): + id = self.get_bom_index(extra_component_long_name(extra["type"], extra.get("subtype", None)), extra.get("unit", None), extra.get("manufacturer", None), extra.get("mpn", None), extra.get("pn", None)) + rows.append(html_line_breaks(component_table_entry(f'{id} ({extra["type"].capitalize()})', qty, extra.get("unit", None), None, None, None))) + else: + rows.append(html_line_breaks(component_table_entry(extra_component_long_name(extra["type"], extra.get("subtype", None)), qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) @@ -359,12 +370,11 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True file.write('') def bom(self): - bom = [] - bom_connectors = [] - bom_connectors_extra = [] - bom_cables = [] - bom_cables_extra = [] - bom_extra = [] + # if the bom has previously been generated then return the generated bom + if len(self._bom) > 0: + return self._bom + bom_items = [] + # connectors connector_group = lambda c: (c.type, c.subtype, c.pincount, c.manufacturer, c.mpn, c.pn) for group in Counter([connector_group(v) for v in self.connectors.values() if v.ignore_in_bom is not True]): @@ -377,13 +387,10 @@ def bom(self): conn_pincount = f', {shared.pincount} pins' if shared.style != 'simple' else '' conn_color = f', {shared.color}' if shared.color else '' name = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}' - item = {'item': name, 'qty': len(designators), 'unit': '', 'designators': designators if shared.show_name else '', + item = {'item': name, 'qty': len(designators), 'unit': '', 'designators': designators if shared.show_name else None, 'manufacturer': remove_line_breaks(shared.manufacturer), 'mpn': remove_line_breaks(shared.mpn), 'pn': shared.pn} - bom_connectors.append(item) - bom_connectors = sorted(bom_connectors, key=lambda k: k['item']) # https://stackoverflow.com/a/73050 - bom.extend(bom_connectors) + bom_items.append(item) - connectors_extra = [] for connector in self.connectors.values(): if connector.additional_components: for part in connector.additional_components: @@ -395,31 +402,17 @@ def bom(self): qty = sum(1 for value in connector.visible_pins.values() if value is True) else: raise ValueError('invalid qty parameter {}'.format(part["qty_multiplier'"])) - connectors_extra.append( + bom_items.append( { - 'type': part.get('type', None), + 'item': extra_component_long_name(part["type"], part.get("subtype", None)), 'qty': qty, 'unit': part.get('unit', None), 'manufacturer': part.get('manufacturer', None), 'mpn': part.get('mpn', None), 'pn': part.get('pn', None), - 'designator': connector.name if connector.show_name else '' + 'designators': connector.name if connector.show_name else None } ) - connector_extra_group = lambda ce: (ce['type'], ce['qty'], ce['unit'], ce['manufacturer'], ce['mpn'], ce['pn']) - for group in Counter([connector_extra_group(v) for v in connectors_extra]): - items = [v for v in connectors_extra if connector_extra_group(v) == group] - shared = items[0] - designators = [i['designator'] for i in items] - designators = list(dict.fromkeys(designators)) # remove duplicates - designators.sort() - total_qty = sum(i['qty'] for i in items) - - item = {'item': shared['type'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, - 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} - bom_connectors_extra.append(item) - bom_connectors_extra = sorted(bom_connectors_extra, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) - bom.extend(bom_connectors_extra) # cables # TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name? @@ -437,7 +430,7 @@ def bom(self): name = f'Cable{cable_type}, {shared.wirecount}{gauge_name}{shield_name}' item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators, 'manufacturer': remove_line_breaks(shared.manufacturer), 'mpn': remove_line_breaks(shared.mpn), 'pn': shared.pn} - bom_cables.append(item) + bom_items.append(item) # bundles (ignores wirecount) wirelist = [] # list all cables again, since bundles are represented as wires internally, with the category='bundle' set @@ -464,11 +457,8 @@ def bom(self): name = f'Wire{wire_type}{gauge_name}{gauge_color}' item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators, 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} - bom_cables.append(item) - bom_cables = sorted(bom_cables, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) - bom.extend(bom_cables) + bom_items.append(item) - cables_extra = [] for cable in self.cables.values(): if cable.additional_components: for part in cable.additional_components: @@ -484,46 +474,60 @@ def bom(self): qty *= cable.length * cable.wirecount else: raise ValueError('invalid qty parameter {}'.format(part["qty_multiplier"])) - cables_extra.append( + bom_items.append( { - 'type': part.get('type', None), + 'item': extra_component_long_name(part["type"], part.get("subtype", None)), 'qty': qty, 'unit': part.get('unit', None), 'manufacturer': part.get('manufacturer', None), 'mpn': part.get('mpn', None), 'pn': part.get('pn', None), - 'designator': cable.name + 'designators': cable.name } ) - cables_extra_group = lambda ce: (ce['type'], ce['qty'], ce['unit'], ce['manufacturer'], ce['mpn'], ce['pn']) - for group in Counter([connector_extra_group(v) for v in cables_extra]): - items = [v for v in cables_extra if connector_extra_group(v) == group] - shared = items[0] - designators = [i['designator'] for i in items] - designators = list(dict.fromkeys(designators)) # remove duplicates - designators.sort() - total_qty = sum(i['qty'] for i in items) - - item = {'item': shared['type'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, - 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} - bom_cables_extra.append(item) - bom_cables_extra = sorted(bom_cables_extra, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) - bom.extend(bom_cables_extra) for item in self.additional_bom_items: name = item['description'] if item.get('description', None) else '' - if isinstance(item.get('designators', None), List): - item['designators'].sort() # sort designators if a list is provided item = {'item': name, 'qty': item.get('qty', None), 'unit': item.get('unit', None), 'designators': item.get('designators', None), 'manufacturer': item.get('manufacturer', None), 'mpn': item.get('mpn', None), 'pn': item.get('pn', None)} - bom_extra.append(item) - bom_extra = sorted(bom_extra, key=lambda k: k['item']) - bom.extend(bom_extra) - return bom + bom_items.append(item) + + # deduplicate bom + bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn']) + for group in Counter([bom_types_group(v) for v in bom_items]): + items = [v for v in bom_items if bom_types_group(v) == group] + shared = items[0] + designators = [] + for item in items: + if "designators" in item and item['designators']: + if isinstance(item['designators'], List): + designators.extend(item['designators']) + else: + designators.append(item['designators']) + designators = list(dict.fromkeys(designators)) # remove duplicates + designators.sort() + total_qty = sum(i['qty'] for i in items) + item = {'item': shared['item'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, + 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} + self._bom.append(item) + + self._bom = sorted(self._bom, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) + # add index + index = 1 + for item in self._bom: + item["id"] = index + index += 1 + return self._bom + + def get_bom_index(self, item, unit, manufacturer, mpn, pn): + for bom_item in self.bom(): + if((bom_item['item'], bom_item['unit'], bom_item['manufacturer'], bom_item['mpn'], bom_item['pn']) == (item, unit, manufacturer, mpn, pn)): + return bom_item['id'] + return None def bom_list(self): bom = self.bom() - keys = ['item', 'qty', 'unit', 'designators'] # these BOM columns will always be included + keys = ['id', 'item', 'qty', 'unit', 'designators'] # these BOM columns will always be included for fieldname in ['pn', 'manufacturer', 'mpn']: # these optional BOM columns will only be included if at least one BOM item actually uses them if any(fieldname in x and x.get(fieldname, None) for x in bom): keys.append(fieldname) diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index cf02d5a6..92d5c19e 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -200,3 +200,9 @@ def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn= # format the above output as left aligned text in a single visable cell output = f'
{output}
' return output + + +def extra_component_long_name(type, subtype): + name_subtype = f', {remove_line_breaks(subtype)}' if subtype else '' + name = f'{type.capitalize()}{name_subtype}' + return name From 5170631cc59dfbc2e6a77be2e49d64fa7f4c439d Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sat, 26 Sep 2020 15:00:30 +0100 Subject: [PATCH 17/30] Update Tutorial 08 sources to include additional components --- tutorial/tutorial08.md | 5 +++-- tutorial/tutorial08.yml | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tutorial/tutorial08.md b/tutorial/tutorial08.md index 1fc884e7..87298ab4 100644 --- a/tutorial/tutorial08.md +++ b/tutorial/tutorial08.md @@ -1,6 +1,7 @@ -## Part numbers +## Part numbers and additional components * Part number information can be added to parts * Only provided fields will be added to the diagram and bom * Bundles can have part information specified by wire -* Additional parts can be added to the bom +* Additional parts can be added to components or just to the bom + * quantities of additional components can be multiplied by features from parent connector or cable diff --git a/tutorial/tutorial08.yml b/tutorial/tutorial08.yml index 2568f29b..27cd3102 100644 --- a/tutorial/tutorial08.yml +++ b/tutorial/tutorial08.yml @@ -5,9 +5,17 @@ connectors: subtype: female manufacturer: Molex # set manufacter name mpn: 22013047 # set manufacturer part number + # add a list of additional components to a part (shown in graph) + additional_components: + - + type: Crimp # short identifier used in graph + subtype: Molex KK 254, 22-30 AWG # extra information added to type in bom + qty_multiplier: populated # multipier for quantity (number of populated pins) + manufacturer: Molex # set manufacter name + mpn: 08500030 # set manufacturer part number X2: <<: *template1 # reuse template - pn: CON4 # set an internal part number + pn: CON4 # set an internal part number for just this connector X3: <<: *template1 # reuse template @@ -28,6 +36,14 @@ cables: manufacturer: [WiresCo,WiresCo,WiresCo,WiresCo] # set a manufacter per wire mpn: [W1-YE,W1-BK,W1-BK,W1-RD] pn: [WIRE1,WIRE2,WIRE2,WIRE3] + # add a list of additional components to a part (shown in graph) + additional_components: + - + type: Sleve # short identifier used in graph + subtype: Braided nylon, black, 3mm # extra information added to type in bom + qty_multiplier: length # multipier for quantity (length of cable) + unit: m + pn: SLV-1 connections: @@ -41,7 +57,7 @@ connections: - X3: [1-4] additional_bom_items: - - # define an additional item to add to the bill of materials + - # define an additional item to add to the bill of materials (does not appear in graph) description: Label, pinout information qty: 2 designators: From 7c67a9e0049d6121ab8e9066fae4c5e8a02d5e69 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Tue, 29 Sep 2020 20:56:29 +0100 Subject: [PATCH 18/30] Implement suggested code cleanup Moved aditional components from dicts to a new dataclass Update dict get refrences to class members Simplify Connector and Cable bom generation (deduplication is now handled with the additional components) Other small sugested cleanups --- src/wireviz/DataClasses.py | 34 ++++-- src/wireviz/Harness.py | 229 ++++++++++++++++--------------------- src/wireviz/wv_helper.py | 6 - 3 files changed, 123 insertions(+), 146 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index bb1c2e17..13869022 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -43,6 +43,21 @@ def __post_init__(self, gv_dir): if self.width: self.height = self.width / aspect_ratio(gv_dir.joinpath(self.src)) +@dataclass +class AdditionalComponent: + type: str + subtype: Optional[str] = None + manufacturer: Optional[str] = None + mpn: Optional[str] = None + pn: Optional[str] = None + qty: float = 1 + unit: Optional[str] = None + qty_multiplier: Optional[str] = None + + def long_name(self) -> str: + name_subtype = f', {self.subtype}' if self.subtype else '' + return f'{self.type.capitalize()}{name_subtype}' + @dataclass class Connector: @@ -66,7 +81,7 @@ class Connector: autogenerate: bool = False loops: List[Any] = field(default_factory=list) ignore_in_bom: bool = False - additional_components: List[Any] = None + additional_components: List[AdditionalComponent] = field(default_factory=list) def __post_init__(self): @@ -116,10 +131,9 @@ def __post_init__(self): if len(loop) != 2: raise Exception('Loops must be between exactly two pins!') - if self.additional_components: - for additional_component in self.additional_components: - if 'type' not in additional_component: - raise Exception('Additional components must have a type specified') + for i, item in enumerate(self.additional_components): + if isinstance(item, dict): + self.additional_components[i] = AdditionalComponent(**item) def activate_pin(self, pin): self.visible_pins[pin] = True @@ -147,7 +161,7 @@ class Cable: show_name: bool = True show_wirecount: bool = True ignore_in_bom: bool = False - additional_components: List[Any] = None + additional_components: List[AdditionalComponent] = field(default_factory=list) def __post_init__(self): @@ -205,11 +219,9 @@ def __post_init__(self): else: raise Exception('lists of part data are only supported for bundles') - if self.additional_components: - for additional_component in self.additional_components: - if 'type' not in additional_component: - raise Exception('Additional components must have a type specified') - + for i, item in enumerate(self.additional_components): + if isinstance(item, dict): + self.additional_components[i] = AdditionalComponent(**item) def connect(self, from_name, from_pin, via_pin, to_name, to_pin): from_pin = int2tuple(from_pin) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index cec804fe..18e8223b 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -9,7 +9,7 @@ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write, \ html_colorbar, html_image, html_caption, manufacturer_info_field, \ - component_table_entry, extra_component_long_name + component_table_entry from collections import Counter from typing import List from pathlib import Path @@ -106,19 +106,19 @@ def create_graph(self) -> Graph: if connector.additional_components is not None: rows.append(["Additional components"]) for extra in connector.additional_components: - qty = extra.get('qty', 1) - if 'qty_multiplier' in extra: - if extra['qty_multiplier'] == 'pincount': + qty = extra.qty + if extra.qty_multiplier: + if extra.qty_multiplier == 'pincount': qty *= connector.pincount - elif extra['qty_multiplier'] == 'populated': - qty *= sum(1 for value in connector.visible_pins.values() if value is True) + elif extra.qty_multiplier == 'populated': + qty *= sum(connector.visible_pins.values()) else: - raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) + raise ValueError(f'invalid qty multiplier parameter {extra["qty_multiplier"]}') if(self.mini_bom_mode): - id = self.get_bom_index(extra_component_long_name(extra["type"], extra.get("subtype", None)), extra.get("unit", None), extra.get("manufacturer", None), extra.get("mpn", None), extra.get("pn", None)) - rows.append(html_line_breaks(component_table_entry(f'{id} ({extra["type"].capitalize()})', qty, extra.get("unit", None), None, None, None))) + id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) + rows.append(html_line_breaks(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit))) else: - rows.append(html_line_breaks(extra_component_long_name(extra["type"], extra.get("subtype", None)), qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None))) + rows.append(html_line_breaks(extra.long_name(), qty, extra.get.unit, extra.pn, extra.manufacturer, extra.mpn)) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -193,26 +193,26 @@ def create_graph(self) -> Graph: [html_image(cable.image)], [html_caption(cable.image)]] - if cable.additional_components is not None: + if len(cable.additional_components) > 0: rows.append(["Additional components"]) for extra in cable.additional_components: - qty = extra.get('qty', 1) - if 'qty_multiplier' in extra: - if extra['qty_multiplier'] == 'wirecount': + qty = extra.qty + if extra.qty_multiplier: + if extra.qty_multiplier == 'wirecount': qty *= cable.wirecount - elif extra['qty_multiplier'] == 'terminations': + elif extra.qty_multiplier == 'terminations': qty *= len(cable.connections) - elif extra['qty_multiplier'] == 'length': + elif extra.qty_multiplier == 'length': qty *= cable.length - elif extra['qty_multiplier'] == 'total_length': + elif extra.qty_multiplier == 'total_length': qty *= cable.length * cable.wirecount else: - raise ValueError('invalid qty parameter {}'.format(extra["qty_multiplier"])) + raise ValueError(f'invalid qty multiplier parameter {extra["qty_multiplier"]}') if(self.mini_bom_mode): - id = self.get_bom_index(extra_component_long_name(extra["type"], extra.get("subtype", None)), extra.get("unit", None), extra.get("manufacturer", None), extra.get("mpn", None), extra.get("pn", None)) - rows.append(html_line_breaks(component_table_entry(f'{id} ({extra["type"].capitalize()})', qty, extra.get("unit", None), None, None, None))) + id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) + rows.append(html_line_breaks(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit))) else: - rows.append(html_line_breaks(component_table_entry(extra_component_long_name(extra["type"], extra.get("subtype", None)), qty, extra.get("unit", None), extra.get("pn", None), extra.get("manufacturer", None), extra.get("mpn", None)))) + rows.append(html_line_breaks(component_table_entry(extra.long_name(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn))) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) @@ -376,120 +376,91 @@ def bom(self): bom_items = [] # connectors - connector_group = lambda c: (c.type, c.subtype, c.pincount, c.manufacturer, c.mpn, c.pn) - for group in Counter([connector_group(v) for v in self.connectors.values() if v.ignore_in_bom is not True]): - items = {k: v for k, v in self.connectors.items() if connector_group(v) == group} - shared = next(iter(items.values())) - designators = list(items.keys()) - designators.sort() - conn_type = f', {remove_line_breaks(shared.type)}' if shared.type else '' - conn_subtype = f', {remove_line_breaks(shared.subtype)}' if shared.subtype else '' - conn_pincount = f', {shared.pincount} pins' if shared.style != 'simple' else '' - conn_color = f', {shared.color}' if shared.color else '' - name = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}' - item = {'item': name, 'qty': len(designators), 'unit': '', 'designators': designators if shared.show_name else None, - 'manufacturer': remove_line_breaks(shared.manufacturer), 'mpn': remove_line_breaks(shared.mpn), 'pn': shared.pn} - bom_items.append(item) - for connector in self.connectors.values(): - if connector.additional_components: - for part in connector.additional_components: - qty = part.get('qty', 1) - if 'qty_multiplier' in part: - if part['qty_multiplier'] == 'pincount': - qty = connector.pincount - elif part['qty_multiplier'] == 'populated': - qty = sum(1 for value in connector.visible_pins.values() if value is True) - else: - raise ValueError('invalid qty parameter {}'.format(part["qty_multiplier'"])) - bom_items.append( - { - 'item': extra_component_long_name(part["type"], part.get("subtype", None)), - 'qty': qty, - 'unit': part.get('unit', None), - 'manufacturer': part.get('manufacturer', None), - 'mpn': part.get('mpn', None), - 'pn': part.get('pn', None), - 'designators': connector.name if connector.show_name else None - } - ) + if not connector.ignore_in_bom: + conn_type = f', {remove_line_breaks(connector.type)}' if connector.type else '' + conn_subtype = f', {remove_line_breaks(connector.subtype)}' if connector.subtype else '' + conn_pincount = f', {connector.pincount} pins' if connector.style != 'simple' else '' + conn_color = f', {connector.color}' if connector.color else '' + name = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}' + item = {'item': name, 'qty': 1, 'unit': '', 'designators': connector.name if connector.show_name else None, + 'manufacturer': remove_line_breaks(connector.manufacturer), 'mpn': remove_line_breaks(connector.mpn), 'pn': connector.pn} + bom_items.append(item) + + for part in connector.additional_components: + qty = part.qty + if part.qty_multiplier: + if part.qty_multiplier == 'pincount': + qty = connector.pincount + elif part.qty_multiplier == 'populated': + qty = sum(connector.visible_pins.values()) + else: + raise ValueError(f'invalid qty multiplier parameter {part.qty_multiplier}') + bom_items.append( + { + 'item': part.long_name(), + 'qty': qty, + 'unit': part.unit, + 'manufacturer': part.manufacturer, + 'mpn': part.mpn, + 'pn': part.pn, + 'designators': connector.name if connector.show_name else None + } + ) # cables # TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name? - # The category needs to be included in cable_group to keep the bundles excluded. - cable_group = lambda c: (c.category, c.type, c.gauge, c.gauge_unit, c.wirecount, c.shield, c.manufacturer, c.mpn, c.pn) - for group in Counter([cable_group(v) for v in self.cables.values() if v.category != 'bundle' and v.ignore_in_bom is not True]): - items = {k: v for k, v in self.cables.items() if cable_group(v) == group} - shared = next(iter(items.values())) - designators = list(items.keys()) - designators.sort() - total_length = sum(i.length for i in items.values()) - cable_type = f', {remove_line_breaks(shared.type)}' if shared.type else '' - gauge_name = f' x {shared.gauge} {shared.gauge_unit}' if shared.gauge else ' wires' - shield_name = ' shielded' if shared.shield else '' - name = f'Cable{cable_type}, {shared.wirecount}{gauge_name}{shield_name}' - item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators, - 'manufacturer': remove_line_breaks(shared.manufacturer), 'mpn': remove_line_breaks(shared.mpn), 'pn': shared.pn} - bom_items.append(item) - # bundles (ignores wirecount) - wirelist = [] - # list all cables again, since bundles are represented as wires internally, with the category='bundle' set - for bundle in self.cables.values(): - if bundle.category == 'bundle': - # add each wire from each bundle to the wirelist - for index, color in enumerate(bundle.colors, 0): - wirelist.append({'type': bundle.type, 'gauge': bundle.gauge, 'gauge_unit': bundle.gauge_unit, 'length': bundle.length, 'color': color, 'designator': bundle.name, - 'manufacturer': remove_line_breaks(index_if_list(bundle.manufacturer, index)), - 'mpn': remove_line_breaks(index_if_list(bundle.mpn, index)), - 'pn': index_if_list(bundle.pn, index)}) - # join similar wires from all the bundles to a single BOM item - wire_group = lambda w: (w.get('type', None), w['gauge'], w['gauge_unit'], w['color'], w['manufacturer'], w['mpn'], w['pn']) - for group in Counter([wire_group(v) for v in wirelist]): - items = [v for v in wirelist if wire_group(v) == group] - shared = items[0] - designators = [i['designator'] for i in items] - designators = list(dict.fromkeys(designators)) # remove duplicates - designators.sort() - total_length = sum(i['length'] for i in items) - wire_type = f', {remove_line_breaks(shared["type"])}' if shared.get('type', None) else '' - gauge_name = f', {shared["gauge"]} {shared["gauge_unit"]}' if shared.get('gauge', None) else '' - gauge_color = f', {shared["color"]}' if 'color' in shared != '' else '' - name = f'Wire{wire_type}{gauge_name}{gauge_color}' - item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators, - 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} - bom_items.append(item) - for cable in self.cables.values(): - if cable.additional_components: - for part in cable.additional_components: - qty = part.get('qty', 1) - if 'qty_multiplier' in part: - if part['qty_multiplier'] == 'wirecount': - qty *= cable.wirecount - elif part['qty_multiplier'] == 'terminations': - qty *= len(cable.connections) - elif part['qty_multiplier'] == 'length': - qty *= cable.length - elif part['qty_multiplier'] == 'total_length': - qty *= cable.length * cable.wirecount - else: - raise ValueError('invalid qty parameter {}'.format(part["qty_multiplier"])) - bom_items.append( - { - 'item': extra_component_long_name(part["type"], part.get("subtype", None)), - 'qty': qty, - 'unit': part.get('unit', None), - 'manufacturer': part.get('manufacturer', None), - 'mpn': part.get('mpn', None), - 'pn': part.get('pn', None), - 'designators': cable.name - } - ) + if not cable.ignore_in_bom: + # create name beilds used by both cables and bundles + cable_type = f', {remove_line_breaks(cable.type)}' if cable.type else '' + gauge_name = f' x {cable.gauge} {cable.gauge_unit}' if cable.gauge else ' wires' + if cable.category != 'bundle': + # process cable as a single entity + shield_name = ' shielded' if cable.shield else '' + name = f'Cable{cable_type}, {cable.wirecount}{gauge_name}{shield_name}' + item = {'item': name, 'qty': cable.length, 'unit': 'm', 'designators': cable.name, + 'manufacturer': remove_line_breaks(cable.manufacturer), 'mpn': remove_line_breaks(cable.mpn), 'pn': cable.pn} + bom_items.append(item) + else: + # add each wire from the bundle to the bom + for index, color in enumerate(cable.colors, 0): + gauge_color = f', {color}' if color else '' + name = f'Wire{cable_type}{gauge_name}{gauge_color}' + item = {'item': name, 'qty': cable.length, 'unit': 'm', 'designators': cable.name, + 'manufacturer': remove_line_breaks(index_if_list(cable.manufacturer, index)), + 'mpn': remove_line_breaks(index_if_list(cable.mpn, index)), 'pn': index_if_list(cable.pn, index)} + bom_items.append(item) + + for part in cable.additional_components: + qty = part.qty + if part.qty_multiplier: + if part.qty_multiplier == 'wirecount': + qty *= cable.wirecount + elif part.qty_multiplier == 'terminations': + qty *= len(cable.connections) + elif part.qty_multiplier == 'length': + qty *= cable.length + elif part.qty_multiplier == 'total_length': + qty *= cable.length * cable.wirecount + else: + raise ValueError(f'invalid qty multiplier parameter {part.qty_multiplier}') + bom_items.append( + { + 'item': part.long_name(), + 'qty': qty, + 'unit': part.unit, + 'manufacturer': part.manufacturer, + 'mpn': part.mpn, + 'pn': part.pn, + 'designators': cable.name + } + ) for item in self.additional_bom_items: name = item['description'] if item.get('description', None) else '' - item = {'item': name, 'qty': item.get('qty', None), 'unit': item.get('unit', None), 'designators': item.get('designators', None), - 'manufacturer': item.get('manufacturer', None), 'mpn': item.get('mpn', None), 'pn': item.get('pn', None)} + item = {'item': name, 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), + 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn')} bom_items.append(item) # deduplicate bom @@ -499,7 +470,7 @@ def bom(self): shared = items[0] designators = [] for item in items: - if "designators" in item and item['designators']: + if item.get('designators'): if isinstance(item['designators'], List): designators.extend(item['designators']) else: @@ -529,7 +500,7 @@ def bom_list(self): bom = self.bom() keys = ['id', 'item', 'qty', 'unit', 'designators'] # these BOM columns will always be included for fieldname in ['pn', 'manufacturer', 'mpn']: # these optional BOM columns will only be included if at least one BOM item actually uses them - if any(fieldname in x and x.get(fieldname, None) for x in bom): + if any(entry.get(fieldname) for entry in bom): keys.append(fieldname) bom_list = [] # list of staic bom header names, headers not specified here are generated by capitilising the internal name diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 92d5c19e..cf02d5a6 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -200,9 +200,3 @@ def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn= # format the above output as left aligned text in a single visable cell output = f'
{output}
' return output - - -def extra_component_long_name(type, subtype): - name_subtype = f', {remove_line_breaks(subtype)}' if subtype else '' - name = f'{type.capitalize()}{name_subtype}' - return name From abcccbd360d278953e0037ea4141e37b760561fd Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Tue, 29 Sep 2020 21:25:51 +0100 Subject: [PATCH 19/30] Move calculation of qty_multiplier into wv_helper --- src/wireviz/Harness.py | 43 ++++++---------------------------------- src/wireviz/wv_helper.py | 31 +++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 18e8223b..8cc9879a 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -9,7 +9,8 @@ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write, \ html_colorbar, html_image, html_caption, manufacturer_info_field, \ - component_table_entry + component_table_entry, \ + calculate_qty_multiplier_connector, calculate_qty_multiplier_cable from collections import Counter from typing import List from pathlib import Path @@ -107,13 +108,7 @@ def create_graph(self) -> Graph: rows.append(["Additional components"]) for extra in connector.additional_components: qty = extra.qty - if extra.qty_multiplier: - if extra.qty_multiplier == 'pincount': - qty *= connector.pincount - elif extra.qty_multiplier == 'populated': - qty *= sum(connector.visible_pins.values()) - else: - raise ValueError(f'invalid qty multiplier parameter {extra["qty_multiplier"]}') + qty *= calculate_qty_multiplier_connector(extra.qty_multiplier, connector) if(self.mini_bom_mode): id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) rows.append(html_line_breaks(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit))) @@ -197,17 +192,7 @@ def create_graph(self) -> Graph: rows.append(["Additional components"]) for extra in cable.additional_components: qty = extra.qty - if extra.qty_multiplier: - if extra.qty_multiplier == 'wirecount': - qty *= cable.wirecount - elif extra.qty_multiplier == 'terminations': - qty *= len(cable.connections) - elif extra.qty_multiplier == 'length': - qty *= cable.length - elif extra.qty_multiplier == 'total_length': - qty *= cable.length * cable.wirecount - else: - raise ValueError(f'invalid qty multiplier parameter {extra["qty_multiplier"]}') + qty *= calculate_qty_multiplier_cable(extra.qty_multiplier, cable) if(self.mini_bom_mode): id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) rows.append(html_line_breaks(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit))) @@ -389,13 +374,7 @@ def bom(self): for part in connector.additional_components: qty = part.qty - if part.qty_multiplier: - if part.qty_multiplier == 'pincount': - qty = connector.pincount - elif part.qty_multiplier == 'populated': - qty = sum(connector.visible_pins.values()) - else: - raise ValueError(f'invalid qty multiplier parameter {part.qty_multiplier}') + qty *= calculate_qty_multiplier_connector(part.qty_multiplier, connector) bom_items.append( { 'item': part.long_name(), @@ -434,17 +413,7 @@ def bom(self): for part in cable.additional_components: qty = part.qty - if part.qty_multiplier: - if part.qty_multiplier == 'wirecount': - qty *= cable.wirecount - elif part.qty_multiplier == 'terminations': - qty *= len(cable.connections) - elif part.qty_multiplier == 'length': - qty *= cable.length - elif part.qty_multiplier == 'total_length': - qty *= cable.length * cable.wirecount - else: - raise ValueError(f'invalid qty multiplier parameter {part.qty_multiplier}') + qty *= calculate_qty_multiplier_cable(part.qty_multiplier, cable) bom_items.append( { 'item': part.long_name(), diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index cf02d5a6..445badfb 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -198,5 +198,32 @@ def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn= if manufacturer_str: output += manufacturer_str # format the above output as left aligned text in a single visable cell - output = f'
{output}
' - return output + return f'
{output}
' + + +def calculate_qty_multiplier_connector(qty_multiplier, connector): + if not qty_multiplier: + return 1 + + if qty_multiplier == 'pincount': + return connector.pincount + elif qty_multiplier == 'populated': + return sum(connector.visible_pins.values()) + else: + raise ValueError(f'invalid qty multiplier parameter for connector {qty_multiplier}') + + +def calculate_qty_multiplier_cable(qty_multiplier, cable): + if not qty_multiplier: + return 1 + + if qty_multiplier == 'wirecount': + return cable.wirecount + elif qty_multiplier == 'terminations': + return len(cable.connections) + elif qty_multiplier == 'length': + return cable.length + elif qty_multiplier == 'total_length': + return cable.length * cable.wirecount + else: + raise ValueError(f'invalid qty multiplier parameter for cable {qty_multiplier}') From 3faaff6ebc460d7fa4faa63ac001f6ee4d943731 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Thu, 1 Oct 2020 23:07:26 +0100 Subject: [PATCH 20/30] Simplify several function calls and typehint qty multipliers --- src/wireviz/DataClasses.py | 4 +++- src/wireviz/Harness.py | 33 ++++++++++++++------------------- src/wireviz/wv_helper.py | 1 + 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 13869022..355d6c94 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -7,6 +7,8 @@ from wireviz.wv_helper import int2tuple, aspect_ratio from wireviz import wv_colors +ConnectorMultiplier = str # = Literal['pincount', 'populated'] +CableMultiplier = str # = Literal['wirecount', 'terminations', 'length', 'total_length'] @dataclass class Image: @@ -52,7 +54,7 @@ class AdditionalComponent: pn: Optional[str] = None qty: float = 1 unit: Optional[str] = None - qty_multiplier: Optional[str] = None + qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None def long_name(self) -> str: name_subtype = f', {self.subtype}' if self.subtype else '' diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 8cc9879a..9b6d5aad 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -104,16 +104,15 @@ def create_graph(self) -> Graph: '' if connector.style != 'simple' else None, [html_image(connector.image)], [html_caption(connector.image)]] - if connector.additional_components is not None: + if connector.additional_components: rows.append(["Additional components"]) for extra in connector.additional_components: - qty = extra.qty - qty *= calculate_qty_multiplier_connector(extra.qty_multiplier, connector) + qty = extra.qty * calculate_qty_multiplier_connector(extra.qty_multiplier, connector) if(self.mini_bom_mode): id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) - rows.append(html_line_breaks(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit))) + rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) else: - rows.append(html_line_breaks(extra.long_name(), qty, extra.get.unit, extra.pn, extra.manufacturer, extra.mpn)) + rows.append(component_table_entry(extra.long_name(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -188,16 +187,15 @@ def create_graph(self) -> Graph: [html_image(cable.image)], [html_caption(cable.image)]] - if len(cable.additional_components) > 0: + if cable.additional_components: rows.append(["Additional components"]) for extra in cable.additional_components: - qty = extra.qty - qty *= calculate_qty_multiplier_cable(extra.qty_multiplier, cable) + qty = extra.qty * calculate_qty_multiplier_cable(extra.qty_multiplier, cable) if(self.mini_bom_mode): id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) - rows.append(html_line_breaks(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit))) + rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) else: - rows.append(html_line_breaks(component_table_entry(extra.long_name(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn))) + rows.append(component_table_entry(extra.long_name(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) @@ -356,7 +354,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True def bom(self): # if the bom has previously been generated then return the generated bom - if len(self._bom) > 0: + if self._bom: return self._bom bom_items = [] @@ -373,8 +371,7 @@ def bom(self): bom_items.append(item) for part in connector.additional_components: - qty = part.qty - qty *= calculate_qty_multiplier_connector(part.qty_multiplier, connector) + qty = part.qty * calculate_qty_multiplier_connector(part.qty_multiplier, connector) bom_items.append( { 'item': part.long_name(), @@ -404,16 +401,15 @@ def bom(self): else: # add each wire from the bundle to the bom for index, color in enumerate(cable.colors, 0): - gauge_color = f', {color}' if color else '' - name = f'Wire{cable_type}{gauge_name}{gauge_color}' + wire_color = f', {color}' if color else '' + name = f'Wire{cable_type}{gauge_name}{wire_color}' item = {'item': name, 'qty': cable.length, 'unit': 'm', 'designators': cable.name, 'manufacturer': remove_line_breaks(index_if_list(cable.manufacturer, index)), 'mpn': remove_line_breaks(index_if_list(cable.mpn, index)), 'pn': index_if_list(cable.pn, index)} bom_items.append(item) for part in cable.additional_components: - qty = part.qty - qty *= calculate_qty_multiplier_cable(part.qty_multiplier, cable) + qty = part.qty * calculate_qty_multiplier_cable(part.qty_multiplier, cable) bom_items.append( { 'item': part.long_name(), @@ -427,8 +423,7 @@ def bom(self): ) for item in self.additional_bom_items: - name = item['description'] if item.get('description', None) else '' - item = {'item': name, 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), + item = {'item': item.get('description', ''), 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn')} bom_items.append(item) diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 445badfb..d6dd5f18 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -197,6 +197,7 @@ def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn= output += ', ' if manufacturer_str: output += manufacturer_str + output = html_line_breaks(output) # format the above output as left aligned text in a single visable cell return f'
{output}
' From 20622e0346f4232bbe5ade750b9cdbe8129753c2 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Thu, 1 Oct 2020 23:40:57 +0100 Subject: [PATCH 21/30] Move qty_multiplier functions into Connector and cable dataclasses Also rename long_name to description --- src/wireviz/DataClasses.py | 27 ++++++++++++++++++++++++++- src/wireviz/Harness.py | 23 +++++++++++------------ src/wireviz/wv_helper.py | 28 ---------------------------- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 355d6c94..cbf23f55 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -10,6 +10,7 @@ ConnectorMultiplier = str # = Literal['pincount', 'populated'] CableMultiplier = str # = Literal['wirecount', 'terminations', 'length', 'total_length'] + @dataclass class Image: gv_dir: InitVar[Path] # Directory of .gv file injected as context during parsing @@ -56,7 +57,7 @@ class AdditionalComponent: unit: Optional[str] = None qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None - def long_name(self) -> str: + def description(self) -> str: name_subtype = f', {self.subtype}' if self.subtype else '' return f'{self.type.capitalize()}{name_subtype}' @@ -140,6 +141,16 @@ def __post_init__(self): def activate_pin(self, pin): self.visible_pins[pin] = True + def get_qty_multiplier(self, qty_multiplier: Optional[ConnectorMultiplier]) -> int: + if not qty_multiplier: + return 1 + elif qty_multiplier == 'pincount': + return self.pincount + elif qty_multiplier == 'populated': + return sum(self.visible_pins.values()) + else: + raise ValueError(f'invalid qty multiplier parameter for connector {qty_multiplier}') + @dataclass class Cable: @@ -235,6 +246,20 @@ def connect(self, from_name, from_pin, via_pin, to_name, to_pin): # self.connections.append((from_name, from_pin[i], via_pin[i], to_name, to_pin[i])) self.connections.append(Connection(from_name, from_pin[i], via_pin[i], to_name, to_pin[i])) + def get_qty_multiplier(self, qty_multiplier: Optional[CableMultiplier]) -> float: + if not qty_multiplier: + return 1 + elif qty_multiplier == 'wirecount': + return self.wirecount + elif qty_multiplier == 'terminations': + return len(self.connections) + elif qty_multiplier == 'length': + return self.length + elif qty_multiplier == 'total_length': + return self.length * self.wirecount + else: + raise ValueError(f'invalid qty multiplier parameter for cable {qty_multiplier}') + @dataclass class Connection: diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 9b6d5aad..fafb4c23 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -9,8 +9,7 @@ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write, \ html_colorbar, html_image, html_caption, manufacturer_info_field, \ - component_table_entry, \ - calculate_qty_multiplier_connector, calculate_qty_multiplier_cable + component_table_entry from collections import Counter from typing import List from pathlib import Path @@ -107,12 +106,12 @@ def create_graph(self) -> Graph: if connector.additional_components: rows.append(["Additional components"]) for extra in connector.additional_components: - qty = extra.qty * calculate_qty_multiplier_connector(extra.qty_multiplier, connector) + qty = extra.qty * connector.get_qty_multiplier(extra.qty_multiplier) if(self.mini_bom_mode): - id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) + id = self.get_bom_index(extra.description(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) else: - rows.append(component_table_entry(extra.long_name(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) + rows.append(component_table_entry(extra.description(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -190,12 +189,12 @@ def create_graph(self) -> Graph: if cable.additional_components: rows.append(["Additional components"]) for extra in cable.additional_components: - qty = extra.qty * calculate_qty_multiplier_cable(extra.qty_multiplier, cable) + qty = extra.qty * cable.get_qty_multiplier(extra.qty_multiplier) if(self.mini_bom_mode): - id = self.get_bom_index(extra.long_name(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) + id = self.get_bom_index(extra.description(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) else: - rows.append(component_table_entry(extra.long_name(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) + rows.append(component_table_entry(extra.description(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) @@ -371,10 +370,10 @@ def bom(self): bom_items.append(item) for part in connector.additional_components: - qty = part.qty * calculate_qty_multiplier_connector(part.qty_multiplier, connector) + qty = part.qty * connector.get_qty_multiplier(part.qty_multiplier) bom_items.append( { - 'item': part.long_name(), + 'item': part.description(), 'qty': qty, 'unit': part.unit, 'manufacturer': part.manufacturer, @@ -409,10 +408,10 @@ def bom(self): bom_items.append(item) for part in cable.additional_components: - qty = part.qty * calculate_qty_multiplier_cable(part.qty_multiplier, cable) + qty = part.qty * cable.get_qty_multiplier(part.qty_multiplier) bom_items.append( { - 'item': part.long_name(), + 'item': part.description(), 'qty': qty, 'unit': part.unit, 'manufacturer': part.manufacturer, diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index d6dd5f18..af101d84 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -200,31 +200,3 @@ def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn= output = html_line_breaks(output) # format the above output as left aligned text in a single visable cell return f'
{output}
' - - -def calculate_qty_multiplier_connector(qty_multiplier, connector): - if not qty_multiplier: - return 1 - - if qty_multiplier == 'pincount': - return connector.pincount - elif qty_multiplier == 'populated': - return sum(connector.visible_pins.values()) - else: - raise ValueError(f'invalid qty multiplier parameter for connector {qty_multiplier}') - - -def calculate_qty_multiplier_cable(qty_multiplier, cable): - if not qty_multiplier: - return 1 - - if qty_multiplier == 'wirecount': - return cable.wirecount - elif qty_multiplier == 'terminations': - return len(cable.connections) - elif qty_multiplier == 'length': - return cable.length - elif qty_multiplier == 'total_length': - return cable.length * cable.wirecount - else: - raise ValueError(f'invalid qty multiplier parameter for cable {qty_multiplier}') From a450c82e720499dd378edb94812bc1099f8ad128 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Thu, 1 Oct 2020 23:41:24 +0100 Subject: [PATCH 22/30] Make description for additional components a property --- src/wireviz/DataClasses.py | 1 + src/wireviz/Harness.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index cbf23f55..93b267e7 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -57,6 +57,7 @@ class AdditionalComponent: unit: Optional[str] = None qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None + @property def description(self) -> str: name_subtype = f', {self.subtype}' if self.subtype else '' return f'{self.type.capitalize()}{name_subtype}' diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index fafb4c23..6a77451b 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -108,10 +108,10 @@ def create_graph(self) -> Graph: for extra in connector.additional_components: qty = extra.qty * connector.get_qty_multiplier(extra.qty_multiplier) if(self.mini_bom_mode): - id = self.get_bom_index(extra.description(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) + id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) else: - rows.append(component_table_entry(extra.description(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) + rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -191,10 +191,10 @@ def create_graph(self) -> Graph: for extra in cable.additional_components: qty = extra.qty * cable.get_qty_multiplier(extra.qty_multiplier) if(self.mini_bom_mode): - id = self.get_bom_index(extra.description(), extra.unit, extra.manufacturer, extra.mpn, extra.pn) + id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) else: - rows.append(component_table_entry(extra.description(), qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) + rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) @@ -373,7 +373,7 @@ def bom(self): qty = part.qty * connector.get_qty_multiplier(part.qty_multiplier) bom_items.append( { - 'item': part.description(), + 'item': part.description, 'qty': qty, 'unit': part.unit, 'manufacturer': part.manufacturer, @@ -411,7 +411,7 @@ def bom(self): qty = part.qty * cable.get_qty_multiplier(part.qty_multiplier) bom_items.append( { - 'item': part.description(), + 'item': part.description, 'qty': qty, 'unit': part.unit, 'manufacturer': part.manufacturer, From cb3e3f44e80a615dc1bad6d721d651e79774539d Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Mon, 5 Oct 2020 22:18:37 +0100 Subject: [PATCH 23/30] Deduplicate additional components functions Also refine bom variable names and fix a comple of typos --- src/wireviz/DataClasses.py | 3 +- src/wireviz/Harness.py | 159 ++++++++++++++++++------------------- src/wireviz/wv_helper.py | 2 +- 3 files changed, 78 insertions(+), 86 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 93b267e7..cf259aa1 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -59,8 +59,7 @@ class AdditionalComponent: @property def description(self) -> str: - name_subtype = f', {self.subtype}' if self.subtype else '' - return f'{self.type.capitalize()}{name_subtype}' + return self.type.capitalize() + (f', {self.subtype}' if self.subtype else '') @dataclass diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 6a77451b..9df5db00 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -11,7 +11,7 @@ html_colorbar, html_image, html_caption, manufacturer_info_field, \ component_table_entry from collections import Counter -from typing import List +from typing import List, Union from pathlib import Path import re @@ -103,15 +103,7 @@ def create_graph(self) -> Graph: '' if connector.style != 'simple' else None, [html_image(connector.image)], [html_caption(connector.image)]] - if connector.additional_components: - rows.append(["Additional components"]) - for extra in connector.additional_components: - qty = extra.qty * connector.get_qty_multiplier(extra.qty_multiplier) - if(self.mini_bom_mode): - id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) - rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) - else: - rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) + rows.extend(self.get_additional_component_table(connector)) rows.append([html_line_breaks(connector.notes)]) html.extend(nested_html_table(rows)) @@ -186,15 +178,7 @@ def create_graph(self) -> Graph: [html_image(cable.image)], [html_caption(cable.image)]] - if cable.additional_components: - rows.append(["Additional components"]) - for extra in cable.additional_components: - qty = extra.qty * cable.get_qty_multiplier(extra.qty_multiplier) - if(self.mini_bom_mode): - id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) - rows.append(component_table_entry(f'{id} ({extra.type.capitalize()})', qty, extra.unit)) - else: - rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) + rows.extend(self.get_additional_component_table(cable)) rows.append([html_line_breaks(cable.notes)]) html.extend(nested_html_table(rows)) @@ -351,99 +335,107 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True file.write('') + def get_additional_component_table(self, component: Union[Connector, Cable]) -> List[str]: + rows = [] + if component.additional_components: + rows.append(["Additional components"]) + for extra in component.additional_components: + qty = extra.qty * component.get_qty_multiplier(extra.qty_multiplier) + if(self.mini_bom_mode): + id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) + rows.append(component_table_entry(f'#{id} ({extra.type.capitalize()})', qty, extra.unit)) + else: + rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) + return(rows) + + def get_additional_component_bom(self, component: Union[Connector, Cable]) -> List[dict]: + bom_entries = [] + for part in component.additional_components: + qty = part.qty * component.get_qty_multiplier(part.qty_multiplier) + bom_entries.append( + { + 'item': part.description, + 'qty': qty, + 'unit': part.unit, + 'manufacturer': part.manufacturer, + 'mpn': part.mpn, + 'pn': part.pn, + 'designators': component.name if component.show_name else None + } + ) + return(bom_entries) + def bom(self): # if the bom has previously been generated then return the generated bom if self._bom: return self._bom - bom_items = [] + bom_entries = [] # connectors for connector in self.connectors.values(): if not connector.ignore_in_bom: - conn_type = f', {remove_line_breaks(connector.type)}' if connector.type else '' - conn_subtype = f', {remove_line_breaks(connector.subtype)}' if connector.subtype else '' + conn_type = f', {connector.type}' if connector.type else '' + conn_subtype = f', {connector.subtype}' if connector.subtype else '' conn_pincount = f', {connector.pincount} pins' if connector.style != 'simple' else '' conn_color = f', {connector.color}' if connector.color else '' - name = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}' - item = {'item': name, 'qty': 1, 'unit': '', 'designators': connector.name if connector.show_name else None, - 'manufacturer': remove_line_breaks(connector.manufacturer), 'mpn': remove_line_breaks(connector.mpn), 'pn': connector.pn} - bom_items.append(item) - - for part in connector.additional_components: - qty = part.qty * connector.get_qty_multiplier(part.qty_multiplier) - bom_items.append( - { - 'item': part.description, - 'qty': qty, - 'unit': part.unit, - 'manufacturer': part.manufacturer, - 'mpn': part.mpn, - 'pn': part.pn, - 'designators': connector.name if connector.show_name else None - } - ) + description = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}' + entry = {'item': description, 'qty': 1, 'unit': '', 'designators': connector.name if connector.show_name else None, + 'manufacturer': connector.manufacturer, 'mpn': connector.mpn, 'pn': connector.pn} + bom_entries.append(entry) + + # add connectors aditional components to bom + bom_entries.extend(self.get_additional_component_bom(connector)) # cables # TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name? for cable in self.cables.values(): if not cable.ignore_in_bom: - # create name beilds used by both cables and bundles - cable_type = f', {remove_line_breaks(cable.type)}' if cable.type else '' - gauge_name = f' x {cable.gauge} {cable.gauge_unit}' if cable.gauge else ' wires' + # create description fields used by both cables and bundles + cable_type = f', {cable.type}' if cable.type else '' + cable_gauge = f' x {cable.gauge} {cable.gauge_unit}' if cable.gauge else ' wires' if cable.category != 'bundle': # process cable as a single entity - shield_name = ' shielded' if cable.shield else '' - name = f'Cable{cable_type}, {cable.wirecount}{gauge_name}{shield_name}' - item = {'item': name, 'qty': cable.length, 'unit': 'm', 'designators': cable.name, - 'manufacturer': remove_line_breaks(cable.manufacturer), 'mpn': remove_line_breaks(cable.mpn), 'pn': cable.pn} - bom_items.append(item) + cable_shield = ' shielded' if cable.shield else '' + name = f'Cable{cable_type}, {cable.wirecount}{cable_gauge}{cable_shield}' + entry = {'item': name, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None, + 'manufacturer': cable.manufacturer, 'mpn': cable.mpn, 'pn': cable.pn} + bom_entries.append(entry) else: # add each wire from the bundle to the bom - for index, color in enumerate(cable.colors, 0): + for index, color in enumerate(cable.colors): wire_color = f', {color}' if color else '' - name = f'Wire{cable_type}{gauge_name}{wire_color}' - item = {'item': name, 'qty': cable.length, 'unit': 'm', 'designators': cable.name, - 'manufacturer': remove_line_breaks(index_if_list(cable.manufacturer, index)), - 'mpn': remove_line_breaks(index_if_list(cable.mpn, index)), 'pn': index_if_list(cable.pn, index)} - bom_items.append(item) - - for part in cable.additional_components: - qty = part.qty * cable.get_qty_multiplier(part.qty_multiplier) - bom_items.append( - { - 'item': part.description, - 'qty': qty, - 'unit': part.unit, - 'manufacturer': part.manufacturer, - 'mpn': part.mpn, - 'pn': part.pn, - 'designators': cable.name - } - ) + description = f'Wire{cable_type}{cable_gauge}{wire_color}' + entry = {'item': description, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None, + 'manufacturer': index_if_list(cable.manufacturer, index), + 'mpn': index_if_list(cable.mpn, index), 'pn': index_if_list(cable.pn, index)} + bom_entries.append(entry) + + # add cable/bundles aditional components to bom + bom_entries.extend(self.get_additional_component_bom(cable)) for item in self.additional_bom_items: - item = {'item': item.get('description', ''), 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), - 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn')} - bom_items.append(item) + entry = {'item': item.get('description', ''), 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), + 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn')} + bom_entries.append(entry) # deduplicate bom bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn']) - for group in Counter([bom_types_group(v) for v in bom_items]): - items = [v for v in bom_items if bom_types_group(v) == group] - shared = items[0] + for group in Counter([bom_types_group(v) for v in bom_entries]): + group_entries = [v for v in bom_entries if bom_types_group(v) == group] + shared = group_entries[0] designators = [] - for item in items: - if item.get('designators'): - if isinstance(item['designators'], List): - designators.extend(item['designators']) + for group_entry in group_entries: + if group_entry.get('designators'): + if isinstance(group_entry['designators'], List): + designators.extend(group_entry['designators']) else: - designators.append(item['designators']) + designators.append(group_entry['designators']) designators = list(dict.fromkeys(designators)) # remove duplicates designators.sort() - total_qty = sum(i['qty'] for i in items) - item = {'item': shared['item'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, - 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} - self._bom.append(item) + total_qty = sum(i['qty'] for i in group_entries) + entry = {'item': shared['item'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, + 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} + self._bom.append(entry) self._bom = sorted(self._bom, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) # add index @@ -476,5 +468,6 @@ def bom_list(self): item_list = [item.get(key, '') for key in keys] # fill missing values with blanks item_list = [', '.join(subitem) if isinstance(subitem, List) else subitem for subitem in item_list] # convert any lists into comma separated strings item_list = ['' if subitem is None else subitem for subitem in item_list] # if a field is missing for some (but not all) BOM items + item_list = [remove_line_breaks(subitem) for subitem in item_list] # remove line breaks if present bom_list.append(item_list) return bom_list diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index af101d84..ba5eef89 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -198,5 +198,5 @@ def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn= if manufacturer_str: output += manufacturer_str output = html_line_breaks(output) - # format the above output as left aligned text in a single visable cell + # format the above output as left aligned text in a single visible cell return f'
{output}
' From b7f184e79c8a8f416294a1d34807a19861b18952 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Tue, 6 Oct 2020 01:16:12 +0100 Subject: [PATCH 24/30] Moved bom entry description generation to be in order of aperance --- src/wireviz/Harness.py | 63 +++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 9df5db00..aad05225 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -374,14 +374,15 @@ def bom(self): # connectors for connector in self.connectors.values(): if not connector.ignore_in_bom: - conn_type = f', {connector.type}' if connector.type else '' - conn_subtype = f', {connector.subtype}' if connector.subtype else '' - conn_pincount = f', {connector.pincount} pins' if connector.style != 'simple' else '' - conn_color = f', {connector.color}' if connector.color else '' - description = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}' - entry = {'item': description, 'qty': 1, 'unit': '', 'designators': connector.name if connector.show_name else None, - 'manufacturer': connector.manufacturer, 'mpn': connector.mpn, 'pn': connector.pn} - bom_entries.append(entry) + description = ('Connector' + + (f', {connector.type}' if connector.type else '') + + (f', {connector.subtype}' if connector.subtype else '') + + (f', {connector.pincount} pins' if connector.show_pincount else '') + + (f', {connector.color}' if connector.color else '')) + bom_entries.append({ + 'item': description, 'qty': 1, 'unit': '', 'designators': connector.name if connector.show_name else None, + 'manufacturer': connector.manufacturer, 'mpn': connector.mpn, 'pn': connector.pn + }) # add connectors aditional components to bom bom_entries.extend(self.get_additional_component_bom(connector)) @@ -390,33 +391,38 @@ def bom(self): # TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name? for cable in self.cables.values(): if not cable.ignore_in_bom: - # create description fields used by both cables and bundles - cable_type = f', {cable.type}' if cable.type else '' - cable_gauge = f' x {cable.gauge} {cable.gauge_unit}' if cable.gauge else ' wires' if cable.category != 'bundle': # process cable as a single entity - cable_shield = ' shielded' if cable.shield else '' - name = f'Cable{cable_type}, {cable.wirecount}{cable_gauge}{cable_shield}' - entry = {'item': name, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None, - 'manufacturer': cable.manufacturer, 'mpn': cable.mpn, 'pn': cable.pn} - bom_entries.append(entry) + description = ('Cable' + + (f', {cable.type}' if cable.type else '') + + (f', {cable.wirecount}') + + (f' x {cable.gauge} {cable.gauge_unit}' if cable.gauge else ' wires') + + (' shielded' if cable.shield else '')) + bom_entries.append({ + 'item': description, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None, + 'manufacturer': cable.manufacturer, 'mpn': cable.mpn, 'pn': cable.pn + }) else: # add each wire from the bundle to the bom for index, color in enumerate(cable.colors): - wire_color = f', {color}' if color else '' - description = f'Wire{cable_type}{cable_gauge}{wire_color}' - entry = {'item': description, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None, - 'manufacturer': index_if_list(cable.manufacturer, index), - 'mpn': index_if_list(cable.mpn, index), 'pn': index_if_list(cable.pn, index)} - bom_entries.append(entry) + description = ('Wire' + + (f', {cable.type}' if cable.type else '') + + (f', {cable.gauge} {cable.gauge_unit}' if cable.gauge else '') + + (f', {color}' if color else '')) + bom_entries.append({ + 'item': description, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None, + 'manufacturer': index_if_list(cable.manufacturer, index), + 'mpn': index_if_list(cable.mpn, index), 'pn': index_if_list(cable.pn, index) + }) # add cable/bundles aditional components to bom bom_entries.extend(self.get_additional_component_bom(cable)) for item in self.additional_bom_items: - entry = {'item': item.get('description', ''), 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), - 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn')} - bom_entries.append(entry) + bom_entries.append({ + 'item': item.get('description', ''), 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), + 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn') + }) # deduplicate bom bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn']) @@ -433,9 +439,10 @@ def bom(self): designators = list(dict.fromkeys(designators)) # remove duplicates designators.sort() total_qty = sum(i['qty'] for i in group_entries) - entry = {'item': shared['item'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, - 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']} - self._bom.append(entry) + self._bom.append({ + 'item': shared['item'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, + 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn'] + }) self._bom = sorted(self._bom, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) # add index From 715fe36116e005941bca63fc23fa0c76118aadb6 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sat, 17 Oct 2020 21:30:23 +0100 Subject: [PATCH 25/30] Remove brackets arround if statements and shorten bom dict opperatiions --- src/wireviz/Harness.py | 48 ++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index aad05225..a1cf41e4 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -202,7 +202,7 @@ def create_graph(self) -> Graph: wirehtml.append(' ') wirehtml.append(' ') wirehtml.append(' ') - if(cable.category == 'bundle'): # for bundles individual wires can have part information + if cable.category == 'bundle': # for bundles individual wires can have part information # create a list of wire parameters wireidentification = [] if isinstance(cable.pn, list): @@ -213,7 +213,7 @@ def create_graph(self) -> Graph: if manufacturer_info: wireidentification.append(html_line_breaks(manufacturer_info)) # print parameters into a table row under the wire - if(len(wireidentification) > 0): + if len(wireidentification) > 0 : wirehtml.append(' ') wirehtml.append(' ') for attrib in wireidentification: @@ -341,7 +341,7 @@ def get_additional_component_table(self, component: Union[Connector, Cable]) -> rows.append(["Additional components"]) for extra in component.additional_components: qty = extra.qty * component.get_qty_multiplier(extra.qty_multiplier) - if(self.mini_bom_mode): + if self.mini_bom_mode: id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) rows.append(component_table_entry(f'#{id} ({extra.type.capitalize()})', qty, extra.unit)) else: @@ -352,17 +352,15 @@ def get_additional_component_bom(self, component: Union[Connector, Cable]) -> Li bom_entries = [] for part in component.additional_components: qty = part.qty * component.get_qty_multiplier(part.qty_multiplier) - bom_entries.append( - { - 'item': part.description, - 'qty': qty, - 'unit': part.unit, - 'manufacturer': part.manufacturer, - 'mpn': part.mpn, - 'pn': part.pn, - 'designators': component.name if component.show_name else None - } - ) + bom_entries.append({ + 'item': part.description, + 'qty': qty, + 'unit': part.unit, + 'manufacturer': part.manufacturer, + 'mpn': part.mpn, + 'pn': part.pn, + 'designators': component.name if component.show_name else None + }) return(bom_entries) def bom(self): @@ -428,7 +426,6 @@ def bom(self): bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn']) for group in Counter([bom_types_group(v) for v in bom_entries]): group_entries = [v for v in bom_entries if bom_types_group(v) == group] - shared = group_entries[0] designators = [] for group_entry in group_entries: if group_entry.get('designators'): @@ -438,24 +435,19 @@ def bom(self): designators.append(group_entry['designators']) designators = list(dict.fromkeys(designators)) # remove duplicates designators.sort() - total_qty = sum(i['qty'] for i in group_entries) - self._bom.append({ - 'item': shared['item'], 'qty': round(total_qty, 3), 'unit': shared['unit'], 'designators': designators, - 'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn'] - }) + total_qty = sum(entry['qty'] for entry in group_entries) + self._bom.append({**group_entries[0], 'qty': round(total_qty, 3), 'designators': designators}) self._bom = sorted(self._bom, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050) - # add index - index = 1 - for item in self._bom: - item["id"] = index - index += 1 + + # add an incrementing id to each bom item + self._bom = [{**entry, 'id': index} for index, entry in enumerate(self._bom, 1)] return self._bom def get_bom_index(self, item, unit, manufacturer, mpn, pn): - for bom_item in self.bom(): - if((bom_item['item'], bom_item['unit'], bom_item['manufacturer'], bom_item['mpn'], bom_item['pn']) == (item, unit, manufacturer, mpn, pn)): - return bom_item['id'] + for entry in self.bom(): + if(entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn']) == (item, unit, manufacturer, mpn, pn): + return entry['id'] return None def bom_list(self): From e6c21bdc02eef4f800681709a80d0bed8d568578 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Sat, 17 Oct 2020 23:07:17 +0100 Subject: [PATCH 26/30] Fix issue with deduplication of connectors. unit is set to emptystring which doesen't deduplicate with the none present when unit is not set --- src/wireviz/Harness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index a1cf41e4..182e06ab 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -378,7 +378,7 @@ def bom(self): + (f', {connector.pincount} pins' if connector.show_pincount else '') + (f', {connector.color}' if connector.color else '')) bom_entries.append({ - 'item': description, 'qty': 1, 'unit': '', 'designators': connector.name if connector.show_name else None, + 'item': description, 'qty': 1, 'unit': None, 'designators': connector.name if connector.show_name else None, 'manufacturer': connector.manufacturer, 'mpn': connector.mpn, 'pn': connector.pn }) From 5e0827bbc0ca7399fabe02968d7f0776f0a83623 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Mon, 19 Oct 2020 00:33:33 +0100 Subject: [PATCH 27/30] Improve multiline field handling to avoid unwanted spaces --- src/wireviz/DataClasses.py | 2 +- src/wireviz/Harness.py | 16 ++++++++++------ src/wireviz/wv_helper.py | 3 +++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index cf259aa1..75b28258 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -59,7 +59,7 @@ class AdditionalComponent: @property def description(self) -> str: - return self.type.capitalize() + (f', {self.subtype}' if self.subtype else '') + return self.type.capitalize().strip() + (f', {self.subtype.strip()}' if self.subtype else '') @dataclass diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 182e06ab..06e3dc4f 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -7,9 +7,9 @@ from wireviz.wv_colors import get_color_hex from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, \ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ - graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write, \ - html_colorbar, html_image, html_caption, manufacturer_info_field, \ - component_table_entry + graphviz_line_breaks, remove_line_breaks, clean_whitespace, open_file_read, \ + open_file_write, html_colorbar, html_image, html_caption, \ + manufacturer_info_field, component_table_entry from collections import Counter from typing import List, Union from pathlib import Path @@ -343,7 +343,7 @@ def get_additional_component_table(self, component: Union[Connector, Cable]) -> qty = extra.qty * component.get_qty_multiplier(extra.qty_multiplier) if self.mini_bom_mode: id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) - rows.append(component_table_entry(f'#{id} ({extra.type.capitalize()})', qty, extra.unit)) + rows.append(component_table_entry(f'#{id} ({extra.type.capitalize().strip()})', qty, extra.unit)) else: rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) return(rows) @@ -422,6 +422,9 @@ def bom(self): 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn') }) + # remove line breaks if present and cleanup any resulting whitespace issues + bom_entries = [{k: clean_whitespace(v) for k, v in entry.items()} for entry in bom_entries] + # deduplicate bom bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn']) for group in Counter([bom_types_group(v) for v in bom_entries]): @@ -445,8 +448,10 @@ def bom(self): return self._bom def get_bom_index(self, item, unit, manufacturer, mpn, pn): + # Remove linebreaks and clean whitespace of values in search + target = tuple(clean_whitespace(v) for v in (item, unit, manufacturer, mpn, pn)) for entry in self.bom(): - if(entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn']) == (item, unit, manufacturer, mpn, pn): + if(entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn']) == target: return entry['id'] return None @@ -467,6 +472,5 @@ def bom_list(self): item_list = [item.get(key, '') for key in keys] # fill missing values with blanks item_list = [', '.join(subitem) if isinstance(subitem, List) else subitem for subitem in item_list] # convert any lists into comma separated strings item_list = ['' if subitem is None else subitem for subitem in item_list] # if a field is missing for some (but not all) BOM items - item_list = [remove_line_breaks(subitem) for subitem in item_list] # remove line breaks if present bom_list.append(item_list) return bom_list diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index ba5eef89..57e7a610 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -152,6 +152,9 @@ def graphviz_line_breaks(inp): def remove_line_breaks(inp): return inp.replace('\n', ' ').strip() if isinstance(inp, str) else inp +def clean_whitespace(inp): + return ' '.join(inp.split()).replace(' ,', ',') if isinstance(inp, str) else inp + def open_file_read(filename): # TODO: Intelligently determine encoding return open(filename, 'r', encoding='UTF-8') From f128ea2d10a2b972495025c8104eaa7fd35f9e40 Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Mon, 19 Oct 2020 00:18:19 +0100 Subject: [PATCH 28/30] Add additional_components to the syntax documentation. --- docs/syntax.md | 57 +++++++++++++++++++++++++++++------------- src/wireviz/Harness.py | 2 +- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/docs/syntax.md b/docs/syntax.md index a0a30c27..a0d9041b 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -43,9 +43,12 @@ additional_bom_items: # custom items to add to BOM notes: # product information (all optional) - pn: # [internal] part number - mpn: # manufacturer part number - manufacturer: # manufacturer name + ignore_in_bom: # if set to true the connector is not added to the BOM + pn: # [internal] part number + mpn: # manufacturer part number + manufacturer: # manufacturer name + additional_components: # additional components + - # additional component (see below) # pinout information # at least one of the following must be specified @@ -108,9 +111,12 @@ Since the auto-incremented and auto-assigned designator is not known to the user notes: # product information (all optional) - pn: # [internal] part number - mpn: # manufacturer part number - manufacturer: # manufacturer name + ignore_in_bom: # if set to true the cable or wires are not added to the BOM + pn: # [internal] part number + mpn: # manufacturer part number + manufacturer: # manufacturer name + additional_components: # additional components + - # additional component (see below) # conductor information # the following combinations are permitted: @@ -212,27 +218,42 @@ For connectors with `autogenerate: true`, a new instance, with auto-generated de - `-` auto-expands to a range. -## BOM items +## BOM items and additional components -Connectors (both regular, and auto-generated), cables, and wires of a bundle are automatically added to the BOM. +Connectors (both regular, and auto-generated), cables, and wires of a bundle are automatically added to the BOM, +unless the `ignore_in_bom` attribute is set to `true`. +Additional items can be added to the BOM as either part of a connector or cable or on their own. - +Parts can be added to a connector or cable in the section `` which will also list them in the graph. -Additional BOM entries can be generated in the sections marked `` above. +```yaml +- + type: # type of additional component + # all the following are optional: + subtype: # additional description (only shown in bom) + qty: # qty to add to the bom (defaults to 1) + qty_multiplier: # multiplies qty by a feature of the parent component + # when used in a connector: + # pincount number of pins of connector + # populated number of populated positions in a connector + # when used in a cable: + # wirecount number of wires of cable/bundle + # terminations number of terminations on a cable/bundle + # length length of cable/bundle + # total_length sum of lengths of each wire in the bundle + unit: + pn: # [internal] part number + mpn: # manufacturer part number + manufacturer: # manufacturer name +``` - +Alternatively items can be added to just the BOM by putting them in the section `` above. ```yaml - description: - qty: # when used in the additional_bom_items section: - # manually specify qty. - # when used within a component: - # manually specify qty. - # pincount match number of pins of connector - # wirecount match number of wires of cable/bundle - # connectioncount match number of connected pins # all the following are optional: + qty: # qty to add to the bom (defaults to 1) unit: designators: pn: # [internal] part number diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 06e3dc4f..b729d0a3 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -418,7 +418,7 @@ def bom(self): for item in self.additional_bom_items: bom_entries.append({ - 'item': item.get('description', ''), 'qty': item.get('qty'), 'unit': item.get('unit'), 'designators': item.get('designators'), + 'item': item.get('description', ''), 'qty': item.get('qty', 1), 'unit': item.get('unit'), 'designators': item.get('designators'), 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn') }) From 052348cd5442b0cb65387c8d2fbe21d07beece6c Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Tue, 20 Oct 2020 21:58:47 +0100 Subject: [PATCH 29/30] Remove unused functions and code formatting tweaks --- src/wireviz/Harness.py | 7 +++---- src/wireviz/wv_helper.py | 11 ++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index b729d0a3..a8379f72 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -7,9 +7,8 @@ from wireviz.wv_colors import get_color_hex from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, \ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ - graphviz_line_breaks, remove_line_breaks, clean_whitespace, open_file_read, \ - open_file_write, html_colorbar, html_image, html_caption, \ - manufacturer_info_field, component_table_entry + clean_whitespace, open_file_read, open_file_write, html_colorbar, \ + html_image, html_caption, manufacturer_info_field, component_table_entry from collections import Counter from typing import List, Union from pathlib import Path @@ -451,7 +450,7 @@ def get_bom_index(self, item, unit, manufacturer, mpn, pn): # Remove linebreaks and clean whitespace of values in search target = tuple(clean_whitespace(v) for v in (item, unit, manufacturer, mpn, pn)) for entry in self.bom(): - if(entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn']) == target: + if (entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn']) == target: return entry['id'] return None diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 57e7a610..79722bd3 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -146,12 +146,6 @@ def index_if_list(value, index): def html_line_breaks(inp): return inp.replace('\n', '
') if isinstance(inp, str) else inp -def graphviz_line_breaks(inp): - return inp.replace('\n', '\\n') if isinstance(inp, str) else inp # \n generates centered new lines. http://www.graphviz.org/doc/info/attrs.html#k:escString - -def remove_line_breaks(inp): - return inp.replace('\n', ' ').strip() if isinstance(inp, str) else inp - def clean_whitespace(inp): return ' '.join(inp.split()).replace(' ,', ',') if isinstance(inp, str) else inp @@ -202,4 +196,7 @@ def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn= output += manufacturer_str output = html_line_breaks(output) # format the above output as left aligned text in a single visible cell - return f'
{output}
' + # indent is set to two to match the indent in the generated html table + return f''' + +
{output}
''' From 4eeff65cf1d670453ba7df45dfcad54f74f1d20a Mon Sep 17 00:00:00 2001 From: Tyler Ward Date: Tue, 20 Oct 2020 22:30:18 +0100 Subject: [PATCH 30/30] Remove aditional component type capitilisation and swap to rstrip. --- src/wireviz/DataClasses.py | 3 ++- src/wireviz/Harness.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 75b28258..e813fbe3 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -7,6 +7,7 @@ from wireviz.wv_helper import int2tuple, aspect_ratio from wireviz import wv_colors +# Literal type aliases below are commented to avoid requiring python 3.8 ConnectorMultiplier = str # = Literal['pincount', 'populated'] CableMultiplier = str # = Literal['wirecount', 'terminations', 'length', 'total_length'] @@ -59,7 +60,7 @@ class AdditionalComponent: @property def description(self) -> str: - return self.type.capitalize().strip() + (f', {self.subtype.strip()}' if self.subtype else '') + return self.type.rstrip() + (f', {self.subtype.rstrip()}' if self.subtype else '') @dataclass diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index a8379f72..437ac205 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -342,7 +342,7 @@ def get_additional_component_table(self, component: Union[Connector, Cable]) -> qty = extra.qty * component.get_qty_multiplier(extra.qty_multiplier) if self.mini_bom_mode: id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) - rows.append(component_table_entry(f'#{id} ({extra.type.capitalize().strip()})', qty, extra.unit)) + rows.append(component_table_entry(f'#{id} ({extra.type.rstrip()})', qty, extra.unit)) else: rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn)) return(rows)