diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index d1054f4..a9497ac 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -5,7 +5,13 @@ from enum import Enum from itertools import zip_longest from typing import Dict, List, Optional, Tuple, Union -from wireviz.wv_colors import COLOR_CODES, Color, ColorMode, Colors, ColorScheme +from wireviz.wv_colors import ( + COLOR_CODES, + ColorOutputMode, + MultiColor, + SingleColor, + get_color_by_colorcode_index, +) from wireviz.wv_helper import aspect_ratio, awg_equiv, mm2_equiv # Each type alias have their legal values described in comments - validation might be implemented in the future @@ -60,12 +66,12 @@ class Metadata(dict): @dataclass class Options: fontname: PlainText = "arial" - bgcolor: Color = "WH" - bgcolor_node: Optional[Color] = "WH" - bgcolor_connector: Optional[Color] = None - bgcolor_cable: Optional[Color] = None - bgcolor_bundle: Optional[Color] = None - color_mode: ColorMode = "SHORT" + bgcolor: SingleColor = "WH" # will be converted to SingleColor in __post_init__ + bgcolor_node: SingleColor = "WH" + bgcolor_connector: SingleColor = None + bgcolor_cable: SingleColor = None + bgcolor_bundle: SingleColor = None + color_mode: ColorOutputMode = ColorOutputMode.EN_UPPER mini_bom_mode: bool = True template_separator: str = "." _pad: int = 0 @@ -74,6 +80,13 @@ class Options: _image_paths: [List] = field(default_factory=list) def __post_init__(self): + + self.bgcolor = SingleColor(self.bgcolor) + self.bgcolor_node = SingleColor(self.bgcolor_node) + self.bgcolor_connector = SingleColor(self.bgcolor_connector) + self.bgcolor_cable = SingleColor(self.bgcolor_cable) + self.bgcolor_bundle = SingleColor(self.bgcolor_bundle) + if not self.bgcolor_node: self.bgcolor_node = self.bgcolor if not self.bgcolor_connector: @@ -99,13 +112,15 @@ class Image: width: Optional[int] = None height: Optional[int] = None fixedsize: Optional[bool] = None - bgcolor: Optional[Color] = None + bgcolor: SingleColor = None # Contents of the text cell just below the image cell: caption: Optional[MultilineHypertext] = None # See also HTML doc at https://graphviz.org/doc/info/shapes.html#html def __post_init__(self): + self.bgcolor = SingleColor(self.bgcolor) + if self.fixedsize is None: # Default True if any dimension specified unless self.scale also is specified. self.fixedsize = (self.width or self.height) and self.scale is None @@ -141,7 +156,10 @@ class AdditionalComponent: qty: float = 1 unit: Optional[str] = None qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None - bgcolor: Optional[Color] = None + bgcolor: SingleColor = None + + def __post_init__(self): + self.bgcolor = SingleColor(self.bgcolor) @property def description(self) -> str: @@ -159,7 +177,7 @@ class PinClass: index: int id: str label: str - color: str + color: MultiColor parent: str # designator of parent connector _anonymous: bool = False # true for pins on autogenerated connectors _simple: bool = False # true for simple connector @@ -178,7 +196,7 @@ class WireClass: index: int id: str label: str - color: str + color: MultiColor parent: str # designator of parent cable/bundle # gauge: Gauge # pn: str @@ -203,8 +221,8 @@ class Connection: @dataclass class Connector(Component): name: Designator - bgcolor: Optional[Color] = None - bgcolor_title: Optional[Color] = None + bgcolor: SingleColor = None + bgcolor_title: SingleColor = None manufacturer: Optional[MultilineHypertext] = None mpn: Optional[MultilineHypertext] = None supplier: Optional[MultilineHypertext] = None @@ -219,8 +237,8 @@ class Connector(Component): notes: Optional[MultilineHypertext] = None pins: List[Pin] = field(default_factory=list) pinlabels: List[Pin] = field(default_factory=list) - pincolors: List[Color] = field(default_factory=list) - color: Optional[Color] = None + pincolors: List[str] = field(default_factory=list) + color: MultiColor = None show_name: Optional[bool] = None show_pincount: Optional[bool] = None hide_disconnected_pins: bool = False @@ -238,6 +256,10 @@ class Connector(Component): def __post_init__(self) -> None: + self.bgcolor = SingleColor(self.bgcolor) + self.bgcolor_title = SingleColor(self.bgcolor_title) + self.color = SingleColor(self.color) + if isinstance(self.image, dict): self.image = Image(**self.image) @@ -280,7 +302,7 @@ class Connector(Component): index=pin_index, id=pin_id, label=pin_label, - color=pin_color, + color=MultiColor(pin_color), parent=self.name, _anonymous=self.is_autogenerated, _simple=self.style == "simple", @@ -341,8 +363,8 @@ class Connector(Component): @dataclass class Cable(Component): name: Designator - bgcolor: Optional[Color] = None - bgcolor_title: Optional[Color] = None + bgcolor: SingleColor = None + bgcolor_title: SingleColor = None manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None supplier: Union[MultilineHypertext, List[MultilineHypertext], None] = None @@ -355,14 +377,14 @@ class Cable(Component): show_equiv: bool = False length: float = 0 length_unit: Optional[str] = None - color: Optional[Color] = None + color: MultiColor = None wirecount: Optional[int] = None - shield: Union[bool, Color] = False + shield: Union[bool, MultiColor] = False image: Optional[Image] = None notes: Optional[MultilineHypertext] = None - colors: List[Colors] = field(default_factory=list) + colors: List[str] = field(default_factory=list) wirelabels: List[Wire] = field(default_factory=list) - color_code: Optional[ColorScheme] = None + color_code: Optional[str] = None show_name: Optional[bool] = None show_wirecount: bool = True show_wirenumbers: Optional[bool] = None @@ -393,6 +415,10 @@ class Cable(Component): def __post_init__(self) -> None: + self.bgcolor = SingleColor(self.bgcolor) + self.bgcolor_title = SingleColor(self.bgcolor_title) + self.color = SingleColor(self.color) + if isinstance(self.image, dict): self.image = Image(**self.image) @@ -441,21 +467,20 @@ class Cable(Component): if self.wirecount: # number of wires explicitly defined if self.colors: # use custom color palette (partly or looped if needed) - pass + self.colors = [ + self.colors[i % len(self.colors)] for i in range(self.wirecount) + ] elif self.color_code: # use standard color palette (partly or looped if needed) if self.color_code not in COLOR_CODES: raise Exception("Unknown color code") - self.colors = COLOR_CODES[self.color_code] + self.colors = [ + get_color_by_colorcode_index(self.color_code, i) + for i in range(self.wirecount) + ] else: # no colors defined, add dummy colors self.colors = [""] * self.wirecount - # make color code loop around if more wires than colors - if self.wirecount > len(self.colors): - m = self.wirecount // len(self.colors) + 1 - self.colors = self.colors * int(m) - # cut off excess after looping - self.colors = self.colors[: self.wirecount] else: # wirecount implicit in length of color list if not self.colors: raise Exception( @@ -491,7 +516,7 @@ class Cable(Component): index=wire_index, # TODO: wire_id id=wire_index + 1, # TODO: wire_id label=wire_label, - color=wire_color, + color=MultiColor(wire_color), parent=self.name, ) ) @@ -504,7 +529,9 @@ class Cable(Component): index=index_offset, id="s", label="Shield", - color=self.shield if isinstance(self.shield, str) else None, + color=MultiColor(self.shield) + if isinstance(self.shield, str) + else MultiColor(None), parent=self.name, ) ) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 8c6572f..e7a7603 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -5,6 +5,7 @@ from pathlib import Path from graphviz import Graph +import wireviz.wv_colors from wireviz.DataClasses import ( Arrow, ArrowWeight, @@ -152,7 +153,7 @@ class Harness: for connector in self.connectors.values(): # generate connector node - gv_html = gv_node_component(connector, self.options) + gv_html = gv_node_component(connector) bgcolor = calculate_node_bgcolor(connector, self.options) dot.node( connector.name, @@ -163,25 +164,27 @@ class Harness: ) # generate edges for connector loops if len(connector.loops) > 0: - dot.attr("edge", color="#000000:#ffffff:#000000") + dot.attr("edge", color="#000000") 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 - pad = any( - len(colorstr) > 2 + multicolor_wires = [ + len(wire.color) > 1 for cable in self.cables.values() - for colorstr in cable.colors - ) - - self.options._pad = pad + for wire in cable.wire_objects + ] + if any(multicolor_wires): + wireviz.wv_colors.padding_amount = 3 + else: + wireviz.wv_colors.padding_amount = 1 for cable in self.cables.values(): # generate cable node # TODO: PN info for bundles (per wire) - gv_html = gv_node_component(cable, self.options) + gv_html = gv_node_component(cable) bgcolor = calculate_node_bgcolor(cable, self.options) style = "filled,dashed" if cable.category == "bundle" else "filled" dot.node( diff --git a/src/wireviz/wv_bom.py b/src/wireviz/wv_bom.py index 3de45d8..4e075a3 100644 --- a/src/wireviz/wv_bom.py +++ b/src/wireviz/wv_bom.py @@ -4,8 +4,7 @@ from dataclasses import asdict from itertools import groupby 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.DataClasses import AdditionalComponent, Cable, Connector from wireviz.wv_gv_html import html_line_breaks from wireviz.wv_helper import clean_whitespace, pn_info_string @@ -99,7 +98,7 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]: + (f", {connector.subtype}" if connector.subtype else "") + (f", {connector.pincount} pins" if connector.show_pincount else "") + ( - f", {translate_color(connector.color, harness.options.color_mode)}" + f", xxx" # {translate_color(connector.color, harness.options.color_mode)} if connector.color else "" ) @@ -132,7 +131,7 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]: ) + (" shielded" if cable.shield else "") + ( - f", {translate_color(cable.color, harness.options.color_mode)}" + f", xxx" # {translate_color(cable.color, harness.options.color_mode)} if cable.color else "" ) @@ -154,7 +153,7 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]: + (f", {cable.type}" if cable.type else "") + (f", {cable.gauge} {cable.gauge_unit}" if cable.gauge else "") + ( - f", {translate_color(color, harness.options.color_mode)}" + f", xxx" # {translate_color(color, harness.options.color_mode)} if color else "" ) @@ -236,7 +235,7 @@ def component_table_entry( type: str, qty: Union[int, float], unit: Optional[str] = None, - bgcolor: Optional[Color] = None, + bgcolor: Optional[str] = None, pn: Optional[str] = None, manufacturer: Optional[str] = None, mpn: Optional[str] = None, diff --git a/src/wireviz/wv_colors.py b/src/wireviz/wv_colors.py index 34d9241..be8fefb 100644 --- a/src/wireviz/wv_colors.py +++ b/src/wireviz/wv_colors.py @@ -1,6 +1,206 @@ # -*- coding: utf-8 -*- -from typing import Dict, List +from collections import namedtuple +from dataclasses import dataclass, field +from enum import Enum +from typing import List + +KnownColor = namedtuple("KnownColor", "html code_de full_en full_de") + +ColorOutputMode = Enum( + "ColorOutputMode", "EN_LOWER EN_UPPER DE_LOWER DE_UPPER HTML_LOWER HTML_UPPER" +) + +known_colors = { # v--------v--------- for future use + "BK": KnownColor("#000000", "sw", "black", "schwarz"), + "WH": KnownColor("#ffffff", "ws", "white", "weiß"), + "GY": KnownColor("#999999", "gr", "grey", "grau"), + "PK": KnownColor("#ff66cc", "rs", "pink", "rosa"), + "RD": KnownColor("#ff0000", "rt", "red", "rot"), + "OG": KnownColor("#ff8000", "or", "orange", "orange"), + "YE": KnownColor("#ffff00", "ge", "yellow", "gelb"), + "OL": KnownColor("#708000", "ol", "olive green", "olivgrün"), + "GN": KnownColor("#00ff00", "gn", "green", "grün"), + "TQ": KnownColor("#00ffff", "tk", "turquoise", "türkis"), + "LB": KnownColor("#a0dfff", "hb", "light blue", "hellblau"), + "BU": KnownColor("#0066ff", "bl", "blue", "blau"), + "VT": KnownColor("#8000ff", "vi", "violet", "violett"), + "BN": KnownColor("#895956", "br", "brown", "braun"), + "BG": KnownColor("#ceb673", "bg", "beige", "beige"), + "IV": KnownColor("#f5f0d0", "eb", "ivory", "elfenbein"), + "SL": KnownColor("#708090", "si", "slate", "schiefer"), + "CU": KnownColor("#d6775e", "ku", "copper", "Kupfer"), + "SN": KnownColor("#aaaaaa", "vz", "tin", "verzinkt"), + "SR": KnownColor("#84878c", "ag", "silver", "Silber"), + "GD": KnownColor("#ffcf80", "au", "gold", "Gold"), +} + +color_output_mode = ColorOutputMode.EN_UPPER + +padding_amount = 1 + + +def convert_case(inp): + if "_LOWER" in color_output_mode.name: + return inp.lower() + elif "_UPPER" in color_output_mode.name: + return inp.upper() + else: # currently not used + return inp + + +def get_color_by_colorcode_index(color_code: str, index: int) -> str: + num_colors_in_code = len(COLOR_CODES[color_code]) + actual_index = index % num_colors_in_code # wrap around if index is out of bounds + return COLOR_CODES[color_code][actual_index] + + # # make color code loop around if more wires than colors + # if self.wirecount > len(self.colors): + # m = self.wirecount // len(self.colors) + 1 + # self.colors = self.colors * int(m) + # # cut off excess after looping + # self.colors = self.colors[: self.wirecount] + + +@dataclass +class SingleColor: + _code_en: str + _html: str + + @property + def code_en(self): + return convert_case(self._code_en) if self._code_en else None + + @property + def code_de(self): + return ( + convert_case(known_colors[self._code_en.upper()].code_de) + if self._code_en + else None + ) + + @property + def html(self): + return convert_case(self._html) if self._code_en else None + + @property + def known(self): + return ( + self.code_en.upper() in known_colors.keys() if self._code_en else True + ) # ? + + def __init__(self, inp): + if inp is None: + self._html = None + self._code_en = None + elif isinstance(inp, int): + hex_str = f"#{inp:06x}" + self._html = hex_str + self._code_en = hex_str # do not perform reverse lookup + elif inp.upper() in known_colors.keys(): + inp_upper = inp.upper() + self._code_en = inp_upper + self._html = known_colors[inp_upper].html + else: # assume valid HTML color + self._html = inp + self._code_en = inp + + @property + def html_padded(self): + return ":".join([self.html] * padding_amount) + + def __bool__(self): + return self._code_en is not None + + def __str__(self): + if self._html is None: + return "" + elif self.known and "EN_" in color_output_mode.name: + return self.code_en + elif self.known and "DE_" in color_output_mode.name: + return self.code_de + else: + return self.html + + +@dataclass +class MultiColor: + colors: List[SingleColor] = field(default_factory=list) + + def __init__(self, inp): + self.colors = [] + if inp is None: + pass + elif isinstance(inp, List): # input is already a list + for item in inp: + if item is None: + pass + elif isinstance(item, SingleColor): + self.colors.append(item) + else: # string + self.colors.append(SingleColor(item)) + elif isinstance(inp, SingleColor): # single color + self.colors = [inp] + else: # split input into list + if ":" in str(inp): + self.colors = [SingleColor(item) for item in inp.split(":")] + else: + if isinstance(inp, int): + self.colors = [SingleColor(inp)] + elif len(inp) % 2 == 0: + items = [inp[i : i + 2] for i in range(0, len(inp), 2)] + known = [item.upper() in known_colors.keys() for item in items] + if all(known): + self.colors = [SingleColor(item) for item in items] + else: # assume it's a HTML color name + self.colors = [SingleColor(inp)] + else: # assume it's a HTML color name + self.colors = [SingleColor(inp)] + + def __len__(self): + return len(self.colors) + + def __bool__(self): + return len(self.colors) >= 1 + + def __str__(self): + if "EN_" in color_output_mode.name or "DE_" in color_output_mode.name: + joiner = "" if self.all_known else ":" + elif "HTML_" in color_output_mode.name: + joiner = ":" + else: + joiner = "???" + return joiner.join([str(color) for color in self.colors]) + + @property + def all_known(self): + return all([color.known for color in self.colors]) + + @property + def html(self): + return ":".join([color.html for color in self.colors]) + + @property + def html_padded_list(self): + # padding only properly works for padding_amount 1 or 3 + if padding_amount == 1: + out = [color.html for color in self.colors] + elif len(self) == 0: + out = [] + elif len(self) == 1: + out = [self.colors[0].html for i in range(3)] + elif len(self) == 2: + out = [self.colors[0].html, self.colors[1].html, self.colors[0].html] + elif len(self) == 3: + out = [color.html for color in self.colors] + else: + raise Exception(f"Padding not supported for len {len(selfq)}") + return [str(color) for color in out] + + @property + def html_padded(self): + return ":".join(self.html_padded_list) + COLOR_CODES = { # fmt: off @@ -38,166 +238,3 @@ COLOR_CODES = { "T568A": ["WHGN", "GN", "WHOG", "BU", "WHBU", "OG", "WHBN", "BN"], "T568B": ["WHOG", "OG", "WHGN", "BU", "WHBU", "GN", "WHBN", "BN"], } - -# Convention: Color names should be 2 letters long, to allow for multicolored wires - -_color_hex = { - "BK": "#000000", - "WH": "#ffffff", - "GY": "#999999", - "PK": "#ff66cc", - "RD": "#ff0000", - "OG": "#ff8000", - "YE": "#ffff00", - "OL": "#708000", # olive green - "GN": "#00ff00", - "TQ": "#00ffff", - "LB": "#a0dfff", # light blue - "BU": "#0066ff", - "VT": "#8000ff", - "BN": "#895956", - "BG": "#ceb673", # beige - "IV": "#f5f0d0", # ivory - "SL": "#708090", - "CU": "#d6775e", # Faux-copper look, for bare CU wire - "SN": "#aaaaaa", # Silvery look for tinned bare wire - "SR": "#84878c", # Darker silver for silvered wire - "GD": "#ffcf80", # Golden color for gold -} - -_color_full = { - "BK": "black", - "WH": "white", - "GY": "grey", - "PK": "pink", - "RD": "red", - "OG": "orange", - "YE": "yellow", - "OL": "olive green", - "GN": "green", - "TQ": "turquoise", - "LB": "light blue", - "BU": "blue", - "VT": "violet", - "BN": "brown", - "BG": "beige", - "IV": "ivory", - "SL": "slate", - "CU": "copper", - "SN": "tin", - "SR": "silver", - "GD": "gold", -} - -_color_ger = { - "BK": "sw", - "WH": "ws", - "GY": "gr", - "PK": "rs", - "RD": "rt", - "OG": "or", - "YE": "ge", - "OL": "ol", # olivgrün - "GN": "gn", - "TQ": "tk", - "LB": "hb", # hellblau - "BU": "bl", - "VT": "vi", - "BN": "br", - "BG": "bg", # beige - "IV": "eb", # elfenbeinfarben - "SL": "si", # Schiefer - "CU": "ku", # Kupfer - "SN": "vz", # verzinkt - "SR": "ag", # Silber - "GD": "au", # Gold -} - - -color_default = "#ffffff" - -_hex_digits = set("0123456789abcdefABCDEF") - - -# 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 - - 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. - - return output - - -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 == "": - 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") - - color_mode = color_mode.lower() - if color_mode == "full": - 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(get_color_translation(_color_ger, input)) - elif color_mode == "short": - output = input - else: - raise Exception("Unknown color mode") - if upper: - return output.upper() - else: - return output.lower() diff --git a/src/wireviz/wv_colors_new.py b/src/wireviz/wv_colors_new.py deleted file mode 100644 index 24fdee9..0000000 --- a/src/wireviz/wv_colors_new.py +++ /dev/null @@ -1,199 +0,0 @@ -# -*- coding: utf-8 -*- - -from collections import namedtuple -from dataclasses import dataclass, field -from enum import Enum -from typing import List - -KnownColor = namedtuple("KnownColor", "html code_de full_en full_de") -ColorOutputMode = Enum("ColorOutputMode", "EN_LOWER EN_UPPER DE_LOWER DE_UPPER HTML_LOWER HTML_UPPER") - -known_colors = { - "BK": KnownColor("#000000", "sw", "black", "schwarz"), - "WH": KnownColor("#ffffff", "ws", "white", "weiß"), - "GY": KnownColor("#999999", "gr", "grey", "grau"), - "PK": KnownColor("#ff66cc", "rs", "pink", "rosa"), - "RD": KnownColor("#ff0000", "rt", "red", "rot"), - "OG": KnownColor("#ff8000", "or", "orange", "orange"), - "YE": KnownColor("#ffff00", "ge", "yellow", "gelb"), - "OL": KnownColor("#708000", "ol", "olive green", "olivgrün"), - "GN": KnownColor("#00ff00", "gn", "green", "grün"), - "TQ": KnownColor("#00ffff", "tk", "turquoise", "türkis"), - "LB": KnownColor("#a0dfff", "hb", "light blue", "hellblau"), - "BU": KnownColor("#0066ff", "bl", "blue", "blau"), - "VT": KnownColor("#8000ff", "vi", "violet", "violett"), - "BN": KnownColor("#895956", "br", "brown", "braun"), - "BG": KnownColor("#ceb673", "bg", "beige", "beige"), - "IV": KnownColor("#f5f0d0", "eb", "ivory", "elfenbein"), - "SL": KnownColor("#708090", "si", "slate", "schiefer"), - "CU": KnownColor("#d6775e", "ku", "copper", "Kupfer"), - "SN": KnownColor("#aaaaaa", "vz", "tin", "verzinkt"), - "SR": KnownColor("#84878c", "ag", "silver", "Silber"), - "GD": KnownColor("#ffcf80", "au", "gold", "Gold"), -} - -color_output_mode = ColorOutputMode.EN_UPPER -padding_amount = 1 - -def convert_case(inp): - if "_LOWER" in color_output_mode.name: - return inp.lower() - elif "_UPPER" in color_output_mode.name: - return inp.upper() - else: # currently not used - return inp - -@dataclass -class SingleColor(): - _code_en: str - _code_de: str - _html: str - - @property - def code_en(self): - return convert_case(self._code_en) - # repeat for _code_de, _html - - @property - def code_de(self): - return convert_case(self._code_de) - # repeat for _code_de, _html - - @property - def html(self): - return convert_case(self._html) - # repeat for _code_de, _html - - @property - def known(self): - return self.code_en.upper() in known_colors.keys() - - def __init__(self, inp): - if isinstance(inp, int): - hex_str = f"#{inp:06x}" - self._html = hex_str - self._code_en = hex_str # do not perform reverse lookup - self._code_de = hex_str - elif inp.upper() in known_colors.keys(): - inp_upper = inp.upper() - self._code_en = inp_upper - self._code_de = known_colors[inp_upper].code_de - self._html = known_colors[inp_upper].html - else: # assume valid HTML color - self._html = inp - self._code_en = inp - self._code_de = inp - - @property - def html_padded(self): - return ":".join([self.html] * padding_amount) - - def __str__(self): - if self.known and "EN_" in color_output_mode.name: - return self.code_en - elif self.known and "DE_" in color_output_mode.name: - return self.code_de - else: - return self.html - -@dataclass -class MultiColor(): - colors: List[SingleColor] = field(default_factory=list) - - def __init__(self, inp): - self.colors = [] - if isinstance(inp, List): # input is already a list - for item in inp: - if isinstance(item, SingleColor): - self.colors.append(item) - else: # string - self.colors.append(SingleColor(item)) - elif isinstance(inp, SingleColor): # single color - self.colors = [inp] - else: # split input into list - if ":" in str(inp): - self.colors = [SingleColor(item) for item in inp.split(":")] - else: - if isinstance(inp, int): - self.colors = [SingleColor(inp)] - elif len(inp) % 2 == 0: - items = [inp[i:i+2] for i in range(0, len(inp), 2)] - known = [item.upper() in known_colors.keys() for item in items] - if all(known): - self.colors = [SingleColor(item) for item in items] - else: # assume it's a HTML color name - self.colors = [SingleColor(inp)] - else: # assume it's a HTML color name - self.colors = [SingleColor(inp)] - - - def __len__(self): - return len(self.colors) - - def __str__(self): - if "EN_" in color_output_mode.name or "DE_" in color_output_mode.name: - joiner = "" if self.all_known else ":" - elif "HTML_" in color_output_mode.name: - joiner = ":" - else: - joiner = "???" - return joiner.join([str(color) for color in self.colors]) - - @property - def all_known(self): - return all([color.known for color in self.colors]) - - @property - def html(self): - return ":".join([color.html for color in self.colors]) - - @property - def html_padded(self): - if len(self) == 0: - out = [] - elif len(self) == 1: - out = [self.colors[0] for i in range(3)] - elif len(self) == 2: - out = [self.colors[0], self.colors[1], self.colors[0]] - elif len(self) == 3: - out = self.colors - else: - raise Exception(f"Padding not supported for len {len(selfq)}") - return ":".join([str(color) for color in out]) - -COLOR_CODES = { - # fmt: off - "DIN": [ - "WH", "BN", "GN", "YE", "GY", "PK", "BU", "RD", "BK", "VT", "GYPK", "RDBU", - "WHGN", "BNGN", "WHYE", "YEBN", "WHGY", "GYBN", "WHPK", "PKBN", "WHBU", "BNBU", - "WHRD", "BNRD", "WHBK", "BNBK", "GYGN", "YEGY", "PKGN", "YEPK", "GNBU", "YEBU", - "GNRD", "YERD", "GNBK", "YEBK", "GYBU", "PKBU", "GYRD", "PKRD", "GYBK", "PKBK", - "BUBK", "RDBK", "WHBNBK", "YEGNBK", "GYPKBK", "RDBUBK", "WHGNBK", "BNGNBK", - "WHYEBK", "YEBNBK", "WHGYBK", "GYBNBK", "WHPKBK", "PKBNBK", "WHBUBK", - "BNBUBK", "WHRDBK", "BNRDBK", - ], - # fmt: on - "IEC": ["BN", "RD", "OG", "YE", "GN", "BU", "VT", "GY", "WH", "BK"], - "BW": ["BK", "WH"], - # 25-pair color code - see also https://en.wikipedia.org/wiki/25-pair_color_code - # 5 major colors (WH,RD,BK,YE,VT) combined with 5 minor colors (BU,OG,GN,BN,SL). - # Each POTS pair tip (+) had major/minor color, and ring (-) had minor/major color. - # fmt: off - "TEL": [ # 25x2: Ring and then tip of each pair - "BUWH", "WHBU", "OGWH", "WHOG", "GNWH", "WHGN", "BNWH", "WHBN", "SLWH", "WHSL", - "BURD", "RDBU", "OGRD", "RDOG", "GNRD", "RDGN", "BNRD", "RDBN", "SLRD", "RDSL", - "BUBK", "BKBU", "OGBK", "BKOG", "GNBK", "BKGN", "BNBK", "BKBN", "SLBK", "BKSL", - "BUYE", "YEBU", "OGYE", "YEOG", "GNYE", "YEGN", "BNYE", "YEBN", "SLYE", "YESL", - "BUVT", "VTBU", "OGVT", "VTOG", "GNVT", "VTGN", "BNVT", "VTBN", "SLVT", "VTSL", - ], - "TELALT": [ # 25x2: Tip and then ring of each pair - "WHBU", "BU", "WHOG", "OG", "WHGN", "GN", "WHBN", "BN", "WHSL", "SL", - "RDBU", "BURD", "RDOG", "OGRD", "RDGN", "GNRD", "RDBN", "BNRD", "RDSL", "SLRD", - "BKBU", "BUBK", "BKOG", "OGBK", "BKGN", "GNBK", "BKBN", "BNBK", "BKSL", "SLBK", - "YEBU", "BUYE", "YEOG", "OGYE", "YEGN", "GNYE", "YEBN", "BNYE", "YESL", "SLYE", - "VTBU", "BUVT", "VTOG", "OGVT", "VTGN", "GNVT", "VTBN", "BNVT", "VTSL", "SLVT", - ], - # fmt: on - "T568A": ["WHGN", "GN", "WHOG", "BU", "WHBU", "OG", "WHBN", "BN"], - "T568B": ["WHOG", "OG", "WHGN", "BU", "WHBU", "GN", "WHBN", "BN"], -} diff --git a/src/wireviz/wv_gv_html.py b/src/wireviz/wv_gv_html.py index 0daaf1d..16dad23 100644 --- a/src/wireviz/wv_gv_html.py +++ b/src/wireviz/wv_gv_html.py @@ -17,7 +17,6 @@ from wireviz.DataClasses import ( ShieldClass, WireClass, ) -from wireviz.wv_colors import get_color_hex, translate_color from wireviz.wv_helper import pn_info_string, remove_links from wireviz.wv_table_util import * # TODO: explicitly import each needed tag later @@ -26,9 +25,7 @@ HEADER_MPN = "MPN" HEADER_SPN = "SPN" -def gv_node_component( - component: Component, harness_options: Options, pad=None -) -> Table: +def gv_node_component(component: Component) -> Table: # If no wires connected (except maybe loop wires)? if isinstance(component, Connector): if not (component.ports_left or component.ports_right): @@ -52,8 +49,8 @@ def gv_node_component( html_line_breaks(component.type), html_line_breaks(component.subtype), f"{component.pincount}-pin" if component.show_pincount else None, - translate_color(component.color, harness_options.color_mode), - colorbar_cell(component.color), + str(component.color) if component.color else None, + colorbar_cell(component.color) if component.color else None, ] elif isinstance(component, Cable): line_info = [ @@ -64,10 +61,12 @@ def gv_node_component( f"{component.length} {component.length_unit}" if component.length > 0 else None, - translate_color(component.color, harness_options.color_mode), - colorbar_cell(component.color), + str(component.color) if component.color else None, + colorbar_cell(component.color) if component.color else None, ] + x = colorbar_cell(component.color) if component.color else None + line_image, line_image_caption = image_and_caption_cells(component) # line_additional_component_table = get_additional_component_table(self, connector) line_additional_component_table = None @@ -79,7 +78,7 @@ def gv_node_component( else: line_ports = None elif isinstance(component, Cable): - line_ports = gv_conductor_table(component, harness_options) + line_ports = gv_conductor_table(component) lines = [ line_name, @@ -109,17 +108,17 @@ def calculate_node_bgcolor(component, harness_options): # assign component node bgcolor at the GraphViz node level # instead of at the HTML table level for better rendering of node outline if component.bgcolor: - return translate_color(component.bgcolor, "HEX") + return component.bgcolor.html elif isinstance(component, Connector) and harness_options.bgcolor_connector: - return translate_color(harness_options.bgcolor_connector, "HEX") + return harness_options.bgcolor_connector.html elif ( isinstance(component, Cable) and component.category == "bundle" and harness_options.bgcolor_bundle ): - return translate_color(harness_options.bgcolor_bundle, "HEX") + return harness_options.bgcolor_bundle.html elif isinstance(component, Cable) and harness_options.bgcolor_cable: - return translate_color(harness_options.bgcolor_cable, "HEX") + return harness_options.bgcolor_cable.html def make_list_of_cells(inp) -> List[Td]: @@ -170,31 +169,25 @@ def nested_table(lines: List[Td]) -> Table: def gv_pin_table(component) -> Table: - pin_tuples = zip_longest( - component.pins, - component.pinlabels, - component.pincolors, - ) - pin_rows = [] - for pinindex, (pinname, pinlabel, pincolor) in enumerate(pin_tuples): - if component.should_show_pin(pinname): - pin_rows.append( - gv_pin_row(pinindex, pinname, pinlabel, pincolor, component) - ) + for pin in component.pin_objects: + if component.should_show_pin(pin.id): # TODO remove True + pin_rows.append(gv_pin_row(pin, component)) tbl = Table(pin_rows, border=0, cellborder=1, cellpadding=3, cellspacing=0) return tbl -def gv_pin_row(pin_index, pin_name, pin_label, pin_color, connector) -> Tr: +def gv_pin_row(pin, connector) -> Tr: # ports in GraphViz are 1-indexed for more natural maping to pin/wire numbers - cell_pin_left = Td(pin_name, port=f"p{pin_index+1}l") - cell_pin_label = Td(pin_label, delete_if_empty=True) - cell_pin_right = Td(pin_name, port=f"p{pin_index+1}r") + cell_pin_left = Td(pin.id, port=f"p{pin.index+1}l") + cell_pin_label = Td(pin.label, delete_if_empty=True) + cell_pin_color = Td(str(pin.color), delete_if_empty=True) + cell_pin_right = Td(pin.id, port=f"p{pin.index+1}r") cells = [ cell_pin_left if connector.ports_left else None, cell_pin_label, + cell_pin_color, # TODO: generate proper color mini-table too cell_pin_right if connector.ports_right else None, ] return Tr(cells) @@ -217,7 +210,7 @@ def gv_connector_loops(connector: Connector) -> List: return loop_edges -def gv_conductor_table(cable, harness_options) -> Table: +def gv_conductor_table(cable) -> Table: rows = [] rows.append(Tr(Td(" "))) # spacer row on top @@ -233,7 +226,7 @@ def gv_conductor_table(cable, harness_options) -> Table: wireinfo = [] if cable.show_wirenumbers and not isinstance(wire, ShieldClass): wireinfo.append(str(wire.id)) - wireinfo.append(translate_color(wire.color, harness_options.color_mode)) + wireinfo.append(str(wire.color)) wireinfo.append(wire.label) ins, outs = [], [] @@ -252,7 +245,7 @@ def gv_conductor_table(cable, harness_options) -> Table: rows.append(Tr(cells_above)) # the wire itself - rows.append(Tr(gv_wire_cell(wire, padding=harness_options._pad))) + rows.append(Tr(gv_wire_cell(wire))) # row below the wire # TODO: PN stuff for bundles @@ -263,16 +256,17 @@ def gv_conductor_table(cable, harness_options) -> Table: return tbl -def gv_wire_cell(wire: Union[WireClass, ShieldClass], padding) -> Td: +def gv_wire_cell(wire: Union[WireClass, ShieldClass]) -> Td: + # import pudb; pudb.set_trace() if wire.color: - color_list = ["#000000"] + get_color_hex(wire.color, pad=padding) + ["#000000"] + color_list = ["#000000"] + wire.color.html_padded_list + ["#000000"] else: color_list = ["#000000"] wire_inner_rows = [] for j, bgcolor in enumerate(color_list[::-1]): wire_inner_cell_attribs = { - "bgcolor": bgcolor if bgcolor != "" else "BK", + "bgcolor": bgcolor if bgcolor != "" else "#000000", "border": 0, "cellpadding": 0, "colspan": 3, @@ -339,16 +333,9 @@ def wire_pn_stuff(): def gv_edge_wire(harness, cable, connection) -> (str, str, str): if connection.via.color: # check if it's an actual wire and not a shield - wire_color = get_color_hex(connection.via.color, pad=harness.options._pad) - color = ":".join(["#000000"] + wire_color + ["#000000"]) + color = f"#000000:{connection.via.color.html_padded}:#000000" else: # it's a shield connection - # shield is shown with specified color and black borders, or as a thin black wire otherwise - if connection.via.color: - shield_color_hex = get_color_hex(connection.via.color)[0] - shield_color_str = ":".join(["#000000", shield_color_hex, "#000000"]) - else: - shield_color_str = "#000000" - color = shield_color_str + color = "#000000" if connection.from_ is not None: # connect to left from_port_str = ( @@ -417,7 +404,7 @@ def gv_edge_mate(mate) -> (str, str, str, str): def colored_cell(contents, bgcolor) -> Td: - return Td(contents, bgcolor=translate_color(bgcolor, "HEX")) + return Td(contents, bgcolor=bgcolor.html) def part_number_str_list(component: Component) -> List[str]: @@ -433,10 +420,7 @@ def part_number_str_list(component: Component) -> List[str]: def colorbar_cell(color) -> Td: - if color: - return Td("", bgcolor=translate_color(color, "HEX"), width=4) - else: - return None + return Td("", bgcolor=color.html, width=4) def image_and_caption_cells(component: Component) -> (Td, Td): @@ -456,7 +440,7 @@ def image_and_caption_cells(component: Component) -> (Td, Td): image_cell.update_attribs( balign="left", - bgcolor=translate_color(component.image.bgcolor, "HEX"), + bgcolor=component.image.bgcolor.html, sides="TLR" if component.image.caption else None, ) @@ -495,7 +479,7 @@ def set_dot_basics(dot, options): "graph", rankdir="LR", ranksep="2", - bgcolor=translate_color(options.bgcolor, "HEX"), + bgcolor=options.bgcolor.html, nodesep="0.33", fontname=options.fontname, ) @@ -506,7 +490,7 @@ def set_dot_basics(dot, options): height="0", margin="0", # Actual size of the node is entirely determined by the label. style="filled", - fillcolor=translate_color(options.bgcolor_node, "HEX"), + fillcolor=options.bgcolor_node.html, fontname=options.fontname, ) dot.attr("edge", style="bold", fontname=options.fontname) diff --git a/src/wireviz/wv_html.py b/src/wireviz/wv_html.py index 1534266..8af39cb 100644 --- a/src/wireviz/wv_html.py +++ b/src/wireviz/wv_html.py @@ -79,7 +79,7 @@ def generate_html_output( replacements = { "": f"{APP_NAME} {__version__} - {APP_URL}", "": options.fontname, - "": wv_colors.translate_color(options.bgcolor, "hex"), + "": options.bgcolor.html, "": svgdata, "": bom_html, "": bom_html_reversed,