From e145ef33c0795a7d49b0a9bf0500f489865697b3 Mon Sep 17 00:00:00 2001 From: Wesley Leismer Date: Tue, 9 Jun 2026 17:09:18 -0400 Subject: [PATCH 1/2] feature: draw a colored braided sleeve around a cable bundle (#503) Add an optional `sleeve_color` cable attribute. When set, the conductor bundle is drawn inside a colored braided sleeve - a two-tone basket-weave band across the top and bottom of the conductor table with thin colored side rails - and a "+ Sleeve" indicator with the color name is added to the cable header. Upstream can already list a sleeve via additional_components (BOM only); this draws it on the diagram. Composes with shield, works for bundles and sheathed cables, and has no effect when unset. Refs #503 --- docs/CHANGELOG.md | 4 +++ docs/syntax.md | 3 +++ src/wireviz/wv_dataclasses.py | 10 +++++--- src/wireviz/wv_graphviz.py | 46 ++++++++++++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3303aa037..c8c7d5fc1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,10 @@ TODO +### New features + +- Add `sleeve_color` cable attribute to draw a colored braided sleeve/wrap around a bundle ([#503](https://github.com/wireviz/WireViz/issues/503)) + ## [0.4.1](https://github.com/wireviz/WireViz/tree/v0.4.1) (2024-07-13) diff --git a/docs/syntax.md b/docs/syntax.md index 77fce9ab9..d92bccb49 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -115,6 +115,9 @@ tweak: # optional tweaking of .gv output # using a color (see below) will render the shield in that color # A shield can be accessed by using 's' as the wire ID color: # see below + sleeve_color: # defaults to none; see below + # draws a colored braided sleeve/wrap around the bundle + # and shows a "+ Sleeve" indicator with the color name image: # see below notes: diff --git a/src/wireviz/wv_dataclasses.py b/src/wireviz/wv_dataclasses.py index ff7fa4b77..aedda7e9a 100644 --- a/src/wireviz/wv_dataclasses.py +++ b/src/wireviz/wv_dataclasses.py @@ -510,7 +510,7 @@ def gauge_str(self): if not self.gauge: return None actual_gauge = f"{self.gauge.number} {self.gauge.unit}" - actual_gauge = actual_gauge.replace("mm2", "mm\u00B2") + actual_gauge = actual_gauge.replace("mm2", "mm\u00b2") return actual_gauge @property @@ -547,6 +547,9 @@ class Cable(TopLevelGraphicalComponent): # wire information in particular wirecount: Optional[int] = None shield: Union[bool, MultiColor] = False + sleeve_color: Optional[SingleColor] = ( + None # outer wrap/sleeve drawn around the bundle + ) colors: List[str] = field(default_factory=list) # legacy wirelabels: List[Wire] = field(default_factory=list) # legacy wire_objects: Dict[Any, WireClass] = field(default_factory=dict) # new @@ -571,7 +574,7 @@ def gauge_str(self): if not self.gauge: return None actual_gauge = f"{self.gauge.number} {self.gauge.unit}" - actual_gauge = actual_gauge.replace("mm2", "mm\u00B2") + actual_gauge = actual_gauge.replace("mm2", "mm\u00b2") return actual_gauge @property @@ -587,7 +590,7 @@ def gauge_str_with_equiv(self): elif self.gauge.unit.upper() == "AWG": equivalent_gauge = f" ({mm2_equiv(self.gauge.number)} mm2)" out = f"{actual_gauge}{equivalent_gauge}" - out = out.replace("mm2", "mm\u00B2") + out = out.replace("mm2", "mm\u00b2") return out @property @@ -647,6 +650,7 @@ def __post_init__(self) -> None: self.bgcolor_title = SingleColor(self.bgcolor_title) self.color = MultiColor(self.color) + self.sleeve_color = SingleColor(self.sleeve_color) # cables do not support custom qty or amount if self.qty is None: diff --git a/src/wireviz/wv_graphviz.py b/src/wireviz/wv_graphviz.py index 60d89ed1f..8de898370 100644 --- a/src/wireviz/wv_graphviz.py +++ b/src/wireviz/wv_graphviz.py @@ -61,6 +61,8 @@ def gv_node_component(component: Component) -> Table: "+ S" if component.shield else None, component.length_str, str(component.color) if component.color else None, + "+ Sleeve" if component.sleeve_color else None, + str(component.sleeve_color) if component.sleeve_color else None, ] if component.additional_parameters: @@ -301,9 +303,47 @@ def gv_connector_loops(connector: Connector) -> List: return loop_edges +def gv_sleeve_braid_band( + hex_main: str, ncells: int = 26, cw: int = 9, ch: int = 5 +) -> Table: + """Return a Table that fakes a braided-sleeve band. + + Two offset rows of alternating cells (the sleeve color and a darker shade) + read as an interlaced basket weave -- the closest braid approximation that + GraphViz HTML labels allow without diagonal hatching or image tiling. + """ + try: + h = hex_main.lstrip("#") + r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16) + f = 0.55 # darken factor for the interlacing strands + hex_alt = f"#{int(r * f):02x}{int(g * f):02x}{int(b * f):02x}" + except (ValueError, IndexError): + hex_alt = hex_main # non-hex color name: fall back to a single tone + band_rows = [] + for row in range(2): + cells = [ + Td( + "", + bgcolor=(hex_main if (c + row) % 2 == 0 else hex_alt), + width=cw, + height=ch, + border=0, + ) + for c in range(ncells) + ] + band_rows.append(Tr(cells)) + return Table(band_rows, border=0, cellborder=0, cellspacing=0, cellpadding=0) + + def gv_conductor_table(cable) -> Table: rows = [] + # a colored sleeve is drawn as a woven braid band across the top and bottom + # of the conductor bundle, with thin colored side rails around the table + sleeve_hex = cable.sleeve_color.html if cable.sleeve_color else None + sleeve_colspan = 6 if cable.category == "bundle" else 5 rows.append(Tr(Td(" "))) # spacer row on top + if sleeve_hex: + rows.append(Tr(Td(gv_sleeve_braid_band(sleeve_hex), colspan=sleeve_colspan))) inserted_break_inbetween = False for wire in cable.wire_objects.values(): @@ -358,7 +398,11 @@ def gv_conductor_table(cable) -> Table: rows.append(Tr(Td(table_below, colspan=len(cells_above)))) rows.append(Tr(Td(" "))) # spacer row on bottom - tbl = Table(rows, border=0, cellborder=0, cellspacing=0) + if sleeve_hex: + rows.append(Tr(Td(gv_sleeve_braid_band(sleeve_hex), colspan=sleeve_colspan))) + tbl = Table(rows, border=2, color=sleeve_hex, cellborder=0, cellspacing=0) + else: + tbl = Table(rows, border=0, cellborder=0, cellspacing=0) return tbl From 9457efc40b1751b7ba9e090f6601e63b37a6c71d Mon Sep 17 00:00:00 2001 From: Wesley Leismer Date: Wed, 10 Jun 2026 08:21:48 -0400 Subject: [PATCH 2/2] docs: add tutorial09 demonstrating sleeve_color (#503) Concise tutorial example (source .yml + .md description) showing how to draw a colored braided sleeve around a bundle. Generated outputs are not committed; the owner rebuilds them. Refs #503 --- tutorial/tutorial09.md | 7 +++++++ tutorial/tutorial09.yml | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tutorial/tutorial09.md create mode 100644 tutorial/tutorial09.yml diff --git a/tutorial/tutorial09.md b/tutorial/tutorial09.md new file mode 100644 index 000000000..e616f229f --- /dev/null +++ b/tutorial/tutorial09.md @@ -0,0 +1,7 @@ +## Cable sleeves + +* Cable sleeve / wrap + * `sleeve_color` draws a colored braided sleeve around the bundle + * Rendered as a woven band across the top and bottom of the conductors + * Adds a `+ Sleeve` indicator with the color name to the cable header + * Works for bundles and sheathed cables, and composes with `shield` diff --git a/tutorial/tutorial09.yml b/tutorial/tutorial09.yml new file mode 100644 index 000000000..ecdd8573f --- /dev/null +++ b/tutorial/tutorial09.yml @@ -0,0 +1,23 @@ +connectors: + X1: + pinlabels: [GND, GND, +24V, +24V] + type: Molex Mini-Fit Jr + subtype: female + X2: + pinlabels: [GND, GND, +24V, +24V] + type: Molex Mini-Fit Jr + subtype: female + +cables: + W1: + category: bundle + gauge: 18 AWG + length: 0.8 + colors: [BK, BK, RD, RD] + sleeve_color: RD # draw a red braided sleeve around the bundle + +connections: + - + - X1: [1-4] + - W1: [1-4] + - X2: [1-4]