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, ...)
This commit is contained in:
parent
8f5b1aaf16
commit
b4791900f2
@ -24,6 +24,7 @@ class Connector:
|
|||||||
show_name: bool = True
|
show_name: bool = True
|
||||||
show_pincount: bool = True
|
show_pincount: bool = True
|
||||||
hide_disconnected_pins: bool = False
|
hide_disconnected_pins: bool = False
|
||||||
|
autogenerate: bool = False
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.ports_left = False
|
self.ports_left = False
|
||||||
|
|||||||
@ -13,6 +13,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
|
|
||||||
from wireviz.Harness import Harness
|
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):
|
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)
|
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()
|
harness = Harness()
|
||||||
|
|
||||||
# add items
|
# add items
|
||||||
@ -74,11 +41,12 @@ def parse(yaml_input, file_out=None, generate_bom=False, return_types: (None, st
|
|||||||
if sec in yaml_data and type(yaml_data[sec]) == ty:
|
if sec in yaml_data and type(yaml_data[sec]) == ty:
|
||||||
if len(yaml_data[sec]) > 0:
|
if len(yaml_data[sec]) > 0:
|
||||||
if ty == dict:
|
if ty == dict:
|
||||||
for key, o in yaml_data[sec].items():
|
for key, attribs in yaml_data[sec].items():
|
||||||
if sec == 'connectors':
|
if sec == 'connectors':
|
||||||
harness.add_connector(name=key, **o)
|
if not attribs.get('autogenerate', False):
|
||||||
|
harness.add_connector(name=key, **attribs)
|
||||||
elif sec == 'cables':
|
elif sec == 'cables':
|
||||||
harness.add_cable(name=key, **o)
|
harness.add_cable(name=key, **attribs)
|
||||||
elif sec == 'ferrules':
|
elif sec == 'ferrules':
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -90,108 +58,92 @@ def parse(yaml_input, file_out=None, generate_bom=False, return_types: (None, st
|
|||||||
yaml_data[sec] = []
|
yaml_data[sec] = []
|
||||||
|
|
||||||
# add connections
|
# add connections
|
||||||
ferrule_counter = 0
|
|
||||||
for connections in yaml_data['connections']:
|
|
||||||
if len(connections) == 3: # format: connector -- cable -- connector
|
|
||||||
|
|
||||||
for connection in connections:
|
def check_designators(what, where): # helper function
|
||||||
if len(list(connection.keys())) != 1: # check that each entry in con has only one key, which is the designator
|
for i, x in enumerate(what):
|
||||||
raise Exception('Too many keys')
|
if x not in yaml_data[where[i]]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
from_name = list(connections[0].keys())[0]
|
autogenerated_ids = {}
|
||||||
via_name = list(connections[1].keys())[0]
|
for connection in yaml_data['connections']:
|
||||||
to_name = list(connections[2].keys())[0]
|
# TODO: check that items are of alternating type CONNECTOR/FERRULE/FERRULE_LIST and CABLE/WIRE
|
||||||
|
# TODO: special case: loops!
|
||||||
|
|
||||||
if not check_designators([from_name, via_name, to_name], ('connectors', 'cables', 'connectors')):
|
# check that all iterable items (lists and dicts) are the same length
|
||||||
print([from_name, via_name, to_name])
|
itemcount = None
|
||||||
raise Exception('Bad connection definition (3)')
|
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!')
|
||||||
|
|
||||||
from_pins = expand(connections[0][from_name])
|
# populate connection list
|
||||||
via_pins = expand(connections[1][via_name])
|
connection_list = []
|
||||||
to_pins = expand(connections[2][to_name])
|
for i, item in enumerate(connection):
|
||||||
|
if isinstance(item, str): # one single-pin component was specified
|
||||||
if len(from_pins) != len(via_pins) or len(via_pins) != len(to_pins):
|
sublist = []
|
||||||
raise Exception('List length mismatch')
|
for i in range(1, itemcount + 1):
|
||||||
|
if yaml_data['connectors'][item].get('autogenerate'):
|
||||||
for (from_pin, via_pin, to_pin) in zip(from_pins, via_pins, to_pins):
|
autogenerated_ids[item] = autogenerated_ids.get(item, 0) + 1
|
||||||
harness.connect(from_name, from_pin, via_name, via_pin, to_name, to_pin)
|
new_id = f'_{item}_{autogenerated_ids[item]}'
|
||||||
|
harness.add_connector(new_id, **yaml_data['connectors'][item])
|
||||||
elif len(connections) == 2:
|
sublist.append([new_id, 1])
|
||||||
|
|
||||||
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:
|
else:
|
||||||
harness.connect(None, None, cable_name, cable_pin, ferrule_id, 1)
|
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')
|
||||||
|
|
||||||
else:
|
# actually connect things using connection list
|
||||||
raise Exception('Wrong number of connection parameters')
|
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:
|
if file_out is not None:
|
||||||
harness.output(filename=file_out, fmt=('png', 'svg'), gen_bom=generate_bom, view=False)
|
harness.output(filename=file_out, fmt=('png', 'svg'), gen_bom=generate_bom, view=False)
|
||||||
|
|||||||
@ -59,6 +59,35 @@ def nested_html_table(rows):
|
|||||||
return html
|
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):
|
def int2tuple(inp):
|
||||||
if isinstance(inp, tuple):
|
if isinstance(inp, tuple):
|
||||||
output = inp
|
output = inp
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user