diff --git a/examples/demo02.yml b/examples/demo02.yml index 5002e7d2a..0df7f44df 100644 --- a/examples/demo02.yml +++ b/examples/demo02.yml @@ -2,12 +2,14 @@ templates: # defining templates to be used later on - &molex_f type: Molex KK 254 subtype: female + url: "https://www.molex.com/molex/products/family/kk_254_rpc_connector_system" - &con_i2c pinlabels: [GND, +5V, SCL, SDA] - &wire_i2c category: bundle gauge: 0.14 mm2 colors: [BK, RD, YE, GN] + url: [bk.html, rd.html, ye.html, gn.html] connectors: X1: @@ -67,3 +69,15 @@ connections: - ferrule_crimp - W4: [1,2] - X4: [1,2] + +additional_bom_items: + - # define an additional item to add to the bill of materials + description: Label, pinout information + qty: 2 + designators: + - X2 + - X3 + manufacturer: generic company + mpn: Label1 + pn: Label-ID-1 + url: "https://www.bradyid.com/wire-cable-labels/bmp71-bmp61-m611-tls-2200-nylon-cloth-wire-general-id-labels-cps-2958789" diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index c2d680d62..3280d8967 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -73,6 +73,7 @@ class AdditionalComponent: manufacturer: Optional[MultilineHypertext] = None mpn: Optional[MultilineHypertext] = None pn: Optional[Hypertext] = None + url: Optional[PlainText] = None qty: float = 1 unit: Optional[str] = None qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None @@ -88,6 +89,7 @@ class Connector: manufacturer: Optional[MultilineHypertext] = None mpn: Optional[MultilineHypertext] = None pn: Optional[Hypertext] = None + url: Optional[PlainText] = None style: Optional[str] = None category: Optional[str] = None type: Optional[MultilineHypertext] = None @@ -170,6 +172,7 @@ class Cable: manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None pn: Union[Hypertext, List[Hypertext], None] = None + url: Optional[PlainText] = None category: Optional[str] = None type: Optional[MultilineHypertext] = None gauge: Optional[float] = None diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 9d83a3dfa..9e44ec4d4 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -14,7 +14,7 @@ from wireviz.wv_gv_html import nested_html_table, html_colorbar, html_image, \ html_caption, remove_links, html_line_breaks from wireviz.wv_bom import manufacturer_info_field, component_table_entry, \ - get_additional_component_table, bom_list, generate_bom + get_additional_component_table, bom_list, generate_bom, index_if_list from wireviz.wv_html import generate_html_output from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, flatten2d, \ open_file_read, open_file_write @@ -159,7 +159,8 @@ def create_graph(self) -> Graph: html = [row.replace('', '\n'.join(pinhtml)) for row in html] html = '\n'.join(html) - dot.node(connector.name, label=f'<\n{html}\n>', shape='none', margin='0', style='filled', fillcolor='white') + dot.node(connector.name, label=f'<\n{html}\n>', shape='none', href=connector.url, + margin='0', style='filled', fillcolor='white') if len(connector.loops) > 0: dot.attr('edge', color='#000000:#ffffff:#000000') @@ -287,11 +288,14 @@ def create_graph(self) -> Graph: # connections for connection in cable.connections: + # Avoid href=None for edges as it seems that Graphviz then uses the href value from a previous edge! if isinstance(connection.via_port, int): # check if it's an actual wire and not a shield - dot.attr('edge', color=':'.join(['#000000'] + wv_colors.get_color_hex(cable.colors[connection.via_port - 1], pad=pad) + ['#000000'])) + dot.attr('edge', color=':'.join(['#000000'] + wv_colors.get_color_hex(cable.colors[connection.via_port - 1], pad=pad) + ['#000000']), + href=index_if_list(cable.url, connection.via_port - 1) or '') else: # it's a shield connection # shield is shown with specified color and black borders, or as a thin black wire otherwise - dot.attr('edge', color=':'.join(['#000000', shield_color_hex, '#000000']) if isinstance(cable.shield, str) else '#000000') + dot.attr('edge', color=':'.join(['#000000', shield_color_hex, '#000000']) if isinstance(cable.shield, str) else '#000000', + href=cable.url if isinstance(cable.url, str) else '') if connection.from_port is not None: # connect to left from_connector = self.connectors[connection.from_name] from_port = f':p{connection.from_port}r' if from_connector.style != 'simple' else '' @@ -327,6 +331,7 @@ def create_graph(self) -> Graph: html = '\n'.join(html) dot.node(cable.name, label=f'<\n{html}\n>', shape='box', + href=cable.url if isinstance(cable.url, str) else None, style='filled,dashed' if cable.category == 'bundle' else '', margin='0', fillcolor='white') return dot diff --git a/src/wireviz/wv_bom.py b/src/wireviz/wv_bom.py index ac5b071dd..94757a6f9 100644 --- a/src/wireviz/wv_bom.py +++ b/src/wireviz/wv_bom.py @@ -15,7 +15,7 @@ def get_additional_component_table(harness, component: Union[Connector, Cable]) for extra in component.additional_components: qty = extra.qty * component.get_qty_multiplier(extra.qty_multiplier) if harness.mini_bom_mode: - id = get_bom_index(harness, extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn) + id = get_bom_index(harness, extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn, extra.url) 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)) @@ -32,7 +32,8 @@ def get_additional_component_bom(component: Union[Connector, Cable]) -> List[dic 'manufacturer': part.manufacturer, 'mpn': part.mpn, 'pn': part.pn, - 'designators': component.name if component.show_name else None + 'designators': component.name if component.show_name else None, + 'url': part.url, }) return(bom_entries) @@ -49,7 +50,7 @@ def generate_bom(harness): + (f', {connector.color}' if connector.color else '')) bom_entries.append({ 'item': description, 'qty': 1, 'unit': None, 'designators': connector.name if connector.show_name else None, - 'manufacturer': connector.manufacturer, 'mpn': connector.mpn, 'pn': connector.pn + 'manufacturer': connector.manufacturer, 'mpn': connector.mpn, 'pn': connector.pn, 'url': connector.url, }) # add connectors aditional components to bom @@ -68,7 +69,8 @@ def generate_bom(harness): + (' shielded' if cable.shield else '')) bom_entries.append({ 'item': description, 'qty': cable.length, 'unit': cable.length_unit, 'designators': cable.name if cable.show_name else None, - 'manufacturer': cable.manufacturer, 'mpn': cable.mpn, 'pn': cable.pn + 'manufacturer': cable.manufacturer, 'mpn': cable.mpn, 'pn': cable.pn, + 'url': cable.url if isinstance(cable.url, str) else None, }) else: # add each wire from the bundle to the bom @@ -80,7 +82,8 @@ def generate_bom(harness): bom_entries.append({ 'item': description, 'qty': cable.length, 'unit': cable.length_unit, '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) + 'mpn': index_if_list(cable.mpn, index), 'pn': index_if_list(cable.pn, index), + 'url': index_if_list(cable.url, index), }) # add cable/bundles aditional components to bom @@ -89,7 +92,7 @@ def generate_bom(harness): for item in harness.additional_bom_items: bom_entries.append({ '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') + 'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn'), 'url': item.get('url'), }) # remove line breaks if present and cleanup any resulting whitespace issues @@ -97,7 +100,7 @@ def generate_bom(harness): # deduplicate bom bom = [] - bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn']) + bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn'], bt['url']) 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] designators = [] @@ -118,24 +121,25 @@ def generate_bom(harness): bom = [{**entry, 'id': index} for index, entry in enumerate(bom, 1)] return bom -def get_bom_index(harness, item, unit, manufacturer, mpn, pn): +def get_bom_index(harness, item, unit, manufacturer, mpn, pn, url): # Remove linebreaks and clean whitespace of values in search - target = tuple(clean_whitespace(v) for v in (item, unit, manufacturer, mpn, pn)) + target = tuple(clean_whitespace(v) for v in (item, unit, manufacturer, mpn, pn, url)) for entry in harness.bom(): - if (entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn']) == target: + if (entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn'], entry['url']) == target: return entry['id'] return None def bom_list(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 + for fieldname in ['pn', 'manufacturer', 'mpn', 'url']: # these optional BOM columns will only be included if at least one BOM item actually uses them 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 bom_headings = { "pn": "P/N", - "mpn": "MPN" + "mpn": "MPN", + "url": "URL", } bom_list.append([(bom_headings[k] if k in bom_headings else k.capitalize()) for k in keys]) # create header row with keys for item in bom: diff --git a/src/wireviz/wv_html.py b/src/wireviz/wv_html.py index b328ba3d6..80c3fcca4 100644 --- a/src/wireviz/wv_html.py +++ b/src/wireviz/wv_html.py @@ -36,6 +36,8 @@ def generate_html_output(filename: (str, Path), bom_list): file.write('