Compare commits

...

9 Commits

Author SHA1 Message Date
Daniel Rojas
1799490bf2 Test additional component table (WIP) 2021-10-15 14:04:44 +02:00
Daniel Rojas
26b505120a Add option to hide BOM IDs 2021-10-15 09:50:54 +02:00
Daniel Rojas
262ea42caf Implement parent classes
- `Component`
- `GraphicalComponent`
- `TopLevelGraphicalComponent`
2021-10-15 09:50:39 +02:00
Daniel Rojas
d1d7a7ced8 Change namedtuples' typenames to match object type 2021-10-15 09:10:15 +02:00
Daniel Rojas
c3d8666467 Edit tutorial08.yml for compatibility with refactored code 2021-10-14 22:44:19 +02:00
Daniel Rojas
be307e4917 Assign BOM IDs to components before generating graphical output 2021-10-14 22:30:18 +02:00
Daniel Rojas
a39104b51c Populate Harness.bom during creation of components
Additionally: harmonize the `additional_components` inside Connectors/Cables with the `additional_bom_items` from the YAML
2021-10-14 21:27:38 +02:00
Daniel Rojas
884dca8a88 Implement .description and .bom_hash for all components
Implemented for connectors, cables, bundles, additional components
2021-10-14 20:19:44 +02:00
Daniel Rojas
e8fc1d2212 Disable all calls to wv_bom.py and rename it to wv_bom_old.py
to ensure no legacy code is called.
2021-10-14 19:46:31 +02:00
8 changed files with 282 additions and 73 deletions

View File

@ -6,7 +6,8 @@ from pathlib import Path
from wireviz.wv_helper import int2tuple, aspect_ratio from wireviz.wv_helper import int2tuple, aspect_ratio
from wireviz.wv_colors import Color, Colors, ColorMode, ColorScheme, COLOR_CODES 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 # 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 PlainText = str # Text not containing HTML tags nor newlines
@ -41,6 +42,7 @@ class Options:
bgcolor_cable: Optional[Color] = None bgcolor_cable: Optional[Color] = None
bgcolor_bundle: Optional[Color] = None bgcolor_bundle: Optional[Color] = None
color_mode: ColorMode = 'SHORT' color_mode: ColorMode = 'SHORT'
show_bom_ids: bool = False
mini_bom_mode: bool = True mini_bom_mode: bool = True
def __post_init__(self): def __post_init__(self):
@ -98,18 +100,70 @@ class Image:
@dataclass @dataclass
class AdditionalComponent: class Component:
type: MultilineHypertext type: Union[MultilineHypertext, List[MultilineHypertext]] = None
subtype: Optional[MultilineHypertext] = None subtype: Union[MultilineHypertext, List[MultilineHypertext]] = None
manufacturer: Optional[MultilineHypertext] = None category: Optional[str] = None # currently only used by cables, to define bundles
mpn: Optional[MultilineHypertext] = None
supplier: Optional[MultilineHypertext] = None pn: Union[Hypertext, List[Hypertext], None] = None
spn: Optional[MultilineHypertext] = None manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None
pn: Optional[Hypertext] = 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 qty: float = 1
unit: Optional[str] = None unit: Optional[str] = None
qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = 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 @property
def description(self) -> str: def description(self) -> str:
@ -117,33 +171,38 @@ class AdditionalComponent:
@dataclass @dataclass
class Connector: class TopLevelGraphicalComponent(GraphicalComponent):
name: Designator name: Designator = None
bgcolor: Optional[Color] = None
bgcolor_title: Optional[Color] = None bgcolor_title: Optional[Color] = None
manufacturer: Optional[MultilineHypertext] = None color: Optional[Color] = 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
image: Optional[Image] = None image: Optional[Image] = None
notes: Optional[MultilineHypertext] = 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) pins: List[Pin] = field(default_factory=list)
pinlabels: List[Pin] = field(default_factory=list) pinlabels: List[Pin] = field(default_factory=list)
pincolors: List[Color] = 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 show_pincount: Optional[bool] = None
hide_disconnected_pins: bool = False hide_disconnected_pins: bool = False
autogenerate: bool = False autogenerate: bool = False
loops: List[List[Pin]] = field(default_factory=list) loops: List[List[Pin]] = field(default_factory=list)
ignore_in_bom: bool = False unit = None
additional_components: List[AdditionalComponent] = field(default_factory=list)
def __post_init__(self) -> None: def __post_init__(self) -> None:
@ -188,10 +247,11 @@ 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) -> None:
self.visible_pins[pin] = True 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: if not qty_multiplier:
return 1 return 1
elif qty_multiplier == 'pincount': elif qty_multiplier == 'pincount':
@ -201,37 +261,32 @@ class Connector:
else: else:
raise ValueError(f'invalid qty multiplier parameter for connector {qty_multiplier}') 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 @dataclass
class Cable: class Cable(TopLevelGraphicalComponent):
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
gauge: Optional[float] = None gauge: Optional[float] = None
gauge_unit: Optional[str] = None gauge_unit: Optional[str] = None
show_equiv: bool = False show_equiv: bool = False
length: float = 0 length: float = 0
length_unit: Optional[str] = None length_unit: Optional[str] = None
color: Optional[Color] = None
wirecount: Optional[int] = None wirecount: Optional[int] = None
shield: Union[bool, Color] = False shield: Union[bool, Color] = False
image: Optional[Image] = None
notes: Optional[MultilineHypertext] = None
colors: List[Colors] = field(default_factory=list) colors: List[Colors] = field(default_factory=list)
wirelabels: List[Wire] = field(default_factory=list) wirelabels: List[Wire] = field(default_factory=list)
color_code: Optional[ColorScheme] = None color_code: Optional[ColorScheme] = None
show_name: bool = True
show_wirecount: bool = True show_wirecount: bool = True
show_wirenumbers: Optional[bool] = None show_wirenumbers: Optional[bool] = None
ignore_in_bom: bool = False
additional_components: List[AdditionalComponent] = field(default_factory=list)
def __post_init__(self) -> None: def __post_init__(self) -> None:
@ -318,6 +373,7 @@ class Cable:
if isinstance(item, dict): if isinstance(item, dict):
self.additional_components[i] = AdditionalComponent(**item) self.additional_components[i] = AdditionalComponent(**item)
# The *_pin arguments accept a tuple, but it seems not in use with the current code. # 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, def connect(self, from_name: Optional[Designator], from_pin: NoneOrMorePinIndices, via_wire: OneOrMoreWires,
to_name: Optional[Designator], to_pin: NoneOrMorePinIndices) -> None: to_name: Optional[Designator], to_pin: NoneOrMorePinIndices) -> None:
@ -329,7 +385,7 @@ class Cable:
for i, _ in enumerate(from_pin): for i, _ in enumerate(from_pin):
self.connections.append(Connection(from_name, from_pin[i], via_wire[i], to_name, to_pin[i])) 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: if not qty_multiplier:
return 1 return 1
elif qty_multiplier == 'wirecount': elif qty_multiplier == 'wirecount':
@ -343,6 +399,37 @@ class Cable:
else: else:
raise ValueError(f'invalid qty multiplier parameter for cable {qty_multiplier}') 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 @dataclass
class Connection: class Connection:

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from graphviz import Graph from graphviz import Graph
from collections import Counter from collections import Counter, defaultdict
from typing import Any, List, Union from typing import Any, List, Union
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
@ -9,17 +9,18 @@ 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 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_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, \
html_image, html_caption, remove_links, html_line_breaks html_image, html_caption, remove_links, html_line_breaks, bom_bubble, html_table
from wireviz.wv_bom import pn_info_string, component_table_entry, \ # from wireviz.wv_bom import pn_info_string, component_table_entry, \
get_additional_component_table, bom_list, generate_bom, \ # get_additional_component_table, bom_list, generate_bom, \
HEADER_PN, HEADER_MPN, HEADER_SPN # 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_html import generate_html_output
from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, flatten2d, \ 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 @dataclass
@ -31,17 +32,68 @@ class Harness:
def __post_init__(self): def __post_init__(self):
self.connectors = {} self.connectors = {}
self.cables = {} 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 = [] self.additional_bom_items = []
def add_connector(self, name: str, *args, **kwargs) -> None: 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: 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: def add_additional_bom_item(self, item: dict) -> None:
self.additional_bom_items.append(item) 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: 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 # check from and to connectors
@ -119,6 +171,8 @@ class Harness:
for connector in self.connectors.values(): 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 no wires connected (except maybe loop wires)?
if not (connector.ports_left or connector.ports_right): if not (connector.ports_left or connector.ports_right):
connector.ports_left = True # Use left side pins. 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)}' rows = [[f'{html_bgcolor(connector.bgcolor_title)}{remove_links(connector.name)}'
if connector.show_name else None], 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_MPN, connector.manufacturer, connector.mpn)),
html_line_breaks(pn_info_string(HEADER_SPN, connector.supplier, connector.spn))], html_line_breaks(pn_info_string(HEADER_SPN, connector.supplier, connector.spn))],
[html_line_breaks(connector.type), [html_line_breaks(connector.type),
@ -137,8 +192,10 @@ class Harness:
html_colorbar(connector.color)], html_colorbar(connector.color)],
'<!-- connector table -->' if connector.style != 'simple' else None, '<!-- connector table -->' if connector.style != 'simple' else None,
[html_image(connector.image)], [html_image(connector.image)],
[html_caption(connector.image)]] [html_caption(connector.image)],
rows.extend(get_additional_component_table(self, connector)) 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)]) rows.append([html_line_breaks(connector.notes)])
html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor))) html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor)))
@ -198,6 +255,15 @@ class Harness:
for cable in self.cables.values(): 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 = [] html = []
awg_fmt = '' awg_fmt = ''
@ -212,7 +278,8 @@ class Harness:
rows = [[f'{html_bgcolor(cable.bgcolor_title)}{remove_links(cable.name)}' rows = [[f'{html_bgcolor(cable.bgcolor_title)}{remove_links(cable.name)}'
if cable.show_name else None], 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, remove_links(cable.pn)) if not isinstance(cable.pn, list) else None,
html_line_breaks(pn_info_string(HEADER_MPN, html_line_breaks(pn_info_string(HEADER_MPN,
cable.manufacturer if not isinstance(cable.manufacturer, list) else None, cable.manufacturer if not isinstance(cable.manufacturer, list) else None,
@ -231,7 +298,8 @@ class Harness:
[html_image(cable.image)], [html_image(cable.image)],
[html_caption(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)]) rows.append([html_line_breaks(cable.notes)])
html.extend(nested_html_table(rows, html_bgcolor_attr(cable.bgcolor))) 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): for i, (connection_color, wirelabel) in enumerate(zip_longest(cable.colors, cable.wirelabels), 1):
wirehtml.append(' <tr>') wirehtml.append(' <tr>')
wirehtml.append(f' <td><!-- {i}_in --></td>') 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>') wirehtml.append(f' <td>')
wireinfo = [] wireinfo = []
@ -430,6 +500,16 @@ class Harness:
return data.read() return data.read()
def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True, fmt: tuple = ('pdf', )) -> None: 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 # graphical output
graph = self.create_graph() graph = self.create_graph()
for f in fmt: for f in fmt:
@ -437,13 +517,14 @@ class Harness:
graph.render(filename=filename, view=view, cleanup=cleanup) graph.render(filename=filename, view=view, cleanup=cleanup)
graph.save(filename=f'{filename}.gv') graph.save(filename=f'{filename}.gv')
# bom output # bom output
bomlist = bom_list(self.bom()) # bomlist = bom_list(self.bom())
with open_file_write(f'{filename}.bom.tsv') as file: # with open_file_write(f'{filename}.bom.tsv') as file:
file.write(tuplelist2tsv(bomlist)) # file.write(tuplelist2tsv(bomlist))
bomlist = [[]]
# HTML output # HTML output
generate_html_output(filename, bomlist, self.metadata, self.options) generate_html_output(filename, bomlist, self.metadata, self.options)
#
def bom(self): # def bom(self):
if not self._bom: # if not self._bom:
self._bom = generate_bom(self) # self._bom = generate_bom(self)
return self._bom # return self._bom

View File

@ -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: if "additional_bom_items" in yaml_data:
for line in yaml_data["additional_bom_items"]: 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: if file_out is not None:
harness.output(filename=file_out, fmt=('png', 'svg'), view=False) harness.output(filename=file_out, fmt=('png', 'svg'), view=False)

20
src/wireviz/wv_bom_new.py Normal file
View 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

View File

@ -32,6 +32,18 @@ def nested_html_table(rows: List[Union[str, List[Optional[str]], None]], table_a
html.append('</table>') html.append('</table>')
return html 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: def html_bgcolor_attr(color: Color) -> str:
"""Return attributes for bgcolor or '' if no color.""" """Return attributes for bgcolor or '' if no color."""
return f' bgcolor="{translate_color(color, "HEX")}"' if color else '' return f' bgcolor="{translate_color(color, "HEX")}"' if color else ''
@ -74,3 +86,6 @@ def html_size_attr(image):
def html_line_breaks(inp): def html_line_breaks(inp):
return remove_links(inp).replace('\n', '<br />') if isinstance(inp, str) else 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>')

View File

@ -116,3 +116,9 @@ def aspect_ratio(image_src):
except Exception as error: except Exception as error:
print(f'aspect_ratio(): {type(error).__name__}: {error}') print(f'aspect_ratio(): {type(error).__name__}: {error}')
return 1 # Assume 1:1 when unable to read actual image size 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

View File

@ -75,7 +75,7 @@ connections:
additional_bom_items: additional_bom_items:
- # define an additional item to add to the bill of materials (does not appear in graph) - # 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 qty: 2
designators: designators:
- X2 - X2