diff --git a/examples/ex16.yml b/examples/ex16.yml
new file mode 100644
index 0000000..6cb58b0
--- /dev/null
+++ b/examples/ex16.yml
@@ -0,0 +1,46 @@
+connectors:
+ X1:
+ type: Connector
+ subtype: Male
+ pincount: 2
+ pins: [1, 2]
+ pinlabels: [A, B]
+ X2:
+ type: Connector
+ subtype: Female
+ pincount: 2
+ pins: [1, 2]
+ pinlabels: [A, B]
+
+conduit-connectors:
+ X1:
+ type: Conduit Connector
+ X2:
+ type: Conduit Connector
+
+cables:
+ W1:
+ wirecount: 1
+ colors: [BU]
+ W2:
+ wirecount: 1
+ colors: [BN]
+
+conduits:
+ C1:
+ type: Conduit
+ ports: 2
+ colors: [BU, BN]
+
+connections:
+ - - X1
+ - W1
+ - X2
+ - - X1
+ - W2
+ - X2
+
+conduit-connections:
+ - - X1
+ - C1
+ - X2
\ No newline at end of file
diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py
index d14e445..4a8d007 100644
--- a/src/wireviz/DataClasses.py
+++ b/src/wireviz/DataClasses.py
@@ -55,6 +55,7 @@ class Options:
bgcolor_connector: Optional[Color] = None
bgcolor_cable: Optional[Color] = None
bgcolor_bundle: Optional[Color] = None
+ bgcolor_conduit: Optional[Color] = None
color_mode: ColorMode = "SHORT"
mini_bom_mode: bool = True
template_separator: str = "."
@@ -68,6 +69,8 @@ class Options:
self.bgcolor_cable = self.bgcolor_node
if not self.bgcolor_bundle:
self.bgcolor_bundle = self.bgcolor_cable
+ if not self.bgcolor_conduit:
+ self.bgcolor_conduit = self.bgcolor_cable
@dataclass
@@ -165,6 +168,15 @@ class Connector:
additional_components: List[AdditionalComponent] = field(default_factory=list)
def __post_init__(self) -> None:
+ if isinstance(self, ConduitConnector):
+ # ConduitConnectors don't need pins
+ if isinstance(self.image, dict):
+ self.image = Image(**self.image)
+ for i, item in enumerate(self.additional_components):
+ if isinstance(item, dict):
+ self.additional_components[i] = AdditionalComponent(**item)
+ return
+
if isinstance(self.image, dict):
self.image = Image(**self.image)
@@ -242,6 +254,11 @@ class Connector:
)
+@dataclass
+class ConduitConnector(Connector):
+ pass
+
+
@dataclass
class Cable:
name: Designator
@@ -272,6 +289,7 @@ class Cable:
show_wirenumbers: Optional[bool] = None
ignore_in_bom: bool = False
additional_components: List[AdditionalComponent] = field(default_factory=list)
+ conduits: List[str] = None
def __post_init__(self) -> None:
if isinstance(self.image, dict):
@@ -293,11 +311,11 @@ class Cable:
if u.upper() == "AWG":
self.gauge_unit = u.upper()
else:
- self.gauge_unit = u.replace("mm2", "mm\u00B2")
+ self.gauge_unit = u.replace("mm2", "mm\u00b2")
elif self.gauge is not None: # gauge specified, assume mm2
if self.gauge_unit is None:
- self.gauge_unit = "mm\u00B2"
+ self.gauge_unit = "mm\u00b2"
else:
pass # gauge not specified
@@ -322,45 +340,54 @@ class Cable:
self.connections = []
- if self.wirecount: # number of wires explicitly defined
- if self.colors: # use custom color palette (partly or looped if needed)
- pass
- elif self.color_code:
- # use standard color palette (partly or looped if needed)
- if self.color_code not in COLOR_CODES:
- raise Exception("Unknown color code")
- self.colors = COLOR_CODES[self.color_code]
- else: # no colors defined, add dummy colors
- self.colors = [""] * self.wirecount
+ if not isinstance(self, Conduit):
+ if self.wirecount: # number of wires explicitly defined
+ if self.colors: # use custom color palette (partly or looped if needed)
+ pass
+ elif self.color_code:
+ # use standard color palette (partly or looped if needed)
+ if self.color_code not in COLOR_CODES:
+ raise Exception("Unknown color code")
+ self.colors = COLOR_CODES[self.color_code]
+ else: # no colors defined, add dummy colors
+ self.colors = [""] * self.wirecount
- # make color code loop around if more wires than colors
- if self.wirecount > len(self.colors):
- m = self.wirecount // len(self.colors) + 1
- self.colors = self.colors * int(m)
- # cut off excess after looping
- self.colors = self.colors[: self.wirecount]
- else: # wirecount implicit in length of color list
- if not self.colors:
- raise Exception(
- "Unknown number of wires. Must specify wirecount or colors (implicit length)"
- )
- self.wirecount = len(self.colors)
+ # make color code loop around if more wires than colors
+ if self.wirecount > len(self.colors):
+ m = self.wirecount // len(self.colors) + 1
+ self.colors = self.colors * int(m)
+ # cut off excess after looping
+ self.colors = self.colors[: self.wirecount]
+ else: # wirecount implicit in length of color list
+ if not self.colors:
+ raise Exception(
+ "Unknown number of wires. Must specify wirecount or colors (implicit length)"
+ )
+ self.wirecount = len(self.colors)
- if self.wirelabels:
- if self.shield and "s" in self.wirelabels:
- raise Exception(
- '"s" may not be used as a wire label for a shielded cable.'
- )
+ if self.wirelabels:
+ if self.shield and "s" in self.wirelabels:
+ raise Exception(
+ '"s" may not be used as a wire label for a shielded cable.'
+ )
- # if lists of part numbers are provided check this is a bundle and that it matches the wirecount.
- for idfield in [self.manufacturer, self.mpn, self.supplier, self.spn, self.pn]:
- if isinstance(idfield, list):
- if self.category == "bundle":
- # check the length
- if len(idfield) != self.wirecount:
- raise Exception("lists of part data must match wirecount")
- else:
- raise Exception("lists of part data are only supported for bundles")
+ # if lists of part numbers are provided check this is a bundle and that it matches the wirecount.
+ for idfield in [
+ self.manufacturer,
+ self.mpn,
+ self.supplier,
+ self.spn,
+ self.pn,
+ ]:
+ if isinstance(idfield, list):
+ if self.category == "bundle":
+ # check the length
+ if len(idfield) != self.wirecount:
+ raise Exception("lists of part data must match wirecount")
+ else:
+ raise Exception(
+ "lists of part data are only supported for bundles"
+ )
if self.show_name is None:
# hide designators for auto-generated cables by default
@@ -410,6 +437,33 @@ class Cable:
)
+@dataclass
+class Conduit(Cable):
+ cables: List[Cable] = field(default_factory=list)
+ ports: int = 0
+ cableports: dict[str] = field(default_factory=dict)
+
+ def get_port(self, cable: Cable, port: int) -> int:
+ conduit_port = None
+ if cable.name in self.cableports:
+ if len(self.cableports[cable.name]) >= port:
+ conduit_port = self.cableports[cable.name][port - 1]
+ else:
+ self.cableports[cable.name] = []
+
+ if conduit_port:
+ return conduit_port
+ else:
+ self.cableports[cable.name].extend(
+ [0] * (port - len(self.cableports[cable.name]))
+ )
+
+ self.ports = self.ports + 1
+ self.cableports[cable.name][port - 1] = self.ports
+ self.colors.append(cable.colors[port - 1])
+ return self.ports
+
+
@dataclass
class Connection:
from_name: Optional[Designator]
diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py
index c4af236..27b9bf9 100644
--- a/src/wireviz/Harness.py
+++ b/src/wireviz/Harness.py
@@ -8,9 +8,12 @@ from pathlib import Path
from typing import Any, List, Union
from graphviz import Graph
+
from wireviz import APP_NAME, APP_URL, __version__, wv_colors
from wireviz.DataClasses import (
Cable,
+ Conduit,
+ ConduitConnector,
Connector,
MateComponent,
MatePin,
@@ -73,7 +76,9 @@ class Harness:
def __post_init__(self):
self.connectors = {}
+ self.conduit_connectors = {}
self.cables = {}
+ self.conduits = {}
self.mates = []
self._bom = [] # Internal Cache for generated bom
self.additional_bom_items = []
@@ -82,9 +87,15 @@ class Harness:
check_old(f"Connector '{name}'", OLD_CONNECTOR_ATTR, kwargs)
self.connectors[name] = Connector(name, *args, **kwargs)
+ def add_conduit_connector(self, name: str, *args, **kwargs) -> None:
+ self.conduit_connectors[name] = ConduitConnector(name, *args, **kwargs)
+
def add_cable(self, name: str, *args, **kwargs) -> None:
self.cables[name] = Cable(name, *args, **kwargs)
+ def add_conduit(self, name: str, *args, **kwargs) -> None:
+ self.conduits[name] = Conduit(name, *args, **kwargs)
+
def add_mate_pin(self, from_name, from_pin, to_name, to_pin, arrow_type) -> None:
self.mates.append(MatePin(from_name, from_pin, to_name, to_pin, arrow_type))
self.connectors[from_name].activate_pin(from_pin, Side.RIGHT)
@@ -100,6 +111,7 @@ class Harness:
self,
from_name: str,
from_pin: (int, str),
+ conduits: [str],
via_name: str,
via_wire: (int, str),
to_name: str,
@@ -128,7 +140,7 @@ class Harness:
if not pin in connector.pins:
raise Exception(f"{name}:{pin} not found.")
- # check via cable
+ # check via cable or conduit
if via_name in self.cables:
cable = self.cables[via_name]
# check if provided name is ambiguous
@@ -153,9 +165,27 @@ class Harness:
via_wire = (
cable.wirelabels.index(via_wire) + 1
) # list index starts at 0, wire IDs start at 1
+ cable.conduits = conduits
+ elif via_name in self.conduits:
+ conduit = self.conduits[via_name]
+ # for conduits, via_wire is the port number
+ if not isinstance(via_wire, int):
+ raise Exception(
+ f"{via_name}:{via_wire} must be an integer port number for conduits."
+ )
+ if via_wire < 1 or via_wire > conduit.ports:
+ raise Exception(f"{via_name}:{via_wire} port out of range.")
+ conduit.conduits = conduits
# perform the actual connection
- self.cables[via_name].connect(from_name, from_pin, via_wire, to_name, to_pin)
+ if via_name in self.cables:
+ self.cables[via_name].connect(
+ from_name, from_pin, via_wire, to_name, to_pin
+ )
+ elif via_name in self.conduits:
+ self.conduits[via_name].connect(
+ from_name, from_pin, via_wire, to_name, to_pin
+ )
if from_name in self.connectors:
self.connectors[from_name].activate_pin(from_pin, Side.RIGHT)
if to_name in self.connectors:
@@ -302,10 +332,10 @@ class Harness:
# Only convert units we actually know about, i.e. currently
# mm2 and awg --- other units _are_ technically allowed,
# and passed through as-is.
- if cable.gauge_unit == "mm\u00B2":
+ if cable.gauge_unit == "mm\u00b2":
awg_fmt = f" ({awg_equiv(cable.gauge)} AWG)"
elif cable.gauge_unit.upper() == "AWG":
- awg_fmt = f" ({mm2_equiv(cable.gauge)} mm\u00B2)"
+ awg_fmt = f" ({mm2_equiv(cable.gauge)} mm\u00b2)"
# fmt: off
rows = [[f'{html_bgcolor(cable.bgcolor_title)}{remove_links(cable.name)}'
@@ -532,6 +562,79 @@ class Harness:
fillcolor=translate_color(bgcolor, "HEX"),
)
+ for conduit in self.conduits.values():
+ html = []
+
+ awg_fmt = ""
+ if conduit.show_equiv:
+ # Only convert units we actually know about, i.e. currently
+ # mm2 and awg --- other units _are_ technically allowed,
+ # and passed through as-is.
+ if conduit.gauge_unit == "mm\u00b2":
+ awg_fmt = f" ({awg_equiv(conduit.gauge)} AWG)"
+ elif conduit.gauge_unit.upper() == "AWG":
+ awg_fmt = f" ({mm2_equiv(conduit.gauge)} mm\u00b2)"
+
+ # fmt: off
+ rows = [[f'{html_bgcolor(conduit.bgcolor_title)}{remove_links(conduit.name)}'
+ if conduit.show_name else None],
+ [pn_info_string(HEADER_PN, None,
+ remove_links(conduit.pn)) if not isinstance(conduit.pn, list) else None,
+ html_line_breaks(pn_info_string(HEADER_MPN,
+ conduit.manufacturer if not isinstance(conduit.manufacturer, list) else None,
+ conduit.mpn if not isinstance(conduit.mpn, list) else None)),
+ html_line_breaks(pn_info_string(HEADER_SPN,
+ conduit.supplier if not isinstance(conduit.supplier, list) else None,
+ conduit.spn if not isinstance(conduit.spn, list) else None))],
+ [html_line_breaks(conduit.type),
+ f'{conduit.gauge} {conduit.gauge_unit}{awg_fmt}' if conduit.gauge else None,
+ f'{conduit.length} {conduit.length_unit}' if conduit.length > 0 else None,
+ translate_color(conduit.color, self.options.color_mode) if conduit.color else None,
+ html_colorbar(cable.color)],
+ '',
+ [html_image(conduit.image)],
+ [html_caption(conduit.image)]]
+ # fmt: on
+
+ rows.extend(get_additional_component_table(self, conduit))
+ rows.append([html_line_breaks(conduit.notes)])
+ html.extend(nested_html_table(rows, html_bgcolor_attr(conduit.bgcolor)))
+
+ wirehtml = []
+ # conductor table
+ wirehtml.append('
')
+ wirehtml.append(" | |
")
+
+ for i in range(1, conduit.ports + 1):
+ # fmt: off
+ bgcolors = ['#000000'] + get_color_hex(conduit.colors[i - 1], pad=pad) + ['#000000']
+ wirehtml.append(f" ")
+ wirehtml.append(f' ')
+ wirehtml.append(' ')
+ for j, bgcolor in enumerate(bgcolors[::-1]): # Reverse to match the curved wires when more than 2 colors
+ wirehtml.append(f' | ')
+ wirehtml.append(" ")
+ wirehtml.append(" | ")
+ wirehtml.append("
")
+ # fmt: on
+
+ wirehtml.append(" | |
")
+
+ wirehtml.append("
")
+
+ html = [
+ row.replace("", "\n".join(wirehtml)) for row in html
+ ]
+
+ html = "\n".join(html)
+ dot.node(
+ conduit.name,
+ label=f"<\n{html}\n>",
+ shape="box",
+ style="dotted",
+ fillcolor=translate_color(self.options.bgcolor_conduit, "HEX"),
+ )
+
# mates
for mate in self.mates:
if mate.shape[-1] == ">":
diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py
index 391b1ac..d0d029b 100755
--- a/src/wireviz/wireviz.py
+++ b/src/wireviz/wireviz.py
@@ -109,7 +109,10 @@ def parse(
# containers for parsed component data and connection sets
template_connectors = {}
template_cables = {}
+ template_conduits = {}
+ template_conduit_connectors = {}
connection_sets = []
+ conduit_connection_sets = []
# actual harness
harness = Harness(
metadata=Metadata(**yaml_data.get("metadata", {})),
@@ -129,8 +132,15 @@ def parse(
# add items
# parse YAML input file ====================================================
- sections = ["connectors", "cables", "connections"]
- types = [dict, dict, list]
+ sections = [
+ "connectors",
+ "cables",
+ "conduits",
+ "conduit-connectors",
+ "connections",
+ "conduit-connections",
+ ]
+ types = [dict, dict, dict, dict, list, list]
for sec, ty in zip(sections, types):
if sec in yaml_data and type(yaml_data[sec]) == ty: # section exists
if len(yaml_data[sec]) > 0: # section has contents
@@ -149,6 +159,10 @@ def parse(
template_connectors[key] = attribs
elif sec == "cables":
template_cables[key] = attribs
+ elif sec == "conduits":
+ template_conduits[key] = attribs
+ elif sec == "conduit-connectors":
+ template_conduit_connectors[key] = attribs
else: # section exists but is empty
pass
else: # section does not exist, create empty section
@@ -158,6 +172,8 @@ def parse(
yaml_data[sec] = []
connection_sets = yaml_data["connections"]
+ conduit_connection_sets = yaml_data.get("conduit-connections", [])
+ conduit_dict = {}
# go through connection sets, generate and connect components ==============
@@ -192,6 +208,7 @@ def parse(
# utilities to check for alternating connectors and cables/arrows ==========
alternating_types = ["connector", "cable/arrow"]
+ alternating_types_conduit = ["conduit-connector", "conduit"]
expected_type = None
def check_type(designator, template, actual_type):
@@ -204,7 +221,7 @@ def parse(
f'Expected {expected_type}, but "{designator}" ("{template}") is {actual_type}'
)
- def alternate_type(): # flip between connector and cable/arrow
+ def alternate_type(alternating_types): # flip between types
nonlocal expected_type
expected_type = alternating_types[1 - alternating_types.index(expected_type)]
@@ -307,7 +324,9 @@ def parse(
f"{template} is an unknown template/designator/arrow."
)
- alternate_type() # entries in connection set must alternate between connectors and cables/arrows
+ alternate_type(
+ alternating_types
+ ) # entries in connection set must alternate between connectors and cables/arrows
# transpose connection set list
# before: one item per component, one subitem per connection in set
@@ -336,7 +355,7 @@ def parse(
entry[index_item + 1]
)
harness.connect(
- from_name, from_pin, via_name, via_pin, to_name, to_pin
+ from_name, from_pin, [], via_name, via_pin, to_name, to_pin
)
elif is_arrow(designator):
@@ -362,10 +381,172 @@ def parse(
# mate two connectors as a whole
harness.add_mate_component(from_name, to_name, designator)
+ # go through conduit connection sets, generate and connect conduit components ==============
+
+ for connection_set in conduit_connection_sets:
+ # figure out number of parallel connections within this set
+ connectioncount = []
+ for entry in connection_set:
+ if isinstance(entry, list):
+ connectioncount.append(len(entry))
+ elif isinstance(entry, dict):
+ connectioncount.append(len(expand(list(entry.values())[0])))
+ # e.g.: - X1: [1-4,6] yields 5
+ else:
+ pass # strings do not reveal connectioncount
+ if not any(connectioncount):
+ # no item in the list revealed connection count;
+ # assume connection count is 1
+ connectioncount = [1]
+ # Example: The following is a valid connection set,
+ # even though no item reveals the connection count;
+ # the count is not needed because only a component-level mate happens.
+ # -
+ # - CONNECTOR
+ # - ==>
+ # - CONNECTOR
+
+ # check that all entries are the same length
+ if len(set(connectioncount)) > 1:
+ raise Exception(
+ "All items in connection set must reference the same number of connections"
+ )
+ # all entries are the same length, connection count is set
+ connectioncount = connectioncount[0]
+
+ # expand string entries to list entries of correct length
+ for index, entry in enumerate(connection_set):
+ if isinstance(entry, str):
+ connection_set[index] = [entry] * connectioncount
+
+ # resolve all designators
+ for index, entry in enumerate(connection_set):
+ if isinstance(entry, list):
+ for subindex, item in enumerate(entry):
+ template, designator = resolve_designator(
+ item, template_separator_char
+ )
+ connection_set[index][subindex] = designator
+ elif isinstance(entry, dict):
+ key = list(entry.keys())[0]
+ template, designator = resolve_designator(key, template_separator_char)
+ value = entry[key]
+ connection_set[index] = {designator: value}
+ else:
+ pass # string entries have been expanded in previous step
+
+ # expand all pin lists
+ for index, entry in enumerate(connection_set):
+ if isinstance(entry, list):
+ connection_set[index] = [{designator: 1} for designator in entry]
+ elif isinstance(entry, dict):
+ designator = list(entry.keys())[0]
+ pinlist = expand(entry[designator])
+ connection_set[index] = [{designator: pin} for pin in pinlist]
+ else:
+ pass # string entries have been expanded in previous step
+
+ # Populate wiring harness ==============================================
+
+ expected_type = None # reset check for alternating types
+ # at the beginning of every connection set
+ # since each set may begin with either type
+
+ # generate components
+ for entry in connection_set:
+ for item in entry:
+ designator = list(item.keys())[0]
+ template = designators_and_templates[designator]
+
+ if (
+ designator in harness.conduit_connectors
+ ): # existing conduit connector instance
+ check_type(designator, template, "conduit-connector")
+ elif template in template_conduit_connectors.keys():
+ # generate new conduit connector instance from template
+ check_type(designator, template, "conduit-connector")
+ harness.add_conduit_connector(
+ name=designator, **template_conduit_connectors[template]
+ )
+
+ elif designator in harness.conduits: # existing conduit instance
+ check_type(designator, template, "conduit")
+ elif template in template_conduits.keys():
+ # generate new conduit instance from template
+ check_type(designator, template, "conduit")
+ harness.add_conduit(name=designator, **template_conduits[template])
+
+ else:
+ raise Exception(
+ f"{template} is an unknown conduit template/designator."
+ )
+
+ alternate_type(
+ alternating_types_conduit
+ ) # entries in connection set must alternate between conduit-connectors and conduits
+
+ # transpose connection set list
+ # before: one item per component, one subitem per connection in set
+ # after: one item per connection in set, one subitem per component
+ connection_set = list(map(list, zip(*connection_set)))
+
+ # connect conduit components
+ for index_entry, entry in enumerate(connection_set):
+ for index_item, item in enumerate(entry):
+ designator = list(item.keys())[0]
+
+ if designator in harness.conduits:
+ if index_item == 0:
+ # list started with a conduit, no conduit connector to join on left side
+ from_name, from_pin = (None, None)
+ else:
+ from_name, from_pin = get_single_key_and_value(
+ entry[index_item - 1]
+ )
+ via_name, via_pin = (designator, item[designator])
+ if index_item == len(entry) - 1:
+ # list ends with a conduit, no conduit connector to join on right side
+ to_name, to_pin = (None, None)
+ else:
+ to_name, to_pin = get_single_key_and_value(
+ entry[index_item + 1]
+ )
+ harness.connect(
+ from_name, from_pin, [], via_name, via_pin, to_name, to_pin
+ )
+
+ # build conduit_dict
+ for conduit_connection_set in conduit_connection_sets:
+ for connection in conduit_connection_set:
+ if len(connection) == 3:
+ from_name = connection[0]
+ via_name = connection[1]
+ to_name = connection[2]
+ if via_name in harness.conduits:
+ conduit = via_name
+ conduit_connectors = [from_name, to_name]
+ for cable_name, cable in harness.cables.items():
+ for conn in cable.connections:
+ if (
+ conn.from_name in conduit_connectors
+ or conn.to_name in conduit_connectors
+ ):
+ if cable_name not in conduit_dict:
+ conduit_dict[cable_name] = []
+ if conduit not in conduit_dict[cable_name]:
+ conduit_dict[cable_name].append(conduit)
+
+ # set conduits for cables
+ for cable_name, cable in harness.cables.items():
+ cable.conduits = conduit_dict.get(cable_name, [])
+
# warn about unused templates
- proposed_components = list(template_connectors.keys()) + list(
- template_cables.keys()
+ proposed_components = (
+ list(template_connectors.keys())
+ + list(template_cables.keys())
+ + list(template_conduits.keys())
+ + list(template_conduit_connectors.keys())
)
used_components = set(designators_and_templates.values())
forgotten_components = [c for c in proposed_components if not c in used_components]
diff --git a/src/wireviz/wv_bom.py b/src/wireviz/wv_bom.py
index 7eecfdf..2224a84 100644
--- a/src/wireviz/wv_bom.py
+++ b/src/wireviz/wv_bom.py
@@ -185,6 +185,56 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]:
# add cable/bundles aditional components to bom
bom_entries.extend(get_additional_component_bom(cable))
+ # conduits
+ for conduit in harness.conduits.values():
+ if not conduit.ignore_in_bom:
+ description = (
+ "Conduit"
+ + (f", {conduit.type}" if conduit.type else "")
+ + (f", {conduit.gauge} {conduit.gauge_unit}" if conduit.gauge else "")
+ + (
+ f", {conduit.length} {conduit.length_unit}"
+ if conduit.length > 0
+ else ""
+ )
+ + (
+ f", {translate_color(conduit.color, harness.options.color_mode)}"
+ if conduit.color
+ else ""
+ )
+ )
+ bom_entries.append(
+ {
+ "description": description,
+ "qty": conduit.length,
+ "unit": conduit.length_unit,
+ "designators": conduit.name if conduit.show_name else None,
+ **optional_fields(conduit),
+ }
+ )
+
+ # add conduits aditional components to bom
+ bom_entries.extend(get_additional_component_bom(conduit))
+
+ # conduit connectors
+ for conduit_connector in harness.conduit_connectors.values():
+ if not conduit_connector.ignore_in_bom:
+ description = "Conduit Connector" + (
+ f", {conduit_connector.type}" if conduit_connector.type else ""
+ )
+ bom_entries.append(
+ {
+ "description": description,
+ "designators": (
+ conduit_connector.name if conduit_connector.show_name else None
+ ),
+ **optional_fields(conduit_connector),
+ }
+ )
+
+ # add conduit connectors aditional components to bom
+ bom_entries.extend(get_additional_component_bom(conduit_connector))
+
# add harness aditional components to bom directly, as they both are List[BOMEntry]
bom_entries.extend(harness.additional_bom_items)
@@ -204,9 +254,11 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]:
bom.append(
{
**group_entries[0],
- "qty": int(total_qty)
- if float(total_qty).is_integer()
- else round(total_qty, 3),
+ "qty": (
+ int(total_qty)
+ if float(total_qty).is_integer()
+ else round(total_qty, 3)
+ ),
"designators": sorted(set(designators)),
}
)
diff --git a/src/wireviz/wv_html.py b/src/wireviz/wv_html.py
index 4230110..d928446 100644
--- a/src/wireviz/wv_html.py
+++ b/src/wireviz/wv_html.py
@@ -111,9 +111,9 @@ def generate_html_output(
if isinstance(entry, Dict):
replacements[f""] = str(category)
for entry_key, entry_value in entry.items():
- replacements[
- f""
- ] = html_line_breaks(str(entry_value))
+ replacements[f""] = (
+ html_line_breaks(str(entry_value))
+ )
elif isinstance(entry, (str, int, float)):
pass # TODO?: replacements[f""] = html_line_breaks(str(entry))