From 82b173f2cebe32b23c8df14bec9e0c2c299ee48c Mon Sep 17 00:00:00 2001 From: Gabe R Date: Mon, 29 Jun 2020 04:39:34 -0500 Subject: [PATCH 1/2] Refactor and clean up code (#39) * Format all files using autopep8 to add basic PEP8 conformity. * Add Exception types to bare excepts to prevent catching `ctrl+c` * Remove some unnecessary assignment to dummy variables before returning * Add `Optional` to type hints that can be `NoneType` * Change a number of single-letter variables to more descriptive names * Replace string.format() use with Python's f-strings, as they tends to be cleaner, and provide a performance boost. * One multiline string was left as string.format() as I do not believe f-strings support multiline * Some of the string.format() instances had unused/ignored arguments. I left them out of the f-strings, but I marked those cases with a comments that begins `# FIXME:` * Rename variables that were shadowding python standard functions (specifically `format->fmt`, `input->inp`, `type->maintype`) * Some instances of `type` were not changed, as it breaks the yaml parsing. Needs to be looked into. * Move classes in `wireviz.py` to two new files `DataClasses.py` and `Harness.py`. Co-authored-by: Daniel Rojas --- src/wireviz/DataClasses.py | 136 +++++++++ src/wireviz/Harness.py | 332 +++++++++++++++++++++ src/wireviz/wireviz.py | 583 +++++-------------------------------- src/wireviz/wv_colors.py | 105 +++---- src/wireviz/wv_helper.py | 71 ++--- 5 files changed, 635 insertions(+), 592 deletions(-) create mode 100644 src/wireviz/DataClasses.py create mode 100644 src/wireviz/Harness.py diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py new file mode 100644 index 0000000..c7dc007 --- /dev/null +++ b/src/wireviz/DataClasses.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Optional, List, Any +from dataclasses import dataclass, field +from wireviz.wv_helper import int2tuple +from wireviz import wv_colors + + +@dataclass +class Connector: + name: str + category: Optional[str] = None + type: Optional[str] = None + subtype: Optional[str] = None + pincount: Optional[int] = None + notes: Optional[str] = None + pinout: List[Any] = field(default_factory=list) + pinnumbers: List[Any] = field(default_factory=list) + color: Optional[str] = None + show_name: bool = True + show_pincount: bool = True + hide_disconnected_pins: bool = False + + def __post_init__(self): + self.ports_left = False + self.ports_right = False + self.loops = [] + self.visible_pins = {} + + if self.pincount is None: + if self.pinout: + self.pincount = len(self.pinout) + elif self.pinnumbers: + self.pincount = len(self.pinnumbers) + elif self.category == 'ferrule': + self.pincount = 1 + else: + raise Exception('You need to specify at least one, pincount, pinout or pinnumbers') + + if self.pinout and self.pinnumbers: + if len(self.pinout) != len(self.pinnumbers): + raise Exception('Given pinout and pinnumbers size mismatch') + + # create default lists for pinnumbers (sequential) and pinouts (blank) if not specified + if not self.pinnumbers: + self.pinnumbers = list(range(1, self.pincount + 1)) + if not self.pinout: + self.pinout = [''] * self.pincount + + def loop(self, from_pin, to_pin): + self.loops.append((from_pin, to_pin)) + if self.hide_disconnected_pins: + self.visible_pins[from_pin] = True + self.visible_pins[to_pin] = True + + def activate_pin(self, pin): + self.visible_pins[pin] = True + + +@dataclass +class Cable: + name: str + category: Optional[str] = None + type: Optional[str] = None + gauge: Optional[float] = None + gauge_unit: Optional[str] = None + show_equiv: bool = False + length: float = 0 + wirecount: Optional[int] = None + shield: bool = False + notes: Optional[str] = None + colors: List[Any] = field(default_factory=list) + color_code: Optional[str] = None + show_name: bool = True + show_pinout: bool = False + show_wirecount: bool = True + + def __post_init__(self): + + if isinstance(self.gauge, str): # gauge and unit specified + try: + g, u = self.gauge.split(' ') + except Exception: + raise Exception('Gauge must be a number, or number and unit separated by a space') + self.gauge = g + self.gauge_unit = u.replace('mm2', 'mm\u00B2') + elif self.gauge is not None: # gauge specified, assume mm2 + if self.gauge_unit is None: + self.gauge_unit = 'mm\u00B2' + else: + pass # gauge not specified + + self.connections = [] + + if self.wirecount: # number of wires explicitly defined + 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: + raise Exception('Unknown color code') + self.colors = wv_colors.COLOR_CODES[self.color_code] + 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('Unknown number of wires. Must specify wirecount or colors (implicit length)') + self.wirecount = len(self.colors) + + # for BOM generation + self.wirecount_and_shield = (self.wirecount, self.shield) + + def connect(self, from_name, from_pin, via_pin, to_name, to_pin): + from_pin = int2tuple(from_pin) + via_pin = int2tuple(via_pin) + to_pin = int2tuple(to_pin) + if len(from_pin) != len(to_pin): + raise Exception('from_pin must have the same number of elements as to_pin') + for i, _ in enumerate(from_pin): + # self.connections.append((from_name, from_pin[i], via_pin[i], to_name, to_pin[i])) + self.connections.append(Connection(from_name, from_pin[i], via_pin[i], to_name, to_pin[i])) + + +@dataclass +class Connection: + from_name: Any + from_port: Any + via_port: Any + to_name: Any + to_port: Any diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py new file mode 100644 index 0000000..595d13d --- /dev/null +++ b/src/wireviz/Harness.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from wireviz.DataClasses import Connector, Cable +from graphviz import Graph +from wireviz import wv_colors +from wireviz.wv_helper import awg_equiv, tuplelist2tsv, nested, flatten2d +from collections import Counter +from typing import List + + +class Harness: + + def __init__(self): + self.color_mode = 'SHORT' + self.connectors = {} + self.cables = {} + + def add_connector(self, name, *args, **kwargs): + self.connectors[name] = Connector(name, *args, **kwargs) + + def add_cable(self, name, *args, **kwargs): + self.cables[name] = Cable(name, *args, **kwargs) + + def loop(self, connector_name, from_pin, to_pin): + self.connectors[connector_name].loop(from_pin, to_pin) + + def connect(self, from_name, from_pin, via_name, via_pin, to_name, to_pin): + self.cables[via_name].connect(from_name, from_pin, via_pin, to_name, to_pin) + if from_name in self.connectors: + self.connectors[from_name].activate_pin(from_pin) + if to_name in self.connectors: + self.connectors[to_name].activate_pin(to_pin) + + def create_graph(self): + dot = Graph() + dot.body.append('// Graph generated by WireViz') + dot.body.append('// https://github.com/formatc1702/WireViz') + font = 'arial' + dot.attr('graph', rankdir='LR', + ranksep='2', + bgcolor='white', + nodesep='0.33', + fontname=font) + dot.attr('node', shape='record', + style='filled', + fillcolor='white', + fontname=font) + dot.attr('edge', style='bold', + fontname=font) + + # prepare ports on connectors depending on which side they will connect + for _, cable in self.cables.items(): + for connection in cable.connections: + if connection.from_port is not None: # connect to left + self.connectors[connection.from_name].ports_right = True + if connection.to_port is not None: # connect to right + self.connectors[connection.to_name].ports_left = True + + for key, connector in self.connectors.items(): + if connector.category == 'ferrule': + subtype = f', {connector.subtype}' if connector.subtype else '' + color = wv_colors.translate_color(connector.color, self.color_mode) if connector.color else '' + infostring = f'{connector.type}{subtype} {color}' + infostring_l = infostring if connector.ports_right else '' + infostring_r = infostring if connector.ports_left else '' + + # INFO: Leaving this one as a string.format form because f-strings do not work well with triple quotes + colorbar = f'' if connector.color else '' + dot.node(key, shape='none', + style='filled', + margin='0', + orientation='0' if connector.ports_left else '180', + label='''< + + + + {colorbar} + +
{infostring_l} {infostring_r}
+ + + >'''.format(infostring_l=infostring_l, infostring_r=infostring_r, colorbar=colorbar)) + + else: # not a ferrule + attributes = [connector.type, + connector.subtype, + f'{connector.pincount}-pin' if connector.show_pincount else''] + pinouts = [[], [], []] + for pinnumber, pinname in zip(connector.pinnumbers, connector.pinout): + if connector.hide_disconnected_pins and not connector.visible_pins.get(pinnumber, False): + continue + pinouts[1].append(pinname) + if connector.ports_left: + pinouts[0].append(f'{pinnumber}') + if connector.ports_right: + pinouts[2].append(f'{pinnumber}') + label = [connector.name if connector.show_name else '', attributes, pinouts, connector.notes] + dot.node(key, label=nested(label)) + + 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: + + # FIXME: Original string.format style had some unused arguments (port_to for 1st arg, + # port_from for 2nd arg). De we need them back? + + dot.edge(f'{connector.name}:p{loop[0]}{loop_side}:{loop_dir}', + f'{connector.name}:p{loop[1]}{loop_side}:{loop_dir}') + + for _, cable in self.cables.items(): + awg_fmt = f' ({awg_equiv(cable.gauge)} AWG)' if cable.gauge_unit == 'mm\u00B2' and cable.show_equiv else '' + attributes = [f'{len(cable.colors)}x' if cable.show_wirecount else '', + f'{cable.gauge} {cable.gauge_unit}{awg_fmt}' if cable.gauge else '', # TODO: show equiv + '+ S' if cable.shield else '', + f'{cable.length} m' if cable.length > 0 else ''] + attributes = list(filter(None, attributes)) + + html = '' # name+attributes table + + html = f'{html}' # spacer between attributes and wires + + html = f'{html}' # main table + if cable.notes: + html = f'{html}' # notes table + html = f'{html}' # spacer at the end + + html = f'{html}
' # main table + + html = f'{html}' # name+attributes table + if cable.show_name: + html = f'{html}' + html = f'{html}' # attribute row + for attrib in attributes: + html = f'{html}' + html = f'{html}' # attribute row + html = f'{html}
{cable.name}
{attrib}
 
' # conductor table + + for i, connection in enumerate(cable.colors, 1): + p = [] + p.append(f'') + p.append(wv_colors.translate_color(connection, self.color_mode)) + p.append(f'') + html = f'{html}' + for bla in p: + html = f'{html}' + html = f'{html}' + bgcolor = wv_colors.translate_color(connection, 'hex') + bgcolor = bgcolor if bgcolor != '' else '#ffffff' + html = f'{html}' + + if cable.shield: + p = ['', 'Shield', ''] + html = f'{html}' # spacer + html = f'{html}' + for bla in p: + html = html + f'' + html = f'{html}' + + # FIXME, original string.format had a unused bgcolor argument. Do we need it back + html = f'{html}' + + html = f'{html}' # spacer at the end + + html = f'{html}
{bla}
 
{bla}
 
' # conductor table + + html = f'{html}
{cable.notes}
 
' # main table + + # connections + for connection in cable.connections: + if isinstance(connection.via_port, int): # check if it's an actual wire and not a shield + search_color = cable.colors[connection.via_port - 1] + if search_color in wv_colors.color_hex: + dot.attr('edge', color=f'#000000:{wv_colors.color_hex[search_color]}:#000000') + else: # color name not found + dot.attr('edge', color='#000000:#ffffff:#000000') + else: # it's a shield connection + dot.attr('edge', color='#000000') + + if connection.from_port is not None: # connect to left + from_ferrule = self.connectors[connection.from_name].category == 'ferrule' + port = f':p{connection.from_port}r' if not from_ferrule else '' + code_left_1 = f'{connection.from_name}{port}:e' + # FIXME: Uncomment, then add to end of f-string if needed + # via_subport = 'i' if c.show_pinout else '' + code_left_2 = f'{cable.name}:w{connection.via_port}:w' + dot.edge(code_left_1, code_left_2) + from_string = f'{connection.from_name}:{connection.from_port}' if not from_ferrule else '' + html = html.replace(f'', from_string) + if connection.to_port is not None: # connect to right + to_ferrule = self.connectors[connection.to_name].category == 'ferrule' + + # FIXME: Add in if it was supposed to be here. the add to fstring two lines down + # via_subport = 'o' if c.show_pinout else '' + code_right_1 = f'{cable.name}:w{connection.via_port}:e' + to_port = f':p{connection.to_port}l' if not to_ferrule else '' + code_right_2 = f'{connection.to_name}{to_port}:w' + dot.edge(code_right_1, code_right_2) + to_string = f'{connection.to_name}:{connection.to_port}' if not to_ferrule else '' + html = html.replace(f'', to_string) + + dot.node(cable.name, label=f'<{html}>', shape='box', + style='filled,dashed' if cable.category == 'bundle' else '', margin='0', fillcolor='white') + + return dot + + def output(self, filename, directory='_output', view=False, cleanup=True, fmt='pdf', gen_bom=False): + # graphical output + digraph = self.create_graph() + for f in fmt: + digraph.format = f + digraph.render(filename=filename, directory=directory, view=view, cleanup=cleanup) + digraph.save(filename=f'{filename}.gv', directory=directory) + # bom output + bom_list = self.bom_list() + with open(f'{filename}.bom.tsv', 'w') as file: + file.write(tuplelist2tsv(bom_list)) + # HTML output + with open(f'{filename}.html', 'w') as file: + file.write('') + + file.write('

Diagram

') + with open(f'{filename}.svg') as svg: + for svgdata in svg: + file.write(svgdata) + + file.write('

Bill of Materials

') + listy = flatten2d(bom_list) + file.write('') + file.write('') + for item in listy[0]: + file.write(f'') + file.write('') + for row in listy[1:]: + file.write('') + for i, item in enumerate(row): + align = 'align="right"' if listy[0][i] == 'Qty' else '' + file.write(f'') + file.write('') + file.write('
{item}
{item}
') + + file.write('') + + def bom(self): + bom = [] + bom_connectors = [] + bom_cables = [] + # connectors + types = Counter([(v.type, v.subtype, v.pincount) for v in self.connectors.values()]) + for maintype in types: + items = {k: v for k, v in self.connectors.items() if (v.type, v.subtype, v.pincount) == maintype} + shared = next(iter(items.values())) + designators = list(items.keys()) + designators.sort() + conn_type = f', {shared.type}' if shared.type else '' + conn_subtype = f', {shared.subtype}' if shared.subtype else '' + conn_pincount = f', {shared.pincount} pins' if shared.category != 'ferrule' else '' + conn_color = f', {shared.color}' if shared.color else '' + name = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}' + item = {'item': name, 'qty': len(designators), 'unit': '', + 'designators': designators if shared.category != 'ferrule' else ''} + bom_connectors.append(item) + bom_connectors = sorted(bom_connectors, key=lambda k: k['item']) # https://stackoverflow.com/a/73050 + bom.extend(bom_connectors) + # cables + types = Counter([(v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield) for v in self.cables.values()]) + for maintype in types: + items = {k: v for k, v in self.cables.items() if ( + v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield) == maintype} + shared = next(iter(items.values())) + if shared.category != 'bundle': + designators = list(items.keys()) + designators.sort() + total_length = sum(i.length for i in items.values()) + gauge_name = f' x {shared.gauge} {shared.gauge_unit}'if shared.gauge else ' wires' + shield_name = ' shielded' if shared.shield else '' + name = f'Cable, {shared.wirecount}{gauge_name}{shield_name}' + item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators} + bom_cables.append(item) + # bundles (ignores wirecount) + wirelist = [] + # list all cables again, since bundles are represented as wires internally, with the category='bundle' set + types = Counter([(v.category, v.gauge, v.gauge_unit, v.length) for v in self.cables.values()]) + for maintype in types: + items = {k: v for k, v in self.cables.items() if (v.category, v.gauge, v.gauge_unit, v.length) == maintype} + shared = next(iter(items.values())) + # filter out cables that are not bundles + if shared.category == 'bundle': + for bundle in items.values(): + # add each wire from each bundle to the wirelist + for color in bundle.colors: + wirelist.append({'gauge': shared.gauge, 'gauge_unit': shared.gauge_unit, + 'length': shared.length, 'color': color, 'designator': bundle.name}) + # join similar wires from all the bundles to a single BOM item + types = Counter([(v['gauge'], v['gauge_unit'], v['color']) for v in wirelist]) + for maintype in types: + items = [v for v in wirelist if (v['gauge'], v['gauge_unit'], v['color']) == maintype] + shared = items[0] + designators = [i['designator'] for i in items] + # remove duplicates + designators = list(dict.fromkeys(designators)) + designators.sort() + total_length = sum(i['length'] for i in items) + gauge_name = f', {shared["gauge"]} {shared["gauge_unit"]}' if shared['gauge'] else '' + gauge_color = f', {shared["color"]}' if shared['color'] != '' else '' + name = f'Wire{gauge_name}{gauge_color}' + item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators} + bom_cables.append(item) + bom_cables = sorted(bom_cables, key=lambda k: k['item']) # https://stackoverflow.com/a/73050 + bom.extend(bom_cables) + return bom + + def bom_list(self): + bom = self.bom() + keys = ['item', 'qty', 'unit', 'designators'] + bom_list = [] + bom_list.append([k.capitalize() for k in keys]) # create header row with keys + for item in bom: + item_list = [item.get(key, '') for key in keys] # fill missing values with blanks + for i, subitem in enumerate(item_list): + if isinstance(subitem, List): # convert any lists into comma separated strings + item_list[i] = ', '.join(subitem) + bom_list.append(item_list) + return bom_list diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 83dba1e..b94b459 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -1,450 +1,18 @@ #!/usr/bin/env python3 +# -*- coding: utf-8 -*- + import argparse -from collections import Counter -from dataclasses import dataclass, field -from graphviz import Graph import os import sys -from typing import Any, List + import yaml -if __name__== '__main__': +if __name__ == '__main__': sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) -from wireviz import wv_colors -from wireviz.wv_helper import nested, int2tuple, awg_equiv, flatten2d, tuplelist2tsv +from wireviz.Harness import Harness -class Harness: - - def __init__(self): - self.color_mode = 'SHORT' - self.connectors = {} - self.cables = {} - - def add_connector(self, name, *args, **kwargs): - self.connectors[name] = Connector(name, *args, **kwargs) - - def add_cable(self, name, *args, **kwargs): - self.cables[name] = Cable(name, *args, **kwargs) - - def loop(self, connector_name, from_pin, to_pin): - self.connectors[connector_name].loop(from_pin, to_pin) - - def connect(self, from_name, from_pin, via_name, via_pin, to_name, to_pin): - self.cables[via_name].connect(from_name, from_pin, via_pin, to_name, to_pin) - if from_name in self.connectors: - self.connectors[from_name].activate_pin(from_pin) - if to_name in self.connectors: - self.connectors[to_name].activate_pin(to_pin) - - def create_graph(self): - dot = Graph() - dot.body.append('// Graph generated by WireViz') - dot.body.append('// https://github.com/formatc1702/WireViz') - font = 'arial' - dot.attr('graph', rankdir='LR', - ranksep='2', - bgcolor='white', - nodesep='0.33', - fontname=font) - dot.attr('node', shape='record', - style='filled', - fillcolor='white', - fontname=font) - dot.attr('edge', style='bold', - fontname=font) - - # prepare ports on connectors depending on which side they will connect - for k, c in self.cables.items(): - for x in c.connections: - if x.from_port is not None: # connect to left - self.connectors[x.from_name].ports_right = True - if x.to_port is not None: # connect to right - self.connectors[x.to_name].ports_left = True - - for k, n in self.connectors.items(): - if n.category == 'ferrule': - infostring = '{type}{subtype} {color}'.format(type=n.type, - subtype=', {}'.format(n.subtype) if n.subtype else '', - color=wv_colors.translate_color(n.color, self.color_mode) if n.color else '') - infostring_l = infostring if n.ports_right else '' - infostring_r = infostring if n.ports_left else '' - - dot.node(k, shape='none', - style='filled', - margin='0', - orientation = '0' if n.ports_left else '180', - label='''< - - - - {colorbar} - -
{infostring_l} {infostring_r}
- - - >'''.format(infostring_l=infostring_l, - infostring_r=infostring_r, - colorbar=''.format(wv_colors.translate_color(n.color, 'HEX')) if n.color else '')) - - else: # not a ferrule - # a = attributes - a = [n.type, - n.subtype, - '{}-pin'.format(n.pincount) if n.show_pincount else ''] - # p = pinout - p = [[],[],[]] - for pinnumber, pinname in zip(n.pinnumbers, n.pinout): - if n.hide_disconnected_pins and not n.visible_pins.get(pinnumber, False): - continue - p[1].append(pinname) - if n.ports_left: - p[0].append('{portno}'.format(portno=pinnumber)) - if n.ports_right: - p[2].append('{portno}'.format(portno=pinnumber)) - # l = label - l = [n.name if n.show_name else '', a, p, n.notes] - dot.node(k, label=nested(l)) - - if len(n.loops) > 0: - dot.attr('edge',color='#000000:#ffffff:#000000') - if n.ports_left: - loop_side = 'l' - loop_dir = 'w' - elif n.ports_right: - loop_side = 'r' - loop_dir = 'e' - else: - raise Exception('No side for loops') - for loop in n.loops: - dot.edge('{name}:p{port_from}{loop_side}:{loop_dir}'.format(name=n.name, port_from=loop[0], port_to=loop[1], loop_side=loop_side, loop_dir=loop_dir), - '{name}:p{port_to}{loop_side}:{loop_dir}'.format(name=n.name, port_from=loop[0], port_to=loop[1], loop_side=loop_side, loop_dir=loop_dir)) - - for k, c in self.cables.items(): - # a = attributes - a = ['{}x'.format(len(c.colors)) if c.show_wirecount else '', - '{} {}{}'.format(c.gauge, c.gauge_unit, ' ({} AWG)'.format(awg_equiv(c.gauge)) if c.gauge_unit == 'mm\u00B2' and c.show_equiv else '') if c.gauge else '', # TODO: show equiv - '+ S' if c.shield else '', - '{} m'.format(c.length) if c.length > 0 else ''] - a = list(filter(None, a)) - - html = '' # name+attributes table - - html = html + '' # spacer between attributes and wires - - html = html + '' # main table - if c.notes: - html = html + ''.format(c.notes) # notes table - html = html + '' # spacer at the end - - html = html + '
' # main table - - html = html + '' # name+attributes table - if c.show_name: - html = html + ''.format(colspan=len(a), name=c.name) - html = html + '' # attribute row - for attrib in a: - html = html + ''.format(attrib=attrib) - html = html + '' # attribute row - html = html + '
{name}
{attrib}
 
' # conductor table - - for i, x in enumerate(c.colors,1): - p = [] - p.append(''.format(i)) - p.append(wv_colors.translate_color(x, self.color_mode)) - p.append(''.format(i)) - html = html + '' - for bla in p: - html = html + ''.format(bla) - html = html + '' - bgcolor = wv_colors.translate_color(x, 'hex') - html = html + ''.format(colspan=len(p), bgcolor=bgcolor if bgcolor != '' else '#ffffff', port='w{}'.format(i)) - - if c.shield: - p = ['', 'Shield', ''] - html = html + '' # spacer - html = html + '' - for bla in p: - html = html + ''.format(bla) - html = html + '' - html = html + ''.format(colspan=len(p), bgcolor=wv_colors.translate_color(x, 'hex'), port='ws') - - html = html + '' # spacer at the end - - html = html + '
{}
 
{}
 
' # conductor table - - html = html + '
{}
 
' # main table - - # connections - for x in c.connections: - if isinstance(x.via_port, int): # check if it's an actual wire and not a shield - search_color = c.colors[x.via_port-1] - if search_color in wv_colors.color_hex: - dot.attr('edge',color='#000000:{wire_color}:#000000'.format(wire_color=wv_colors.color_hex[search_color])) - else: # color name not found - dot.attr('edge',color='#000000:#ffffff:#000000') - else: # it's a shield connection - dot.attr('edge',color='#000000') - - if x.from_port is not None: # connect to left - from_ferrule = self.connectors[x.from_name].category == 'ferrule' - code_left_1 = '{from_name}{from_port}:e'.format(from_name=x.from_name, from_port=':p{}r'.format(x.from_port) if not from_ferrule else '') - code_left_2 = '{via_name}:w{via_wire}:w'.format(via_name=c.name, via_wire=x.via_port, via_subport='i' if c.show_pinout else '') - dot.edge(code_left_1, code_left_2) - from_string = '{}:{}'.format(x.from_name, x.from_port) if not from_ferrule else '' - html = html.replace(''.format(x.via_port), from_string) - if x.to_port is not None: # connect to right - to_ferrule = self.connectors[x.to_name].category == 'ferrule' - code_right_1 = '{via_name}:w{via_wire}:e'.format(via_name=c.name, via_wire=x.via_port, via_subport='o' if c.show_pinout else '') - code_right_2 = '{to_name}{to_port}:w'.format(to_name=x.to_name, to_port=':p{}l'.format(x.to_port) if not to_ferrule else '') - dot.edge(code_right_1, code_right_2) - to_string = '{}:{}'.format(x.to_name, x.to_port) if not to_ferrule else '' - html = html.replace(''.format(x.via_port), to_string) - - dot.node(c.name, label='<{html}>'.format(html=html), shape='box', style='filled,dashed' if c.category=='bundle' else '', margin='0', fillcolor='white') - - return dot - - def output(self, filename, directory='_output', view=False, cleanup=True, format='pdf', gen_bom=False): - # graphical output - d = self.create_graph() - for f in format: - d.format = f - d.render(filename=filename, directory=directory, view=view, cleanup=cleanup) - d.save(filename='{}.gv'.format(filename), directory=directory) - # bom output - bom_list = self.bom_list() - with open('{}.bom.tsv'.format(filename),'w') as file: - file.write(tuplelist2tsv(bom_list)) - # HTML output - with open('{}.html'.format(filename),'w') as file: - file.write('') - - file.write('

Diagram

') - with open('{}.svg'.format(filename),'r') as svg: - for l in svg: - file.write(l) - - file.write('

Bill of Materials

') - listy = flatten2d(bom_list) - file.write('') - file.write('') - for item in listy[0]: - file.write(''.format(item)) - file.write('') - for row in listy[1:]: - file.write('') - for i, item in enumerate(row): - file.write(''.format(content=item, align='align="right"' if listy[0][i] == 'Qty' else '')) - file.write('') - file.write('
{}
{content}
') - - file.write('') - - def bom(self): - bom = [] - bom_connectors = [] - bom_cables = [] - # connectors - types = Counter([(v.type, v.subtype, v.pincount) for v in self.connectors.values()]) - for type in types: - items = {k: v for k, v in self.connectors.items() if (v.type, v.subtype, v.pincount) == type} - shared = next(iter(items.values())) - designators = list(items.keys()) - designators.sort() - name = 'Connector{type}{subtype}{pincount}{color}'.format(type = ', {}'.format(shared.type) if shared.type else '', - subtype = ', {}'.format(shared.subtype) if shared.subtype else '', - pincount = ', {} pins'.format(shared.pincount) if shared.category != 'ferrule' else '', - color = ', {}'.format(shared.color) if shared.color else '') - item = {'item': name, 'qty': len(designators), 'unit': '', 'designators': designators if shared.category != 'ferrule' else ''} - bom_connectors.append(item) - bom_connectors = sorted(bom_connectors, key=lambda k: k['item']) # https://stackoverflow.com/a/73050 - bom.extend(bom_connectors) - # cables - types = Counter([(v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield) for v in self.cables.values()]) - for type in types: - items = {k: v for k, v in self.cables.items() if (v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield) == type} - shared = next(iter(items.values())) - if shared.category != 'bundle': - designators = list(items.keys()) - designators.sort() - total_length = sum(i.length for i in items.values()) - name = 'Cable, {wirecount}{gauge}{shield}'.format(wirecount = shared.wirecount, - gauge = ' x {} {}'.format(shared.gauge, shared.gauge_unit) if shared.gauge else ' wires', - shield = ' shielded' if shared.shield else '') - item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators} - bom_cables.append(item) - # bundles (ignores wirecount) - wirelist = [] - # list all cables again, since bundles are represented as wires internally, with the category='bundle' set - types = Counter([(v.category, v.gauge, v.gauge_unit, v.length) for v in self.cables.values()]) - for type in types: - items = {k: v for k, v in self.cables.items() if (v.category, v.gauge, v.gauge_unit, v.length) == type} - shared = next(iter(items.values())) - # filter out cables that are not bundles - if shared.category == 'bundle': - for bundle in items.values(): - # add each wire from each bundle to the wirelist - for color in bundle.colors: - wirelist.append({'gauge': shared.gauge, 'gauge_unit': shared.gauge_unit, 'length': shared.length, 'color': color, 'designator': bundle.name}) - # join similar wires from all the bundles to a single BOM item - types = Counter([(v['gauge'], v['gauge_unit'], v['color']) for v in wirelist]) - for type in types: - items = [v for v in wirelist if (v['gauge'], v['gauge_unit'], v['color']) == type] - shared = items[0] - designators = [i['designator'] for i in items] - # remove duplicates - designators = list(dict.fromkeys(designators)) - designators.sort() - total_length = sum(i['length'] for i in items) - name = 'Wire{gauge}{color}'.format(gauge=', {} {}'.format(shared['gauge'], shared['gauge_unit']) if shared['gauge'] else '', - color=', {}'.format(shared['color']) if shared['color'] != '' else '') - item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators} - bom_cables.append(item) - bom_cables = sorted(bom_cables, key=lambda k: k['item']) # https://stackoverflow.com/a/73050 - bom.extend(bom_cables) - return bom - - def bom_list(self): - bom = self.bom() - keys = ['item', 'qty', 'unit', 'designators'] - bom_list = [] - bom_list.append([k.capitalize() for k in keys]) # create header row with keys - for item in bom: - item_list = [item.get(key, '') for key in keys] # fill missing values with blanks - for i, subitem in enumerate(item_list): - if isinstance(subitem, List): # convert any lists into comma separated strings - item_list[i] = ', '.join(subitem) - bom_list.append(item_list) - return bom_list - -@dataclass -class Connector: - name: str - category: str = None - type: str = None - subtype: str = None - pincount: int = None - notes: str = None - pinout: List[Any] = field(default_factory=list) - pinnumbers: List[Any] = field(default_factory=list) - color: str = None - show_name: bool = True - show_pincount: bool = True - hide_disconnected_pins: bool = False - - def __post_init__(self): - self.ports_left = False - self.ports_right = False - self.loops = [] - self.visible_pins = {} - - if self.pincount is None: - if self.pinout: - self.pincount = len(self.pinout) - elif self.pinnumbers: - self.pincount = len(self.pinnumbers) - elif self.category == 'ferrule': - self.pincount = 1 - else: - raise Exception('You need to specify at least one, pincount, pinout or pinnumbers') - - if self.pinout and self.pinnumbers: - if len(self.pinout) != len(self.pinnumbers): - raise Exception('Given pinout and pinnumbers size mismatch') - - # create default lists for pinnumbers (sequential) and pinouts (blank) if not specified - if not self.pinnumbers: - self.pinnumbers = list(range(1,self.pincount + 1)) - if not self.pinout: - self.pinout = [''] * self.pincount - - def loop(self, from_pin, to_pin): - self.loops.append((from_pin, to_pin)) - if self.hide_disconnected_pins: - self.visible_pins[from_pin] = True - self.visible_pins[to_pin] = True - - def activate_pin(self, pin): - self.visible_pins[pin] = True - -@dataclass -class Cable: - name: str - category : str = None - type: str = None - gauge: float = None - gauge_unit : str = None - show_equiv: bool = False - length: float = 0 - wirecount: int = None - shield: bool = False - notes: str = None - colors: List[Any] = field(default_factory=list) - color_code: str = None - show_name: bool = True - show_pinout: bool = False - show_wirecount: bool = True - - def __post_init__(self): - - if isinstance(self.gauge, str): # gauge and unit specified - try: - g, u = self.gauge.split(' ') - except: - raise Exception('Gauge must be a number, or number and unit separated by a space') - self.gauge = g - self.gauge_unit = u.replace('mm2','mm\u00B2') - elif self.gauge is not None: # gauge specified, assume mm2 - if self.gauge_unit is None: - self.gauge_unit = 'mm\u00B2' - else: - pass # gauge not specified - - self.connections = [] - - if self.wirecount: # number of wires explicitly defined - 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: - raise Exception('Unknown color code') - self.colors = wv_colors.COLOR_CODES[self.color_code] - 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('Unknown number of wires. Must specify wirecount or colors (implicit length)') - self.wirecount = len(self.colors) - - # for BOM generation - self.wirecount_and_shield = (self.wirecount, self.shield) - - def connect(self, from_name, from_pin, via_pin, to_name, to_pin): - from_pin = int2tuple(from_pin) - via_pin = int2tuple(via_pin) - to_pin = int2tuple(to_pin) - if len(from_pin) != len(to_pin): - raise Exception('from_pin must have the same number of elements as to_pin') - for i, x in enumerate(from_pin): - # self.connections.append((from_name, from_pin[i], via_pin[i], to_name, to_pin[i])) - self.connections.append(Connection(from_name, from_pin[i], via_pin[i], to_name, to_pin[i])) - -@dataclass -class Connection: - from_name: Any - from_port: Any - via_port: Any - to_name: Any - to_port: Any def parse(yaml_input, file_out=None, generate_bom=False): @@ -457,23 +25,23 @@ def parse(yaml_input, file_out=None, generate_bom=False): # if str is of the format '#-#', it is treated as a range (inclusive) and expanded output = [] if not isinstance(yaml_data, list): - yaml_data = [yaml_data,] + yaml_data = [yaml_data] for e in yaml_data: e = str(e) - if '-' in e: # list of pins + if '-' in e: # list of pins a, b = tuple(map(int, e.split('-'))) if a < b: - for x in range(a,b+1): + for x in range(a, b + 1): output.append(x) elif a > b: - for x in range(a,b-1,-1): + for x in range(a, b - 1, -1): output.append(x) elif a == b: output.append(a) else: try: x = int(e) - except: + except Exception: x = e output.append(x) return output @@ -484,25 +52,25 @@ def parse(yaml_input, file_out=None, generate_bom=False): return False return True - h = Harness() + harness = Harness() # add items - sections = ['connectors','cables','ferrules','connections'] - types = [dict, dict, dict, list] + sections = ['connectors', 'cables', 'ferrules', 'connections'] + types = [dict, dict, dict, list] for sec, ty in zip(sections, types): if sec in yaml_data and type(yaml_data[sec]) == ty: if len(yaml_data[sec]) > 0: if ty == dict: - for k, o in yaml_data[sec].items(): + for key, o in yaml_data[sec].items(): if sec == 'connectors': - h.add_connector(name=k, **o) + harness.add_connector(name=key, **o) elif sec == 'cables': - h.add_cable(name=k, **o) + harness.add_cable(name=key, **o) elif sec == 'ferrules': pass else: - pass # section exists but is empty - else: # section does not exist, create empty section + pass # section exists but is empty + else: # section does not exist, create empty section if ty == dict: yaml_data[sec] = {} elif ty == list: @@ -510,64 +78,63 @@ def parse(yaml_input, file_out=None, generate_bom=False): # add connections ferrule_counter = 0 - for con in yaml_data['connections']: - if len(con) == 3: # format: connector -- cable -- connector + for connections in yaml_data['connections']: + if len(connections) == 3: # format: connector -- cable -- connector - for c in con: - if len(list(c.keys())) != 1: # check that each entry in con has only one key, which is the designator + for connection in connections: + if len(list(connection.keys())) != 1: # check that each entry in con has only one key, which is the designator raise Exception('Too many keys') - from_name = list(con[0].keys())[0] - via_name = list(con[1].keys())[0] - to_name = list(con[2].keys())[0] + from_name = list(connections[0].keys())[0] + via_name = list(connections[1].keys())[0] + to_name = list(connections[2].keys())[0] - if not check_designators([from_name,via_name,to_name],('connectors','cables','connectors')): - print([from_name,via_name,to_name]) + if not check_designators([from_name, via_name, to_name], ('connectors', 'cables', 'connectors')): + print([from_name, via_name, to_name]) raise Exception('Bad connection definition (3)') - from_pins = expand(con[0][from_name]) - via_pins = expand(con[1][via_name]) - to_pins = expand(con[2][to_name]) + from_pins = expand(connections[0][from_name]) + via_pins = expand(connections[1][via_name]) + to_pins = expand(connections[2][to_name]) if len(from_pins) != len(via_pins) or len(via_pins) != len(to_pins): raise Exception('List length mismatch') for (from_pin, via_pin, to_pin) in zip(from_pins, via_pins, to_pins): - h.connect(from_name, from_pin, via_name, via_pin, to_name, to_pin) + harness.connect(from_name, from_pin, via_name, via_pin, to_name, to_pin) - elif len(con) == 2: + elif len(connections) == 2: - for c in con: - if type(c) is dict: - if len(list(c.keys())) != 1: # check that each entry in con has only one key, which is the designator + for connection in connections: + if type(connection) is dict: + if len(list(connection.keys())) != 1: # check that each entry in con has only one key, which is the designator raise Exception('Too many keys') # hack to make the format for ferrules compatible with the formats for connectors and cables - if type(con[0]) == str: - name = con[0] - con[0] = {} - con[0][name] = name - if type(con[1]) == str: - name = con[1] - con[1] = {} - con[1][name] = name + if type(connections[0]) == str: + name = connections[0] + connections[0] = {} + connections[0][name] = name + if type(connections[1]) == str: + name = connections[1] + connections[1] = {} + connections[1][name] = name - from_name = list(con[0].keys())[0] - to_name = list(con[1].keys())[0] + from_name = list(connections[0].keys())[0] + to_name = list(connections[1].keys())[0] - con_cbl = check_designators([from_name, to_name],('connectors','cables')) - cbl_con = check_designators([from_name, to_name],('cables','connectors')) - con_con = check_designators([from_name, to_name],('connectors','connectors')) + con_cbl = check_designators([from_name, to_name], ('connectors', 'cables')) + cbl_con = check_designators([from_name, to_name], ('cables', 'connectors')) + con_con = check_designators([from_name, to_name], ('connectors', 'connectors')) - - fer_cbl = check_designators([from_name, to_name],('ferrules','cables')) - cbl_fer = check_designators([from_name, to_name],('cables','ferrules')) + fer_cbl = check_designators([from_name, to_name], ('ferrules', 'cables')) + cbl_fer = check_designators([from_name, to_name], ('cables', 'ferrules')) if not con_cbl and not cbl_con and not con_con and not fer_cbl and not cbl_fer: raise Exception('Wrong designators') - from_pins = expand(con[0][from_name]) - to_pins = expand(con[1][to_name]) + from_pins = expand(connections[0][from_name]) + to_pins = expand(connections[1][to_name]) if con_cbl or cbl_con or con_con: if len(from_pins) != len(to_pins): @@ -576,19 +143,19 @@ def parse(yaml_input, file_out=None, generate_bom=False): if con_cbl or cbl_con: for (from_pin, to_pin) in zip(from_pins, to_pins): if con_cbl: - h.connect(from_name, from_pin, to_name, to_pin, None, None) - else: # cbl_con - h.connect(None, None, from_name, from_pin, to_name, to_pin) + harness.connect(from_name, from_pin, to_name, to_pin, None, None) + else: # cbl_con + harness.connect(None, None, from_name, from_pin, to_name, to_pin) elif con_con: - cocon_coname = list(con[0].keys())[0] - from_pins = expand(con[0][from_name]) - to_pins = expand(con[1][to_name]) + cocon_coname = list(connections[0].keys())[0] + from_pins = expand(connections[0][from_name]) + to_pins = expand(connections[1][to_name]) for (from_pin, to_pin) in zip(from_pins, to_pins): - h.loop(cocon_coname, from_pin, to_pin) + harness.loop(cocon_coname, from_pin, to_pin) if fer_cbl or cbl_fer: - from_pins = expand(con[0][from_name]) - to_pins = expand(con[1][to_name]) + from_pins = expand(connections[0][from_name]) + to_pins = expand(connections[1][to_name]) if fer_cbl: ferrule_name = from_name @@ -602,19 +169,19 @@ def parse(yaml_input, file_out=None, generate_bom=False): ferrule_params = yaml_data['ferrules'][ferrule_name] for cable_pin in cable_pins: ferrule_counter = ferrule_counter + 1 - ferrule_id = '_F{}'.format(ferrule_counter) - h.add_connector(ferrule_id, category='ferrule', **ferrule_params) + ferrule_id = f'_F{ferrule_counter}' + harness.add_connector(ferrule_id, category='ferrule', **ferrule_params) if fer_cbl: - h.connect(ferrule_id, 1, cable_name, cable_pin, None, None) + harness.connect(ferrule_id, 1, cable_name, cable_pin, None, None) else: - h.connect(None, None, cable_name, cable_pin, ferrule_id, 1) - + harness.connect(None, None, cable_name, cable_pin, ferrule_id, 1) else: raise Exception('Wrong number of connection parameters') - h.output(filename=file_out, format=('png','svg'), gen_bom=generate_bom, view=False) + harness.output(filename=file_out, fmt=('png', 'svg'), gen_bom=generate_bom, view=False) + def parse_file(yaml_file, file_out=None, generate_bom=False): with open(yaml_file, 'r') as file: @@ -630,27 +197,26 @@ def parse_file(yaml_file, file_out=None, generate_bom=False): def parse_cmdline(): parser = argparse.ArgumentParser( - description='Generate cable and wiring harness documentation from YAML descriptions' - ) + description='Generate cable and wiring harness documentation from YAML descriptions', + ) parser.add_argument('input_file', action='store', type=str, metavar='YAML_FILE') - parser.add_argument('-o', '--output_file', action='store', type=str, metavar='OUTPUT') + parser.add_argument('-o', '--output_file', action='store', type=str, metavar='OUTPUT') parser.add_argument('--generate-bom', action='store_true', default=True) parser.add_argument('--prepend-file', action='store', type=str, metavar='YAML_FILE') - args = parser.parse_args() + return parser.parse_args() - return args def main(): args = parse_cmdline() if not os.path.exists(args.input_file): - print('Error: input file {} inaccessible or does not exist, check path'.format(args.input_file)) + print(f'Error: input file {args.input_file} inaccessible or does not exist, check path') sys.exit(1) with open(args.input_file) as fh: @@ -658,7 +224,7 @@ def main(): if args.prepend_file: if not os.path.exists(args.prepend_file): - print('Error: prepend input file {} inaccessible or does not exist, check path'.format(args.prepend_file)) + print(f'Error: prepend input file {args.prepend_file} inaccessible or does not exist, check path') sys.exit(1) with open(args.prepend_file) as fh: prepend = fh.read() @@ -667,12 +233,13 @@ def main(): if not args.output_file: file_out = args.input_file pre, _ = os.path.splitext(file_out) - file_out = pre # extension will be added by graphviz output function + file_out = pre # extension will be added by graphviz output function else: file_out = args.output_file file_out = os.path.abspath(file_out) parse(yaml_input, file_out=file_out, generate_bom=args.generate_bom) + if __name__ == '__main__': main() diff --git a/src/wireviz/wv_colors.py b/src/wireviz/wv_colors.py index 32c90c9..ad22659 100644 --- a/src/wireviz/wv_colors.py +++ b/src/wireviz/wv_colors.py @@ -1,74 +1,77 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- COLOR_CODES = { - 'DIN': ['WH','BN','GN','YE','GY','PK','BU','RD','BK','VT'], # ,'GYPK','RDBU','WHGN','BNGN','WHYE','YEBN','WHGY','GYBN','WHPK','PKBN'], - 'IEC': ['BN','RD','OG','YE','GN','BU','VT','GY','WH','BK'], - 'BW': ['BK','WH'] - } + 'DIN': ['WH', 'BN', 'GN', 'YE', 'GY', 'PK', 'BU', 'RD', 'BK', 'VT'], # ,'GYPK','RDBU','WHGN','BNGN','WHYE','YEBN','WHGY','GYBN','WHPK','PKBN'], + 'IEC': ['BN', 'RD', 'OG', 'YE', 'GN', 'BU', 'VT', 'GY', 'WH', 'BK'], + 'BW': ['BK', 'WH'], +} color_hex = { - 'BK': '#000000', - 'WH': '#ffffff', - 'GY': '#999999', - 'PK': '#ff66cc', - 'RD': '#ff0000', - 'OG': '#ff8000', - 'YE': '#ffff00', - 'GN': '#00ff00', - 'TQ': '#00ffff', - 'BU': '#0066ff', - 'VT': '#8000ff', - 'BN': '#666600', - } + 'BK': '#000000', + 'WH': '#ffffff', + 'GY': '#999999', + 'PK': '#ff66cc', + 'RD': '#ff0000', + 'OG': '#ff8000', + 'YE': '#ffff00', + 'GN': '#00ff00', + 'TQ': '#00ffff', + 'BU': '#0066ff', + 'VT': '#8000ff', + 'BN': '#666600', +} color_full = { - 'BK': 'black', - 'WH': 'white', - 'GY': 'grey', - 'PK': 'pink', - 'RD': 'red', - 'OG': 'orange', - 'YE': 'yellow', - 'GN': 'green', - 'TQ': 'turquoise', - 'BU': 'blue', - 'VT': 'violet', - 'BN': 'brown', + 'BK': 'black', + 'WH': 'white', + 'GY': 'grey', + 'PK': 'pink', + 'RD': 'red', + 'OG': 'orange', + 'YE': 'yellow', + 'GN': 'green', + 'TQ': 'turquoise', + 'BU': 'blue', + 'VT': 'violet', + 'BN': 'brown', } color_ger = { - 'BK': 'sw', - 'WH': 'ws', - 'GY': 'gr', - 'PK': 'rs', - 'RD': 'rt', - 'OG': 'or', - 'YE': 'ge', - 'GN': 'gn', - 'TQ': 'tk', - 'BU': 'bl', - 'VT': 'vi', - 'BN': 'br', + 'BK': 'sw', + 'WH': 'ws', + 'GY': 'gr', + 'PK': 'rs', + 'RD': 'rt', + 'OG': 'or', + 'YE': 'ge', + 'GN': 'gn', + 'TQ': 'tk', + 'BU': 'bl', + 'VT': 'vi', + 'BN': 'br', } -def translate_color(input, color_mode): - if input == '': + +def translate_color(inp, color_mode): + if inp == '': output = '' else: if color_mode == 'full': - output = color_full[input].lower() + output = color_full[inp].lower() elif color_mode == 'FULL': - output = color_full[input].upper() + output = color_full[inp].upper() elif color_mode == 'hex': - output = color_hex[input].lower() + output = color_hex[inp].lower() elif color_mode == 'HEX': - output = color_hex[input].upper() + output = color_hex[inp].upper() elif color_mode == 'ger': - output = color_ger[input].lower() + output = color_ger[inp].lower() elif color_mode == 'GER': - output = color_ger[input].upper() + output = color_ger[inp].upper() elif color_mode == 'short': - output = input.lower() + output = inp.lower() elif color_mode == 'SHORT': - output = input.upper() + output = inp.upper() else: raise Exception('Unknown color mode') return output diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 1c803fd..b06d40e 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -1,33 +1,37 @@ -from typing import Any, List +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import List + def awg_equiv(mm2): awg_equiv_table = { - '0.09': 28, - '0.14': 26, - '0.25': 24, - '0.34': 22, - '0.5': 21, - '0.75': 20, - '1': 18, - '1.5': 16, - '2.5': 14, - '4': 12, - '6': 10, - '10': 8, - '16': 6, - '25': 4, - '35': 2, - '50': 1, - } + '0.09': 28, + '0.14': 26, + '0.25': 24, + '0.34': 22, + '0.5': 21, + '0.75': 20, + '1': 18, + '1.5': 16, + '2.5': 14, + '4': 12, + '6': 10, + '10': 8, + '16': 6, + '25': 4, + '35': 2, + '50': 1, + } k = str(mm2) if k in awg_equiv_table: return awg_equiv_table[k] else: return 'unknown' -def nested(input): + +def nested(inp): l = [] - for x in input: + for x in inp: if isinstance(x, list): if len(x) > 0: n = nested(x) @@ -37,25 +41,26 @@ def nested(input): if x is not None: if x != '': l.append(str(x)) - s = '|'.join(l) - return s + return '|'.join(l) -def int2tuple(input): - if isinstance(input, tuple): - output = input + +def int2tuple(inp): + if isinstance(inp, tuple): + output = inp else: - output = (input,) + output = (inp,) return output -def flatten2d(input): - output = [[str(item) if not isinstance(item, List) else ', '.join(item) for item in row] for row in input] - return output -def tuplelist2tsv(input, header=None): +def flatten2d(inp): + return [[str(item) if not isinstance(item, List) else ', '.join(item) for item in row] for row in inp] + + +def tuplelist2tsv(inp, header=None): output = '' if header is not None: - input.insert(0, header) - input = flatten2d(input) - for row in input: + inp.insert(0, header) + inp = flatten2d(inp) + for row in inp: output = output + '\t'.join(str(item) for item in row) + '\n' return output From c42b33b38d3bf3f2ee4aa157a7445caeedde5bb3 Mon Sep 17 00:00:00 2001 From: Daniel Rojas Date: Mon, 29 Jun 2020 12:30:07 +0200 Subject: [PATCH 2/2] Address FIXMEs and fine-tune merge of #39 --- src/wireviz/DataClasses.py | 2 +- src/wireviz/Harness.py | 19 ++++--------------- src/wireviz/build_examples.py | 1 + src/wireviz/wireviz.py | 6 ------ src/wireviz/wv_colors.py | 1 + src/wireviz/wv_helper.py | 1 + 6 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index c7dc007..a2f2a9e 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + from typing import Optional, List, Any from dataclasses import dataclass, field from wireviz.wv_helper import int2tuple @@ -72,7 +73,6 @@ class Cable: colors: List[Any] = field(default_factory=list) color_code: Optional[str] = None show_name: bool = True - show_pinout: bool = False show_wirecount: bool = True def __post_init__(self): diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 595d13d..c11ad9a 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -109,10 +109,6 @@ class Harness: else: raise Exception('No side for loops') for loop in connector.loops: - - # FIXME: Original string.format style had some unused arguments (port_to for 1st arg, - # port_from for 2nd arg). De we need them back? - dot.edge(f'{connector.name}:p{loop[0]}{loop_side}:{loop_dir}', f'{connector.name}:p{loop[1]}{loop_side}:{loop_dir}') @@ -159,8 +155,6 @@ class Harness: for bla in p: html = html + f'{bla}' html = f'{html}' - - # FIXME, original string.format had a unused bgcolor argument. Do we need it back html = f'{html}' html = f'{html} ' # spacer at the end @@ -189,17 +183,12 @@ class Harness: from_ferrule = self.connectors[connection.from_name].category == 'ferrule' port = f':p{connection.from_port}r' if not from_ferrule else '' code_left_1 = f'{connection.from_name}{port}:e' - # FIXME: Uncomment, then add to end of f-string if needed - # via_subport = 'i' if c.show_pinout else '' code_left_2 = f'{cable.name}:w{connection.via_port}:w' dot.edge(code_left_1, code_left_2) from_string = f'{connection.from_name}:{connection.from_port}' if not from_ferrule else '' html = html.replace(f'', from_string) if connection.to_port is not None: # connect to right to_ferrule = self.connectors[connection.to_name].category == 'ferrule' - - # FIXME: Add in if it was supposed to be here. the add to fstring two lines down - # via_subport = 'o' if c.show_pinout else '' code_right_1 = f'{cable.name}:w{connection.via_port}:e' to_port = f':p{connection.to_port}l' if not to_ferrule else '' code_right_2 = f'{connection.to_name}{to_port}:w' @@ -214,11 +203,11 @@ class Harness: def output(self, filename, directory='_output', view=False, cleanup=True, fmt='pdf', gen_bom=False): # graphical output - digraph = self.create_graph() + graph = self.create_graph() for f in fmt: - digraph.format = f - digraph.render(filename=filename, directory=directory, view=view, cleanup=cleanup) - digraph.save(filename=f'{filename}.gv', directory=directory) + graph.format = f + graph.render(filename=filename, directory=directory, view=view, cleanup=cleanup) + graph.save(filename=f'{filename}.gv', directory=directory) # bom output bom_list = self.bom_list() with open(f'{filename}.bom.tsv', 'w') as file: diff --git a/src/wireviz/build_examples.py b/src/wireviz/build_examples.py index fc04fce..078e4e2 100755 --- a/src/wireviz/build_examples.py +++ b/src/wireviz/build_examples.py @@ -1,4 +1,5 @@ #!/usr/bin/python3 +# -*- coding: utf-8 -*- import os import sys diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index b94b459..81b4771 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- - import argparse import os import sys @@ -199,15 +198,10 @@ def parse_cmdline(): parser = argparse.ArgumentParser( description='Generate cable and wiring harness documentation from YAML descriptions', ) - parser.add_argument('input_file', action='store', type=str, metavar='YAML_FILE') - parser.add_argument('-o', '--output_file', action='store', type=str, metavar='OUTPUT') - parser.add_argument('--generate-bom', action='store_true', default=True) - parser.add_argument('--prepend-file', action='store', type=str, metavar='YAML_FILE') - return parser.parse_args() diff --git a/src/wireviz/wv_colors.py b/src/wireviz/wv_colors.py index ad22659..055b467 100644 --- a/src/wireviz/wv_colors.py +++ b/src/wireviz/wv_colors.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + COLOR_CODES = { 'DIN': ['WH', 'BN', 'GN', 'YE', 'GY', 'PK', 'BU', 'RD', 'BK', 'VT'], # ,'GYPK','RDBU','WHGN','BNGN','WHYE','YEBN','WHGY','GYBN','WHPK','PKBN'], 'IEC': ['BN', 'RD', 'OG', 'YE', 'GN', 'BU', 'VT', 'GY', 'WH', 'BK'], diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index b06d40e..65ccdb3 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + from typing import List