diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index cffb2155..ef5b8cbc 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -99,6 +99,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 6deb10ac..c92ca4f6 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -100,25 +100,22 @@ def create_graph(self) -> Graph: '' if connector.style != 'simple' else None] 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: '+ S' if cable.shield else None, f'{cable.length} m' if cable.length > 0 else None, cable.color, '' if cable.color else None], - '', - [html_line_breaks(cable.notes)]] + ''] + + 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)) if cable.color: # add color bar next to color info, if present @@ -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):