Add Conduit support for cable bundling
- Add Conduit and ConduitConnector classes in DataClasses.py - Extend parsing in wireviz.py for conduits/conduit-connectors/conduit-connections - Add rendering logic in Harness.py (dotted style, port colors) - Update BOM in wv_bom.py - Add example ex16.yml Refs #485
This commit is contained in:
parent
e4fe099f8c
commit
bed9f43d04
46
examples/ex16.yml
Normal file
46
examples/ex16.yml
Normal file
@ -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
|
||||
@ -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]
|
||||
|
||||
@ -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)],
|
||||
'<!-- wire table -->',
|
||||
[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('<table border="0" cellspacing="0" cellborder="0">')
|
||||
wirehtml.append(" <tr><td> </td></tr>")
|
||||
|
||||
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" <tr>")
|
||||
wirehtml.append(f' <td colspan="3" border="0" cellspacing="0" cellpadding="0" port="w{i}" height="{(2 * len(bgcolors))}">')
|
||||
wirehtml.append(' <table cellspacing="0" cellborder="0" border="0">')
|
||||
for j, bgcolor in enumerate(bgcolors[::-1]): # Reverse to match the curved wires when more than 2 colors
|
||||
wirehtml.append(f' <tr><td colspan="3" cellpadding="0" height="2" bgcolor="{bgcolor if bgcolor != "" else wv_colors.default_color}" border="0"></td></tr>')
|
||||
wirehtml.append(" </table>")
|
||||
wirehtml.append(" </td>")
|
||||
wirehtml.append(" </tr>")
|
||||
# fmt: on
|
||||
|
||||
wirehtml.append(" <tr><td> </td></tr>")
|
||||
|
||||
wirehtml.append(" </table>")
|
||||
|
||||
html = [
|
||||
row.replace("<!-- wire table -->", "\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] == ">":
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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)),
|
||||
}
|
||||
)
|
||||
|
||||
@ -111,9 +111,9 @@ def generate_html_output(
|
||||
if isinstance(entry, Dict):
|
||||
replacements[f"<!-- %{item}_{index+1}% -->"] = str(category)
|
||||
for entry_key, entry_value in entry.items():
|
||||
replacements[
|
||||
f"<!-- %{item}_{index+1}_{entry_key}% -->"
|
||||
] = html_line_breaks(str(entry_value))
|
||||
replacements[f"<!-- %{item}_{index+1}_{entry_key}% -->"] = (
|
||||
html_line_breaks(str(entry_value))
|
||||
)
|
||||
elif isinstance(entry, (str, int, float)):
|
||||
pass # TODO?: replacements[f"<!-- %{item}_{category}% -->"] = html_line_breaks(str(entry))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user