Allow addittional BOM items within components (#115)
This commit is contained in:
parent
4e4dac8597
commit
e85ee5d285
@ -43,9 +43,12 @@ additional_bom_items: # custom items to add to BOM
|
||||
notes: <str>
|
||||
|
||||
# product information (all optional)
|
||||
ignore_in_bom: <bool> # if set to true the connector is not added to the BOM
|
||||
pn: <str> # [internal] part number
|
||||
mpn: <str> # manufacturer part number
|
||||
manufacturer: <str> # manufacturer name
|
||||
additional_components: # additional components
|
||||
- <additional-component> # additional component (see below)
|
||||
|
||||
# pinout information
|
||||
# at least one of the following must be specified
|
||||
@ -108,9 +111,12 @@ Since the auto-incremented and auto-assigned designator is not known to the user
|
||||
notes: <str>
|
||||
|
||||
# product information (all optional)
|
||||
ignore_in_bom: <bool> # if set to true the cable or wires are not added to the BOM
|
||||
pn: <str> # [internal] part number
|
||||
mpn: <str> # manufacturer part number
|
||||
manufacturer: <str> # manufacturer name
|
||||
additional_components: # additional components
|
||||
- <additional-component> # additional component (see below)
|
||||
|
||||
# conductor information
|
||||
# the following combinations are permitted:
|
||||
@ -212,27 +218,42 @@ For connectors with `autogenerate: true`, a new instance, with auto-generated de
|
||||
- `<int>-<int>` auto-expands to a range.
|
||||
|
||||
|
||||
## BOM items
|
||||
## BOM items and additional components
|
||||
|
||||
Connectors (both regular, and auto-generated), cables, and wires of a bundle are automatically added to the BOM.
|
||||
Connectors (both regular, and auto-generated), cables, and wires of a bundle are automatically added to the BOM,
|
||||
unless the `ignore_in_bom` attribute is set to `true`.
|
||||
Additional items can be added to the BOM as either part of a connector or cable or on their own.
|
||||
|
||||
<!-- unless the `ignore_in_bom` attribute is set to `true` (#115) -->
|
||||
Parts can be added to a connector or cable in the section `<additional-component>` which will also list them in the graph.
|
||||
|
||||
Additional BOM entries can be generated in the sections marked `<bom-item>` above.
|
||||
```yaml
|
||||
-
|
||||
type: <str> # type of additional component
|
||||
# all the following are optional:
|
||||
subtype: <str> # additional description (only shown in bom)
|
||||
qty: <int/float> # qty to add to the bom (defaults to 1)
|
||||
qty_multiplier: <str> # multiplies qty by a feature of the parent component
|
||||
# when used in a connector:
|
||||
# pincount number of pins of connector
|
||||
# populated number of populated positions in a connector
|
||||
# when used in a cable:
|
||||
# wirecount number of wires of cable/bundle
|
||||
# terminations number of terminations on a cable/bundle
|
||||
# length length of cable/bundle
|
||||
# total_length sum of lengths of each wire in the bundle
|
||||
unit: <str>
|
||||
pn: <str> # [internal] part number
|
||||
mpn: <str> # manufacturer part number
|
||||
manufacturer: <str> # manufacturer name
|
||||
```
|
||||
|
||||
<!-- BOM items inside connectors/cables are not implemented yet, but should be soon (#50) -->
|
||||
Alternatively items can be added to just the BOM by putting them in the section `<bom-item>` above.
|
||||
|
||||
```yaml
|
||||
-
|
||||
description: <str>
|
||||
qty: <int/str> # when used in the additional_bom_items section:
|
||||
# <int> manually specify qty.
|
||||
# when used within a component:
|
||||
# <int> manually specify qty.
|
||||
# pincount match number of pins of connector
|
||||
# wirecount match number of wires of cable/bundle
|
||||
# connectioncount match number of connected pins
|
||||
# all the following are optional:
|
||||
qty: <int/float> # qty to add to the bom (defaults to 1)
|
||||
unit: <str>
|
||||
designators: <List>
|
||||
pn: <str> # [internal] part number
|
||||
|
||||
@ -7,6 +7,10 @@ from pathlib import Path
|
||||
from wireviz.wv_helper import int2tuple, aspect_ratio
|
||||
from wireviz import wv_colors
|
||||
|
||||
# Literal type aliases below are commented to avoid requiring python 3.8
|
||||
ConnectorMultiplier = str # = Literal['pincount', 'populated']
|
||||
CableMultiplier = str # = Literal['wirecount', 'terminations', 'length', 'total_length']
|
||||
|
||||
|
||||
@dataclass
|
||||
class Image:
|
||||
@ -43,6 +47,21 @@ class Image:
|
||||
if self.width:
|
||||
self.height = self.width / aspect_ratio(gv_dir.joinpath(self.src))
|
||||
|
||||
@dataclass
|
||||
class AdditionalComponent:
|
||||
type: str
|
||||
subtype: Optional[str] = None
|
||||
manufacturer: Optional[str] = None
|
||||
mpn: Optional[str] = None
|
||||
pn: Optional[str] = None
|
||||
qty: float = 1
|
||||
unit: Optional[str] = None
|
||||
qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self.type.rstrip() + (f', {self.subtype.rstrip()}' if self.subtype else '')
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connector:
|
||||
@ -65,6 +84,8 @@ class Connector:
|
||||
hide_disconnected_pins: bool = False
|
||||
autogenerate: bool = False
|
||||
loops: List[Any] = field(default_factory=list)
|
||||
ignore_in_bom: bool = False
|
||||
additional_components: List[AdditionalComponent] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
|
||||
@ -114,9 +135,23 @@ class Connector:
|
||||
if len(loop) != 2:
|
||||
raise Exception('Loops must be between exactly two pins!')
|
||||
|
||||
for i, item in enumerate(self.additional_components):
|
||||
if isinstance(item, dict):
|
||||
self.additional_components[i] = AdditionalComponent(**item)
|
||||
|
||||
def activate_pin(self, pin):
|
||||
self.visible_pins[pin] = True
|
||||
|
||||
def get_qty_multiplier(self, qty_multiplier: Optional[ConnectorMultiplier]) -> int:
|
||||
if not qty_multiplier:
|
||||
return 1
|
||||
elif qty_multiplier == 'pincount':
|
||||
return self.pincount
|
||||
elif qty_multiplier == 'populated':
|
||||
return sum(self.visible_pins.values())
|
||||
else:
|
||||
raise ValueError(f'invalid qty multiplier parameter for connector {qty_multiplier}')
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cable:
|
||||
@ -139,6 +174,8 @@ class Cable:
|
||||
color_code: Optional[str] = None
|
||||
show_name: bool = True
|
||||
show_wirecount: bool = True
|
||||
ignore_in_bom: bool = False
|
||||
additional_components: List[AdditionalComponent] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
|
||||
@ -196,6 +233,9 @@ class Cable:
|
||||
else:
|
||||
raise Exception('lists of part data are only supported for bundles')
|
||||
|
||||
for i, item in enumerate(self.additional_components):
|
||||
if isinstance(item, dict):
|
||||
self.additional_components[i] = AdditionalComponent(**item)
|
||||
|
||||
def connect(self, from_name, from_pin, via_pin, to_name, to_pin):
|
||||
from_pin = int2tuple(from_pin)
|
||||
@ -207,6 +247,20 @@ class Cable:
|
||||
# self.connections.append((from_name, from_pin[i], via_pin[i], to_name, to_pin[i]))
|
||||
self.connections.append(Connection(from_name, from_pin[i], via_pin[i], to_name, to_pin[i]))
|
||||
|
||||
def get_qty_multiplier(self, qty_multiplier: Optional[CableMultiplier]) -> float:
|
||||
if not qty_multiplier:
|
||||
return 1
|
||||
elif qty_multiplier == 'wirecount':
|
||||
return self.wirecount
|
||||
elif qty_multiplier == 'terminations':
|
||||
return len(self.connections)
|
||||
elif qty_multiplier == 'length':
|
||||
return self.length
|
||||
elif qty_multiplier == 'total_length':
|
||||
return self.length * self.wirecount
|
||||
else:
|
||||
raise ValueError(f'invalid qty multiplier parameter for cable {qty_multiplier}')
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connection:
|
||||
|
||||
@ -7,10 +7,10 @@ from wireviz import wv_colors, wv_helper, __version__, APP_NAME, APP_URL
|
||||
from wireviz.wv_colors import get_color_hex
|
||||
from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, \
|
||||
nested_html_table, flatten2d, index_if_list, html_line_breaks, \
|
||||
graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write, \
|
||||
html_colorbar, html_image, html_caption, manufacturer_info_field
|
||||
clean_whitespace, open_file_read, open_file_write, html_colorbar, \
|
||||
html_image, html_caption, manufacturer_info_field, component_table_entry
|
||||
from collections import Counter
|
||||
from typing import List
|
||||
from typing import List, Union
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
@ -19,8 +19,10 @@ class Harness:
|
||||
|
||||
def __init__(self):
|
||||
self.color_mode = 'SHORT'
|
||||
self.mini_bom_mode = True
|
||||
self.connectors = {}
|
||||
self.cables = {}
|
||||
self._bom = [] # Internal Cache for generated bom
|
||||
self.additional_bom_items = []
|
||||
|
||||
def add_connector(self, name: str, *args, **kwargs) -> None:
|
||||
@ -99,8 +101,9 @@ class Harness:
|
||||
connector.color, html_colorbar(connector.color)],
|
||||
'<!-- connector table -->' if connector.style != 'simple' else None,
|
||||
[html_image(connector.image)],
|
||||
[html_caption(connector.image)],
|
||||
[html_line_breaks(connector.notes)]]
|
||||
[html_caption(connector.image)]]
|
||||
rows.extend(self.get_additional_component_table(connector))
|
||||
rows.append([html_line_breaks(connector.notes)])
|
||||
html.extend(nested_html_table(rows))
|
||||
|
||||
if connector.style != 'simple':
|
||||
@ -172,8 +175,10 @@ class Harness:
|
||||
cable.color, html_colorbar(cable.color)],
|
||||
'<!-- wire table -->',
|
||||
[html_image(cable.image)],
|
||||
[html_caption(cable.image)],
|
||||
[html_line_breaks(cable.notes)]]
|
||||
[html_caption(cable.image)]]
|
||||
|
||||
rows.extend(self.get_additional_component_table(cable))
|
||||
rows.append([html_line_breaks(cable.notes)])
|
||||
html.extend(nested_html_table(rows))
|
||||
|
||||
wirehtml = []
|
||||
@ -196,7 +201,7 @@ class Harness:
|
||||
wirehtml.append(' </table>')
|
||||
wirehtml.append(' </td>')
|
||||
wirehtml.append(' </tr>')
|
||||
if(cable.category == 'bundle'): # for bundles individual wires can have part information
|
||||
if cable.category == 'bundle': # for bundles individual wires can have part information
|
||||
# create a list of wire parameters
|
||||
wireidentification = []
|
||||
if isinstance(cable.pn, list):
|
||||
@ -207,7 +212,7 @@ class Harness:
|
||||
if manufacturer_info:
|
||||
wireidentification.append(html_line_breaks(manufacturer_info))
|
||||
# print parameters into a table row under the wire
|
||||
if(len(wireidentification) > 0):
|
||||
if len(wireidentification) > 0 :
|
||||
wirehtml.append(' <tr><td colspan="3">')
|
||||
wirehtml.append(' <table border="0" cellspacing="0" cellborder="0"><tr>')
|
||||
for attrib in wireidentification:
|
||||
@ -329,91 +334,131 @@ class Harness:
|
||||
|
||||
file.write('</body></html>')
|
||||
|
||||
def get_additional_component_table(self, component: Union[Connector, Cable]) -> List[str]:
|
||||
rows = []
|
||||
if component.additional_components:
|
||||
rows.append(["Additional components"])
|
||||
for extra in component.additional_components:
|
||||
qty = extra.qty * component.get_qty_multiplier(extra.qty_multiplier)
|
||||
if self.mini_bom_mode:
|
||||
id = self.get_bom_index(extra.description, extra.unit, extra.manufacturer, extra.mpn, extra.pn)
|
||||
rows.append(component_table_entry(f'#{id} ({extra.type.rstrip()})', qty, extra.unit))
|
||||
else:
|
||||
rows.append(component_table_entry(extra.description, qty, extra.unit, extra.pn, extra.manufacturer, extra.mpn))
|
||||
return(rows)
|
||||
|
||||
def get_additional_component_bom(self, component: Union[Connector, Cable]) -> List[dict]:
|
||||
bom_entries = []
|
||||
for part in component.additional_components:
|
||||
qty = part.qty * component.get_qty_multiplier(part.qty_multiplier)
|
||||
bom_entries.append({
|
||||
'item': part.description,
|
||||
'qty': qty,
|
||||
'unit': part.unit,
|
||||
'manufacturer': part.manufacturer,
|
||||
'mpn': part.mpn,
|
||||
'pn': part.pn,
|
||||
'designators': component.name if component.show_name else None
|
||||
})
|
||||
return(bom_entries)
|
||||
|
||||
def bom(self):
|
||||
bom = []
|
||||
bom_connectors = []
|
||||
bom_cables = []
|
||||
bom_extra = []
|
||||
# if the bom has previously been generated then return the generated bom
|
||||
if self._bom:
|
||||
return self._bom
|
||||
bom_entries = []
|
||||
|
||||
# connectors
|
||||
connector_group = lambda c: (c.type, c.subtype, c.pincount, c.manufacturer, c.mpn, c.pn)
|
||||
for group in Counter([connector_group(v) for v in self.connectors.values()]):
|
||||
items = {k: v for k, v in self.connectors.items() if connector_group(v) == group}
|
||||
shared = next(iter(items.values()))
|
||||
designators = list(items.keys())
|
||||
designators.sort()
|
||||
conn_type = f', {remove_line_breaks(shared.type)}' if shared.type else ''
|
||||
conn_subtype = f', {remove_line_breaks(shared.subtype)}' if shared.subtype else ''
|
||||
conn_pincount = f', {shared.pincount} pins' if shared.style != 'simple' else ''
|
||||
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': 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)
|
||||
for connector in self.connectors.values():
|
||||
if not connector.ignore_in_bom:
|
||||
description = ('Connector'
|
||||
+ (f', {connector.type}' if connector.type else '')
|
||||
+ (f', {connector.subtype}' if connector.subtype else '')
|
||||
+ (f', {connector.pincount} pins' if connector.show_pincount else '')
|
||||
+ (f', {connector.color}' if connector.color else ''))
|
||||
bom_entries.append({
|
||||
'item': description, 'qty': 1, 'unit': None, 'designators': connector.name if connector.show_name else None,
|
||||
'manufacturer': connector.manufacturer, 'mpn': connector.mpn, 'pn': connector.pn
|
||||
})
|
||||
|
||||
# add connectors aditional components to bom
|
||||
bom_entries.extend(self.get_additional_component_bom(connector))
|
||||
|
||||
# cables
|
||||
# TODO: If category can have other non-empty values than 'bundle', maybe it should be part of item name?
|
||||
# The category needs to be included in cable_group to keep the bundles excluded.
|
||||
cable_group = lambda c: (c.category, c.type, c.gauge, c.gauge_unit, c.wirecount, c.shield, c.manufacturer, c.mpn, c.pn)
|
||||
for group in Counter([cable_group(v) for v in self.cables.values() if v.category != 'bundle']):
|
||||
items = {k: v for k, v in self.cables.items() if cable_group(v) == group}
|
||||
shared = next(iter(items.values()))
|
||||
designators = list(items.keys())
|
||||
designators.sort()
|
||||
total_length = sum(i.length for i in items.values())
|
||||
cable_type = f', {remove_line_breaks(shared.type)}' if shared.type else ''
|
||||
gauge_name = f' x {shared.gauge} {shared.gauge_unit}' if shared.gauge else ' wires'
|
||||
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': remove_line_breaks(shared.manufacturer), 'mpn': remove_line_breaks(shared.mpn), 'pn': shared.pn}
|
||||
bom_cables.append(item)
|
||||
# bundles (ignores wirecount)
|
||||
wirelist = []
|
||||
# list all cables again, since bundles are represented as wires internally, with the category='bundle' set
|
||||
for bundle in self.cables.values():
|
||||
if bundle.category == 'bundle':
|
||||
# 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': 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'])
|
||||
for group in Counter([wire_group(v) for v in wirelist]):
|
||||
items = [v for v in wirelist if wire_group(v) == group]
|
||||
shared = items[0]
|
||||
designators = [i['designator'] for i in items]
|
||||
designators = list(dict.fromkeys(designators)) # remove duplicates
|
||||
designators.sort()
|
||||
total_length = sum(i['length'] for i in items)
|
||||
wire_type = f', {remove_line_breaks(shared["type"])}' if shared.get('type', None) else ''
|
||||
gauge_name = f', {shared["gauge"]} {shared["gauge_unit"]}' if shared.get('gauge', None) else ''
|
||||
gauge_color = f', {shared["color"]}' if 'color' in shared != '' else ''
|
||||
name = f'Wire{wire_type}{gauge_name}{gauge_color}'
|
||||
item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators,
|
||||
'manufacturer': shared['manufacturer'], 'mpn': shared['mpn'], 'pn': shared['pn']}
|
||||
bom_cables.append(item)
|
||||
bom_cables = sorted(bom_cables, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050)
|
||||
bom.extend(bom_cables)
|
||||
for cable in self.cables.values():
|
||||
if not cable.ignore_in_bom:
|
||||
if cable.category != 'bundle':
|
||||
# process cable as a single entity
|
||||
description = ('Cable'
|
||||
+ (f', {cable.type}' if cable.type else '')
|
||||
+ (f', {cable.wirecount}')
|
||||
+ (f' x {cable.gauge} {cable.gauge_unit}' if cable.gauge else ' wires')
|
||||
+ (' shielded' if cable.shield else ''))
|
||||
bom_entries.append({
|
||||
'item': description, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None,
|
||||
'manufacturer': cable.manufacturer, 'mpn': cable.mpn, 'pn': cable.pn
|
||||
})
|
||||
else:
|
||||
# add each wire from the bundle to the bom
|
||||
for index, color in enumerate(cable.colors):
|
||||
description = ('Wire'
|
||||
+ (f', {cable.type}' if cable.type else '')
|
||||
+ (f', {cable.gauge} {cable.gauge_unit}' if cable.gauge else '')
|
||||
+ (f', {color}' if color else ''))
|
||||
bom_entries.append({
|
||||
'item': description, 'qty': cable.length, 'unit': 'm', 'designators': cable.name if cable.show_name else None,
|
||||
'manufacturer': index_if_list(cable.manufacturer, index),
|
||||
'mpn': index_if_list(cable.mpn, index), 'pn': index_if_list(cable.pn, index)
|
||||
})
|
||||
|
||||
# add cable/bundles aditional components to bom
|
||||
bom_entries.extend(self.get_additional_component_bom(cable))
|
||||
|
||||
for item in self.additional_bom_items:
|
||||
name = item['description'] if item.get('description', None) else ''
|
||||
if isinstance(item.get('designators', None), List):
|
||||
item['designators'].sort() # sort designators if a list is provided
|
||||
item = {'item': name, 'qty': item.get('qty', None), 'unit': item.get('unit', None), 'designators': item.get('designators', None),
|
||||
'manufacturer': item.get('manufacturer', None), 'mpn': item.get('mpn', None), 'pn': item.get('pn', None)}
|
||||
bom_extra.append(item)
|
||||
bom_extra = sorted(bom_extra, key=lambda k: k['item'])
|
||||
bom.extend(bom_extra)
|
||||
return bom
|
||||
bom_entries.append({
|
||||
'item': item.get('description', ''), 'qty': item.get('qty', 1), 'unit': item.get('unit'), 'designators': item.get('designators'),
|
||||
'manufacturer': item.get('manufacturer'), 'mpn': item.get('mpn'), 'pn': item.get('pn')
|
||||
})
|
||||
|
||||
# remove line breaks if present and cleanup any resulting whitespace issues
|
||||
bom_entries = [{k: clean_whitespace(v) for k, v in entry.items()} for entry in bom_entries]
|
||||
|
||||
# deduplicate bom
|
||||
bom_types_group = lambda bt: (bt['item'], bt['unit'], bt['manufacturer'], bt['mpn'], bt['pn'])
|
||||
for group in Counter([bom_types_group(v) for v in bom_entries]):
|
||||
group_entries = [v for v in bom_entries if bom_types_group(v) == group]
|
||||
designators = []
|
||||
for group_entry in group_entries:
|
||||
if group_entry.get('designators'):
|
||||
if isinstance(group_entry['designators'], List):
|
||||
designators.extend(group_entry['designators'])
|
||||
else:
|
||||
designators.append(group_entry['designators'])
|
||||
designators = list(dict.fromkeys(designators)) # remove duplicates
|
||||
designators.sort()
|
||||
total_qty = sum(entry['qty'] for entry in group_entries)
|
||||
self._bom.append({**group_entries[0], 'qty': round(total_qty, 3), 'designators': designators})
|
||||
|
||||
self._bom = sorted(self._bom, key=lambda k: k['item']) # sort list of dicts by their values (https://stackoverflow.com/a/73050)
|
||||
|
||||
# add an incrementing id to each bom item
|
||||
self._bom = [{**entry, 'id': index} for index, entry in enumerate(self._bom, 1)]
|
||||
return self._bom
|
||||
|
||||
def get_bom_index(self, item, unit, manufacturer, mpn, pn):
|
||||
# Remove linebreaks and clean whitespace of values in search
|
||||
target = tuple(clean_whitespace(v) for v in (item, unit, manufacturer, mpn, pn))
|
||||
for entry in self.bom():
|
||||
if (entry['item'], entry['unit'], entry['manufacturer'], entry['mpn'], entry['pn']) == target:
|
||||
return entry['id']
|
||||
return None
|
||||
|
||||
def bom_list(self):
|
||||
bom = self.bom()
|
||||
keys = ['item', 'qty', 'unit', 'designators'] # these BOM columns will always be included
|
||||
keys = ['id', 'item', 'qty', 'unit', 'designators'] # these BOM columns will always be included
|
||||
for fieldname in ['pn', 'manufacturer', 'mpn']: # these optional BOM columns will only be included if at least one BOM item actually uses them
|
||||
if any(fieldname in x and x.get(fieldname, None) for x in bom):
|
||||
if any(entry.get(fieldname) for entry in bom):
|
||||
keys.append(fieldname)
|
||||
bom_list = []
|
||||
# list of staic bom header names, headers not specified here are generated by capitilising the internal name
|
||||
|
||||
@ -146,11 +146,8 @@ def index_if_list(value, index):
|
||||
def html_line_breaks(inp):
|
||||
return inp.replace('\n', '<br />') if isinstance(inp, str) else inp
|
||||
|
||||
def graphviz_line_breaks(inp):
|
||||
return inp.replace('\n', '\\n') if isinstance(inp, str) else inp # \n generates centered new lines. http://www.graphviz.org/doc/info/attrs.html#k:escString
|
||||
|
||||
def remove_line_breaks(inp):
|
||||
return inp.replace('\n', ' ').strip() if isinstance(inp, str) else inp
|
||||
def clean_whitespace(inp):
|
||||
return ' '.join(inp.split()).replace(' ,', ',') if isinstance(inp, str) else inp
|
||||
|
||||
def open_file_read(filename):
|
||||
# TODO: Intelligently determine encoding
|
||||
@ -181,3 +178,25 @@ def manufacturer_info_field(manufacturer, mpn):
|
||||
return f'{manufacturer if manufacturer else "MPN"}{": " + str(mpn) if mpn else ""}'
|
||||
else:
|
||||
return None
|
||||
|
||||
def component_table_entry(type, qty, unit=None, pn=None, manufacturer=None, mpn=None):
|
||||
output = f'{qty}'
|
||||
if unit:
|
||||
output += f' {unit}'
|
||||
output += f' x {type}'
|
||||
# print an extra line with part and manufacturer information if provided
|
||||
manufacturer_str = manufacturer_info_field(manufacturer, mpn)
|
||||
if pn or manufacturer_str:
|
||||
output += '<br/>'
|
||||
if pn:
|
||||
output += f'P/N: {pn}'
|
||||
if manufacturer_str:
|
||||
output += ', '
|
||||
if manufacturer_str:
|
||||
output += manufacturer_str
|
||||
output = html_line_breaks(output)
|
||||
# format the above output as left aligned text in a single visible cell
|
||||
# indent is set to two to match the indent in the generated html table
|
||||
return f'''<table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr>
|
||||
<td align="left" balign="left">{output}</td>
|
||||
</tr></table>'''
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
## Part numbers
|
||||
## Part numbers and additional components
|
||||
|
||||
* Part number information can be added to parts
|
||||
* Only provided fields will be added to the diagram and bom
|
||||
* Bundles can have part information specified by wire
|
||||
* Additional parts can be added to the bom
|
||||
* Additional parts can be added to components or just to the bom
|
||||
* quantities of additional components can be multiplied by features from parent connector or cable
|
||||
|
||||
@ -5,9 +5,17 @@ connectors:
|
||||
subtype: female
|
||||
manufacturer: Molex # set manufacter name
|
||||
mpn: 22013047 # set manufacturer part number
|
||||
# add a list of additional components to a part (shown in graph)
|
||||
additional_components:
|
||||
-
|
||||
type: Crimp # short identifier used in graph
|
||||
subtype: Molex KK 254, 22-30 AWG # extra information added to type in bom
|
||||
qty_multiplier: populated # multipier for quantity (number of populated pins)
|
||||
manufacturer: Molex # set manufacter name
|
||||
mpn: 08500030 # set manufacturer part number
|
||||
X2:
|
||||
<<: *template1 # reuse template
|
||||
pn: CON4 # set an internal part number
|
||||
pn: CON4 # set an internal part number for just this connector
|
||||
X3:
|
||||
<<: *template1 # reuse template
|
||||
|
||||
@ -28,6 +36,14 @@ cables:
|
||||
manufacturer: [WiresCo,WiresCo,WiresCo,WiresCo] # set a manufacter per wire
|
||||
mpn: [W1-YE,W1-BK,W1-BK,W1-RD]
|
||||
pn: [WIRE1,WIRE2,WIRE2,WIRE3]
|
||||
# add a list of additional components to a part (shown in graph)
|
||||
additional_components:
|
||||
-
|
||||
type: Sleve # short identifier used in graph
|
||||
subtype: Braided nylon, black, 3mm # extra information added to type in bom
|
||||
qty_multiplier: length # multipier for quantity (length of cable)
|
||||
unit: m
|
||||
pn: SLV-1
|
||||
|
||||
|
||||
connections:
|
||||
@ -41,7 +57,7 @@ connections:
|
||||
- X3: [1-4]
|
||||
|
||||
additional_bom_items:
|
||||
- # define an additional item to add to the bill of materials
|
||||
- # define an additional item to add to the bill of materials (does not appear in graph)
|
||||
description: Label, pinout information
|
||||
qty: 2
|
||||
designators:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user