Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bgcolor attribute to connectors and cables #219

Merged
merged 7 commits into from
Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ tweak: # optional tweaking of .gv output
# no color marks will be added to remaining pins

# rendering information (all optional)
bgcolor: <color> # Background color of diagram connector box
bgcolor_title: <color> # Background color of title in diagram connector box
style: <style> # may be set to simple for single pin connectors
show_name: <bool> # defaults to true for regular connectors,
# false for simple connectors
Expand Down Expand Up @@ -198,6 +200,8 @@ Since the auto-incremented and auto-assigned designator is not known to the user
wirelabels: <List> # optional; one label for each wire

# rendering information (all optional)
bgcolor: <color> # Background color of diagram cable box
bgcolor_title: <color> # Background color of title in diagram cable box
show_name: <bool> # defaults to true
show_wirecount: <bool> # defaults to true
show_wirenumbers: <bool> # defaults to true for cables; false for bundles
Expand Down Expand Up @@ -319,6 +323,7 @@ Parts can be added to a connector or cable in the section `<additional-component
mpn: <str> # manufacturer part number
supplier: <str> # supplier name
spn: <str> # supplier part number
bgcolor: <color> # Background color of entry in diagram component box
```

Alternatively items can be added to just the BOM by putting them in the section `<bom-item>` above.
Expand Down Expand Up @@ -394,6 +399,9 @@ The following colors are understood:
<!-- color list generated with a helper script: -->
<!-- https://gist.github.com/formatc1702/3c93fb4c5e392364899283f78672b952 -->

It is also possible to specify colors as hexadecimal RGB values, e.g. `#112233` or `#FFFF00:#009900`.
Remember quoting strings containing a `#` in the YAML file.

## Cable color codes

Supported color codes:
Expand All @@ -420,6 +428,7 @@ image:
src: <path> # path to the image file
# optional parameters:
caption: <str> # text to display below the image
bgcolor: <color> # Background color of entry in diagram component box
width: <int> # range: 1~65535; unit: points
height: <int> # range: 1~65535; unit: points
# if only one dimension (width/height) is specified, the image is scaled proportionally.
Expand Down
16 changes: 9 additions & 7 deletions src/wireviz/DataClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path

from wireviz.wv_helper import int2tuple, aspect_ratio
from wireviz import wv_colors
from wireviz.wv_colors import Color, Colors, ColorMode, ColorScheme, COLOR_CODES


# Each type alias have their legal values described in comments - validation might be implemented in the future
Expand All @@ -19,12 +19,8 @@
ConnectorMultiplier = PlainText # = Literal['pincount', 'populated']
CableMultiplier = PlainText # = Literal['wirecount', 'terminations', 'length', 'total_length']
ImageScale = PlainText # = Literal['false', 'true', 'width', 'height', 'both']
Color = PlainText # Two-letter color name = Literal[wv_colors._color_hex.keys()]
ColorMode = PlainText # = Literal['full', 'FULL', 'hex', 'HEX', 'short', 'SHORT', 'ger', 'GER']
ColorScheme = PlainText # Color scheme name = Literal[wv_colors.COLOR_CODES.keys()]

# Type combinations
Colors = PlainText # One or more two-letter color names (Color) concatenated into one string
Pin = Union[int, PlainText] # Pin identifier
PinIndex = int # Zero-based pin index
Wire = Union[int, PlainText] # Wire number or Literal['s'] for shield
Expand Down Expand Up @@ -75,6 +71,7 @@ class Image:
width: Optional[int] = None
height: Optional[int] = None
fixedsize: Optional[bool] = None
bgcolor: Optional[Color] = None
# Contents of the text cell <td> just below the image cell:
caption: Optional[MultilineHypertext] = None
# See also HTML doc at https://graphviz.org/doc/info/shapes.html#html
Expand Down Expand Up @@ -113,6 +110,7 @@ class AdditionalComponent:
qty: float = 1
unit: Optional[str] = None
qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None
bgcolor: Optional[Color] = None

@property
def description(self) -> str:
Expand All @@ -122,6 +120,8 @@ def description(self) -> str:
@dataclass
class Connector:
name: Designator
bgcolor: Optional[Color] = None
bgcolor_title: Optional[Color] = None
manufacturer: Optional[MultilineHypertext] = None
mpn: Optional[MultilineHypertext] = None
supplier: Optional[MultilineHypertext] = None
Expand Down Expand Up @@ -206,6 +206,8 @@ def get_qty_multiplier(self, qty_multiplier: Optional[ConnectorMultiplier]) -> i
@dataclass
class Cable:
name: Designator
bgcolor: Optional[Color] = None
bgcolor_title: Optional[Color] = None
manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None
mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None
supplier: Union[MultilineHypertext, List[MultilineHypertext], None] = None
Expand Down Expand Up @@ -278,9 +280,9 @@ def __post_init__(self) -> None:
if self.colors: # use custom color palette (partly or looped if needed)
pass
elif self.color_code: # use standard color palette (partly or looped if needed)
if self.color_code not in wv_colors.COLOR_CODES:
if self.color_code not in COLOR_CODES:
raise Exception('Unknown color code')
self.colors = wv_colors.COLOR_CODES[self.color_code]
self.colors = COLOR_CODES[self.color_code]
else: # no colors defined, add dummy colors
self.colors = [''] * self.wirecount

Expand Down
15 changes: 9 additions & 6 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
from wireviz import wv_colors, __version__, APP_NAME, APP_URL
from wireviz.DataClasses import Metadata, Options, Tweak, Connector, Cable
from wireviz.wv_colors import get_color_hex, translate_color
from wireviz.wv_gv_html import nested_html_table, html_colorbar, html_image, \
html_caption, remove_links, html_line_breaks
from wireviz.wv_gv_html import nested_html_table, \
html_bgcolor_attr, html_bgcolor, html_colorbar, \
html_image, html_caption, remove_links, html_line_breaks
from wireviz.wv_bom import pn_info_string, component_table_entry, \
get_additional_component_table, bom_list, generate_bom, \
HEADER_PN, HEADER_MPN, HEADER_SPN
Expand Down Expand Up @@ -125,7 +126,8 @@ def create_graph(self) -> Graph:

html = []

rows = [[remove_links(connector.name) if connector.show_name else None],
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))],
Expand All @@ -139,7 +141,7 @@ def create_graph(self) -> Graph:
[html_caption(connector.image)]]
rows.extend(get_additional_component_table(self, connector))
rows.append([html_line_breaks(connector.notes)])
html.extend(nested_html_table(rows))
html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor)))

if connector.style != 'simple':
pinhtml = []
Expand Down Expand Up @@ -209,7 +211,8 @@ def create_graph(self) -> Graph:
elif cable.gauge_unit.upper() == 'AWG':
awg_fmt = f' ({mm2_equiv(cable.gauge)} mm\u00B2)'

rows = [[remove_links(cable.name) if cable.show_name else None],
rows = [[f'{html_bgcolor(cable.bgcolor_title)}{remove_links(cable.name)}'
if cable.show_name else None],
[pn_info_string(HEADER_PN, None,
remove_links(cable.pn)) if not isinstance(cable.pn, list) else None,
html_line_breaks(pn_info_string(HEADER_MPN,
Expand All @@ -231,7 +234,7 @@ def create_graph(self) -> Graph:

rows.extend(get_additional_component_table(self, cable))
rows.append([html_line_breaks(cable.notes)])
html.extend(nested_html_table(rows))
html.extend(nested_html_table(rows, html_bgcolor_attr(cable.bgcolor)))

wirehtml = []
wirehtml.append('<table border="0" cellspacing="0" cellborder="0">') # conductor table
Expand Down
8 changes: 5 additions & 3 deletions src/wireviz/wv_bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
from itertools import groupby
from typing import Any, Dict, List, Optional, Tuple, Union

from wireviz.DataClasses import AdditionalComponent, Connector, Cable
from wireviz.DataClasses import AdditionalComponent, Cable, Color, Connector
from wireviz.wv_colors import translate_color
from wireviz.wv_gv_html import html_line_breaks
from wireviz.wv_gv_html import html_bgcolor_attr, html_line_breaks
from wireviz.wv_helper import clean_whitespace

BOM_COLUMNS_ALWAYS = ('id', 'description', 'qty', 'unit', 'designators')
Expand Down Expand Up @@ -36,6 +36,7 @@ def get_additional_component_table(harness: "Harness", component: Union[Connecto
common_args = {
'qty': part.qty * component.get_qty_multiplier(part.qty_multiplier),
'unit': part.unit,
'bgcolor': part.bgcolor,
}
if harness.options.mini_bom_mode:
id = get_bom_index(harness.bom(), bom_entry_key({**asdict(part), 'description': part.description}))
Expand Down Expand Up @@ -158,6 +159,7 @@ def component_table_entry(
type: str,
qty: Union[int, float],
unit: Optional[str] = None,
bgcolor: Optional[Color] = None,
pn: Optional[str] = None,
manufacturer: Optional[str] = None,
mpn: Optional[str] = None,
Expand All @@ -177,7 +179,7 @@ def component_table_entry(
+ (', '.join([pn for pn in part_number_list if pn])))
# format the above output as left aligned text in a single visible cell
# indent is set to two to match the indent in the generated html table
return f'''<table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr>
return f'''<table border="0" cellspacing="0" cellpadding="3" cellborder="1"{html_bgcolor_attr(bgcolor)}><tr>
<td align="left" balign="left">{html_line_breaks(output)}</td>
</tr></table>'''

Expand Down
66 changes: 50 additions & 16 deletions src/wireviz/wv_colors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
from typing import Dict, List

COLOR_CODES = {
'DIN': ['WH', 'BN', 'GN', 'YE', 'GY', 'PK', 'BU', 'RD', 'BK', 'VT', 'GYPK', 'RDBU', 'WHGN', 'BNGN', 'WHYE', 'YEBN',
Expand Down Expand Up @@ -107,27 +107,61 @@

color_default = '#ffffff'

_hex_digits = set('0123456789abcdefABCDEF')

def get_color_hex(input, pad=False):

# Literal type aliases below are commented to avoid requiring python 3.8
Color = str # Two-letter color name = Literal[_color_hex.keys()]
Colors = str # One or more two-letter color names (Color) concatenated into one string
ColorMode = str # = Literal['full', 'FULL', 'hex', 'HEX', 'short', 'SHORT', 'ger', 'GER']
ColorScheme = str # Color scheme name = Literal[COLOR_CODES.keys()]


def get_color_hex(input: Colors, pad: bool = False) -> List[str]:
"""Return list of hex colors from either a string of color names or :-separated hex colors."""
if input is None or input == '':
return [color_default]
elif input[0] == '#': # Hex color(s)
output = input.split(':')
for i, c in enumerate(output):
if c[0] != '#' or not all(d in _hex_digits for d in c[1:]):
if c != input:
c += f' in input: {input}'
print(f'Invalid hex color: {c}')
output[i] = color_default
else: # Color name(s)
def lookup(c: str) -> str:
try:
return _color_hex[c]
except KeyError:
if c != input:
c += f' in input: {input}'
print(f'Unknown color name: {c}')
return color_default

if len(input) == 4: # give wires with EXACTLY 2 colors that striped/banded look
padded = input + input[:2]
elif pad and len(input) == 2: # hacky style fix: give single color wires a triple-up so that wires are the same size
padded = input + input + input
else:
padded = input
output = [lookup(input[i:i + 2]) for i in range(0, len(input), 2)]

if len(output) == 2: # Give wires with EXACTLY 2 colors that striped look.
output += output[:1]
elif pad and len(output) == 1: # Hacky style fix: Give single color wires
output *= 3 # a triple-up so that wires are the same size.

try:
output = [_color_hex[padded[i:i + 2]] for i in range(0, len(padded), 2)]
except KeyError:
print(f'Unknown color specified: {input}')
output = [color_default]
return output


def translate_color(input, color_mode):
def get_color_translation(translate: Dict[Color, str], input: Colors) -> List[str]:
"""Return list of colors translations from either a string of color names or :-separated hex colors."""
def from_hex(hex_input: str) -> str:
for color, hex in _color_hex.items():
if hex == hex_input:
return translate[color]
return f'({",".join(str(int(hex_input[i:i+2], 16)) for i in range(1, 6, 2))})'

return [from_hex(h) for h in input.lower().split(':')] if input[0] == '#' else \
[translate.get(input[i:i+2], '??') for i in range(0, len(input), 2)]


def translate_color(input: Colors, color_mode: ColorMode) -> str:
if input == '' or input is None:
return ''
upper = color_mode.isupper()
Expand All @@ -136,11 +170,11 @@ def translate_color(input, color_mode):

color_mode = color_mode.lower()
if color_mode == 'full':
output = "/".join([_color_full[input[i:i+2]] for i in range(0,len(input),2)])
output = "/".join(get_color_translation(_color_full, input))
elif color_mode == 'hex':
output = ':'.join(get_color_hex(input, pad=False))
elif color_mode == 'ger':
output = "".join([_color_ger[input[i:i+2]] for i in range(0,len(input),2)])
output = "".join(get_color_translation(_color_ger, input))
elif color_mode == 'short':
output = input
else:
Expand Down
25 changes: 18 additions & 7 deletions src/wireviz/wv_gv_html.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from typing import List, Union
from typing import List, Optional, Union
import re

from wireviz.DataClasses import Color
from wireviz.wv_colors import translate_color
from wireviz.wv_helper import remove_links

def nested_html_table(rows):
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('<table border="0" cellspacing="0" cellpadding="0">')
html.append(f'<table border="0" cellspacing="0" cellpadding="0"{table_attrs or ""}>')
for row in rows:
if isinstance(row, List):
if len(row) > 0 and any(row):
Expand All @@ -32,8 +33,17 @@ def nested_html_table(rows):
html.append('</table>')
return html

def html_colorbar(color):
return f'<tdX bgcolor="{translate_color(color, "HEX")}" width="4">' if color else None
def html_bgcolor_attr(color: Color) -> str:
"""Return attributes for bgcolor or '' if no color."""
return f' bgcolor="{translate_color(color, "HEX")}"' if color else ''

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
Expand All @@ -49,11 +59,12 @@ def html_image(image):
<td{html}</td>
</tr></table>
'''
return f'''<tdX{' sides="TLR"' if image.caption else ''}{html}'''
return f'''<tdX{' sides="TLR"' if image.caption else ''}{html_bgcolor_attr(image.bgcolor)}{html}'''

def html_caption(image):
from wireviz.DataClasses import Image
return f'<tdX sides="BLR">{html_line_breaks(image.caption)}' if image and image.caption else None
return (f'<tdX sides="BLR"{html_bgcolor_attr(image.bgcolor)}>{html_line_breaks(image.caption)}'
if image and image.caption else None)

def html_size_attr(image):
from wireviz.DataClasses import Image
Expand Down