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

Feature/gv html refactor #136

Merged
merged 7 commits into from
Aug 13, 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
3 changes: 1 addition & 2 deletions src/wireviz/DataClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class Cable:
gauge_unit: Optional[str] = None
show_equiv: bool = False
length: float = 0
color: Optional[str] = None
wirecount: Optional[int] = None
shield: bool = False
notes: Optional[str] = None
Expand Down Expand Up @@ -149,8 +150,6 @@ def __post_init__(self):
else:
raise Exception('lists of part data are only supported for bundles')

# for BOM generation
self.wirecount_and_shield = (self.wirecount, self.shield)

def connect(self, from_name, from_pin, via_pin, to_name, to_pin):
from_pin = int2tuple(from_pin)
Expand Down
180 changes: 86 additions & 94 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,44 +86,47 @@ def create_graph(self) -> Graph:
if connection_color.to_port is not None: # connect to right
self.connectors[connection_color.to_name].ports_left = True

for key, connector in self.connectors.items():
for connector in self.connectors.values():

html = []

rows = [[connector.name if connector.show_name else None],
[f'P/N: {connector.pn}' if connector.pn else None,
manufacturer_info_field(connector.manufacturer, connector.mpn)],
html_line_breaks(manufacturer_info_field(connector.manufacturer, connector.mpn))],
[html_line_breaks(connector.type),
html_line_breaks(connector.subtype),
f'{connector.pincount}-pin' if connector.show_pincount else None,
connector.color, '<!-- colorbar -->' if connector.color else None],
'<!-- connector table -->' if connector.style != 'simple' else None,
[html_line_breaks(connector.notes)]]
html = nested_html_table(rows)
html.extend(nested_html_table(rows))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider html = nested_html_table(rows) instead, or is there a good reason behind extending an empty list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In this case I find it more understandable to start with an empty list (line 91) and then populate it slowly... a matter of style, nothing else.


if connector.color: # add color bar next to color info, if present
colorbar = f' bgcolor="{wv_colors.translate_color(connector.color, "HEX")}" width="4"></td>' # leave out '<td' from string to preserve any existing attributes of the <td> tag
html = html.replace('><!-- colorbar --></td>', colorbar)
html = [row.replace('><!-- colorbar --></td>', colorbar) for row in html]

if connector.style != 'simple':
pinlist = []
pinhtml = []
pinhtml.append('<table border="0" cellspacing="0" cellpadding="3" cellborder="1">')

for pin, pinlabel in zip(connector.pins, connector.pinlabels):
if connector.hide_disconnected_pins and not connector.visible_pins.get(pin, False):
continue
pinlist.append([f'<td port="p{pin}l">{pin}</td>' if connector.ports_left else None,
f'<td>{pinlabel}</td>' if pinlabel else '',
f'<td port="p{pin}r">{pin}</td>' if connector.ports_right else None])
pinhtml.append(' <tr>')
if connector.ports_left:
pinhtml.append(f' <td port="p{pin}l">{pin}</td>')
if pinlabel:
pinhtml.append(f' <td>{pinlabel}</td>')
if connector.ports_right:
pinhtml.append(f' <td port="p{pin}r">{pin}</td>')
pinhtml.append(' </tr>')

pinhtml = '<table border="0" cellspacing="0" cellpadding="3" cellborder="1">'
for i, pin in enumerate(pinlist):
pinhtml = f'{pinhtml}<tr>'
for column in pin:
if column is not None:
pinhtml = f'{pinhtml}{column}'
pinhtml = f'{pinhtml}</tr>'
pinhtml = f'{pinhtml}</table>'
html = html.replace('<!-- connector table -->', pinhtml)
pinhtml.append(' </table>')

html = [row.replace('<!-- connector table -->', '\n'.join(pinhtml)) for row in html]

dot.node(key, label=f'<{html}>', shape='none', margin='0', style='filled', fillcolor='white')
html = '\n'.join(html)
dot.node(connector.name, label=f'<\n{html}\n>', shape='none', margin='0', style='filled', fillcolor='white')

if len(connector.loops) > 0:
dot.attr('edge', color='#000000:#ffffff:#000000')
Expand All @@ -139,11 +142,14 @@ def create_graph(self) -> Graph:
dot.edge(f'{connector.name}:p{loop[0]}{loop_side}:{loop_dir}',
f'{connector.name}:p{loop[1]}{loop_side}:{loop_dir}')


# determine if there are double- or triple-colored wires in the harness;
# if so, pad single-color wires to make all wires of equal thickness
pad = any(len(colorstr) > 2 for cable in self.cables.values() for colorstr in cable.colors)

for _, cable in self.cables.items():
for cable in self.cables.values():

html = []

formatc1702 marked this conversation as resolved.
Show resolved Hide resolved
awg_fmt = ''
if cable.show_equiv:
Expand All @@ -155,99 +161,84 @@ def create_graph(self) -> Graph:
elif cable.gauge_unit.upper() == 'AWG':
awg_fmt = f' ({mm2_equiv(cable.gauge)} mm\u00B2)'

identification = [f'P/N: {cable.pn}' if (cable.pn and not isinstance(cable.pn, list)) else '',
manufacturer_info_field(cable.manufacturer if not isinstance(cable.manufacturer, list) else None,
cable.mpn if not isinstance(cable.mpn, list) else None)]
identification = list(filter(None, identification))

attributes = [html_line_breaks(cable.type) if cable.type else '',
f'{len(cable.colors)}x' if cable.show_wirecount else '',
f'{cable.gauge} {cable.gauge_unit}{awg_fmt}' if cable.gauge else '',
'+ S' if cable.shield else '',
f'{cable.length} m' if cable.length > 0 else '']
attributes = list(filter(None, attributes))

html = '<table border="0" cellspacing="0" cellpadding="0">' # main table

if cable.show_name or len(attributes) > 0:
html = f'{html}<tr><td><table border="0" cellspacing="0" cellpadding="3" cellborder="1">' # name+attributes table
if cable.show_name:
html = f'{html}<tr><td colspan="{max(len(attributes), 1)}">{cable.name}</td></tr>'
if(len(identification) > 0): # print an identification row if values specified
html = f'{html}<tr><td colspan="{len(attributes)}" cellpadding="0"><table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr>'
for attrib in identification[0:-1]:
html = f'{html}<td sides="R">{attrib}</td>' # all columns except last have a border on the right (sides="R")
if len(identification) > 0:
html = f'{html}<td border="0">{identification[-1]}</td>' # last column has no border on the right because the enclosing table borders it
html = f'{html}</tr></table></td></tr>' # end identification row
if(len(attributes) > 0):
html = f'{html}<tr>' # attribute row
for attrib in attributes:
html = f'{html}<td balign="left">{attrib}</td>'
html = f'{html}</tr>' # attribute row
html = f'{html}</table></td></tr>' # name+attributes table

html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer between attributes and wires

html = f'{html}<tr><td><table border="0" cellspacing="0" cellborder="0">' # conductor table
rows = [[cable.name if cable.show_name else None],
[f'P/N: {cable.pn}' if (cable.pn and not isinstance(cable.pn, list)) else None,
html_line_breaks(manufacturer_info_field(
cable.manufacturer if not isinstance(cable.manufacturer, list) else None,
cable.mpn if not isinstance(cable.mpn, list) else None))],
[html_line_breaks(cable.type),
f'{cable.wirecount}x' if cable.show_wirecount else None,
f'{cable.gauge} {cable.gauge_unit}{awg_fmt}' if cable.gauge else None,
'+ S' if cable.shield else None,
f'{cable.length} m' if cable.length > 0 else None,
cable.color, '<!-- colorbar -->' if cable.color else None],
'<!-- wire table -->',
[html_line_breaks(cable.notes)]]
html.extend(nested_html_table(rows))
formatc1702 marked this conversation as resolved.
Show resolved Hide resolved

if cable.color: # add color bar next to color info, if present
colorbar = f' bgcolor="{wv_colors.translate_color(cable.color, "HEX")}" width="4"></td>' # leave out '<td' from string to preserve any existing attributes of the <td> tag
html = [row.replace('><!-- colorbar --></td>', colorbar) for row in html]

wirehtml = []
wirehtml.append('<table border="0" cellspacing="0" cellborder="0">') # conductor table
wirehtml.append(' <tr><td>&nbsp;</td></tr>')

for i, connection_color in enumerate(cable.colors, 1):
p = []
p.append(f'<!-- {i}_in -->')
p.append(wv_colors.translate_color(connection_color, self.color_mode))
p.append(f'<!-- {i}_out -->')
html = f'{html}<tr>'
for bla in p:
html = f'{html}<td>{bla}</td>'
html = f'{html}</tr>'
wirehtml.append(' <tr>')
wirehtml.append(f' <td><!-- {i}_in --></td>')
wirehtml.append(f' <td>{wv_colors.translate_color(connection_color, self.color_mode)}</td>')
wirehtml.append(f' <td><!-- {i}_out --></td>')
wirehtml.append(' </tr>')

bgcolors = ['#000000'] + get_color_hex(connection_color, pad=pad) + ['#000000']
html = f'{html}<tr><td colspan="{len(p)}" border="0" cellspacing="0" cellpadding="0" port="w{i}" height="{(2 * len(bgcolors))}"><table cellspacing="0" cellborder="0" border = "0">'
wirehtml.append(f' <tr>')
wirehtml.append(f' <td colspan="3" border="0" cellspacing="0" cellpadding="0" port="w{i}" height="{(2 * len(bgcolors))}">')
wirehtml.append(' <table cellspacing="0" cellborder="0" border="0">')
for j, bgcolor in enumerate(bgcolors[::-1]): # Reverse to match the curved wires when more than 2 colors
html = f'{html}<tr><td colspan="{len(p)}" cellpadding="0" height="2" bgcolor="{bgcolor if bgcolor != "" else wv_colors.default_color}" border="0"></td></tr>'
html = html + '</table></td></tr>'
wirehtml.append(f' <tr><td colspan="3" cellpadding="0" height="2" bgcolor="{bgcolor if bgcolor != "" else wv_colors.default_color}" border="0"></td></tr>')
wirehtml.append(' </table>')
wirehtml.append(' </td>')
wirehtml.append(' </tr>')
if(cable.category == 'bundle'): # for bundles individual wires can have part information
# create a list of wire parameters
wireidentification = []
if isinstance(cable.pn, list):
wireidentification.append(f'P/N: {cable.pn[i - 1]}')
manufacturer_info = manufacturer_info_field(cable.manufacturer[i - 1] if isinstance(cable.manufacturer, list) else None,
cable.mpn[i - 1] if isinstance(cable.mpn, list) else None)
manufacturer_info = manufacturer_info_field(
cable.manufacturer[i - 1] if isinstance(cable.manufacturer, list) else None,
cable.mpn[i - 1] if isinstance(cable.mpn, list) else None)
if manufacturer_info:
wireidentification.append(manufacturer_info)
wireidentification.append(html_line_breaks(manufacturer_info))
# print parameters into a table row under the wire
if(len(wireidentification) > 0):
html = f'{html}<tr><td colspan="{len(p)}"><table border="0" cellspacing="0" cellborder="0"><tr>'
wirehtml.append(' <tr><td colspan="3">')
wirehtml.append(' <table border="0" cellspacing="0" cellborder="0"><tr>')
for attrib in wireidentification:
html = f'{html}<td>{attrib}</td>'
html = f'{html}</tr></table></td></tr>'
wirehtml.append(f' <td>{attrib}</td>')
wirehtml.append(' </tr></table>')
wirehtml.append(' </td></tr>')

if cable.shield:
p = ['<!-- s_in -->', 'Shield', '<!-- s_out -->']
html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer
html = f'{html}<tr>'
for bla in p:
html = html + f'<td>{bla}</td>'
html = f'{html}</tr>'
wirehtml.append(' <tr><td>&nbsp;</td></tr>') # spacer
wirehtml.append(' <tr>')
wirehtml.append(' <td><!-- s_in --></td>')
wirehtml.append(' <td>Shield</td>')
wirehtml.append(' <td><!-- s_out --></td>')
wirehtml.append(' </tr>')
if isinstance(cable.shield, str):
# shield is shown with specified color and black borders
shield_color_hex = wv_colors.get_color_hex(cable.shield)[0]
attributes = f'height="6" bgcolor="{shield_color_hex}" border="2" sides="tb"'
else:
# shield is shown as a thin black wire
attributes = f'height="2" bgcolor="#000000" border="0"'
html = f'{html}<tr><td colspan="{len(p)}" cellpadding="0" {attributes} port="ws"></td></tr>'

html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer at the end

html = f'{html}</table>' # conductor table
wirehtml.append(f' <tr><td colspan="3" cellpadding="0" {attributes} port="ws"></td></tr>')

html = f'{html}</td></tr>' # main table
if cable.notes:
html = f'{html}<tr><td cellpadding="3" balign="left">{html_line_breaks(cable.notes)}</td></tr>' # notes table
html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer at the end
wirehtml.append(' <tr><td>&nbsp;</td></tr>')
wirehtml.append(' </table>')

html = f'{html}</table>' # main table
html = [row.replace('<!-- wire table -->', '\n'.join(wirehtml)) for row in html]

# connections
for connection_color in cable.connections:
Expand All @@ -262,16 +253,17 @@ def create_graph(self) -> Graph:
code_left_2 = f'{cable.name}:w{connection_color.via_port}:w'
dot.edge(code_left_1, code_left_2)
from_string = f'{connection_color.from_name}:{connection_color.from_port}' if self.connectors[connection_color.from_name].show_name else ''
html = html.replace(f'<!-- {connection_color.via_port}_in -->', from_string)
html = [row.replace(f'<!-- {connection_color.via_port}_in -->', from_string) for row in html]
if connection_color.to_port is not None: # connect to right
code_right_1 = f'{cable.name}:w{connection_color.via_port}:e'
to_port = f':p{connection_color.to_port}l' if self.connectors[connection_color.to_name].style != 'simple' else ''
code_right_2 = f'{connection_color.to_name}{to_port}:w'
dot.edge(code_right_1, code_right_2)
to_string = f'{connection_color.to_name}:{connection_color.to_port}' if self.connectors[connection_color.to_name].show_name else ''
html = html.replace(f'<!-- {connection_color.via_port}_out -->', to_string)
html = [row.replace(f'<!-- {connection_color.via_port}_out -->', to_string) for row in html]

dot.node(cable.name, label=f'<{html}>', shape='box',
html = '\n'.join(html)
dot.node(cable.name, label=f'<\n{html}\n>', shape='box',
Comment on lines +265 to +266
Copy link
Collaborator

@kvid kvid Aug 4, 2020

Choose a reason for hiding this comment

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

Consider htmlstr = '\n'.join(html) to avoid reusing the list variable as a string variable.

Copy link
Collaborator Author

@formatc1702 formatc1702 Aug 10, 2020

Choose a reason for hiding this comment

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

In this case I will leave it as is, since the join can't be built into the f-String easily... the variable is never used again after the following line, so I don't see an issue with reusing it.

style='filled,dashed' if cable.category == 'bundle' else '', margin='0', fillcolor='white')

return dot
Expand Down Expand Up @@ -355,7 +347,7 @@ def bom(self):
conn_color = f', {shared.color}' if shared.color else ''
name = f'Connector{conn_type}{conn_subtype}{conn_pincount}{conn_color}'
item = {'item': name, 'qty': len(designators), 'unit': '', 'designators': designators if shared.show_name else '',
'manufacturer': shared.manufacturer, 'mpn': shared.mpn, 'pn': shared.pn}
'manufacturer': remove_line_breaks(shared.manufacturer), 'mpn': remove_line_breaks(shared.mpn), 'pn': shared.pn}
bom_connectors.append(item)
bom_connectors = sorted(bom_connectors, key=lambda k: k['item']) # https://stackoverflow.com/a/73050
bom.extend(bom_connectors)
Expand All @@ -374,7 +366,7 @@ def bom(self):
shield_name = ' shielded' if shared.shield else ''
name = f'Cable{cable_type}, {shared.wirecount}{gauge_name}{shield_name}'
item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators,
'manufacturer': shared.manufacturer, 'mpn': shared.mpn, 'pn': shared.pn}
'manufacturer': remove_line_breaks(shared.manufacturer), 'mpn': remove_line_breaks(shared.mpn), 'pn': shared.pn}
bom_cables.append(item)
# bundles (ignores wirecount)
wirelist = []
Expand All @@ -384,8 +376,8 @@ def bom(self):
# add each wire from each bundle to the wirelist
for index, color in enumerate(bundle.colors, 0):
wirelist.append({'type': bundle.type, 'gauge': bundle.gauge, 'gauge_unit': bundle.gauge_unit, 'length': bundle.length, 'color': color, 'designator': bundle.name,
'manufacturer': index_if_list(bundle.manufacturer, index),
'mpn': index_if_list(bundle.mpn, index),
'manufacturer': remove_line_breaks(index_if_list(bundle.manufacturer, index)),
'mpn': remove_line_breaks(index_if_list(bundle.mpn, index)),
'pn': index_if_list(bundle.pn, index)})
# join similar wires from all the bundles to a single BOM item
wire_group = lambda w: (w.get('type', None), w['gauge'], w['gauge_unit'], w['color'], w['manufacturer'], w['mpn'], w['pn'])
Expand Down
Loading