From b4791900f2af2efbf628a973ff816d5c447bffa8 Mon Sep 17 00:00:00 2001 From: Daniel Rojas Date: Thu, 9 Jul 2020 21:26:27 +0200 Subject: [PATCH] Make connecting components together easier and more flexible Closes #67. - Allow defining arbitrarily long lists of alternating connectors and cables in a connection set. - Start work towards removing 'ferrules' as special case, merging them with normal connectors - Stramline auto-generation of simple, one pin connectors (ferrules, wire splices, ...) --- src/wireviz/DataClasses.py | 1 + src/wireviz/wireviz.py | 228 +++++++++++++++---------------------- src/wireviz/wv_helper.py | 29 +++++ 3 files changed, 120 insertions(+), 138 deletions(-) diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index 44694174..f481d284 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -24,6 +24,7 @@ class Connector: show_name: bool = True show_pincount: bool = True hide_disconnected_pins: bool = False + autogenerate: bool = False def __post_init__(self): self.ports_left = False diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 465b3899..84fb1821 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -13,6 +13,7 @@ from wireviz.Harness import Harness +from wireviz.wv_helper import expand def parse(yaml_input, file_out=None, generate_bom=False, return_types: (None, str, Tuple[str]) = None): @@ -31,40 +32,6 @@ def parse(yaml_input, file_out=None, generate_bom=False, return_types: (None, st yaml_data = yaml.safe_load(yaml_input) - def expand(yaml_data): - # yaml_data can be: - # - a singleton (normally str or int) - # - a list of str or int - # 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] - for e in yaml_data: - e = str(e) - if '-' in e: # list of pins - a, b = tuple(map(int, e.split('-'))) - if a < b: - for x in range(a, b + 1): - output.append(x) - elif a > b: - for x in range(a, b - 1, -1): - output.append(x) - elif a == b: - output.append(a) - else: - try: - x = int(e) - except Exception: - x = e - output.append(x) - return output - - def check_designators(what, where): - for i, x in enumerate(what): - if x not in yaml_data[where[i]]: - return False - return True - harness = Harness() # add items @@ -74,11 +41,12 @@ def check_designators(what, where): if sec in yaml_data and type(yaml_data[sec]) == ty: if len(yaml_data[sec]) > 0: if ty == dict: - for key, o in yaml_data[sec].items(): + for key, attribs in yaml_data[sec].items(): if sec == 'connectors': - harness.add_connector(name=key, **o) + if not attribs.get('autogenerate', False): + harness.add_connector(name=key, **attribs) elif sec == 'cables': - harness.add_cable(name=key, **o) + harness.add_cable(name=key, **attribs) elif sec == 'ferrules': pass else: @@ -90,108 +58,92 @@ def check_designators(what, where): yaml_data[sec] = [] # add connections - ferrule_counter = 0 - for connections in yaml_data['connections']: - if len(connections) == 3: # format: connector -- cable -- connector - - 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(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]) - raise Exception('Bad connection definition (3)') - - 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): - harness.connect(from_name, from_pin, via_name, via_pin, to_name, to_pin) - - elif len(connections) == 2: - - 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(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(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')) - - 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(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): - raise Exception('List length mismatch') - - if con_cbl or cbl_con: - for (from_pin, to_pin) in zip(from_pins, to_pins): - if con_cbl: - 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(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): - harness.loop(cocon_coname, from_pin, to_pin) - if fer_cbl or cbl_fer: - from_pins = expand(connections[0][from_name]) - to_pins = expand(connections[1][to_name]) - - if fer_cbl: - ferrule_name = from_name - cable_name = to_name - cable_pins = to_pins - else: - ferrule_name = to_name - cable_name = from_name - cable_pins = from_pins - - ferrule_params = yaml_data['ferrules'][ferrule_name] - for cable_pin in cable_pins: - ferrule_counter = ferrule_counter + 1 - ferrule_id = f'_F{ferrule_counter}' - harness.add_connector(ferrule_id, category='ferrule', **ferrule_params) - - if fer_cbl: - harness.connect(ferrule_id, 1, cable_name, cable_pin, None, None) - else: - harness.connect(None, None, cable_name, cable_pin, ferrule_id, 1) - else: - raise Exception('Wrong number of connection parameters') + def check_designators(what, where): # helper function + for i, x in enumerate(what): + if x not in yaml_data[where[i]]: + return False + return True + + autogenerated_ids = {} + for connection in yaml_data['connections']: + # TODO: check that items are of alternating type CONNECTOR/FERRULE/FERRULE_LIST and CABLE/WIRE + # TODO: special case: loops! + + # check that all iterable items (lists and dicts) are the same length + itemcount = None + for item in connection: + if isinstance(item, list): + itemcount_new = len(item) + elif isinstance(item, dict): + if len(item.keys()) != 1: + raise Exception('Dicts may contain only one item here!') + itemcount_new = len(expand(list(item.values())[0])) + elif isinstance(item, str): + continue + if itemcount is not None and itemcount_new != itemcount: + raise Exception('All lists and dict lists must be the same length!') + itemcount = itemcount_new + if itemcount is None: + raise Exception('No item revealed the number of connections to make!') + + # populate connection list + connection_list = [] + for i, item in enumerate(connection): + if isinstance(item, str): # one single-pin component was specified + sublist = [] + for i in range(1, itemcount + 1): + if yaml_data['connectors'][item].get('autogenerate'): + autogenerated_ids[item] = autogenerated_ids.get(item, 0) + 1 + new_id = f'_{item}_{autogenerated_ids[item]}' + harness.add_connector(new_id, **yaml_data['connectors'][item]) + sublist.append([new_id, 1]) + else: + sublist.append([item, 1]) + connection_list.append(sublist) + elif isinstance(item, list): # a list of single-pin components were specified + sublist = [] + for subitem in item: + if yaml_data['connectors'][subitem].get('autogenerate'): + autogenerated_ids[subitem] = autogenerated_ids.get(subitem, 0) + 1 + new_id = f'_{subitem}_{autogenerated_ids[subitem]}' + harness.add_connector(new_id, **yaml_data['connectors'][subitem]) + sublist.append([new_id, 1]) + else: + sublist.append([subitem, 1]) + connection_list.append(sublist) + elif isinstance(item, dict): # a component with multiple pins was specified + sublist = [] + id = list(item.keys())[0] + pins = expand(list(item.values())[0]) + for pin in pins: + sublist.append([id, pin]) + connection_list.append(sublist) + elif False: # TODO: placeholer; a loop inside a connector was specified + pass + else: + raise Exception('Unexpected item in connection list') + + # actually connect things using connection list + for i, item in enumerate(connection_list): + id = item[0][0] # TODO: make more elegant/robust/pythonic + if id in harness.cables: + for j, con in enumerate(item): + if i == 0: # list started with a cable, no connector to join on left side + from_name = None + from_pin = None + else: + from_name = connection_list[i-1][j][0] + from_pin = connection_list[i-1][j][1] + via_name = item[j][0] + via_pin = item[j][1] + if i == len(connection_list) - 1: # list ends with a cable, no connector to join on right side + to_name = None + to_pin = None + else: + to_name = connection_list[i+1][j][0] + to_pin = connection_list[i+1][j][1] + harness.connect(from_name, from_pin, via_name, via_pin, to_name, to_pin) if file_out is not None: harness.output(filename=file_out, fmt=('png', 'svg'), gen_bom=generate_bom, view=False) diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 83ee46ee..9c19a5e4 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -59,6 +59,35 @@ def nested_html_table(rows): return html +def expand(yaml_data): + # yaml_data can be: + # - a singleton (normally str or int) + # - a list of str or int + # 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] + for e in yaml_data: + e = str(e) + if '-' in e: # list of pins + a, b = tuple(map(int, e.split('-'))) + if a < b: + for x in range(a, b + 1): + output.append(x) + elif a > b: + for x in range(a, b - 1, -1): + output.append(x) + elif a == b: + output.append(a) + else: + try: + x = int(e) + except Exception: + x = e + output.append(x) + return output + + def int2tuple(inp): if isinstance(inp, tuple): output = inp