Compare commits
9 Commits
master
...
refactor/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1799490bf2 | ||
|
|
26b505120a | ||
|
|
262ea42caf | ||
|
|
d1d7a7ced8 | ||
|
|
c3d8666467 | ||
|
|
be307e4917 | ||
|
|
a39104b51c | ||
|
|
884dca8a88 | ||
|
|
e8fc1d2212 |
@ -6,7 +6,8 @@ from pathlib import Path
|
||||
|
||||
from wireviz.wv_helper import int2tuple, aspect_ratio
|
||||
from wireviz.wv_colors import Color, Colors, ColorMode, ColorScheme, COLOR_CODES
|
||||
|
||||
from wireviz.wv_bom_new import Bom_hash, Bom_hash_list
|
||||
from wireviz.wv_gv_html import nested_html_table, bom_bubble
|
||||
|
||||
# Each type alias have their legal values described in comments - validation might be implemented in the future
|
||||
PlainText = str # Text not containing HTML tags nor newlines
|
||||
@ -41,6 +42,7 @@ class Options:
|
||||
bgcolor_cable: Optional[Color] = None
|
||||
bgcolor_bundle: Optional[Color] = None
|
||||
color_mode: ColorMode = 'SHORT'
|
||||
show_bom_ids: bool = False
|
||||
mini_bom_mode: bool = True
|
||||
|
||||
def __post_init__(self):
|
||||
@ -98,18 +100,70 @@ class Image:
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdditionalComponent:
|
||||
type: MultilineHypertext
|
||||
subtype: Optional[MultilineHypertext] = None
|
||||
manufacturer: Optional[MultilineHypertext] = None
|
||||
mpn: Optional[MultilineHypertext] = None
|
||||
supplier: Optional[MultilineHypertext] = None
|
||||
spn: Optional[MultilineHypertext] = None
|
||||
pn: Optional[Hypertext] = None
|
||||
class Component:
|
||||
type: Union[MultilineHypertext, List[MultilineHypertext]] = None
|
||||
subtype: Union[MultilineHypertext, List[MultilineHypertext]] = None
|
||||
category: Optional[str] = None # currently only used by cables, to define bundles
|
||||
|
||||
pn: Union[Hypertext, List[Hypertext], None] = None
|
||||
manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
supplier: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
spn: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
|
||||
ignore_in_bom: bool = False
|
||||
bom_id: Optional[str] = None # to be filled after harness is built
|
||||
|
||||
@property
|
||||
def bom_hash(self) -> Bom_hash:
|
||||
|
||||
def force_list(inp):
|
||||
if isinstance(inp, list):
|
||||
return inp
|
||||
else:
|
||||
return [inp for i in range(len(self.colors))]
|
||||
|
||||
if self.category == 'bundle':
|
||||
# create a single item that includes the necessary fields,
|
||||
# which may or may not be lists
|
||||
_hash_list = Bom_hash_list(
|
||||
self.description,
|
||||
self.unit,
|
||||
self.pn,
|
||||
self.manufacturer,
|
||||
self.mpn,
|
||||
self.supplier,
|
||||
self.spn,
|
||||
)
|
||||
# convert elements that are not lists, into lists
|
||||
_hash_matrix = list(map(force_list, [elem for elem in _hash_list]))
|
||||
# transpose list of lists, convert to tuple for next step
|
||||
_hash_matrix = list(map(tuple, zip(*_hash_matrix)))
|
||||
# generate list of Bom_hashes
|
||||
hash_list = [Bom_hash(*item) for item in _hash_matrix]
|
||||
return hash_list
|
||||
else:
|
||||
return Bom_hash(
|
||||
self.description,
|
||||
self.unit,
|
||||
self.pn,
|
||||
self.manufacturer,
|
||||
self.mpn,
|
||||
self.supplier,
|
||||
self.spn,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class GraphicalComponent(Component):
|
||||
bgcolor: Optional[Color] = None
|
||||
|
||||
@dataclass
|
||||
class AdditionalComponent(Component):
|
||||
qty: float = 1
|
||||
unit: Optional[str] = None
|
||||
qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None
|
||||
bgcolor: Optional[Color] = None
|
||||
designators: Optional[str] = None # used for components define in the `additional_bom_items` YAML section
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
@ -117,33 +171,38 @@ class AdditionalComponent:
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connector:
|
||||
name: Designator
|
||||
bgcolor: Optional[Color] = None
|
||||
class TopLevelGraphicalComponent(GraphicalComponent):
|
||||
name: Designator = None
|
||||
bgcolor_title: Optional[Color] = None
|
||||
manufacturer: Optional[MultilineHypertext] = None
|
||||
mpn: Optional[MultilineHypertext] = None
|
||||
supplier: Optional[MultilineHypertext] = None
|
||||
spn: Optional[MultilineHypertext] = None
|
||||
pn: Optional[Hypertext] = None
|
||||
style: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
type: Optional[MultilineHypertext] = None
|
||||
subtype: Optional[MultilineHypertext] = None
|
||||
pincount: Optional[int] = None
|
||||
color: Optional[Color] = None
|
||||
image: Optional[Image] = None
|
||||
notes: Optional[MultilineHypertext] = None
|
||||
additional_components: List[AdditionalComponent] = field(default_factory=list)
|
||||
|
||||
show_name: bool = True
|
||||
|
||||
def gen_add_bom_table(self):
|
||||
if self.additional_components:
|
||||
rows = []
|
||||
for comp in self.additional_components:
|
||||
rows.append([bom_bubble(comp.bom_id), comp.qty, comp.description, comp.pn])
|
||||
return rows
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connector(TopLevelGraphicalComponent):
|
||||
style: Optional[str] = None
|
||||
pincount: Optional[int] = None
|
||||
pins: List[Pin] = field(default_factory=list)
|
||||
pinlabels: List[Pin] = field(default_factory=list)
|
||||
pincolors: List[Color] = field(default_factory=list)
|
||||
color: Optional[Color] = None
|
||||
show_name: Optional[bool] = None
|
||||
show_pincount: Optional[bool] = None
|
||||
hide_disconnected_pins: bool = False
|
||||
autogenerate: bool = False
|
||||
loops: List[List[Pin]] = field(default_factory=list)
|
||||
ignore_in_bom: bool = False
|
||||
additional_components: List[AdditionalComponent] = field(default_factory=list)
|
||||
unit = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
|
||||
@ -188,10 +247,11 @@ class Connector:
|
||||
if isinstance(item, dict):
|
||||
self.additional_components[i] = AdditionalComponent(**item)
|
||||
|
||||
|
||||
def activate_pin(self, pin: Pin) -> None:
|
||||
self.visible_pins[pin] = True
|
||||
|
||||
def get_qty_multiplier(self, qty_multiplier: Optional[ConnectorMultiplier]) -> int:
|
||||
def qty_factor(self, qty_multiplier: Optional[ConnectorMultiplier]) -> int:
|
||||
if not qty_multiplier:
|
||||
return 1
|
||||
elif qty_multiplier == 'pincount':
|
||||
@ -201,37 +261,32 @@ class Connector:
|
||||
else:
|
||||
raise ValueError(f'invalid qty multiplier parameter for connector {qty_multiplier}')
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
substrs = [
|
||||
'Connector',
|
||||
self.type,
|
||||
self.subtype,
|
||||
self.pincount if self.show_pincount else None,
|
||||
str(self.color) + ' (reimplement color translation!)' if self.color else None, # translate_color(self.color, harness.options.color_mode)] <- get harness.color_mode!
|
||||
]
|
||||
return ', '.join([str(s) for s in substrs if s is not None and s != ''])
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cable:
|
||||
name: Designator
|
||||
bgcolor: Optional[Color] = None
|
||||
bgcolor_title: Optional[Color] = None
|
||||
manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
supplier: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
spn: Union[MultilineHypertext, List[MultilineHypertext], None] = None
|
||||
pn: Union[Hypertext, List[Hypertext], None] = None
|
||||
category: Optional[str] = None
|
||||
type: Optional[MultilineHypertext] = None
|
||||
class Cable(TopLevelGraphicalComponent):
|
||||
gauge: Optional[float] = None
|
||||
gauge_unit: Optional[str] = None
|
||||
show_equiv: bool = False
|
||||
length: float = 0
|
||||
length_unit: Optional[str] = None
|
||||
color: Optional[Color] = None
|
||||
wirecount: Optional[int] = None
|
||||
shield: Union[bool, Color] = False
|
||||
image: Optional[Image] = None
|
||||
notes: Optional[MultilineHypertext] = None
|
||||
colors: List[Colors] = field(default_factory=list)
|
||||
wirelabels: List[Wire] = field(default_factory=list)
|
||||
color_code: Optional[ColorScheme] = None
|
||||
show_name: bool = True
|
||||
show_wirecount: bool = True
|
||||
show_wirenumbers: Optional[bool] = None
|
||||
ignore_in_bom: bool = False
|
||||
additional_components: List[AdditionalComponent] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
|
||||
@ -318,6 +373,7 @@ class Cable:
|
||||
if isinstance(item, dict):
|
||||
self.additional_components[i] = AdditionalComponent(**item)
|
||||
|
||||
|
||||
# The *_pin arguments accept a tuple, but it seems not in use with the current code.
|
||||
def connect(self, from_name: Optional[Designator], from_pin: NoneOrMorePinIndices, via_wire: OneOrMoreWires,
|
||||
to_name: Optional[Designator], to_pin: NoneOrMorePinIndices) -> None:
|
||||
@ -329,7 +385,7 @@ class Cable:
|
||||
for i, _ in enumerate(from_pin):
|
||||
self.connections.append(Connection(from_name, from_pin[i], via_wire[i], to_name, to_pin[i]))
|
||||
|
||||
def get_qty_multiplier(self, qty_multiplier: Optional[CableMultiplier]) -> float:
|
||||
def qty_factor(self, qty_multiplier: Optional[CableMultiplier]) -> float:
|
||||
if not qty_multiplier:
|
||||
return 1
|
||||
elif qty_multiplier == 'wirecount':
|
||||
@ -343,6 +399,37 @@ class Cable:
|
||||
else:
|
||||
raise ValueError(f'invalid qty multiplier parameter for cable {qty_multiplier}')
|
||||
|
||||
@property
|
||||
def unit(self): # for compatibility with parent class
|
||||
return self.length_unit
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
if self.category == 'bundle':
|
||||
desc_list = []
|
||||
for index, color in enumerate(self.colors):
|
||||
substrs = [
|
||||
'Wire',
|
||||
self.type,
|
||||
self.subtype,
|
||||
f'{self.gauge} {self.gauge_unit}' if self.gauge else None,
|
||||
str(self.color) + ' (reimplement color translation!)' if self.color else None, # translate_color(self.color, harness.options.color_mode)] <- get harness.color_mode!
|
||||
]
|
||||
desc_list.append(', '.join([s for s in substrs if s is not None and s != '']))
|
||||
return desc_list
|
||||
else:
|
||||
substrs = [
|
||||
('', 'Cable'),
|
||||
(', ', self.type),
|
||||
(', ', self.subtype),
|
||||
(', ', self.wirecount),
|
||||
(' ', f'x {self.gauge} {self.gauge_unit}' if self.gauge else ' wires'),
|
||||
(' ', 'shielded' if self.shield else None),
|
||||
(', ', str(self.color) + ' (reimplement color translation!)' if self.color else None)
|
||||
]
|
||||
desc = ''.join([f'{s[0]}{s[1]}' for s in substrs if s[1] is not None and s[1] != ''])
|
||||
return desc
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connection:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from graphviz import Graph
|
||||
from collections import Counter
|
||||
from collections import Counter, defaultdict
|
||||
from typing import Any, List, Union
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
@ -9,17 +9,18 @@ from itertools import zip_longest
|
||||
import re
|
||||
|
||||
from wireviz import wv_colors, __version__, APP_NAME, APP_URL
|
||||
from wireviz.DataClasses import Metadata, Options, Tweak, Connector, Cable
|
||||
from wireviz.DataClasses import AdditionalComponent, Metadata, Options, Tweak, Connector, Cable
|
||||
from wireviz.wv_colors import get_color_hex, translate_color
|
||||
from wireviz.wv_gv_html import nested_html_table, \
|
||||
html_bgcolor_attr, html_bgcolor, html_colorbar, \
|
||||
html_image, html_caption, remove_links, html_line_breaks
|
||||
from wireviz.wv_bom import pn_info_string, component_table_entry, \
|
||||
get_additional_component_table, bom_list, generate_bom, \
|
||||
HEADER_PN, HEADER_MPN, HEADER_SPN
|
||||
html_image, html_caption, remove_links, html_line_breaks, bom_bubble, html_table
|
||||
# from wireviz.wv_bom import pn_info_string, component_table_entry, \
|
||||
# get_additional_component_table, bom_list, generate_bom, \
|
||||
# HEADER_PN, HEADER_MPN, HEADER_SPN
|
||||
from wireviz.wv_bom_new import pn_info_string, HEADER_PN, HEADER_MPN, HEADER_SPN
|
||||
from wireviz.wv_html import generate_html_output
|
||||
from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, flatten2d, \
|
||||
open_file_read, open_file_write
|
||||
open_file_read, open_file_write, remove_empty_columns
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -31,17 +32,68 @@ class Harness:
|
||||
def __post_init__(self):
|
||||
self.connectors = {}
|
||||
self.cables = {}
|
||||
self._bom = [] # Internal Cache for generated bom
|
||||
# self.bom = defaultdict(lambda: defaultdict(list)) # https://stackoverflow.com/questions/19189274
|
||||
self.bom = defaultdict(dict)
|
||||
self.additional_bom_items = []
|
||||
|
||||
def add_connector(self, name: str, *args, **kwargs) -> None:
|
||||
self.connectors[name] = Connector(name, *args, **kwargs)
|
||||
self.connectors[name] = Connector(name=name, *args, **kwargs)
|
||||
self._add_to_internal_bom(self.connectors[name])
|
||||
|
||||
def add_cable(self, name: str, *args, **kwargs) -> None:
|
||||
self.cables[name] = Cable(name, *args, **kwargs)
|
||||
self.cables[name] = Cable(name=name, *args, **kwargs)
|
||||
self._add_to_internal_bom(self.cables[name])
|
||||
|
||||
def add_bom_item(self, item: dict) -> None:
|
||||
self.additional_bom_items.append(item)
|
||||
def add_additional_bom_item(self, item: dict) -> None:
|
||||
new_item = AdditionalComponent(**item)
|
||||
self.additional_bom_items.append(new_item)
|
||||
self._add_to_internal_bom(new_item)
|
||||
|
||||
def _add_to_internal_bom(self, item):
|
||||
|
||||
if item.ignore_in_bom:
|
||||
return
|
||||
|
||||
def _add(thing, designator=None, qty=1, category=None):
|
||||
# generate entry
|
||||
bom_entry = self.bom[thing]
|
||||
# initialize missing fields
|
||||
if not 'qty' in bom_entry:
|
||||
bom_entry['qty'] = 0
|
||||
if not 'designators' in bom_entry:
|
||||
bom_entry['designators'] = set()
|
||||
# update fields
|
||||
bom_entry['qty'] += qty
|
||||
if designator:
|
||||
if isinstance(designator, str):
|
||||
bom_entry['designators'].add(designator)
|
||||
else:
|
||||
bom_entry['designators'].update(designator)
|
||||
bom_entry['category'] = category
|
||||
|
||||
if isinstance(item, Connector):
|
||||
_add(item.bom_hash, designator=item.name, category='connector')
|
||||
for comp in item.additional_components:
|
||||
if comp.ignore_in_bom:
|
||||
continue
|
||||
_add(comp.bom_hash, designator=item.name, qty=comp.qty, category='connector/additional')
|
||||
elif isinstance(item, Cable):
|
||||
_bom_hash = item.bom_hash
|
||||
if isinstance(_bom_hash, list):
|
||||
_cat = 'bundle'
|
||||
for subhash in _bom_hash:
|
||||
_add(subhash, designator=item.name, category=_cat)
|
||||
else:
|
||||
_cat = 'cable'
|
||||
_add(item.bom_hash, designator=item.name, category=_cat)
|
||||
for comp in item.additional_components:
|
||||
if comp.ignore_in_bom:
|
||||
continue
|
||||
_add(comp.bom_hash, designator=item.name, qty=comp.qty, category=f'{_cat}/additional')
|
||||
elif isinstance(item, AdditionalComponent): # additional component
|
||||
_add(item.bom_hash, designator=item.designators, qty=item.qty, category='additional')
|
||||
else:
|
||||
raise Exception(f'Unknown type of item:\n{item}')
|
||||
|
||||
def connect(self, from_name: str, from_pin: (int, str), via_name: str, via_wire: (int, str), to_name: str, to_pin: (int, str)) -> None:
|
||||
# check from and to connectors
|
||||
@ -119,6 +171,8 @@ class Harness:
|
||||
|
||||
for connector in self.connectors.values():
|
||||
|
||||
connector.bom_id = self.bom[connector.bom_hash]['bom_id']
|
||||
|
||||
# If no wires connected (except maybe loop wires)?
|
||||
if not (connector.ports_left or connector.ports_right):
|
||||
connector.ports_left = True # Use left side pins.
|
||||
@ -127,7 +181,8 @@ class Harness:
|
||||
|
||||
rows = [[f'{html_bgcolor(connector.bgcolor_title)}{remove_links(connector.name)}'
|
||||
if connector.show_name else None],
|
||||
[pn_info_string(HEADER_PN, None, remove_links(connector.pn)),
|
||||
[bom_bubble(connector.bom_id) if self.options.show_bom_ids else None,
|
||||
pn_info_string(HEADER_PN, None, remove_links(connector.pn)),
|
||||
html_line_breaks(pn_info_string(HEADER_MPN, connector.manufacturer, connector.mpn)),
|
||||
html_line_breaks(pn_info_string(HEADER_SPN, connector.supplier, connector.spn))],
|
||||
[html_line_breaks(connector.type),
|
||||
@ -137,8 +192,10 @@ class Harness:
|
||||
html_colorbar(connector.color)],
|
||||
'<!-- connector table -->' if connector.style != 'simple' else None,
|
||||
[html_image(connector.image)],
|
||||
[html_caption(connector.image)]]
|
||||
rows.extend(get_additional_component_table(self, connector))
|
||||
[html_caption(connector.image)],
|
||||
html_table(remove_empty_columns(connector.gen_add_bom_table()), 2)]
|
||||
# rows.extend(get_additional_component_table(self, connector))
|
||||
# rows.append()
|
||||
rows.append([html_line_breaks(connector.notes)])
|
||||
html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor)))
|
||||
|
||||
@ -198,6 +255,15 @@ class Harness:
|
||||
|
||||
for cable in self.cables.values():
|
||||
|
||||
if isinstance(cable.bom_hash, list):
|
||||
cable.bom_id = [self.bom[_hash]['bom_id'] for _hash in cable.bom_hash]
|
||||
cable_bom_id_str = None
|
||||
cable_bom_id_str_list = [bom_bubble(_id) for _id in cable.bom_id] if self.options.show_bom_ids else None
|
||||
else:
|
||||
cable.bom_id = self.bom[cable.bom_hash]['bom_id']
|
||||
cable_bom_id_str = bom_bubble(cable.bom_id) if self.options.show_bom_ids else None
|
||||
cable_bom_id_str_list = None
|
||||
|
||||
html = []
|
||||
|
||||
awg_fmt = ''
|
||||
@ -212,7 +278,8 @@ class Harness:
|
||||
|
||||
rows = [[f'{html_bgcolor(cable.bgcolor_title)}{remove_links(cable.name)}'
|
||||
if cable.show_name else None],
|
||||
[pn_info_string(HEADER_PN, None,
|
||||
[cable_bom_id_str,
|
||||
pn_info_string(HEADER_PN, None,
|
||||
remove_links(cable.pn)) if not isinstance(cable.pn, list) else None,
|
||||
html_line_breaks(pn_info_string(HEADER_MPN,
|
||||
cable.manufacturer if not isinstance(cable.manufacturer, list) else None,
|
||||
@ -231,7 +298,8 @@ class Harness:
|
||||
[html_image(cable.image)],
|
||||
[html_caption(cable.image)]]
|
||||
|
||||
rows.extend(get_additional_component_table(self, cable))
|
||||
# rows.extend(get_additional_component_table(self, cable))
|
||||
rows.append(['Reimplement additional component table!'])
|
||||
rows.append([html_line_breaks(cable.notes)])
|
||||
html.extend(nested_html_table(rows, html_bgcolor_attr(cable.bgcolor)))
|
||||
|
||||
@ -242,6 +310,8 @@ class Harness:
|
||||
for i, (connection_color, wirelabel) in enumerate(zip_longest(cable.colors, cable.wirelabels), 1):
|
||||
wirehtml.append(' <tr>')
|
||||
wirehtml.append(f' <td><!-- {i}_in --></td>')
|
||||
if cable_bom_id_str_list:
|
||||
wirehtml.append(f'<td>{cable_bom_id_str_list[i - 1]}</td>')
|
||||
wirehtml.append(f' <td>')
|
||||
|
||||
wireinfo = []
|
||||
@ -430,6 +500,16 @@ class Harness:
|
||||
return data.read()
|
||||
|
||||
def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True, fmt: tuple = ('pdf', )) -> None:
|
||||
# bom generation
|
||||
# sort BOM according to TODO key
|
||||
# TODO
|
||||
# assign BOM IDs
|
||||
for i, (k, v) in enumerate(self.bom.items(), 1):
|
||||
v['bom_id'] = i
|
||||
for k, v in self.bom.items():
|
||||
print(k)
|
||||
print(v)
|
||||
print()
|
||||
# graphical output
|
||||
graph = self.create_graph()
|
||||
for f in fmt:
|
||||
@ -437,13 +517,14 @@ class Harness:
|
||||
graph.render(filename=filename, view=view, cleanup=cleanup)
|
||||
graph.save(filename=f'{filename}.gv')
|
||||
# bom output
|
||||
bomlist = bom_list(self.bom())
|
||||
with open_file_write(f'{filename}.bom.tsv') as file:
|
||||
file.write(tuplelist2tsv(bomlist))
|
||||
# bomlist = bom_list(self.bom())
|
||||
# with open_file_write(f'{filename}.bom.tsv') as file:
|
||||
# file.write(tuplelist2tsv(bomlist))
|
||||
bomlist = [[]]
|
||||
# HTML output
|
||||
generate_html_output(filename, bomlist, self.metadata, self.options)
|
||||
|
||||
def bom(self):
|
||||
if not self._bom:
|
||||
self._bom = generate_bom(self)
|
||||
return self._bom
|
||||
#
|
||||
# def bom(self):
|
||||
# if not self._bom:
|
||||
# self._bom = generate_bom(self)
|
||||
# return self._bom
|
||||
|
||||
@ -184,7 +184,7 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st
|
||||
|
||||
if "additional_bom_items" in yaml_data:
|
||||
for line in yaml_data["additional_bom_items"]:
|
||||
harness.add_bom_item(line)
|
||||
harness.add_additional_bom_item(line)
|
||||
|
||||
if file_out is not None:
|
||||
harness.output(filename=file_out, fmt=('png', 'svg'), view=False)
|
||||
|
||||
20
src/wireviz/wv_bom_new.py
Normal file
20
src/wireviz/wv_bom_new.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from collections import namedtuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
BOM_HASH_FIELDS = 'description unit pn manufacturer mpn supplier spn'
|
||||
Bom_hash = namedtuple('Bom_hash', BOM_HASH_FIELDS)
|
||||
Bom_hash_list = namedtuple('Bom_hash_list', BOM_HASH_FIELDS)
|
||||
|
||||
HEADER_PN = 'P/N'
|
||||
HEADER_MPN = 'MPN'
|
||||
HEADER_SPN = 'SPN'
|
||||
|
||||
def pn_info_string(header: str, name: Optional[str], number: Optional[str]) -> Optional[str]:
|
||||
"""Return the company name and/or the part number in one single string or None otherwise."""
|
||||
number = str(number).strip() if number is not None else ''
|
||||
if name or number:
|
||||
return f'{name if name else header}{": " + number if number else ""}'
|
||||
else:
|
||||
return None
|
||||
@ -32,6 +32,18 @@ def nested_html_table(rows: List[Union[str, List[Optional[str]], None]], table_a
|
||||
html.append('</table>')
|
||||
return html
|
||||
|
||||
def html_table(rows: List[List[str]], indent_level: int = 0) -> str:
|
||||
html = []
|
||||
html.append('<table border="0" cellspacing="0" cellpadding="3" cellborder="1">')
|
||||
for row in rows:
|
||||
html.append(' <tr>')
|
||||
for item in row:
|
||||
html.append(f' <td>{item}</td>')
|
||||
html.append(' </tr>')
|
||||
html.append('</table>')
|
||||
html = [' '*indent_level + row for row in html]
|
||||
return '\n'.join(html)
|
||||
|
||||
def html_bgcolor_attr(color: Color) -> str:
|
||||
"""Return attributes for bgcolor or '' if no color."""
|
||||
return f' bgcolor="{translate_color(color, "HEX")}"' if color else ''
|
||||
@ -74,3 +86,6 @@ def html_size_attr(image):
|
||||
|
||||
def html_line_breaks(inp):
|
||||
return remove_links(inp).replace('\n', '<br />') if isinstance(inp, str) else inp
|
||||
|
||||
def bom_bubble(inp):
|
||||
return(f'<table border="0"><tr><td border="1" style="rounded">{inp}</td></tr></table>')
|
||||
|
||||
@ -116,3 +116,9 @@ def aspect_ratio(image_src):
|
||||
except Exception as error:
|
||||
print(f'aspect_ratio(): {type(error).__name__}: {error}')
|
||||
return 1 # Assume 1:1 when unable to read actual image size
|
||||
|
||||
def remove_empty_columns(inp: List[List]) -> List[List]:
|
||||
transp = list(map(list, zip(*inp))) # transpose list
|
||||
transp = [item for item in transp if any(item)] # remove empty rows (easier)
|
||||
out = list(map(list, zip(*transp))) # transpose back
|
||||
return out
|
||||
|
||||
@ -75,7 +75,7 @@ connections:
|
||||
|
||||
additional_bom_items:
|
||||
- # define an additional item to add to the bill of materials (does not appear in graph)
|
||||
description: Label, pinout information
|
||||
type: Label, pinout information
|
||||
qty: 2
|
||||
designators:
|
||||
- X2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user