diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index d99786f..c72ab17 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -33,6 +33,9 @@ from wireviz.wv_bom import ( ) from wireviz.wv_colors import get_color_hex, translate_color from wireviz.wv_gv_html import ( + gv_connector_loops, + gv_node_connector, + gv_pin, html_bgcolor, html_bgcolor_attr, html_caption, @@ -174,102 +177,21 @@ class Harness: dot.attr("edge", style="bold", fontname=self.options.fontname) for connector in self.connectors.values(): - - # If no wires connected (except maybe loop wires)? - if not (connector.ports_left or connector.ports_right): - connector.ports_left = True # Use left side pins. - - html = [] - # fmt: off - rows = [[f'{html_bgcolor(connector.bgcolor_title)}{remove_links(connector.name)}' - if connector.show_name else None], - [pn_info_string(HEADER_PN, None, remove_links(connector.pn)), - html_line_breaks(pn_info_string(HEADER_MPN, connector.manufacturer, connector.mpn)), - html_line_breaks(pn_info_string(HEADER_SPN, connector.supplier, connector.spn))], - [html_line_breaks(connector.type), - html_line_breaks(connector.subtype), - f'{connector.pincount}-pin' if connector.show_pincount else None, - translate_color(connector.color, self.options.color_mode) if connector.color else None, - html_colorbar(connector.color)], - '' if connector.style != 'simple' else None, - [html_image(connector.image)], - [html_caption(connector.image)]] - # fmt: on - - rows.extend(get_additional_component_table(self, connector)) - rows.append([html_line_breaks(connector.notes)]) - html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor))) - - if connector.style != "simple": - pinhtml = [] - pinhtml.append( - '' - ) - - for pinindex, (pinname, pinlabel, pincolor) in enumerate( - zip_longest( - connector.pins, connector.pinlabels, connector.pincolors - ) - ): - if ( - connector.hide_disconnected_pins - and not connector.visible_pins.get(pinname, False) - ): - continue - - pinhtml.append(" ") - if connector.ports_left: - pinhtml.append(f' ') - if pinlabel: - pinhtml.append(f" ") - if connector.pincolors: - if pincolor in wv_colors._color_hex.keys(): - # fmt: off - pinhtml.append(f' ') - pinhtml.append( ' ') - # fmt: on - else: - pinhtml.append(' ') - - if connector.ports_right: - pinhtml.append(f' ') - pinhtml.append(" ") - - pinhtml.append("
{pinname}{pinlabel}{translate_color(pincolor, self.options.color_mode)}') - pinhtml.append( ' ') - pinhtml.append(f' ') - pinhtml.append( '
') - pinhtml.append( '
{pinname}
") - - html = [ - row.replace("", "\n".join(pinhtml)) - for row in html - ] - - html = "\n".join(html) + gv_html = gv_node_connector(connector, self.options) + _default_fillcolor = translate_color(self.options.bgcolor_connector, "HEX") dot.node( connector.name, - label=f"<\n{html}\n>", + label=f"<\n{gv_html}\n>", shape="box", style="filled", - fillcolor=translate_color(self.options.bgcolor_connector, "HEX"), + fillcolor=_default_fillcolor, ) if len(connector.loops) > 0: dot.attr("edge", color="#000000:#ffffff:#000000") - if connector.ports_left: - loop_side = "l" - loop_dir = "w" - elif connector.ports_right: - loop_side = "r" - loop_dir = "e" - else: - raise Exception("No side for loops") - for loop in connector.loops: - dot.edge( - f"{connector.name}:p{loop[0]}{loop_side}:{loop_dir}", - f"{connector.name}:p{loop[1]}{loop_side}:{loop_dir}", - ) + loops = gv_connector_loops(connector) + for head, tail in loops: + dot.edge(head, tail) # determine if there are double- or triple-colored wires in the harness; # if so, pad single-color wires to make all wires of equal thickness diff --git a/src/wireviz/wv_bom.py b/src/wireviz/wv_bom.py index 6689d79..d28e3a1 100644 --- a/src/wireviz/wv_bom.py +++ b/src/wireviz/wv_bom.py @@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union from wireviz.DataClasses import AdditionalComponent, Cable, Color, Connector from wireviz.wv_colors import translate_color from wireviz.wv_gv_html import html_bgcolor_attr, html_line_breaks -from wireviz.wv_helper import clean_whitespace +from wireviz.wv_helper import clean_whitespace, pn_info_string BOM_COLUMNS_ALWAYS = ("id", "description", "qty", "unit", "designators") BOM_COLUMNS_OPTIONAL = ("pn", "manufacturer", "mpn", "supplier", "spn") @@ -263,17 +263,6 @@ def component_table_entry( """ -def pn_info_string( - header: str, name: Optional[str], number: Optional[str] -) -> Optional[str]: - """Return the company name and/or the part number in one single string or None otherwise.""" - number = str(number).strip() if number is not None else "" - if name or number: - return f'{name if name else header}{": " + number if number else ""}' - else: - return None - - def index_if_list(value: Any, index: int) -> Any: """Return the value indexed if it is a list, or simply the value otherwise.""" return value[index] if isinstance(value, list) else value diff --git a/src/wireviz/wv_colors.py b/src/wireviz/wv_colors.py index 857f307..34d9241 100644 --- a/src/wireviz/wv_colors.py +++ b/src/wireviz/wv_colors.py @@ -178,8 +178,10 @@ def get_color_translation(translate: Dict[Color, str], input: Colors) -> List[st def translate_color(input: Colors, color_mode: ColorMode) -> str: - if input == "" or input is None: + if input == "": return "" + if input is None: + return None upper = color_mode.isupper() if not (color_mode.isupper() or color_mode.islower()): raise Exception("Unknown color mode capitalization") diff --git a/src/wireviz/wv_gv_html.py b/src/wireviz/wv_gv_html.py index ec80aa7..92c29a5 100644 --- a/src/wireviz/wv_gv_html.py +++ b/src/wireviz/wv_gv_html.py @@ -1,11 +1,155 @@ # -*- coding: utf-8 -*- import re +from itertools import zip_longest from typing import List, Optional, Union -from wireviz.DataClasses import Color +from wireviz.DataClasses import Color, Connector, Options from wireviz.wv_colors import translate_color -from wireviz.wv_helper import remove_links +from wireviz.wv_helper import pn_info_string, remove_links +from wireviz.wv_table_util import * # TODO: explicitly import each needed tag later + +HEADER_PN = "P/N" +HEADER_MPN = "MPN" +HEADER_SPN = "SPN" + +# TODO: remove harness argument; only used by get_additional_component_table() +def gv_node_connector(connector: Connector, harness_options: Options) -> str: + # If no wires connected (except maybe loop wires)? + if not (connector.ports_left or connector.ports_right): + connector.ports_left = True # Use left side pins by default + + html = [] + if connector.show_name: + row_name = [ + f"{html_bgcolor(connector.bgcolor_title)}" f"{remove_links(connector.name)}" + ] + else: + row_name = [] + + row_pn = [ + pn_info_string(HEADER_PN, None, connector.pn), + pn_info_string(HEADER_MPN, connector.manufacturer, connector.mpn), + pn_info_string(HEADER_SPN, connector.supplier, connector.spn), + ] + row_pn = [html_line_breaks(cell) for cell in row_pn] + + row_info = [ + html_line_breaks(connector.type), + html_line_breaks(connector.subtype), + f"{connector.pincount}-pin" if connector.show_pincount else None, + translate_color(connector.color, harness_options.color_mode), + html_colorbar(connector.color), + ] + + if connector.style != "simple": + row_connector_table = "" + else: + row_connector_table = None + + row_image = [html_image(connector.image)] + row_image_caption = [html_caption(connector.image)] + row_notes = [html_line_breaks(connector.notes)] + # row_additional_component_table = get_additional_component_table(self, connector) + row_additional_component_table = None + + rows = [ + row_name, + row_pn, + row_info, + row_connector_table, + row_image, + row_image_caption, + row_additional_component_table, + row_notes, + ] + + html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor))) + + if connector.style != "simple": + pinhtml = [] + # fmt: off + # pinhtml.append('') + # fmt: on + + pin_tuples = zip_longest( + connector.pins, + connector.pinlabels, + connector.pincolors, + ) + + contents = [] + for pinindex, (pinname, pinlabel, pincolor) in enumerate(pin_tuples): + if connector.hide_disconnected_pins and not connector.visible_pins.get( + pinname, False + ): + continue + + contents.append(gv_pin(pinindex, pinname, pinlabel, pincolor, connector)) + + table_attribs = { + "border": 0, + "cellspacing": 0, + "cellpadding": 3, + "cellborder": 1, + } + pinhtml.append(str(Table(contents, attribs=Attribs(table_attribs)))) + + pin_html_joined = "\n".join(pinhtml) + + html = [ + row.replace("", pin_html_joined) for row in html + ] + + html = "\n".join(html) + + return html + + +def gv_pin(pinindex, pinname, pinlabel, pincolor, connector): + pinhtml = [] + pinhtml.append(" ") + if connector.ports_left: + pinhtml.append(f' ') + if pinlabel: + pinhtml.append(f" ") + if connector.pincolors: + if pincolor in wv_colors._color_hex.keys(): + # fmt: off + pinhtml.append(f' ') + pinhtml.append( ' ') + # fmt: on + else: + pinhtml.append(' ') + + if connector.ports_right: + pinhtml.append(f' ') + pinhtml.append(" ") + + pinhtml = "\n".join(pinhtml) + + return pinhtml + + +def gv_connector_loops(connector: Connector) -> List: + loop_edges = [] + if connector.ports_left: + loop_side = "l" + loop_dir = "w" + elif connector.ports_right: + loop_side = "r" + loop_dir = "e" + else: + raise Exception("No side for loops") + for loop in connector.loops: + head = f"{connector.name}:p{loop[0]}{loop_side}:{loop_dir}" + tail = f"{connector.name}:p{loop[1]}{loop_side}:{loop_dir}" + loop_edges.append((head, tail)) + return loop_edges def nested_html_table( diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index a10f6ac..8c46299 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -2,7 +2,7 @@ import re from pathlib import Path -from typing import Dict, List +from typing import Dict, List, Optional awg_equiv_table = { "0.09": "28", @@ -176,3 +176,14 @@ def smart_file_resolve(filename: str, possible_paths: (str, List[str])) -> Path: f"{filename} was not found in any of the following locations: \n" + "\n".join([str(x) for x in possible_paths]) ) + + +def pn_info_string( + header: str, name: Optional[str], number: Optional[str] +) -> Optional[str]: + """Return the company name and/or the part number in one single string or None otherwise.""" + number = str(number).strip() if number is not None else "" + if name or number: + return f'{name if name else header}{": " + number if number else ""}' + else: + return None diff --git a/src/wireviz/wv_table_util.py b/src/wireviz/wv_table_util.py new file mode 100644 index 0000000..eb76408 --- /dev/null +++ b/src/wireviz/wv_table_util.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +from dataclasses import dataclass, field +from typing import Dict, List, Optional +from collections.abc import Iterable + + +class Attribs(Dict): + def __repr__(self): + if len(self) == 0: + return "" + + html = [] + for k, v in self.items(): + if v is not None: + html.append(f' {k}="{v}"') + else: + html.append(f" {k}") + return "".join(html) + + +@dataclass +class Tag: + contents: str + attribs: Attribs = field(default_factory=Attribs) + one_line: bool = False + + @property + def tagname(self): + return type(self).__name__.lower() + + def get_contents(self): + if isinstance(self.contents, Iterable): + return "\n".join([str(c) for c in self.contents]) + else: + return str(self.contents) + + + def __repr__(self): + separator = "" if self.one_line else "\n" + html = [ + f"<{self.tagname}{str(self.attribs)}>", + self.get_contents(), + f"", + ] + return separator.join(html) + + +@dataclass +class TagSingleton(Tag): + def __repr__(self): + return f"<{self.tagname}{self.attribs} />" + + +@dataclass +class Br(TagSingleton): + pass + + +class Td(Tag): + pass + # contents: str = "" + # + # def __init__(self, contents, *args, **kwargs): + # self.contents = contents + # super().__init__(*args, **kwargs) + # + # def __repr__(self): + # html = [ + # f"", + # self.contents, + # f"", + # ] + # return "\n".join(html) + + +class Tr(Tag): + pass + # cells: List[Cell] = field(default_factory=list) + # + # def __init__(self, cells, *args, **kwargs): + # self.cells = cells + # super().__init__(*args, **kwargs) + # + # def __repr__(self): + # html = [ + # f"", + # "\n".join([str(c) for c in self.cells]), + # f"", + # ] + # return "\n".join(html) + + +class Table(Tag): + pass + # rows: List[Row] = field(default_factory=list) + # + # def __repr__(self): + # html = [ + # f"", + # "\n".join([str(r) for r in self.rows]), + # "
{pinname}{pinlabel}{translate_color(pincolor, harness_options.color_mode)}') + pinhtml.append( ' ') + pinhtml.append(f' ') + pinhtml.append( '
') + pinhtml.append( '
{pinname}
", + # ] + # return "\n".join(html)