This commit is contained in:
Daniel Rojas 2021-10-17 15:33:41 +02:00 committed by KV
parent 82b5cb710f
commit f7359ff9b1
6 changed files with 276 additions and 104 deletions

View File

@ -33,6 +33,9 @@ from wireviz.wv_bom import (
) )
from wireviz.wv_colors import get_color_hex, translate_color from wireviz.wv_colors import get_color_hex, translate_color
from wireviz.wv_gv_html import ( from wireviz.wv_gv_html import (
gv_connector_loops,
gv_node_connector,
gv_pin,
html_bgcolor, html_bgcolor,
html_bgcolor_attr, html_bgcolor_attr,
html_caption, html_caption,
@ -174,102 +177,21 @@ class Harness:
dot.attr("edge", style="bold", fontname=self.options.fontname) dot.attr("edge", style="bold", fontname=self.options.fontname)
for connector in self.connectors.values(): for connector in self.connectors.values():
gv_html = gv_node_connector(connector, self.options)
# If no wires connected (except maybe loop wires)? _default_fillcolor = translate_color(self.options.bgcolor_connector, "HEX")
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)],
'<!-- connector table -->' 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(
'<table border="0" cellspacing="0" cellpadding="3" cellborder="1">'
)
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(" <tr>")
if connector.ports_left:
pinhtml.append(f' <td port="p{pinindex+1}l">{pinname}</td>')
if pinlabel:
pinhtml.append(f" <td>{pinlabel}</td>")
if connector.pincolors:
if pincolor in wv_colors._color_hex.keys():
# fmt: off
pinhtml.append(f' <td sides="tbl">{translate_color(pincolor, self.options.color_mode)}</td>')
pinhtml.append( ' <td sides="tbr">')
pinhtml.append( ' <table border="0" cellborder="1"><tr>')
pinhtml.append(f' <td bgcolor="{wv_colors.translate_color(pincolor, "HEX")}" width="8" height="8" fixedsize="true"></td>')
pinhtml.append( ' </tr></table>')
pinhtml.append( ' </td>')
# fmt: on
else:
pinhtml.append(' <td colspan="2"></td>')
if connector.ports_right:
pinhtml.append(f' <td port="p{pinindex+1}r">{pinname}</td>')
pinhtml.append(" </tr>")
pinhtml.append(" </table>")
html = [
row.replace("<!-- connector table -->", "\n".join(pinhtml))
for row in html
]
html = "\n".join(html)
dot.node( dot.node(
connector.name, connector.name,
label=f"<\n{html}\n>", label=f"<\n{gv_html}\n>",
shape="box", shape="box",
style="filled", style="filled",
fillcolor=translate_color(self.options.bgcolor_connector, "HEX"), fillcolor=_default_fillcolor,
) )
if len(connector.loops) > 0: if len(connector.loops) > 0:
dot.attr("edge", color="#000000:#ffffff:#000000") dot.attr("edge", color="#000000:#ffffff:#000000")
if connector.ports_left: loops = gv_connector_loops(connector)
loop_side = "l" for head, tail in loops:
loop_dir = "w" dot.edge(head, tail)
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}",
)
# determine if there are double- or triple-colored wires in the harness; # 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 # if so, pad single-color wires to make all wires of equal thickness

View File

@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
from wireviz.DataClasses import AdditionalComponent, Cable, Color, Connector from wireviz.DataClasses import AdditionalComponent, Cable, Color, Connector
from wireviz.wv_colors import translate_color from wireviz.wv_colors import translate_color
from wireviz.wv_gv_html import html_bgcolor_attr, html_line_breaks 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_ALWAYS = ("id", "description", "qty", "unit", "designators")
BOM_COLUMNS_OPTIONAL = ("pn", "manufacturer", "mpn", "supplier", "spn") BOM_COLUMNS_OPTIONAL = ("pn", "manufacturer", "mpn", "supplier", "spn")
@ -263,17 +263,6 @@ def component_table_entry(
</tr></table>""" </tr></table>"""
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: def index_if_list(value: Any, index: int) -> Any:
"""Return the value indexed if it is a list, or simply the value otherwise.""" """Return the value indexed if it is a list, or simply the value otherwise."""
return value[index] if isinstance(value, list) else value return value[index] if isinstance(value, list) else value

View File

@ -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: def translate_color(input: Colors, color_mode: ColorMode) -> str:
if input == "" or input is None: if input == "":
return "" return ""
if input is None:
return None
upper = color_mode.isupper() upper = color_mode.isupper()
if not (color_mode.isupper() or color_mode.islower()): if not (color_mode.isupper() or color_mode.islower()):
raise Exception("Unknown color mode capitalization") raise Exception("Unknown color mode capitalization")

View File

@ -1,11 +1,155 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
from itertools import zip_longest
from typing import List, Optional, Union 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_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 = "<!-- 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('<table border="0" cellspacing="0" cellpadding="3" cellborder="1">')
# 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("<!-- connector table -->", pin_html_joined) for row in html
]
html = "\n".join(html)
return html
def gv_pin(pinindex, pinname, pinlabel, pincolor, connector):
pinhtml = []
pinhtml.append(" <tr>")
if connector.ports_left:
pinhtml.append(f' <td port="p{pinindex+1}l">{pinname}</td>')
if pinlabel:
pinhtml.append(f" <td>{pinlabel}</td>")
if connector.pincolors:
if pincolor in wv_colors._color_hex.keys():
# fmt: off
pinhtml.append(f' <td sides="tbl">{translate_color(pincolor, harness_options.color_mode)}</td>')
pinhtml.append( ' <td sides="tbr">')
pinhtml.append( ' <table border="0" cellborder="1"><tr>')
pinhtml.append(f' <td bgcolor="{wv_colors.translate_color(pincolor, "HEX")}" width="8" height="8" fixedsize="true"></td>')
pinhtml.append( ' </tr></table>')
pinhtml.append( ' </td>')
# fmt: on
else:
pinhtml.append(' <td colspan="2"></td>')
if connector.ports_right:
pinhtml.append(f' <td port="p{pinindex+1}r">{pinname}</td>')
pinhtml.append(" </tr>")
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( def nested_html_table(

View File

@ -2,7 +2,7 @@
import re import re
from pathlib import Path from pathlib import Path
from typing import Dict, List from typing import Dict, List, Optional
awg_equiv_table = { awg_equiv_table = {
"0.09": "28", "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" f"{filename} was not found in any of the following locations: \n"
+ "\n".join([str(x) for x in possible_paths]) + "\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

View File

@ -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"</{self.tagname}>",
]
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"<td{self.attribs}>",
# self.contents,
# f"</td>",
# ]
# 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"<tr{self.attribs}>",
# "\n".join([str(c) for c in self.cells]),
# f"</tr>",
# ]
# return "\n".join(html)
class Table(Tag):
pass
# rows: List[Row] = field(default_factory=list)
#
# def __repr__(self):
# html = [
# f"<table{self.attribs}>",
# "\n".join([str(r) for r in self.rows]),
# "</table>",
# ]
# return "\n".join(html)