This commit is contained in:
Daniel Rojas 2021-10-18 13:42:51 +02:00 committed by KV
parent 1f8dd49eb9
commit f46bce6867
5 changed files with 156 additions and 231 deletions

View File

@ -138,7 +138,11 @@ class AdditionalComponent:
@dataclass @dataclass
class Connector: class Component():
pass
@dataclass
class Connector(Component):
name: Designator name: Designator
bgcolor: Optional[Color] = None bgcolor: Optional[Color] = None
bgcolor_title: Optional[Color] = None bgcolor_title: Optional[Color] = None
@ -247,7 +251,7 @@ class Connector:
@dataclass @dataclass
class Cable: class Cable(Component):
name: Designator name: Designator
bgcolor: Optional[Color] = None bgcolor: Optional[Color] = None
bgcolor_title: Optional[Color] = None bgcolor_title: Optional[Color] = None

View File

@ -34,15 +34,8 @@ 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_connector_loops,
gv_node_cable, gv_node_component,
gv_node_connector,
html_bgcolor,
html_bgcolor_attr,
html_caption,
html_colorbar,
html_image,
html_line_breaks, html_line_breaks,
nested_html_table,
remove_links, remove_links,
) )
from wireviz.wv_helper import ( from wireviz.wv_helper import (
@ -176,7 +169,7 @@ class Harness:
for connector in self.connectors.values(): for connector in self.connectors.values():
# generate connector node # generate connector node
gv_html = gv_node_connector(connector, self.options) gv_html = gv_node_component(connector, self.options)
dot.node( dot.node(
connector.name, connector.name,
label=f"<\n{gv_html}\n>", label=f"<\n{gv_html}\n>",
@ -200,7 +193,7 @@ class Harness:
for cable in self.cables.values(): for cable in self.cables.values():
gv_html = gv_node_cable(cable, self.options, pad) gv_html = gv_node_component(cable, self.options, pad)
dot.node( dot.node(
cable.name, cable.name,
label=f"<\n{gv_html}\n>", label=f"<\n{gv_html}\n>",

View File

@ -6,7 +6,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_line_breaks
from wireviz.wv_helper import clean_whitespace, pn_info_string 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")

View File

@ -4,7 +4,7 @@ import re
from itertools import zip_longest from itertools import zip_longest
from typing import List, Optional, Union from typing import List, Optional, Union
from wireviz.DataClasses import Cable, Color, Connector, Options from wireviz.DataClasses import Cable, Color, Connector, Component, Options
from wireviz.wv_colors import get_color_hex, translate_color from wireviz.wv_colors import get_color_hex, translate_color
from wireviz.wv_helper import pn_info_string, 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 from wireviz.wv_table_util import * # TODO: explicitly import each needed tag later
@ -14,63 +14,80 @@ HEADER_MPN = "MPN"
HEADER_SPN = "SPN" HEADER_SPN = "SPN"
def gv_node_connector(connector: Connector, harness_options: Options) -> Table: def gv_node_component(component: Component, harness_options: Options, pad=None) -> Table:
# If no wires connected (except maybe loop wires)? # If no wires connected (except maybe loop wires)?
if not (connector.ports_left or connector.ports_right): if isinstance(component, Connector):
connector.ports_left = True # Use left side pins by default if not (component.ports_left or component.ports_right):
component.ports_left = True # Use left side pins by default
# generate all rows to be shown in the node # generate all rows to be shown in the node
if connector.show_name: if component.show_name:
str_name = f"{remove_links(connector.name)}" str_name = f"{remove_links(component.name)}"
row_name = [colored_cell(str_name, connector.bgcolor_title)] row_name = [colored_cell(str_name, component.bgcolor_title)]
else: else:
row_name = [] row_name = []
row_pn = par_number_cell_list(connector) row_pn = par_number_cell_list(component)
row_info = [ if isinstance(component, Connector):
html_line_breaks(connector.type), row_info = [
html_line_breaks(connector.subtype), html_line_breaks(component.type),
f"{connector.pincount}-pin" if connector.show_pincount else None, html_line_breaks(component.subtype),
translate_color(connector.color, harness_options.color_mode), f"{component.pincount}-pin" if component.show_pincount else None,
colorbar_cell(connector.color), translate_color(component.color, harness_options.color_mode),
] colorbar_cell(component.color),
]
elif isinstance(component, Cable):
row_info = [
html_line_breaks(component.type),
f"{component.wirecount}x" if component.show_wirecount else None,
f"{component.gauge_str}" if component.gauge else None,
"+ S" if component.shield else None,
f"{component.length} {component.length_unit}" if component.length > 0 else None,
translate_color(component.color, harness_options.color_mode),
colorbar_cell(component.color),
]
row_image, row_image_caption = image_and_caption_cells(connector) row_image, row_image_caption = image_and_caption_cells(component)
# row_additional_component_table = get_additional_component_table(self, connector) # row_additional_component_table = get_additional_component_table(self, connector)
row_additional_component_table = None row_additional_component_table = None
row_notes = [html_line_breaks(connector.notes)] row_notes = [html_line_breaks(component.notes)]
if connector.style != "simple":
pin_tuples = zip_longest(
connector.pins,
connector.pinlabels,
connector.pincolors,
)
pin_rows = [] if isinstance(component, Connector):
for pinindex, (pinname, pinlabel, pincolor) in enumerate(pin_tuples): # pin table
if connector.should_show_pin(pinname): if component.style != "simple":
pin_rows.append( pin_tuples = zip_longest(
gv_pin_row(pinindex, pinname, pinlabel, pincolor, connector) component.pins,
) component.pinlabels,
component.pincolors,
)
table_attribs = { pin_rows = []
"border": 0, for pinindex, (pinname, pinlabel, pincolor) in enumerate(pin_tuples):
"cellspacing": 0, if component.should_show_pin(pinname):
"cellpadding": 3, pin_rows.append(
"cellborder": 1, gv_pin_row(pinindex, pinname, pinlabel, pincolor, component)
} )
row_connector_table = str(Table(pin_rows, attribs=table_attribs))
else: table_attribs = {
row_connector_table = None "border": 0,
"cellspacing": 0,
"cellpadding": 3,
"cellborder": 1,
}
row_ports = str(Table(pin_rows, attribs=table_attribs))
else:
row_ports = None
elif isinstance(component, Cable):
row_ports = str(gv_conductor_table(component, harness_options, pad))
rows = [ rows = [
row_name, row_name,
row_pn, row_pn,
row_info, row_info,
row_connector_table, row_ports,
row_image, row_image,
row_image_caption, row_image_caption,
row_additional_component_table, row_additional_component_table,
@ -79,12 +96,18 @@ def gv_node_connector(connector: Connector, harness_options: Options) -> Table:
tbl = nested_table(rows) tbl = nested_table(rows)
if connector.bgcolor: if component.bgcolor:
tbl.attribs["bgcolor"] = translate_color(connector.bgcolor, "HEX") tbl.attribs["bgcolor"] = translate_color(component.bgcolor, "HEX")
elif harness_options.bgcolor_connector: else:
tbl.attribs["bgcolor"] = translate_color( if isinstance(component, Connector) and harness_options.bgcolor_connector:
harness_options.bgcolor_connector, "HEX" tbl.attribs["bgcolor"] = translate_color(
) harness_options.bgcolor_connector, "HEX"
)
elif isinstance(component, Cable) and harness_options.bgcolor_cable:
tbl.attribs["bgcolor"] = translate_color(
harness_options.bgcolor_cable, "HEX"
)
return tbl return tbl
@ -119,50 +142,6 @@ def gv_connector_loops(connector: Connector) -> List:
return loop_edges return loop_edges
def gv_node_cable(cable: Cable, harness_options: Options, pad) -> Table:
if cable.show_name:
str_name = f"{remove_links(cable.name)}"
row_name = [colored_cell(str_name, cable.bgcolor_title)]
else:
row_name = []
row_pn = par_number_cell_list(cable)
row_info = [
html_line_breaks(cable.type),
f"{cable.wirecount}x" if cable.show_wirecount else None,
f"{cable.gauge_str}" if cable.gauge else None,
"+ S" if cable.shield else None,
f"{cable.length} {cable.length_unit}" if cable.length > 0 else None,
translate_color(cable.color, self.options.color_mode) if cable.color else None,
html_colorbar(cable.color),
]
row_image, row_image_caption = image_and_caption_cells(cable)
row_conductor_table = str(gv_conductor_table(cable, harness_options, pad))
# row_additional_component_table = get_additional_component_table(self, cable)
row_additional_component_table = None
row_notes = [html_line_breaks(cable.notes)]
rows = [
row_name,
row_pn,
row_info,
row_conductor_table,
row_image,
row_image_caption,
row_additional_component_table,
row_notes,
]
tbl = nested_table(rows)
return tbl
def gv_conductor_table(cable, harness_options, pad) -> Table: def gv_conductor_table(cable, harness_options, pad) -> Table:
rows = [] rows = []
@ -249,26 +228,45 @@ def colorbar_cell(color) -> Td:
return None return None
# def html_image_new(image):
# from wireviz.DataClasses import Image
# if not image:
# return None
# image_tag = Img(attribs={"scale": image.scale, "src": image.src})
# image_table = Table(Tr(Td(image_tag, attribs=html_size_attr_dict(image))), attribs={"border": 0, "cellspacing": 0, "cellborder": 0})
# return image_table
def image_and_caption_cells(component): def image_and_caption_cells(component):
if component.image: if component.image:
row_image_attribs = html_size_attr_dict(component.image) # outer_cell_attribs = {}
row_image_attribs["balign"] = "left" # outer_cell_attribs["balign"] = "left"
if component.image.bgcolor: # outer_cell_attribs["bgcolor"] = translate_color(
row_image_attribs["bgcolor"] = translate_color( # component.image.bgcolor, "HEX"
component.image.bgcolor, "HEX" # )
) # if component.image.caption:
if component.image.caption: # outer_cell_attribs["sides"] = "TLR"
row_image_attribs["sides"] = "TLR"
row_image = [Td(html_image_new(component.image), attribs=row_image_attribs)] image_tag = Img(attribs={"scale": component.image.scale, "src": component.image.src})
cell_attribs = html_size_attr_dict(component.image)
image_cell = Td(image_tag, attribs=cell_attribs)
image_row = Tr(image_cell)
image_table_attribs = {
"border": 0,
"cellspacing": 0,
"cellpadding": 0,
"cellborder": 1,
}
image_table = Table(image_row, attribs=image_table_attribs)
outer_cell = Td(image_table)
# return:
row_image = outer_cell
if component.image.caption: if component.image.caption:
row_caption_attribs = {"balign": "left", "sides": "BLR"} row_caption_attribs = {"balign": "left", "sides": "BLR", "id": "td_caption"}
row_image_caption = [ row_image_caption = Td(
Td( html_caption_new(component.image),
html_caption_new(component.image), attribs=row_caption_attribs,
attribs=row_caption_attribs, )
)
]
else: else:
row_image_caption = None row_image_caption = None
return (row_image, row_image_caption) return (row_image, row_image_caption)
@ -288,12 +286,14 @@ def par_number_cell_list(component) -> List[Td]:
return None return None
def nested_table(rows_in: List[Tr]): def nested_table(rows_in: List[Tr]) -> Table:
outer_rows = [] outer_rows = []
for row in rows_in: for row in rows_in:
if isinstance(row, List) and len(row) > 0 and any(row): if isinstance(row, List) and len(row) > 0 and any(row):
# row is a nested list
# remove rows which are none # remove rows which are none
row_no_empty = [cell for cell in row if cell is not None] row_no_empty = [cell for cell in row if cell is not None]
# if row item is Td, append directly; else convert to Td
inner_cells = [] inner_cells = []
for cell in row_no_empty: for cell in row_no_empty:
if isinstance(cell, Td): if isinstance(cell, Td):
@ -311,8 +311,13 @@ def nested_table(rows_in: List[Tr]):
if len(inner_cells) > 0: if len(inner_cells) > 0:
inner_table = Table(Tr(inner_cells), attribs=inner_table_attribs) inner_table = Table(Tr(inner_cells), attribs=inner_table_attribs)
outer_rows.append(Tr(Td(inner_table))) outer_rows.append(Tr(Td(inner_table)))
elif row is not None and any(row): elif row is not None:
outer_rows.append(Tr(Td(row))) if isinstance(row, Iterable) and not any(row):
continue
# row is a single item
# if item is Td, append directly; else convert to Td
cell = row if isinstance(row, Td) else Td(row)
outer_rows.append(Tr(cell))
if len(outer_rows) == 0: if len(outer_rows) == 0:
outer_rows = Tr(Td("")) # Generate empty cell to avoid GraphViz errors outer_rows = Tr(Td("")) # Generate empty cell to avoid GraphViz errors
outer_table_attribs = {"border": 0, "cellspacing": 0, "cellpadding": 0} outer_table_attribs = {"border": 0, "cellspacing": 0, "cellpadding": 0}
@ -321,106 +326,25 @@ def nested_table(rows_in: List[Tr]):
return outer_table return outer_table
def nested_html_table(
rows: List[Union[str, List[Optional[str]], None]], table_attrs: str = ""
) -> str:
# input: list, each item may be scalar or list
# output: a parent table with one child table per parent item that is list, and one cell per parent item that is scalar
# purpose: create the appearance of one table, where cell widths are independent between rows
# attributes in any leading <tdX> inside a list are injected into to the preceeding <td> tag
html = []
html.append(
f'<table border="0" cellspacing="0" cellpadding="0"{table_attrs or ""}>'
)
num_rows = 0
for row in rows:
if isinstance(row, List):
if len(row) > 0 and any(row):
html.append(" <tr><td>")
# fmt: off
html.append(' <table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr>')
# fmt: on
for cell in row:
if cell is not None:
# Inject attributes to the preceeding <td> tag where needed
# fmt: off
html.append(f' <td balign="left">{cell}</td>'.replace("><tdX", ""))
# fmt: on
html.append(" </tr></table>")
html.append(" </td></tr>")
num_rows = num_rows + 1
elif row is not None:
html.append(" <tr><td>")
html.append(f" {row}")
html.append(" </td></tr>")
num_rows = num_rows + 1
if num_rows == 0: # empty table
# generate empty cell to avoid GraphViz errors
html.append("<tr><td></td></tr>")
html.append("</table>")
return html
def html_bgcolor_attr(color: Color) -> str: # def html_image(image):
"""Return attributes for bgcolor or '' if no color.""" # from wireviz.DataClasses import Image
return f' bgcolor="{translate_color(color, "HEX")}"' if color else "" # if not image:
# return None
# # The leading attributes belong to the preceeding tag. See where used below.
# html = f'{html_size_attr(image)}><img scale="{image.scale}" src="{image.src}"/>'
# if image.fixedsize:
# # Close the preceeding tag and enclose the image cell in a table without
# # borders to avoid narrow borders when the fixed width < the node width.
# html = f""">
# <table border="0" cellspacing="0" cellborder="0"><tr>
# <td{html}</td>
# </tr></table>
# """
# return f"""<tdX{' sides="TLR"' if image.caption else ''}{html_bgcolor_attr(image.bgcolor)}{html}"""
def html_bgcolor(color: Color, _extra_attr: str = "") -> str:
"""Return <td> attributes prefix for bgcolor or '' if no color."""
return f"<tdX{html_bgcolor_attr(color)}{_extra_attr}>" if color else ""
def html_colorbar(color: Color) -> str:
"""Return <tdX> attributes prefix for bgcolor and minimum width or None if no color."""
return html_bgcolor(color, ' width="4"') if color else None
def html_image(image):
from wireviz.DataClasses import Image
if not image:
return None
# The leading attributes belong to the preceeding tag. See where used below.
html = f'{html_size_attr(image)}><img scale="{image.scale}" src="{image.src}"/>'
if image.fixedsize:
# Close the preceeding tag and enclose the image cell in a table without
# borders to avoid narrow borders when the fixed width < the node width.
html = f""">
<table border="0" cellspacing="0" cellborder="0"><tr>
<td{html}</td>
</tr></table>
"""
return f"""<tdX{' sides="TLR"' if image.caption else ''}{html_bgcolor_attr(image.bgcolor)}{html}"""
def html_image_new(image):
from wireviz.DataClasses import Image
if not image:
return None
# The leading attributes belong to the preceeding tag. See where used below.
html = f'<img scale="{image.scale}" src="{image.src}"/>'
if image.fixedsize:
# Close the preceeding tag and enclose the image cell in a table without
# borders to avoid narrow borders when the fixed width < the node width.
html = f"""
<table border="0" cellspacing="0" cellborder="0"><tr>
<td>{html}</td>
</tr></table>
"""
return f"{html_bgcolor_attr(image.bgcolor)}{html}"
def html_caption(image):
from wireviz.DataClasses import Image
return (
f'<tdX sides="BLR"{html_bgcolor_attr(image.bgcolor)}>{html_line_breaks(image.caption)}'
if image and image.caption
else None
)
def html_caption_new(image): def html_caption_new(image):
@ -429,19 +353,19 @@ def html_caption_new(image):
return f"{html_line_breaks(image.caption)}" if image and image.caption else None return f"{html_line_breaks(image.caption)}" if image and image.caption else None
def html_size_attr(image): # def html_size_attr(image):
from wireviz.DataClasses import Image # from wireviz.DataClasses import Image
#
# Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object # # Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object
return ( # return (
( # (
(f' width="{image.width}"' if image.width else "") # (f' width="{image.width}"' if image.width else "")
+ (f' height="{image.height}"' if image.height else "") # + (f' height="{image.height}"' if image.height else "")
+ (' fixedsize="true"' if image.fixedsize else "") # + (' fixedsize="true"' if image.fixedsize else "")
) # )
if image # if image
else "" # else ""
) # )
def html_size_attr_dict(image): def html_size_attr_dict(image):

View File

@ -23,7 +23,7 @@ class Attribs(Dict):
@dataclass @dataclass
class Tag: class Tag:
contents: str contents: str = None
attribs: Attribs = field(default_factory=Attribs) attribs: Attribs = field(default_factory=Attribs)
flat: bool = False flat: bool = False
empty_is_none: bool = False empty_is_none: bool = False
@ -99,6 +99,10 @@ class Br(TagSingleton):
pass pass
class Img(TagSingleton):
pass
class Td(Tag): class Td(Tag):
pass pass