Refactor functions for harness building

- Use pin names instead of pin indices, until the last moment when generating the ports for the GraphViz nodes
- `Harness.add_mate_pin()` now uses pin names
- Remove unused `if is_arrow()` check from `Harness.connect()`
- Consolidate calling of `Connector.activate_pin()` to prevent subtle bugs
  - Call it from `connect()` and `add_mate_pin()`
  - No longer call it from `create_graph()`
- Misc. other tuning
This commit is contained in:
Daniel Rojas 2021-10-16 21:46:31 +02:00
parent f0b63de3c7
commit 6b1e274d57
2 changed files with 52 additions and 52 deletions

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from enum import Enum, auto
from typing import Dict, List, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
from dataclasses import dataclass, field, InitVar from dataclasses import dataclass, field, InitVar
from pathlib import Path from pathlib import Path
@ -23,11 +24,17 @@ ImageScale = PlainText # = Literal['false', 'true', 'width', 'height', 'both']
Pin = Union[int, PlainText] # Pin identifier Pin = Union[int, PlainText] # Pin identifier
PinIndex = int # Zero-based pin index PinIndex = int # Zero-based pin index
Wire = Union[int, PlainText] # Wire number or Literal['s'] for shield Wire = Union[int, PlainText] # Wire number or Literal['s'] for shield
NoneOrMorePins = Union[Pin, Tuple[Pin, ...], None] # None, one, or a tuple of pin identifiers
NoneOrMorePinIndices = Union[PinIndex, Tuple[PinIndex, ...], None] # None, one, or a tuple of zero-based pin indices NoneOrMorePinIndices = Union[PinIndex, Tuple[PinIndex, ...], None] # None, one, or a tuple of zero-based pin indices
OneOrMoreWires = Union[Wire, Tuple[Wire, ...]] # One or a tuple of wires OneOrMoreWires = Union[Wire, Tuple[Wire, ...]] # One or a tuple of wires
# Metadata can contain whatever is needed by the HTML generation/template. # Metadata can contain whatever is needed by the HTML generation/template.
MetadataKeys = PlainText # Literal['title', 'description', 'notes', ...] MetadataKeys = PlainText # Literal['title', 'description', 'notes', ...]
class Side(Enum):
LEFT = auto()
RIGHT = auto()
class Metadata(dict): class Metadata(dict):
pass pass
@ -188,8 +195,12 @@ class Connector:
if isinstance(item, dict): if isinstance(item, dict):
self.additional_components[i] = AdditionalComponent(**item) self.additional_components[i] = AdditionalComponent(**item)
def activate_pin(self, pin: Pin) -> None: def activate_pin(self, pin: Pin, side: Side) -> None:
self.visible_pins[pin] = True self.visible_pins[pin] = True
if side == Side.LEFT:
self.ports_left = True
elif side == Side.RIGHT:
self.ports_right = True
def get_qty_multiplier(self, qty_multiplier: Optional[ConnectorMultiplier]) -> int: def get_qty_multiplier(self, qty_multiplier: Optional[ConnectorMultiplier]) -> int:
if not qty_multiplier: if not qty_multiplier:
@ -349,17 +360,17 @@ class Cable:
@dataclass @dataclass
class Connection: class Connection:
from_name: Optional[Designator] from_name: Optional[Designator]
from_port: Optional[PinIndex] from_pin: Optional[Pin]
via_port: Wire via_port: Wire
to_name: Optional[Designator] to_name: Optional[Designator]
to_port: Optional[PinIndex] to_pin: Optional[Pin]
@dataclass @dataclass
class MatePin: class MatePin:
from_name: Designator from_name: Designator
from_port: PinIndex from_pin: Pin
to_name: Designator to_name: Designator
to_port: PinIndex to_pin: Pin
shape: str shape: str
@dataclass @dataclass

View File

@ -9,7 +9,7 @@ from itertools import zip_longest
import re import re
from wireviz import wv_colors, __version__, APP_NAME, APP_URL from wireviz import wv_colors, __version__, APP_NAME, APP_URL
from wireviz.DataClasses import Cable, Connector, MatePin, MateComponent, Metadata, Options, Tweak from wireviz.DataClasses import Cable, Connector, MatePin, MateComponent, Metadata, Options, Tweak, Side
from wireviz.wv_colors import get_color_hex, translate_color from wireviz.wv_colors import get_color_hex, translate_color
from wireviz.wv_gv_html import nested_html_table, \ from wireviz.wv_gv_html import nested_html_table, \
html_bgcolor_attr, html_bgcolor, html_colorbar, \ html_bgcolor_attr, html_bgcolor, html_colorbar, \
@ -41,9 +41,9 @@ class Harness:
self.cables[name] = Cable(name, *args, **kwargs) self.cables[name] = Cable(name, *args, **kwargs)
def add_mate_pin(self, from_name, from_pin, to_name, to_pin, arrow_type) -> None: def add_mate_pin(self, from_name, from_pin, to_name, to_pin, arrow_type) -> None:
from_pin_id = self.connectors[from_name].pins.index(from_pin) self.mates.append(MatePin(from_name, from_pin, to_name, to_pin, arrow_type))
to_pin_id = self.connectors[to_name].pins.index(to_pin) self.connectors[from_name].activate_pin(from_pin, Side.RIGHT)
self.mates.append(MatePin(from_name, from_pin_id, to_name, to_pin_id, arrow_type)) self.connectors[to_name].activate_pin(to_pin, Side.LEFT)
def add_mate_component(self, from_name, to_name, arrow_type) -> None: def add_mate_component(self, from_name, to_name, arrow_type) -> None:
self.mates.append(MateComponent(from_name, to_name, arrow_type)) self.mates.append(MateComponent(from_name, to_name, arrow_type))
@ -74,12 +74,7 @@ class Harness:
raise Exception(f'{name}:{pin} not found.') raise Exception(f'{name}:{pin} not found.')
# check via cable # check via cable
if is_arrow(via_name): if via_name in self.cables:
if '-' in via_name:
self.mates[(from_name, from_pin, to_name, to_pin)] = via_name
elif '=' in via_name:
self.mates[(from_name, to_name)] = via_name
elif via_name in self.cables:
cable = self.cables[via_name] cable = self.cables[via_name]
# check if provided name is ambiguous # check if provided name is ambiguous
if via_wire in cable.colors and via_wire in cable.wirelabels: if via_wire in cable.colors and via_wire in cable.wirelabels:
@ -95,14 +90,12 @@ class Harness:
raise Exception(f'{via_name}:{via_wire} is used for more than one wire.') raise Exception(f'{via_name}:{via_wire} is used for more than one wire.')
via_wire = cable.wirelabels.index(via_wire) + 1 # list index starts at 0, wire IDs start at 1 via_wire = cable.wirelabels.index(via_wire) + 1 # list index starts at 0, wire IDs start at 1
from_pin_id = self.connectors[from_name].pins.index(from_pin) if from_pin is not None else None # perform the actual connection
to_pin_id = self.connectors[to_name].pins.index(to_pin) if to_pin is not None else None self.cables[via_name].connect(from_name, from_pin, via_wire, to_name, to_pin)
self.cables[via_name].connect(from_name, from_pin_id, via_wire, to_name, to_pin_id)
if from_name in self.connectors: if from_name in self.connectors:
self.connectors[from_name].activate_pin(from_pin) self.connectors[from_name].activate_pin(from_pin, Side.RIGHT)
if to_name in self.connectors: if to_name in self.connectors:
self.connectors[to_name].activate_pin(to_pin) self.connectors[to_name].activate_pin(to_pin, Side.LEFT)
def create_graph(self) -> Graph: def create_graph(self) -> Graph:
dot = Graph() dot = Graph()
@ -122,23 +115,6 @@ class Harness:
dot.attr('edge', style='bold', dot.attr('edge', style='bold',
fontname=self.options.fontname) fontname=self.options.fontname)
# prepare ports on connectors depending on which side they will connect
for _, cable in self.cables.items():
for connection_color in cable.connections:
if connection_color.from_port is not None: # connect to left
self.connectors[connection_color.from_name].ports_right = True
self.connectors[connection_color.from_name].activate_pin(connection_color.from_port)
if connection_color.to_port is not None: # connect to right
self.connectors[connection_color.to_name].ports_left = True
self.connectors[connection_color.to_name].activate_pin(connection_color.to_port)
for mate in self.mates:
if isinstance(mate, MatePin):
self.connectors[mate.from_name].ports_right = True
self.connectors[mate.from_name].activate_pin(mate.from_port)
self.connectors[mate.to_name].ports_left = True
self.connectors[mate.to_name].activate_pin(mate.to_port)
for connector in self.connectors.values(): for connector in self.connectors.values():
# If no wires connected (except maybe loop wires)? # If no wires connected (except maybe loop wires)?
@ -341,32 +317,34 @@ class Harness:
else: # it's a shield connection else: # it's a shield connection
# shield is shown with specified color and black borders, or as a thin black wire otherwise # shield is shown with specified color and black borders, or as a thin black wire otherwise
dot.attr('edge', color=':'.join(['#000000', shield_color_hex, '#000000']) if isinstance(cable.shield, str) else '#000000') dot.attr('edge', color=':'.join(['#000000', shield_color_hex, '#000000']) if isinstance(cable.shield, str) else '#000000')
if connection.from_port is not None: # connect to left if connection.from_pin is not None: # connect to left
from_connector = self.connectors[connection.from_name] from_connector = self.connectors[connection.from_name]
from_port = f':p{connection.from_port+1}r' if from_connector.style != 'simple' else '' from_pin_index = from_connector.pins.index(connection.from_pin)
code_left_1 = f'{connection.from_name}{from_port}:e' from_port_str = f':p{from_pin_index+1}r' if from_connector.style != 'simple' else ''
code_left_1 = f'{connection.from_name}{from_port_str}:e'
code_left_2 = f'{cable.name}:w{connection.via_port}:w' code_left_2 = f'{cable.name}:w{connection.via_port}:w'
dot.edge(code_left_1, code_left_2) dot.edge(code_left_1, code_left_2)
if from_connector.show_name: if from_connector.show_name:
from_info = [str(connection.from_name), str(self.connectors[connection.from_name].pins[connection.from_port])] from_info = [str(connection.from_name), str(connection.from_pin)]
if from_connector.pinlabels: if from_connector.pinlabels:
pinlabel = from_connector.pinlabels[connection.from_port] pinlabel = from_connector.pinlabels[from_pin_index]
if pinlabel != '': if pinlabel != '':
from_info.append(pinlabel) from_info.append(pinlabel)
from_string = ':'.join(from_info) from_string = ':'.join(from_info)
else: else:
from_string = '' from_string = ''
html = [row.replace(f'<!-- {connection.via_port}_in -->', from_string) for row in html] html = [row.replace(f'<!-- {connection.via_port}_in -->', from_string) for row in html]
if connection.to_port is not None: # connect to right if connection.to_pin is not None: # connect to right
to_connector = self.connectors[connection.to_name] to_connector = self.connectors[connection.to_name]
to_pin_index = to_connector.pins.index(connection.to_pin)
to_port_str = f':p{to_pin_index+1}l' if to_connector.style != 'simple' else ''
code_right_1 = f'{cable.name}:w{connection.via_port}:e' code_right_1 = f'{cable.name}:w{connection.via_port}:e'
to_port = f':p{connection.to_port+1}l' if self.connectors[connection.to_name].style != 'simple' else '' code_right_2 = f'{connection.to_name}{to_port_str}:w'
code_right_2 = f'{connection.to_name}{to_port}:w'
dot.edge(code_right_1, code_right_2) dot.edge(code_right_1, code_right_2)
if to_connector.show_name: if to_connector.show_name:
to_info = [str(connection.to_name), str(self.connectors[connection.to_name].pins[connection.to_port])] to_info = [str(connection.to_name), str(connection.to_pin)]
if to_connector.pinlabels: if to_connector.pinlabels:
pinlabel = to_connector.pinlabels[connection.to_port] pinlabel = to_connector.pinlabels[to_pin_index]
if pinlabel != '': if pinlabel != '':
to_info.append(pinlabel) to_info.append(pinlabel)
to_string = ':'.join(to_info) to_string = ':'.join(to_info)
@ -448,11 +426,22 @@ class Harness:
else: else:
raise Exception(f'{mate} is an unknown mate') raise Exception(f'{mate} is an unknown mate')
from_connector = self.connectors[mate.from_name]
if isinstance(mate, MatePin) and self.connectors[mate.from_name].style != 'simple':
from_pin_index = from_connector.pins.index(mate.from_pin)
from_port_str = f':p{from_pin_index+1}r'
else: # MateComponent or style == 'simple'
from_port_str = ''
if isinstance(mate, MatePin) and self.connectors[mate.to_name].style != 'simple':
to_pin_index = to_connector.pins.index(mate.to_pin)
to_port_str = f':p{to_pin_index+1}l' if isinstance(mate, MatePin) and self.connectors[mate.to_name].style != 'simple' else ''
else: # MateComponent or style == 'simple'
to_port_str = ''
code_from = f'{mate.from_name}{from_port_str}:e'
to_connector = self.connectors[mate.to_name]
code_to = f'{mate.to_name}{to_port_str}:w'
dot.attr('edge', color=color, style='dashed', dir=dir) dot.attr('edge', color=color, style='dashed', dir=dir)
from_port = f':p{mate.from_port+1}r' if isinstance(mate, MatePin) and self.connectors[mate.from_name].style != 'simple' else ''
code_from = f'{mate.from_name}{from_port}:e'
to_port = f':p{mate.to_port+1}l' if isinstance(mate, MatePin) and self.connectors[mate.to_name].style != 'simple' else ''
code_to = f'{mate.to_name}{to_port}:w'
dot.edge(code_from, code_to) dot.edge(code_from, code_to)
return dot return dot