Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make connecting components together easier and more flexible #75

Merged
merged 4 commits into from
Jul 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/demo02.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ templates: # defining templates to be used later on
gauge: 0.14 mm2
colors: [BK, RD, YE, GN]

ferrules:
ferrule_crimp:
type: Crimp ferrule
subtype: 0.25 mm²

connectors:
X1:
<<: *molex_f # copying items from the template
Expand All @@ -27,6 +22,11 @@ connectors:
X4:
<<: *molex_f
pinout: [GND, +12V, MISO, MOSI, SCK]
ferrule_crimp:
category: ferrule
autogenerate: true
type: Crimp ferrule
subtype: 0.25 mm²

cables:
W1:
Expand Down
14 changes: 3 additions & 11 deletions examples/ex04.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
# connectors:
# X1:
# type: D-Sub
# subtype: female
# pincount: 4
# X2:
# type: Molex KK 254
# subtype: female
# pincount: 3

cables:
W1:
gauge: 0.25 mm2
Expand All @@ -17,8 +7,10 @@ cables:
wirecount: 6
category: bundle

ferrules:
connectors:
ferrule_crimp:
category: ferrule
autogenerate: true
type: Crimp ferrule
show_name: false
show_pincount: false
Expand Down
14 changes: 8 additions & 6 deletions src/wireviz/DataClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ class Connector:
show_name: bool = True
show_pincount: bool = True
hide_disconnected_pins: bool = False
autogenerate: bool = False
loops: List[Any] = field(default_factory=list)

def __post_init__(self):
self.ports_left = False
self.ports_right = False
self.loops = []
self.visible_pins = {}

if self.pincount is None:
Expand All @@ -54,11 +55,12 @@ def __post_init__(self):
if len(self.pinnumbers) != len(set(self.pinnumbers)):
raise Exception('Pin numbers are not unique')

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
for loop in self.loops:
# TODO: check that pins to connect actually exist
# TODO: allow using pin labels in addition to pin numbers, just like when defining regular connections
# TODO: include properties of wire used to create the loop
if len(loop) != 2:
raise Exception('Loops must be between exactly two pins!')

def activate_pin(self, pin):
self.visible_pins[pin] = True
Expand Down
4 changes: 0 additions & 4 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ def add_connector(self, 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):

for (name, pin) in zip([from_name, to_name], [from_pin, to_pin]): # check from and to connectors
Expand All @@ -46,7 +43,6 @@ def connect(self, from_name, from_pin, via_name, via_pin, to_name, to_pin):
from_pin = pin
if name == to_name:
to_pin = pin
# TODO: what happens with loops?
if not pin in connector.pinnumbers:
raise Exception(f'{name}:{pin} not found.')

Expand Down
251 changes: 114 additions & 137 deletions src/wireviz/wireviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -90,108 +58,117 @@ 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)

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']:
# find first component (potentially nested inside list or dict)
first_item = connection[0]
if isinstance(first_item, list):
first_item = first_item[0]
elif isinstance(first_item, dict):
first_item = list(first_item.keys())[0]
elif isinstance(first_item, str):
pass

# check which section the first item belongs to
alternating_sections = ['connectors','cables']
for index, section in enumerate(alternating_sections):
if first_item in yaml_data[section]:
expected_index = index
break
else:
raise Exception('Wrong number of connection parameters')
raise Exception('First item not found anywhere.')
expected_index = 1 - expected_index # flip once since it is flipped back at the *beginning* of every loop

# check that all iterable items (lists and dicts) are the same length
# and that they are alternating between connectors and cables/bundles, starting with either
itemcount = None
for item in connection:
expected_index = 1 - expected_index # make sure items alternate between connectors and cables
expected_section = alternating_sections[expected_index]
if isinstance(item, list):
itemcount_new = len(item)
for subitem in item:
if not subitem in yaml_data[expected_section]:
raise Exception(f'{subitem} is not in {expected_section}')
elif isinstance(item, dict):
if len(item.keys()) != 1:
raise Exception('Dicts may contain only one key here!')
itemcount_new = len(expand(list(item.values())[0]))
subitem = list(item.keys())[0]
if not subitem in yaml_data[expected_section]:
raise Exception(f'{subitem} is not in {expected_section}')
elif isinstance(item, str):
if not item in yaml_data[expected_section]:
raise Exception(f'{item} is not in {expected_section}')
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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the responsibility of the user to make sure it's really a single-pin component. If not, the code doesn't care and simply assumes you want to connect pin 1. Same for the next elif.

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)
else:
raise Exception('Unexpected item in connection list')

# actually connect components 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)
Expand Down
Loading