diff --git a/examples/bundles.yml b/examples/bundles.yml new file mode 100644 index 0000000..052781d --- /dev/null +++ b/examples/bundles.yml @@ -0,0 +1,64 @@ +templates: # defining templates to be used later on + - &molex_f + type: Molex KK 254 + gender: female + - &con_i2c + pinout: [GND, +5V, SCL, SDA] + - &wire_i2c + mm2: 0.14 + length: 0.2 + colors: [BK, RD, YE, GN] + +nodes: + X1: + <<: *molex_f # copying items from the template + pinout: [GND, +5V, SCL, SDA, MISO, MOSI, SCK, N/C] + X2: + <<: *molex_f + <<: *con_i2c # it is possible to copy from more than one template + X3: + <<: *molex_f + <<: *con_i2c + X4: + <<: *molex_f + pinout: [GND, +12V, MISO, MOSI, SCK] + X5: + type: Molex Micro-Fit + gender: male + pinout: [GND, +12V] + +wires: + W1: + <<: *wire_i2c + type: bundle + W2: + <<: *wire_i2c + type: bundle + W3: + mm2: 0.14 + length: 0.2 + type: bundle + colors: [BK, BU, OG, VT] + W4: + mm2: 0.5 + length: 0.35 + colors: [BK, RD] + type: bundle + +connections: + - + - X1: [1-4] + - W1: [1-4] + - X2: [1-4] + - + - X1: [1-4] + - W2: [1-4] + - X3: [1-4] + - + - X1: [1,5-7] + - W3: [1-4] + - X4: [1,3-5] + - + - X5: [1,2] + - W4: [1,2] + - X4: [1,2] diff --git a/examples/example2.yml b/examples/example2.yml index 54adfaf..494c2b9 100644 --- a/examples/example2.yml +++ b/examples/example2.yml @@ -21,7 +21,7 @@ nodes: <<: *con_i2c X4: <<: *molex_f - pinout: [GND, +12V, MISO, MOSI, flachstecker] + pinout: [GND, +12V, MISO, MOSI, SCK] X5: type: Molex Micro-Fit gender: male @@ -30,16 +30,20 @@ nodes: wires: W1: <<: *wire_i2c + show_name: false W2: <<: *wire_i2c + show_name: false W3: mm2: 0.14 length: 0.2 colors: [BK, BU, OG, VT] + show_name: false W4: mm2: 0.5 length: 0.35 colors: [BK, RD] + show_name: false connections: - @@ -58,28 +62,3 @@ connections: - X5: [1,2] - W4: [1,2] - X4: [1,2] - -# - -# - X1: 1 -# - W1: 1 -# - X2: 1 -# - -# - X1: [2,3,4] -# - W1: [2,3,4] -# - X2: [4,3,2] -# - -# - X1: [5-10] -# - W1: [5-7,10,9,8] -# - X2: [10-5] -# - -# - X1: 11 -# - W1: s -# - -# - X1: [1-5] -# - W1: [11-15] -# - -# - W1: [12-15] -# - X2: [2-5] -# - -# - X1: [12,14] -# - X1: [13,15] diff --git a/examples/ferrules.yml b/examples/ferrules.yml index 9fb8319..8a88f62 100644 --- a/examples/ferrules.yml +++ b/examples/ferrules.yml @@ -11,10 +11,11 @@ nodes: wires: W1: mm2: 0.25 + show_equiv: true length: 0.2 color_code: IEC num_wires: 10 - shield: true + type: bundle ferrules: F_test: @@ -25,9 +26,6 @@ connections: - X1: [1-3] - W1: [1-3] - X2: [1-3] - - - - X1: 4 - - W1: s - - F_test - W1: [4-10] diff --git a/src/wireviz.py b/src/wireviz.py index fa47aea..63d803c 100644 --- a/src/wireviz.py +++ b/src/wireviz.py @@ -6,7 +6,6 @@ COLOR_CODES = {'DIN': ['WH','BN','GN','YE','GY','PK','BU','RD','BK','VT'], # ,'G 'IEC': ['BN','RD','OG','YE','GN','BU','VT','GY','WH','BK'], 'BW': ['BK','WH']} -# TODO: parse and render double-colored cables ('RDBU' etc) color_hex = { 'BK': '#000000', 'WH': '#ffffff', @@ -15,7 +14,7 @@ color_hex = { 'RD': '#ff0000', 'OG': '#ff8000', 'YE': '#ffff00', - 'GN': '#009900', + 'GN': '#00ff00', 'TQ': '#00ffff', 'BU': '#0066ff', 'VT': '#8000ff', @@ -79,17 +78,25 @@ class Harness: 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='transparent', fontname=font) - dot.attr('node', shape='record', style='rounded,filled', fillcolor='white', fontname=font) - dot.attr('edge', style='bold', fontname=font) + dot.attr('graph', rankdir='LR', + ranksep='2', + bgcolor='transparent', + 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[1] is not None: # connect to left - self.nodes[x[0]].ports_right = True - if x[4] is not None: # connect to right - self.nodes[x[3]].ports_left = True + if x.from_port is not None: # connect to left + self.nodes[x.from_name].ports_right = True + if x.to_port is not None: # connect to right + self.nodes[x.to_name].ports_left = True for k, n in self.nodes.items(): # a = attributes @@ -118,9 +125,9 @@ class Harness: loop_dir = 'e' else: raise Exception('No side for loops') - for x in n.loops: - dot.edge('{name}:p{port_from}{loop_side}:{loop_dir}'.format(name=n.name, port_from=x[0], port_to=x[1], loop_side=loop_side, loop_dir=loop_dir), - '{name}:p{port_to}{loop_side}:{loop_dir}'.format(name=n.name, port_from=x[0], port_to=x[1], loop_side=loop_side, loop_dir=loop_dir)) + 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 @@ -147,26 +154,69 @@ class Harness: p[1].append('Shield') # l = label l = [c.name if c.show_name else '', a, p] - dot.node(k, label=nested(l)) + if c.type == 'bundle': + # create subgraph for wire bundle, add to main graph afterwards + bun = Graph(name='cluster_{}'.format(k)) + labeltext = ' | '.join(p for p in a if p) + '\n ' # newline to add space between label and wires + bun.attr('graph', label=labeltext, + style='filled, dashed', + fillcolor='white') + bun.attr('node', shape='point', + label='', + fixedsize='true', + width='0', height='0') + for i, x in enumerate(c.colors,1): + bun.node('{}_w{}l'.format(k,i)) + bun.node('{}_w{}r'.format(k,i)) + else: + dot.node(k, label=nested(l)) + + # add bundle subgraph to main graph + if c.type == 'bundle': + dot.subgraph(bun) # connections + existing_connections = [] # for bundles, avoid multiple edges between a bundle's wire's start and end node for x in c.connections: - if isinstance(x[2], int): # check if it's an actual wire and not a shield - search_color = c.colors[x[2]-1] + 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 color_hex: dot.attr('edge',color='#000000:{wire_color}:#000000'.format(wire_color=color_hex[search_color])) else: # color name not found dot.attr('edge',color='#000000') else: # it's a shield connection dot.attr('edge',color='#000000') - if x[1] is not None: # connect to left - dot.edge('{from_name}:p{from_port}r'.format(from_name=x[0],from_port=x[1]), - '{via_name}:w{via_wire}{via_subport}'.format(via_name=c.name, via_wire=x[2], via_subport='i' if c.show_pinout else '')) - # self.nodes[x[0]].ports_right = True - if x[4] is not None: # connect to right - dot.edge('{via_name}:w{via_wire}{via_subport}'.format(via_name=c.name, via_wire=x[2], via_subport='o' if c.show_pinout else ''), - '{to_name}:p{to_port}l'.format(to_name=x[3], to_port=x[4])) - # self.nodes[x[3]].ports_left = True + + if c.type == 'bundle': + labeltext = '{sp}{color}'.format(color=translate_color(c.colors[x.via_port-1], self.color_mode), sp=' ' * 35) + if x.via_port not in existing_connections: + dot.edge('{via_name}_w{via_wire}l'.format(via_name=c.name, via_wire=x.via_port), + '{via_name}_w{via_wire}r'.format(via_name=c.name, via_wire=x.via_port), + taillabel=labeltext, + labelangle='60', + labeldist='0') + existing_connections.append(x.via_port) + + if x.from_port is not None: # connect to left + if c.type == 'bundle': + dot.edge('{from_name}:p{from_port}r'.format(from_name=x.from_name, from_port=x.from_port), + '{via_name}_w{via_wire}l:w'.format(via_name=c.name, via_wire=x.via_port), + headlabel='{}{}:{}'.format(' ' * 12, x.from_name, x.from_port), + labelangle='-60', + labeldist='0') + else: + dot.edge('{from_name}:p{from_port}r'.format(from_name=x.from_name, from_port=x.from_port), + '{via_name}:w{via_wire}{via_subport}'.format(via_name=c.name, via_wire=x.via_port, via_subport='i' if c.show_pinout else '')) + if x.to_port is not None: # connect to right + if c.type == 'bundle': + dot.edge('{via_name}_w{via_wire}r:e'.format(via_name=c.name, via_wire=x.via_port), + '{to_name}:p{to_port}l'.format(to_name=x.to_name, to_port=x.to_port), + taillabel='{}:{}{}'.format(x.to_name, x.to_port,' ' * 12), + labelangle='60', + labeldist='0') + else: + dot.edge('{via_name}:w{via_wire}{via_subport}'.format(via_name=c.name, via_wire=x.via_port, via_subport='o' if c.show_pinout else ''), + '{to_name}:p{to_port}l'.format(to_name=x.to_name, to_port=x.to_port)) return dot @@ -184,8 +234,8 @@ class Node: gender: str = None num_pins: int = None pinout: List[Any] = field(default_factory=list) - show_name: bool = False - show_num_pins: bool = False + show_name: bool = True + show_num_pins: bool = True def __post_init__(self): self.ports_left = False @@ -206,6 +256,7 @@ class Node: @dataclass class Cable: name: str + type: str = None mm2: float = None awg: int = None show_equiv: bool = False @@ -214,7 +265,7 @@ class Cable: shield: bool = False colors: List[Any] = field(default_factory=list) color_code: str = None - show_name: bool = False + show_name: bool = True show_pinout: bool = False show_num_wires: bool = True @@ -252,11 +303,20 @@ class Cable: 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((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])) def connect_all_straight(self, from_name, to_name): self.connect(from_name, 'auto', 'auto', to_name, 'auto') +@dataclass +class Connection: + from_name: Any + from_port: Any + via_port: Any + to_name: Any + to_port: Any + def nested(input): l = [] for x in input: @@ -286,7 +346,7 @@ def translate_color(input, color_mode): if color_mode == 'full': output = color_full[input].lower() elif color_mode == 'FULL': - output = color_hex[input].upper() + output = color_full[input].upper() elif color_mode == 'hex': output = color_hex[input].lower() elif color_mode == 'HEX': diff --git a/src/yaml2wireviz.py b/src/yaml2wireviz.py index b922d4e..2275e3c 100644 --- a/src/yaml2wireviz.py +++ b/src/yaml2wireviz.py @@ -4,6 +4,7 @@ import wireviz filename = '../examples/example1.yml' filename = '../examples/example2.yml' filename = '../examples/ferrules.yml' +filename = '../examples/bundles.yml' def check_designators(what, where): for i, x in enumerate(what): diff --git a/todo.md b/todo.md index f27e892..2bb0277 100644 --- a/todo.md +++ b/todo.md @@ -1,28 +1,58 @@ # To-do: -* Set global parameters (show_pins, ...) and allow override on per-item basis +## Support for more connector types + * Generic connectors - * ferrules - * blade terminals - * loose ends - * graphical representation? -* Support for cable splicing (as connector type) -* new wire look? - * distinguish between cables and wire bundles -* improve nomenclature + * Ferrules + * Blade terminals + * Loose ends / stubs + * Graphical representation? +* Inline connectors (IDC) + * Possibly join two logical wires into one physical wire, add up length for BOM creation + * Designators like W1_1, W1_2 or similar to group them? + +## Support for more wire types + +* Coax cables + * Graphical representation +* Twisted pairs + * Logical representation + * Graphical representation +* Ribbon cables + * Folds + * Splits + * Orientation of IDC connectors + +## Support for more links/connections + +* Cable splicing + * as pseudo-connector? +* Heatshrink / sheathing + +## Visualization + +* Parse and render double-colored, striped cables ('RDBU' etc) +* Show from/to inside wire node (better netlist) + * Implemented in wire bundles only +* Display picture of connector underneath (including pin 1 location) + +## Export + +* Export to PDF with frame, title block, ... +* Automatic BOM generation + +## Other + +* Set global parameters (show_pins, ...) and allow override on per-item basis +* Improve nomenclature * terminal (connector, ferrule, blade, loose) * link (cable, wire bundle) -* show from/to inside wire node * Allow custom GraphViz code before/after WireViz-generated code -* Display picture of connector underneath (including pin 1 location) -* export to PDF with frame, title block, ... -* Automatic BOM generation -* Allow -* make "unit tests" for different features/situations - * missing parameters - * connection formats +* Make "unit tests" for different features/situations + * Missing parameters + * Connection formats * single wire 1 * multiple wires [1,2,3] * wire ranges [1-10] - * loops + * Loops * ...