Merged in refactoring changes from upstream

This commit is contained in:
Tyler Ward 2020-07-02 00:50:48 +01:00
commit c200f66009
25 changed files with 891 additions and 844 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ __pycache__
build build
data data
dist dist
venv/

View File

@ -17,7 +17,7 @@ WireViz is a tool for easily documenting cables, wiring harnesses and connector
* [DIN 47100](https://en.wikipedia.org/wiki/DIN_47100) (WT/BN/GN/YE/GY/PK/BU/RD/BK/VT/...) * [DIN 47100](https://en.wikipedia.org/wiki/DIN_47100) (WT/BN/GN/YE/GY/PK/BU/RD/BK/VT/...)
* [IEC 62](https://en.wikipedia.org/wiki/Electronic_color_code#Color_band_system) (BN/RD/OR/YE/GN/BU/VT/GY/WT/BK/...) * [IEC 62](https://en.wikipedia.org/wiki/Electronic_color_code#Color_band_system) (BN/RD/OR/YE/GN/BU/VT/GY/WT/BK/...)
* Understands wire gauge in mm² or AWG * Understands wire gauge in mm² or AWG
* Optionally auto-calculates and displays AWG equivalent when specifying mm² * Optionally auto-calculates equivalent gauge between mm² and AWG
* Allows more than one connector per side, as well as loopbacks * Allows more than one connector per side, as well as loopbacks
* Allows for easy-autorouting for 1-to-1 wiring * Allows for easy-autorouting for 1-to-1 wiring
* Generates BOM (Bill of Materials) * Generates BOM (Bill of Materials)
@ -63,7 +63,7 @@ cables:
connections: connections:
- -
- X1: [5,2,1] - X1: [5,2,3]
- W1: [1,2,3] - W1: [1,2,3]
- X2: [1,3,2] - X2: [1,3,2]
- -
@ -88,14 +88,6 @@ Output file:
See the [tutorial page](tutorial/readme.md) for sample code, See the [tutorial page](tutorial/readme.md) for sample code,
as well as the [example gallery](examples/readme.md) to see more of what WireViz can do. as well as the [example gallery](examples/readme.md) to see more of what WireViz can do.
### (Re-)Building the example projects
If you would like to rebuild all of the included demos, examples and tutorials, use the ```build_examples.py``` script:
```cd src/wireviz
./build_examples.py
```
## Usage ## Usage
``` ```
@ -112,6 +104,13 @@ mywire.bom.tsv BOM (bill of materials) as tab-separated text file
mywire.html HTML page with wiring diagram and BOM embedded mywire.html HTML page with wiring diagram and BOM embedded
``` ```
### (Re-)Building the example projects
If you would like to rebuild all of the included demos, examples and tutorials, use the ```build_examples.py``` script:
```cd src/wireviz
./build_examples.py
```
## Status ## Status
This is very much a [work in progress](https://github.com/formatc1702/WireViz/projects/1). Source code, API, syntax and functionality may change wildly at any time. This is very much a [work in progress](https://github.com/formatc1702/WireViz/projects/1). Source code, API, syntax and functionality may change wildly at any time.

View File

@ -13,9 +13,9 @@ graph {
X1:p2r:e -- W1:w2:w X1:p2r:e -- W1:w2:w
W1:w2:e -- X2:p3l:w W1:w2:e -- X2:p3l:w
edge [color="#000000:#00ff00:#000000"] edge [color="#000000:#00ff00:#000000"]
X1:p1r:e -- W1:w3:w X1:p3r:e -- W1:w3:w
W1:w3:e -- X2:p2l:w W1:w3:e -- X2:p2l:w
edge [color="#000000"] edge [color="#000000"]
X1:p5r:e -- W1:ws:w X1:p5r:e -- W1:ws:w
W1 [label=<<table border="0" cellspacing="0" cellpadding="0"><tr><td><table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr><td colspan="4">W1</td></tr><tr><td>3x</td><td>0.25 mm²</td><td>+ S</td><td>0.2 m</td></tr></table></td></tr><tr><td>&nbsp;</td></tr><tr><td><table border="0" cellspacing="0" cellborder="0"><tr><td>X1:5</td><td>WH</td><td>X2:1</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#ffffff" border="2" sides="tb" port="w1"></td></tr><tr><td>X1:2</td><td>BN</td><td>X2:3</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#666600" border="2" sides="tb" port="w2"></td></tr><tr><td>X1:1</td><td>GN</td><td>X2:2</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#00ff00" border="2" sides="tb" port="w3"></td></tr><tr><td>&nbsp;</td></tr><tr><td>X1:5</td><td>Shield</td><td><!-- s_out --></td></tr><tr><td colspan="3" cellpadding="0" height="6" border="2" sides="b" port="ws"></td></tr><tr><td>&nbsp;</td></tr></table></td></tr></table>> fillcolor=white margin=0 shape=box style=""] W1 [label=<<table border="0" cellspacing="0" cellpadding="0"><tr><td><table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr><td colspan="4">W1</td></tr><tr><td>3x</td><td>0.25 mm²</td><td>+ S</td><td>0.2 m</td></tr></table></td></tr><tr><td>&nbsp;</td></tr><tr><td><table border="0" cellspacing="0" cellborder="0"><tr><td>X1:5</td><td>WH</td><td>X2:1</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#ffffff" border="2" sides="tb" port="w1"></td></tr><tr><td>X1:2</td><td>BN</td><td>X2:3</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#666600" border="2" sides="tb" port="w2"></td></tr><tr><td>X1:3</td><td>GN</td><td>X2:2</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#00ff00" border="2" sides="tb" port="w3"></td></tr><tr><td>&nbsp;</td></tr><tr><td>X1:5</td><td>Shield</td><td><!-- s_out --></td></tr><tr><td colspan="3" cellpadding="0" height="6" border="2" sides="b" port="ws"></td></tr><tr><td>&nbsp;</td></tr></table></td></tr></table>> fillcolor=white margin=0 shape=box style=""]
} }

View File

@ -83,7 +83,7 @@
<polygon fill="#666600" stroke="transparent" stroke-width="2" points="304.5,-170 304.5,-176 467.5,-176 467.5,-170 304.5,-170"/> <polygon fill="#666600" stroke="transparent" stroke-width="2" points="304.5,-170 304.5,-176 467.5,-176 467.5,-170 304.5,-170"/>
<polyline fill="none" stroke="black" stroke-width="2" points="305.5,-171 466.5,-171 "/> <polyline fill="none" stroke="black" stroke-width="2" points="305.5,-171 466.5,-171 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="466.5,-175 305.5,-175 "/> <polyline fill="none" stroke="black" stroke-width="2" points="466.5,-175 305.5,-175 "/>
<text text-anchor="start" x="316" y="-156.8" font-family="arial" font-size="14.00">X1:1</text> <text text-anchor="start" x="316" y="-156.8" font-family="arial" font-size="14.00">X1:3</text>
<text text-anchor="start" x="375" y="-156.8" font-family="arial" font-size="14.00">GN</text> <text text-anchor="start" x="375" y="-156.8" font-family="arial" font-size="14.00">GN</text>
<text text-anchor="start" x="428" y="-156.8" font-family="arial" font-size="14.00">X2:2</text> <text text-anchor="start" x="428" y="-156.8" font-family="arial" font-size="14.00">X2:2</text>
<polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="304.5,-145 304.5,-151 467.5,-151 467.5,-145 304.5,-145"/> <polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="304.5,-145 304.5,-151 467.5,-151 467.5,-145 304.5,-145"/>
@ -112,9 +112,9 @@
<!-- X1&#45;&#45;W1 --> <!-- X1&#45;&#45;W1 -->
<g id="edge5" class="edge"> <g id="edge5" class="edge">
<title>X1:e&#45;&#45;W1:w</title> <title>X1:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M160,-194C225.5,-195.63 234.57,-147.63 304,-146"/> <path fill="none" stroke="#000000" stroke-width="2" d="M160,-148C223.76,-148.02 239.75,-146.02 304,-146"/>
<path fill="none" stroke="#00ff00" stroke-width="2" d="M160,-196C227.46,-196 236.54,-148 304,-148"/> <path fill="none" stroke="#00ff00" stroke-width="2" d="M160,-150C224.01,-150 239.99,-148 304,-148"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M160,-198C229.43,-196.37 238.5,-148.37 304,-150"/> <path fill="none" stroke="#000000" stroke-width="2" d="M160,-152C224.25,-151.98 240.24,-149.98 304,-150"/>
</g> </g>
<!-- X1&#45;&#45;W1 --> <!-- X1&#45;&#45;W1 -->
<g id="edge7" class="edge"> <g id="edge7" class="edge">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -83,7 +83,7 @@
<polygon fill="#666600" stroke="transparent" stroke-width="2" points="304.5,-170 304.5,-176 467.5,-176 467.5,-170 304.5,-170"/> <polygon fill="#666600" stroke="transparent" stroke-width="2" points="304.5,-170 304.5,-176 467.5,-176 467.5,-170 304.5,-170"/>
<polyline fill="none" stroke="black" stroke-width="2" points="305.5,-171 466.5,-171 "/> <polyline fill="none" stroke="black" stroke-width="2" points="305.5,-171 466.5,-171 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="466.5,-175 305.5,-175 "/> <polyline fill="none" stroke="black" stroke-width="2" points="466.5,-175 305.5,-175 "/>
<text text-anchor="start" x="316" y="-156.8" font-family="arial" font-size="14.00">X1:1</text> <text text-anchor="start" x="316" y="-156.8" font-family="arial" font-size="14.00">X1:3</text>
<text text-anchor="start" x="375" y="-156.8" font-family="arial" font-size="14.00">GN</text> <text text-anchor="start" x="375" y="-156.8" font-family="arial" font-size="14.00">GN</text>
<text text-anchor="start" x="428" y="-156.8" font-family="arial" font-size="14.00">X2:2</text> <text text-anchor="start" x="428" y="-156.8" font-family="arial" font-size="14.00">X2:2</text>
<polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="304.5,-145 304.5,-151 467.5,-151 467.5,-145 304.5,-145"/> <polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="304.5,-145 304.5,-151 467.5,-151 467.5,-145 304.5,-145"/>
@ -112,9 +112,9 @@
<!-- X1&#45;&#45;W1 --> <!-- X1&#45;&#45;W1 -->
<g id="edge5" class="edge"> <g id="edge5" class="edge">
<title>X1:e&#45;&#45;W1:w</title> <title>X1:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M160,-194C225.5,-195.63 234.57,-147.63 304,-146"/> <path fill="none" stroke="#000000" stroke-width="2" d="M160,-148C223.76,-148.02 239.75,-146.02 304,-146"/>
<path fill="none" stroke="#00ff00" stroke-width="2" d="M160,-196C227.46,-196 236.54,-148 304,-148"/> <path fill="none" stroke="#00ff00" stroke-width="2" d="M160,-150C224.01,-150 239.99,-148 304,-148"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M160,-198C229.43,-196.37 238.5,-148.37 304,-150"/> <path fill="none" stroke="#000000" stroke-width="2" d="M160,-152C224.25,-151.98 240.24,-149.98 304,-150"/>
</g> </g>
<!-- X1&#45;&#45;W1 --> <!-- X1&#45;&#45;W1 -->
<g id="edge7" class="edge"> <g id="edge7" class="edge">

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -18,7 +18,7 @@ cables:
connections: connections:
- -
- X1: [5,2,1] - X1: [5,2,3]
- W1: [1,2,3] - W1: [1,2,3]
- X2: [1,3,2] - X2: [1,3,2]
- -

View File

@ -1,4 +1,5 @@
Item Qty Unit Designators Item Qty Unit Designators
Connector, Molex Micro-Fit, female, 2 pins 3 X2, X3, X4 Connector, Molex Micro-Fit, female, 2 pins 3 X2, X3, X4
Connector, Molex Micro-Fit, male, 2 pins 1 X1 Connector, Molex Micro-Fit, male, 2 pins 1 X1
Cable, 2 x 0.25 mm² 0.6 m W1, W2, W3 Cable, 2 x 0.25 mm² 0.4 m W1, W2
Cable, 2 x 20 AWG 0.2 m W3

1 Item Qty Unit Designators
2 Connector, Molex Micro-Fit, female, 2 pins 3 X2, X3, X4
3 Connector, Molex Micro-Fit, male, 2 pins 1 X1
4 Cable, 2 x 0.25 mm² 0.6 0.4 m W1, W2, W3 W1, W2
5 Cable, 2 x 20 AWG 0.2 m W3

View File

@ -28,5 +28,5 @@ graph {
edge [color="#000000:#ff0000:#000000"] edge [color="#000000:#ff0000:#000000"]
X1:p2r:e -- W3:w2:w X1:p2r:e -- W3:w2:w
W3:w2:e -- X4:p2l:w W3:w2:e -- X4:p2l:w
W3 [label=<<table border="0" cellspacing="0" cellpadding="0"><tr><td><table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr><td colspan="3">W3</td></tr><tr><td>2x</td><td>0.25 mm² (24 AWG)</td><td>0.2 m</td></tr></table></td></tr><tr><td>&nbsp;</td></tr><tr><td><table border="0" cellspacing="0" cellborder="0"><tr><td>X1:1</td><td>BK</td><td>X4:1</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#000000" border="2" sides="tb" port="w1"></td></tr><tr><td>X1:2</td><td>RD</td><td>X4:2</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#ff0000" border="2" sides="tb" port="w2"></td></tr><tr><td>&nbsp;</td></tr></table></td></tr></table>> fillcolor=white margin=0 shape=box style=""] W3 [label=<<table border="0" cellspacing="0" cellpadding="0"><tr><td><table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr><td colspan="3">W3</td></tr><tr><td>2x</td><td>20 AWG (0.75 mm²)</td><td>0.2 m</td></tr></table></td></tr><tr><td>&nbsp;</td></tr><tr><td><table border="0" cellspacing="0" cellborder="0"><tr><td>X1:1</td><td>BK</td><td>X4:1</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#000000" border="2" sides="tb" port="w1"></td></tr><tr><td>X1:2</td><td>RD</td><td>X4:2</td></tr><tr><td colspan="3" cellpadding="0" height="6" bgcolor="#ff0000" border="2" sides="tb" port="w2"></td></tr><tr><td>&nbsp;</td></tr></table></td></tr></table>> fillcolor=white margin=0 shape=box style=""]
} }

View File

@ -119,7 +119,7 @@
<polygon fill="none" stroke="black" points="348,-84 348,-107 371,-107 371,-84 348,-84"/> <polygon fill="none" stroke="black" points="348,-84 348,-107 371,-107 371,-84 348,-84"/>
<text text-anchor="start" x="352" y="-91.8" font-family="arial" font-size="14.00">2x</text> <text text-anchor="start" x="352" y="-91.8" font-family="arial" font-size="14.00">2x</text>
<polygon fill="none" stroke="black" points="371,-84 371,-107 503,-107 503,-84 371,-84"/> <polygon fill="none" stroke="black" points="371,-84 371,-107 503,-107 503,-84 371,-84"/>
<text text-anchor="start" x="375" y="-91.8" font-family="arial" font-size="14.00">0.25 mm² (24 AWG)</text> <text text-anchor="start" x="375" y="-91.8" font-family="arial" font-size="14.00">20 AWG (0.75 mm²)</text>
<polygon fill="none" stroke="black" points="503,-84 503,-107 546,-107 546,-84 503,-84"/> <polygon fill="none" stroke="black" points="503,-84 503,-107 546,-107 546,-84 503,-84"/>
<text text-anchor="start" x="507" y="-91.8" font-family="arial" font-size="14.00">0.2 m</text> <text text-anchor="start" x="507" y="-91.8" font-family="arial" font-size="14.00">0.2 m</text>
<text text-anchor="start" x="445" y="-72.8" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="445" y="-72.8" font-family="arial" font-size="14.00"> </text>
@ -255,4 +255,4 @@
</g> </g>
</g> </g>
</svg> </svg>
<h1>Bill of Materials</h1><table style="border:1px solid #000000; font-size: 14pt; border-spacing: 0px"><tr><th align="left" style="border:1px solid #000000; padding: 8px">Item</th><th align="left" style="border:1px solid #000000; padding: 8px">Qty</th><th align="left" style="border:1px solid #000000; padding: 8px">Unit</th><th align="left" style="border:1px solid #000000; padding: 8px">Designators</th></tr><tr><td style="border:1px solid #000000; padding: 4px">Connector, Molex Micro-Fit, female, 2 pins</td><td align="right" style="border:1px solid #000000; padding: 4px">3</td><td style="border:1px solid #000000; padding: 4px"></td><td style="border:1px solid #000000; padding: 4px">X2, X3, X4</td></tr><tr><td style="border:1px solid #000000; padding: 4px">Connector, Molex Micro-Fit, male, 2 pins</td><td align="right" style="border:1px solid #000000; padding: 4px">1</td><td style="border:1px solid #000000; padding: 4px"></td><td style="border:1px solid #000000; padding: 4px">X1</td></tr><tr><td style="border:1px solid #000000; padding: 4px">Cable, 2 x 0.25 mm²</td><td align="right" style="border:1px solid #000000; padding: 4px">0.6</td><td style="border:1px solid #000000; padding: 4px">m</td><td style="border:1px solid #000000; padding: 4px">W1, W2, W3</td></tr></table></body></html> <h1>Bill of Materials</h1><table style="border:1px solid #000000; font-size: 14pt; border-spacing: 0px"><tr><th align="left" style="border:1px solid #000000; padding: 8px">Item</th><th align="left" style="border:1px solid #000000; padding: 8px">Qty</th><th align="left" style="border:1px solid #000000; padding: 8px">Unit</th><th align="left" style="border:1px solid #000000; padding: 8px">Designators</th></tr><tr><td style="border:1px solid #000000; padding: 4px">Connector, Molex Micro-Fit, female, 2 pins</td><td align="right" style="border:1px solid #000000; padding: 4px">3</td><td style="border:1px solid #000000; padding: 4px"></td><td style="border:1px solid #000000; padding: 4px">X2, X3, X4</td></tr><tr><td style="border:1px solid #000000; padding: 4px">Connector, Molex Micro-Fit, male, 2 pins</td><td align="right" style="border:1px solid #000000; padding: 4px">1</td><td style="border:1px solid #000000; padding: 4px"></td><td style="border:1px solid #000000; padding: 4px">X1</td></tr><tr><td style="border:1px solid #000000; padding: 4px">Cable, 2 x 0.25 mm²</td><td align="right" style="border:1px solid #000000; padding: 4px">0.4</td><td style="border:1px solid #000000; padding: 4px">m</td><td style="border:1px solid #000000; padding: 4px">W1, W2</td></tr><tr><td style="border:1px solid #000000; padding: 4px">Cable, 2 x 20 AWG</td><td align="right" style="border:1px solid #000000; padding: 4px">0.2</td><td style="border:1px solid #000000; padding: 4px">m</td><td style="border:1px solid #000000; padding: 4px">W3</td></tr></table></body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -119,7 +119,7 @@
<polygon fill="none" stroke="black" points="348,-84 348,-107 371,-107 371,-84 348,-84"/> <polygon fill="none" stroke="black" points="348,-84 348,-107 371,-107 371,-84 348,-84"/>
<text text-anchor="start" x="352" y="-91.8" font-family="arial" font-size="14.00">2x</text> <text text-anchor="start" x="352" y="-91.8" font-family="arial" font-size="14.00">2x</text>
<polygon fill="none" stroke="black" points="371,-84 371,-107 503,-107 503,-84 371,-84"/> <polygon fill="none" stroke="black" points="371,-84 371,-107 503,-107 503,-84 371,-84"/>
<text text-anchor="start" x="375" y="-91.8" font-family="arial" font-size="14.00">0.25 mm² (24 AWG)</text> <text text-anchor="start" x="375" y="-91.8" font-family="arial" font-size="14.00">20 AWG (0.75 mm²)</text>
<polygon fill="none" stroke="black" points="503,-84 503,-107 546,-107 546,-84 503,-84"/> <polygon fill="none" stroke="black" points="503,-84 503,-107 546,-107 546,-84 503,-84"/>
<text text-anchor="start" x="507" y="-91.8" font-family="arial" font-size="14.00">0.2 m</text> <text text-anchor="start" x="507" y="-91.8" font-family="arial" font-size="14.00">0.2 m</text>
<text text-anchor="start" x="445" y="-72.8" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="445" y="-72.8" font-family="arial" font-size="14.00"> </text>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -22,6 +22,7 @@ cables:
<<: *wire_power # create from template <<: *wire_power # create from template
W3: W3:
<<: *wire_power # create from template <<: *wire_power # create from template
gauge: 20 awg
connections: connections:
- -

View File

@ -4,7 +4,7 @@ graph {
graph [bgcolor=white fontname=arial nodesep=0.33 rankdir=LR ranksep=2] graph [bgcolor=white fontname=arial nodesep=0.33 rankdir=LR ranksep=2]
node [fillcolor=white fontname=arial shape=record style=filled] node [fillcolor=white fontname=arial shape=record style=filled]
edge [fontname=arial style=bold] edge [fontname=arial style=bold]
Key [label="Key|{Phone Connector|male 3.5|3-pin}|{{Dot|Dash|Ground}|{<pTr>T|<pRr>R|<pSr>S}}"] Key [label="Key|{Phone Connector|male 3.5}|{{Dot|Dash|Ground}|{<pTr>T|<pRr>R|<pSr>S}}"]
edge [color="#000000:#ffffff:#000000"] edge [color="#000000:#ffffff:#000000"]
Key:pSr:e -- W1:w1:w Key:pSr:e -- W1:w1:w
edge [color="#000000:#666600:#000000"] edge [color="#000000:#666600:#000000"]

View File

@ -4,95 +4,93 @@
<!-- Generated by graphviz version 2.44.0 (20200408.0750) <!-- Generated by graphviz version 2.44.0 (20200408.0750)
--> -->
<!-- Pages: 1 --> <!-- Pages: 1 -->
<svg width="546pt" height="207pt" <svg width="499pt" height="207pt"
viewBox="0.00 0.00 546.00 207.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 499.00 207.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 203)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 203)">
<polygon fill="white" stroke="transparent" points="-4,4 -4,-203 542,-203 542,4 -4,4"/> <polygon fill="white" stroke="transparent" points="-4,4 -4,-203 495,-203 495,4 -4,4"/>
<!-- Key --> <!-- Key -->
<g id="node1" class="node"> <g id="node1" class="node">
<title>Key</title> <title>Key</title>
<polygon fill="white" stroke="black" points="0,-33 0,-148 238,-148 238,-33 0,-33"/> <polygon fill="white" stroke="black" points="0,-33 0,-148 191,-148 191,-33 0,-33"/>
<text text-anchor="middle" x="119" y="-132.8" font-family="arial" font-size="14.00">Key</text> <text text-anchor="middle" x="95.5" y="-132.8" font-family="arial" font-size="14.00">Key</text>
<polyline fill="none" stroke="black" points="0,-125 238,-125 "/> <polyline fill="none" stroke="black" points="0,-125 191,-125 "/>
<text text-anchor="middle" x="61" y="-109.8" font-family="arial" font-size="14.00">Phone Connector</text> <text text-anchor="middle" x="61" y="-109.8" font-family="arial" font-size="14.00">Phone Connector</text>
<polyline fill="none" stroke="black" points="122,-102 122,-125 "/> <polyline fill="none" stroke="black" points="122,-102 122,-125 "/>
<text text-anchor="middle" x="156.5" y="-109.8" font-family="arial" font-size="14.00">male 3.5</text> <text text-anchor="middle" x="156.5" y="-109.8" font-family="arial" font-size="14.00">male 3.5</text>
<polyline fill="none" stroke="black" points="191,-102 191,-125 "/> <polyline fill="none" stroke="black" points="0,-102 191,-102 "/>
<text text-anchor="middle" x="214.5" y="-109.8" font-family="arial" font-size="14.00">3&#45;pin</text> <text text-anchor="middle" x="56.5" y="-86.8" font-family="arial" font-size="14.00">Dot</text>
<polyline fill="none" stroke="black" points="0,-102 238,-102 "/> <polyline fill="none" stroke="black" points="0,-79 113,-79 "/>
<text text-anchor="middle" x="68.5" y="-86.8" font-family="arial" font-size="14.00">Dot</text> <text text-anchor="middle" x="56.5" y="-63.8" font-family="arial" font-size="14.00">Dash</text>
<polyline fill="none" stroke="black" points="0,-79 137,-79 "/> <polyline fill="none" stroke="black" points="0,-56 113,-56 "/>
<text text-anchor="middle" x="68.5" y="-63.8" font-family="arial" font-size="14.00">Dash</text> <text text-anchor="middle" x="56.5" y="-40.8" font-family="arial" font-size="14.00">Ground</text>
<polyline fill="none" stroke="black" points="0,-56 137,-56 "/> <polyline fill="none" stroke="black" points="113,-33 113,-102 "/>
<text text-anchor="middle" x="68.5" y="-40.8" font-family="arial" font-size="14.00">Ground</text> <text text-anchor="middle" x="152" y="-86.8" font-family="arial" font-size="14.00">T</text>
<polyline fill="none" stroke="black" points="137,-33 137,-102 "/> <polyline fill="none" stroke="black" points="113,-79 191,-79 "/>
<text text-anchor="middle" x="187.5" y="-86.8" font-family="arial" font-size="14.00">T</text> <text text-anchor="middle" x="152" y="-63.8" font-family="arial" font-size="14.00">R</text>
<polyline fill="none" stroke="black" points="137,-79 238,-79 "/> <polyline fill="none" stroke="black" points="113,-56 191,-56 "/>
<text text-anchor="middle" x="187.5" y="-63.8" font-family="arial" font-size="14.00">R</text> <text text-anchor="middle" x="152" y="-40.8" font-family="arial" font-size="14.00">S</text>
<polyline fill="none" stroke="black" points="137,-56 238,-56 "/>
<text text-anchor="middle" x="187.5" y="-40.8" font-family="arial" font-size="14.00">S</text>
</g> </g>
<!-- W1 --> <!-- W1 -->
<g id="node2" class="node"> <g id="node2" class="node">
<title>W1</title> <title>W1</title>
<polygon fill="none" stroke="black" points="538,-199 382,-199 382,0 538,0 538,-199"/> <polygon fill="none" stroke="black" points="491,-199 335,-199 335,0 491,0 491,-199"/>
<polygon fill="none" stroke="black" points="382,-175.5 382,-198.5 538,-198.5 538,-175.5 382,-175.5"/> <polygon fill="none" stroke="black" points="335,-175.5 335,-198.5 491,-198.5 491,-175.5 335,-175.5"/>
<text text-anchor="start" x="449" y="-183.3" font-family="arial" font-size="14.00">W1</text> <text text-anchor="start" x="402" y="-183.3" font-family="arial" font-size="14.00">W1</text>
<polygon fill="none" stroke="black" points="382,-152.5 382,-175.5 405,-175.5 405,-152.5 382,-152.5"/> <polygon fill="none" stroke="black" points="335,-152.5 335,-175.5 358,-175.5 358,-152.5 335,-152.5"/>
<text text-anchor="start" x="386" y="-160.3" font-family="arial" font-size="14.00">3x</text> <text text-anchor="start" x="339" y="-160.3" font-family="arial" font-size="14.00">3x</text>
<polygon fill="none" stroke="black" points="405,-152.5 405,-175.5 465,-175.5 465,-152.5 405,-152.5"/> <polygon fill="none" stroke="black" points="358,-152.5 358,-175.5 418,-175.5 418,-152.5 358,-152.5"/>
<text text-anchor="start" x="409" y="-160.3" font-family="arial" font-size="14.00">24 AWG</text> <text text-anchor="start" x="362" y="-160.3" font-family="arial" font-size="14.00">24 AWG</text>
<polygon fill="none" stroke="black" points="465,-152.5 465,-175.5 495,-175.5 495,-152.5 465,-152.5"/> <polygon fill="none" stroke="black" points="418,-152.5 418,-175.5 448,-175.5 448,-152.5 418,-152.5"/>
<text text-anchor="start" x="469" y="-160.3" font-family="arial" font-size="14.00">+ S</text> <text text-anchor="start" x="422" y="-160.3" font-family="arial" font-size="14.00">+ S</text>
<polygon fill="none" stroke="black" points="495,-152.5 495,-175.5 538,-175.5 538,-152.5 495,-152.5"/> <polygon fill="none" stroke="black" points="448,-152.5 448,-175.5 491,-175.5 491,-152.5 448,-152.5"/>
<text text-anchor="start" x="499" y="-160.3" font-family="arial" font-size="14.00">0.2 m</text> <text text-anchor="start" x="452" y="-160.3" font-family="arial" font-size="14.00">0.2 m</text>
<text text-anchor="start" x="458" y="-141.3" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="411" y="-141.3" font-family="arial" font-size="14.00"> </text>
<text text-anchor="start" x="395.5" y="-124.3" font-family="arial" font-size="14.00">Key:S</text> <text text-anchor="start" x="348.5" y="-124.3" font-family="arial" font-size="14.00">Key:S</text>
<text text-anchor="start" x="466.5" y="-124.3" font-family="arial" font-size="14.00">WH</text> <text text-anchor="start" x="419.5" y="-124.3" font-family="arial" font-size="14.00">WH</text>
<polygon fill="#ffffff" stroke="transparent" points="382,-112.5 382,-118.5 538,-118.5 538,-112.5 382,-112.5"/> <polygon fill="#ffffff" stroke="transparent" points="335,-112.5 335,-118.5 491,-118.5 491,-112.5 335,-112.5"/>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-113.5 537,-113.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-113.5 490,-113.5 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="537,-117.5 383,-117.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="490,-117.5 336,-117.5 "/>
<text text-anchor="start" x="395.5" y="-99.3" font-family="arial" font-size="14.00">Key:R</text> <text text-anchor="start" x="348.5" y="-99.3" font-family="arial" font-size="14.00">Key:R</text>
<text text-anchor="start" x="469" y="-99.3" font-family="arial" font-size="14.00">BN</text> <text text-anchor="start" x="422" y="-99.3" font-family="arial" font-size="14.00">BN</text>
<polygon fill="#666600" stroke="transparent" stroke-width="2" points="382,-87.5 382,-93.5 538,-93.5 538,-87.5 382,-87.5"/> <polygon fill="#666600" stroke="transparent" stroke-width="2" points="335,-87.5 335,-93.5 491,-93.5 491,-87.5 335,-87.5"/>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-88.5 537,-88.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-88.5 490,-88.5 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="537,-92.5 383,-92.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="490,-92.5 336,-92.5 "/>
<text text-anchor="start" x="396" y="-74.3" font-family="arial" font-size="14.00">Key:T</text> <text text-anchor="start" x="349" y="-74.3" font-family="arial" font-size="14.00">Key:T</text>
<text text-anchor="start" x="467.5" y="-74.3" font-family="arial" font-size="14.00">GN</text> <text text-anchor="start" x="420.5" y="-74.3" font-family="arial" font-size="14.00">GN</text>
<polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="382,-62.5 382,-68.5 538,-68.5 538,-62.5 382,-62.5"/> <polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="335,-62.5 335,-68.5 491,-68.5 491,-62.5 335,-62.5"/>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-63.5 537,-63.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-63.5 490,-63.5 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="537,-67.5 383,-67.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="490,-67.5 336,-67.5 "/>
<text text-anchor="start" x="412" y="-49.3" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="365" y="-49.3" font-family="arial" font-size="14.00"> </text>
<text text-anchor="start" x="395.5" y="-30.3" font-family="arial" font-size="14.00">Key:S</text> <text text-anchor="start" x="348.5" y="-30.3" font-family="arial" font-size="14.00">Key:S</text>
<text text-anchor="start" x="459.5" y="-30.3" font-family="arial" font-size="14.00">Shield</text> <text text-anchor="start" x="412.5" y="-30.3" font-family="arial" font-size="14.00">Shield</text>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-19.5 537,-19.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-19.5 490,-19.5 "/>
<text text-anchor="start" x="412" y="-5.3" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="365" y="-5.3" font-family="arial" font-size="14.00"> </text>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge1" class="edge"> <g id="edge1" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-42.5C311.36,-44.46 312.64,-115.46 382,-113.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-42.5C264.36,-44.46 265.64,-115.46 335,-113.5"/>
<path fill="none" stroke="#ffffff" stroke-width="2" d="M238,-44.5C309.36,-44.5 310.64,-115.5 382,-115.5"/> <path fill="none" stroke="#ffffff" stroke-width="2" d="M191,-44.5C262.36,-44.5 263.64,-115.5 335,-115.5"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-46.5C307.36,-44.54 308.64,-115.54 382,-117.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-46.5C260.36,-44.54 261.64,-115.54 335,-117.5"/>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge2" class="edge"> <g id="edge2" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-65.5C304.51,-66.44 318.88,-89.44 382,-88.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-65.5C257.51,-66.44 271.88,-89.44 335,-88.5"/>
<path fill="none" stroke="#666600" stroke-width="2" d="M238,-67.5C302.81,-67.5 317.19,-90.5 382,-90.5"/> <path fill="none" stroke="#666600" stroke-width="2" d="M191,-67.5C255.81,-67.5 270.19,-90.5 335,-90.5"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-69.5C301.12,-68.56 315.49,-91.56 382,-92.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-69.5C254.12,-68.56 268.49,-91.56 335,-92.5"/>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge3" class="edge"> <g id="edge3" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-88.5C301.21,-89.52 315.3,-64.52 382,-63.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-88.5C254.21,-89.52 268.3,-64.52 335,-63.5"/>
<path fill="none" stroke="#00ff00" stroke-width="2" d="M238,-90.5C302.96,-90.5 317.04,-65.5 382,-65.5"/> <path fill="none" stroke="#00ff00" stroke-width="2" d="M191,-90.5C255.96,-90.5 270.04,-65.5 335,-65.5"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-92.5C304.7,-91.48 318.79,-66.48 382,-67.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-92.5C257.7,-91.48 271.79,-66.48 335,-67.5"/>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge4" class="edge"> <g id="edge4" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-44.5C302.81,-44.5 317.19,-21.5 382,-21.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-44.5C255.81,-44.5 270.19,-21.5 335,-21.5"/>
</g> </g>
</g> </g>
</svg> </svg>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -4,95 +4,93 @@
<!-- Generated by graphviz version 2.44.0 (20200408.0750) <!-- Generated by graphviz version 2.44.0 (20200408.0750)
--> -->
<!-- Pages: 1 --> <!-- Pages: 1 -->
<svg width="546pt" height="207pt" <svg width="499pt" height="207pt"
viewBox="0.00 0.00 546.00 207.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> viewBox="0.00 0.00 499.00 207.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 203)"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 203)">
<polygon fill="white" stroke="transparent" points="-4,4 -4,-203 542,-203 542,4 -4,4"/> <polygon fill="white" stroke="transparent" points="-4,4 -4,-203 495,-203 495,4 -4,4"/>
<!-- Key --> <!-- Key -->
<g id="node1" class="node"> <g id="node1" class="node">
<title>Key</title> <title>Key</title>
<polygon fill="white" stroke="black" points="0,-33 0,-148 238,-148 238,-33 0,-33"/> <polygon fill="white" stroke="black" points="0,-33 0,-148 191,-148 191,-33 0,-33"/>
<text text-anchor="middle" x="119" y="-132.8" font-family="arial" font-size="14.00">Key</text> <text text-anchor="middle" x="95.5" y="-132.8" font-family="arial" font-size="14.00">Key</text>
<polyline fill="none" stroke="black" points="0,-125 238,-125 "/> <polyline fill="none" stroke="black" points="0,-125 191,-125 "/>
<text text-anchor="middle" x="61" y="-109.8" font-family="arial" font-size="14.00">Phone Connector</text> <text text-anchor="middle" x="61" y="-109.8" font-family="arial" font-size="14.00">Phone Connector</text>
<polyline fill="none" stroke="black" points="122,-102 122,-125 "/> <polyline fill="none" stroke="black" points="122,-102 122,-125 "/>
<text text-anchor="middle" x="156.5" y="-109.8" font-family="arial" font-size="14.00">male 3.5</text> <text text-anchor="middle" x="156.5" y="-109.8" font-family="arial" font-size="14.00">male 3.5</text>
<polyline fill="none" stroke="black" points="191,-102 191,-125 "/> <polyline fill="none" stroke="black" points="0,-102 191,-102 "/>
<text text-anchor="middle" x="214.5" y="-109.8" font-family="arial" font-size="14.00">3&#45;pin</text> <text text-anchor="middle" x="56.5" y="-86.8" font-family="arial" font-size="14.00">Dot</text>
<polyline fill="none" stroke="black" points="0,-102 238,-102 "/> <polyline fill="none" stroke="black" points="0,-79 113,-79 "/>
<text text-anchor="middle" x="68.5" y="-86.8" font-family="arial" font-size="14.00">Dot</text> <text text-anchor="middle" x="56.5" y="-63.8" font-family="arial" font-size="14.00">Dash</text>
<polyline fill="none" stroke="black" points="0,-79 137,-79 "/> <polyline fill="none" stroke="black" points="0,-56 113,-56 "/>
<text text-anchor="middle" x="68.5" y="-63.8" font-family="arial" font-size="14.00">Dash</text> <text text-anchor="middle" x="56.5" y="-40.8" font-family="arial" font-size="14.00">Ground</text>
<polyline fill="none" stroke="black" points="0,-56 137,-56 "/> <polyline fill="none" stroke="black" points="113,-33 113,-102 "/>
<text text-anchor="middle" x="68.5" y="-40.8" font-family="arial" font-size="14.00">Ground</text> <text text-anchor="middle" x="152" y="-86.8" font-family="arial" font-size="14.00">T</text>
<polyline fill="none" stroke="black" points="137,-33 137,-102 "/> <polyline fill="none" stroke="black" points="113,-79 191,-79 "/>
<text text-anchor="middle" x="187.5" y="-86.8" font-family="arial" font-size="14.00">T</text> <text text-anchor="middle" x="152" y="-63.8" font-family="arial" font-size="14.00">R</text>
<polyline fill="none" stroke="black" points="137,-79 238,-79 "/> <polyline fill="none" stroke="black" points="113,-56 191,-56 "/>
<text text-anchor="middle" x="187.5" y="-63.8" font-family="arial" font-size="14.00">R</text> <text text-anchor="middle" x="152" y="-40.8" font-family="arial" font-size="14.00">S</text>
<polyline fill="none" stroke="black" points="137,-56 238,-56 "/>
<text text-anchor="middle" x="187.5" y="-40.8" font-family="arial" font-size="14.00">S</text>
</g> </g>
<!-- W1 --> <!-- W1 -->
<g id="node2" class="node"> <g id="node2" class="node">
<title>W1</title> <title>W1</title>
<polygon fill="none" stroke="black" points="538,-199 382,-199 382,0 538,0 538,-199"/> <polygon fill="none" stroke="black" points="491,-199 335,-199 335,0 491,0 491,-199"/>
<polygon fill="none" stroke="black" points="382,-175.5 382,-198.5 538,-198.5 538,-175.5 382,-175.5"/> <polygon fill="none" stroke="black" points="335,-175.5 335,-198.5 491,-198.5 491,-175.5 335,-175.5"/>
<text text-anchor="start" x="449" y="-183.3" font-family="arial" font-size="14.00">W1</text> <text text-anchor="start" x="402" y="-183.3" font-family="arial" font-size="14.00">W1</text>
<polygon fill="none" stroke="black" points="382,-152.5 382,-175.5 405,-175.5 405,-152.5 382,-152.5"/> <polygon fill="none" stroke="black" points="335,-152.5 335,-175.5 358,-175.5 358,-152.5 335,-152.5"/>
<text text-anchor="start" x="386" y="-160.3" font-family="arial" font-size="14.00">3x</text> <text text-anchor="start" x="339" y="-160.3" font-family="arial" font-size="14.00">3x</text>
<polygon fill="none" stroke="black" points="405,-152.5 405,-175.5 465,-175.5 465,-152.5 405,-152.5"/> <polygon fill="none" stroke="black" points="358,-152.5 358,-175.5 418,-175.5 418,-152.5 358,-152.5"/>
<text text-anchor="start" x="409" y="-160.3" font-family="arial" font-size="14.00">24 AWG</text> <text text-anchor="start" x="362" y="-160.3" font-family="arial" font-size="14.00">24 AWG</text>
<polygon fill="none" stroke="black" points="465,-152.5 465,-175.5 495,-175.5 495,-152.5 465,-152.5"/> <polygon fill="none" stroke="black" points="418,-152.5 418,-175.5 448,-175.5 448,-152.5 418,-152.5"/>
<text text-anchor="start" x="469" y="-160.3" font-family="arial" font-size="14.00">+ S</text> <text text-anchor="start" x="422" y="-160.3" font-family="arial" font-size="14.00">+ S</text>
<polygon fill="none" stroke="black" points="495,-152.5 495,-175.5 538,-175.5 538,-152.5 495,-152.5"/> <polygon fill="none" stroke="black" points="448,-152.5 448,-175.5 491,-175.5 491,-152.5 448,-152.5"/>
<text text-anchor="start" x="499" y="-160.3" font-family="arial" font-size="14.00">0.2 m</text> <text text-anchor="start" x="452" y="-160.3" font-family="arial" font-size="14.00">0.2 m</text>
<text text-anchor="start" x="458" y="-141.3" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="411" y="-141.3" font-family="arial" font-size="14.00"> </text>
<text text-anchor="start" x="395.5" y="-124.3" font-family="arial" font-size="14.00">Key:S</text> <text text-anchor="start" x="348.5" y="-124.3" font-family="arial" font-size="14.00">Key:S</text>
<text text-anchor="start" x="466.5" y="-124.3" font-family="arial" font-size="14.00">WH</text> <text text-anchor="start" x="419.5" y="-124.3" font-family="arial" font-size="14.00">WH</text>
<polygon fill="#ffffff" stroke="transparent" points="382,-112.5 382,-118.5 538,-118.5 538,-112.5 382,-112.5"/> <polygon fill="#ffffff" stroke="transparent" points="335,-112.5 335,-118.5 491,-118.5 491,-112.5 335,-112.5"/>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-113.5 537,-113.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-113.5 490,-113.5 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="537,-117.5 383,-117.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="490,-117.5 336,-117.5 "/>
<text text-anchor="start" x="395.5" y="-99.3" font-family="arial" font-size="14.00">Key:R</text> <text text-anchor="start" x="348.5" y="-99.3" font-family="arial" font-size="14.00">Key:R</text>
<text text-anchor="start" x="469" y="-99.3" font-family="arial" font-size="14.00">BN</text> <text text-anchor="start" x="422" y="-99.3" font-family="arial" font-size="14.00">BN</text>
<polygon fill="#666600" stroke="transparent" stroke-width="2" points="382,-87.5 382,-93.5 538,-93.5 538,-87.5 382,-87.5"/> <polygon fill="#666600" stroke="transparent" stroke-width="2" points="335,-87.5 335,-93.5 491,-93.5 491,-87.5 335,-87.5"/>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-88.5 537,-88.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-88.5 490,-88.5 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="537,-92.5 383,-92.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="490,-92.5 336,-92.5 "/>
<text text-anchor="start" x="396" y="-74.3" font-family="arial" font-size="14.00">Key:T</text> <text text-anchor="start" x="349" y="-74.3" font-family="arial" font-size="14.00">Key:T</text>
<text text-anchor="start" x="467.5" y="-74.3" font-family="arial" font-size="14.00">GN</text> <text text-anchor="start" x="420.5" y="-74.3" font-family="arial" font-size="14.00">GN</text>
<polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="382,-62.5 382,-68.5 538,-68.5 538,-62.5 382,-62.5"/> <polygon fill="#00ff00" stroke="transparent" stroke-width="2" points="335,-62.5 335,-68.5 491,-68.5 491,-62.5 335,-62.5"/>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-63.5 537,-63.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-63.5 490,-63.5 "/>
<polyline fill="none" stroke="black" stroke-width="2" points="537,-67.5 383,-67.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="490,-67.5 336,-67.5 "/>
<text text-anchor="start" x="412" y="-49.3" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="365" y="-49.3" font-family="arial" font-size="14.00"> </text>
<text text-anchor="start" x="395.5" y="-30.3" font-family="arial" font-size="14.00">Key:S</text> <text text-anchor="start" x="348.5" y="-30.3" font-family="arial" font-size="14.00">Key:S</text>
<text text-anchor="start" x="459.5" y="-30.3" font-family="arial" font-size="14.00">Shield</text> <text text-anchor="start" x="412.5" y="-30.3" font-family="arial" font-size="14.00">Shield</text>
<polyline fill="none" stroke="black" stroke-width="2" points="383,-19.5 537,-19.5 "/> <polyline fill="none" stroke="black" stroke-width="2" points="336,-19.5 490,-19.5 "/>
<text text-anchor="start" x="412" y="-5.3" font-family="arial" font-size="14.00"> </text> <text text-anchor="start" x="365" y="-5.3" font-family="arial" font-size="14.00"> </text>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge1" class="edge"> <g id="edge1" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-42.5C311.36,-44.46 312.64,-115.46 382,-113.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-42.5C264.36,-44.46 265.64,-115.46 335,-113.5"/>
<path fill="none" stroke="#ffffff" stroke-width="2" d="M238,-44.5C309.36,-44.5 310.64,-115.5 382,-115.5"/> <path fill="none" stroke="#ffffff" stroke-width="2" d="M191,-44.5C262.36,-44.5 263.64,-115.5 335,-115.5"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-46.5C307.36,-44.54 308.64,-115.54 382,-117.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-46.5C260.36,-44.54 261.64,-115.54 335,-117.5"/>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge2" class="edge"> <g id="edge2" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-65.5C304.51,-66.44 318.88,-89.44 382,-88.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-65.5C257.51,-66.44 271.88,-89.44 335,-88.5"/>
<path fill="none" stroke="#666600" stroke-width="2" d="M238,-67.5C302.81,-67.5 317.19,-90.5 382,-90.5"/> <path fill="none" stroke="#666600" stroke-width="2" d="M191,-67.5C255.81,-67.5 270.19,-90.5 335,-90.5"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-69.5C301.12,-68.56 315.49,-91.56 382,-92.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-69.5C254.12,-68.56 268.49,-91.56 335,-92.5"/>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge3" class="edge"> <g id="edge3" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-88.5C301.21,-89.52 315.3,-64.52 382,-63.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-88.5C254.21,-89.52 268.3,-64.52 335,-63.5"/>
<path fill="none" stroke="#00ff00" stroke-width="2" d="M238,-90.5C302.96,-90.5 317.04,-65.5 382,-65.5"/> <path fill="none" stroke="#00ff00" stroke-width="2" d="M191,-90.5C255.96,-90.5 270.04,-65.5 335,-65.5"/>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-92.5C304.7,-91.48 318.79,-66.48 382,-67.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-92.5C257.7,-91.48 271.79,-66.48 335,-67.5"/>
</g> </g>
<!-- Key&#45;&#45;W1 --> <!-- Key&#45;&#45;W1 -->
<g id="edge4" class="edge"> <g id="edge4" class="edge">
<title>Key:e&#45;&#45;W1:w</title> <title>Key:e&#45;&#45;W1:w</title>
<path fill="none" stroke="#000000" stroke-width="2" d="M238,-44.5C302.81,-44.5 317.19,-21.5 382,-21.5"/> <path fill="none" stroke="#000000" stroke-width="2" d="M191,-44.5C255.81,-44.5 270.19,-21.5 335,-21.5"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -6,6 +6,7 @@ connectors:
subtype: male 3.5 subtype: male 3.5
pinnumbers: [T, R, S] pinnumbers: [T, R, S]
pinout: [Dot, Dash, Ground] pinout: [Dot, Dash, Ground]
show_pincount: false
cables: cables:
W1: W1:

View File

@ -26,7 +26,7 @@ setup(
], ],
license='GPLv3', license='GPLv3',
keywords='cable connector hardware harness wiring wiring-diagram wiring-harness', keywords='cable connector hardware harness wiring wiring-diagram wiring-harness',
url='https://github.com/n42/WireViz', url='https://github.com/formatc1702/WireViz',
package_dir={'': 'src'}, package_dir={'': 'src'},
packages=find_packages('src'), packages=find_packages('src'),
entry_points={ entry_points={

157
src/wireviz/DataClasses.py Normal file
View File

@ -0,0 +1,157 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Optional, List, Any, Union
from dataclasses import dataclass, field
from wireviz.wv_helper import int2tuple
from wireviz import wv_colors
@dataclass
class Connector:
name: str
manufacturer: Optional[str] = None
manufacturer_part_number: Optional[str] = None
internal_part_number: Optional[str] = None
category: Optional[str] = None
type: Optional[str] = None
subtype: Optional[str] = None
pincount: Optional[int] = None
notes: Optional[str] = None
pinout: List[Any] = field(default_factory=list)
pinnumbers: List[Any] = field(default_factory=list)
color: Optional[str] = None
show_name: bool = True
show_pincount: bool = True
hide_disconnected_pins: bool = False
def __post_init__(self):
self.ports_left = False
self.ports_right = False
self.loops = []
self.visible_pins = {}
if self.pincount is None:
if self.pinout:
self.pincount = len(self.pinout)
elif self.pinnumbers:
self.pincount = len(self.pinnumbers)
elif self.category == 'ferrule':
self.pincount = 1
else:
raise Exception('You need to specify at least one, pincount, pinout or pinnumbers')
if self.pinout and self.pinnumbers:
if len(self.pinout) != len(self.pinnumbers):
raise Exception('Given pinout and pinnumbers size mismatch')
# create default lists for pinnumbers (sequential) and pinouts (blank) if not specified
if not self.pinnumbers:
self.pinnumbers = list(range(1, self.pincount + 1))
if not self.pinout:
self.pinout = [''] * self.pincount
def loop(self, from_pin, to_pin):
self.loops.append((from_pin, to_pin))
if self.hide_disconnected_pins:
self.visible_pins[from_pin] = True
self.visible_pins[to_pin] = True
def activate_pin(self, pin):
self.visible_pins[pin] = True
@dataclass
class Cable:
name: str
manufacturer: Optional[Union[str, List[str]]] = None
manufacturer_part_number: Optional[Union[str, List[str]]] = None
internal_part_number: Optional[Union[str, List[str]]] = None
category: Optional[str] = None
type: Optional[str] = None
gauge: Optional[float] = None
gauge_unit: Optional[str] = None
show_equiv: bool = False
length: float = 0
wirecount: Optional[int] = None
shield: bool = False
notes: Optional[str] = None
colors: List[Any] = field(default_factory=list)
color_code: Optional[str] = None
show_name: bool = True
show_wirecount: bool = True
def __post_init__(self):
if isinstance(self.gauge, str): # gauge and unit specified
try:
g, u = self.gauge.split(' ')
except Exception:
raise Exception('Gauge must be a number, or number and unit separated by a space')
self.gauge = g
if u.upper() == 'AWG':
self.gauge_unit = u.upper()
else:
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'
else:
pass # gauge not specified
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 wv_colors.COLOR_CODES:
raise Exception('Unknown color code')
self.colors = wv_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)
# if lists of part numbers are provided check this is a bundle and that it matches the wirecount.
for idfield in [self.manufacturer, self.manufacturer_part_number, self.internal_part_number]:
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')
# for BOM generation
self.wirecount_and_shield = (self.wirecount, self.shield)
def connect(self, from_name, from_pin, via_pin, to_name, to_pin):
from_pin = int2tuple(from_pin)
via_pin = int2tuple(via_pin)
to_pin = int2tuple(to_pin)
if len(from_pin) != len(to_pin):
raise Exception('from_pin must have the same number of elements as to_pin')
for i, _ in enumerate(from_pin):
# 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]))
@dataclass
class Connection:
from_name: Any
from_port: Any
via_port: Any
to_name: Any
to_port: Any

408
src/wireviz/Harness.py Normal file
View File

@ -0,0 +1,408 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from wireviz.DataClasses import Connector, Cable
from graphviz import Graph
from wireviz import wv_colors
from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, nested, flatten2d
from collections import Counter
from typing import List
class Harness:
def __init__(self):
self.color_mode = 'SHORT'
self.connectors = {}
self.cables = {}
def add_connector(self, name, *args, **kwargs):
self.connectors[name] = Connector(name, *args, **kwargs)
def add_cable(self, name, *args, **kwargs):
self.cables[name] = Cable(name, *args, **kwargs)
def loop(self, connector_name, from_pin, to_pin):
self.connectors[connector_name].loop(from_pin, to_pin)
def connect(self, from_name, from_pin, via_name, via_pin, to_name, to_pin):
self.cables[via_name].connect(from_name, from_pin, via_pin, to_name, to_pin)
if from_name in self.connectors:
self.connectors[from_name].activate_pin(from_pin)
if to_name in self.connectors:
self.connectors[to_name].activate_pin(to_pin)
def create_graph(self):
dot = Graph()
dot.body.append('// Graph generated by WireViz')
dot.body.append('// https://github.com/formatc1702/WireViz')
font = 'arial'
dot.attr('graph', rankdir='LR',
ranksep='2',
bgcolor='white',
nodesep='0.33',
fontname=font)
dot.attr('node', shape='record',
style='filled',
fillcolor='white',
fontname=font)
dot.attr('edge', style='bold',
fontname=font)
# prepare ports on connectors depending on which side they will connect
for _, cable in self.cables.items():
for connection in cable.connections:
if connection.from_port is not None: # connect to left
self.connectors[connection.from_name].ports_right = True
if connection.to_port is not None: # connect to right
self.connectors[connection.to_name].ports_left = True
for key, connector in self.connectors.items():
if connector.category == 'ferrule':
subtype = f', {connector.subtype}' if connector.subtype else ''
color = wv_colors.translate_color(connector.color, self.color_mode) if connector.color else ''
infostring = f'{connector.type}{subtype} {color}'
# id = identification
identification = [connector.manufacturer,
f'MPN: {connector.manufacturer_part_number}' if connector.manufacturer_part_number else '',
f'IPN: {connector.internal_part_number}' if connector.internal_part_number else '']
identification = list(filter(None, identification))
if(len(identification) > 0):
infostring = f'{infostring}<br/>'
for attrib in identification:
infostring = f'{infostring}{attrib}, '
infostring = infostring[:-2] # remove trainling comma and space
infostring_l = infostring if connector.ports_right else ''
infostring_r = infostring if connector.ports_left else ''
# INFO: Leaving this one as a string.format form because f-strings do not work well with triple quotes
colorbar = f'<TD BGCOLOR="{wv_colors.translate_color(connector.color, "HEX")}" BORDER="1" SIDES="LR" WIDTH="4"></TD>' if connector.color else ''
dot.node(key, shape='none',
style='filled',
margin='0',
orientation='0' if connector.ports_left else '180',
label='''<
<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0" CELLPADDING="2"><TR>
<TD PORT="p1l"> {infostring_l} </TD>
{colorbar}
<TD PORT="p1r"> {infostring_r} </TD>
</TR></TABLE>
>'''.format(infostring_l=infostring_l, infostring_r=infostring_r, colorbar=colorbar))
else: # not a ferrule
identification = [connector.manufacturer,
f'MPN: {connector.manufacturer_part_number}' if connector.manufacturer_part_number else '',
f'IPN: {connector.internal_part_number}' if connector.internal_part_number else '']
attributes = [connector.type,
connector.subtype,
f'{connector.pincount}-pin' if connector.show_pincount else'']
pinouts = [[], [], []]
for pinnumber, pinname in zip(connector.pinnumbers, connector.pinout):
if connector.hide_disconnected_pins and not connector.visible_pins.get(pinnumber, False):
continue
pinouts[1].append(pinname)
if connector.ports_left:
pinouts[0].append(f'<p{pinnumber}l>{pinnumber}')
if connector.ports_right:
pinouts[2].append(f'<p{pinnumber}r>{pinnumber}')
label = [connector.name if connector.show_name else '', identification, attributes, pinouts, connector.notes]
dot.node(key, label=nested(label))
if len(connector.loops) > 0:
dot.attr('edge', color='#000000:#ffffff:#000000')
if connector.ports_left:
loop_side = 'l'
loop_dir = 'w'
elif connector.ports_right:
loop_side = 'r'
loop_dir = 'e'
else:
raise Exception('No side for loops')
for loop in connector.loops:
dot.edge(f'{connector.name}:p{loop[0]}{loop_side}:{loop_dir}',
f'{connector.name}:p{loop[1]}{loop_side}:{loop_dir}')
for _, cable in self.cables.items():
awg_fmt = ''
if cable.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 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)'
identification = [cable.manufacturer if not isinstance(cable.manufacturer, list) else '',
f'MPN: {cable.manufacturer_part_number}' if (cable.manufacturer_part_number and not isinstance(cable.manufacturer_part_number, list)) else '',
f'IPN: {cable.internal_part_number}' if (cable.internal_part_number and not isinstance(cable.internal_part_number, list)) else '']
identification = list(filter(None, identification))
attributes = [f'{len(cable.colors)}x' if cable.show_wirecount else '',
f'{cable.gauge} {cable.gauge_unit}{awg_fmt}' if cable.gauge else '',
'+ S' if cable.shield else '',
f'{cable.length} m' if cable.length > 0 else '']
attributes = list(filter(None, attributes))
html = '<table border="0" cellspacing="0" cellpadding="0"><tr><td>' # main table
html = f'{html}<table border="0" cellspacing="0" cellpadding="3" cellborder="1">' # name+attributes table
if cable.show_name:
html = f'{html}<tr><td colspan="{len(attributes)}">{cable.name}</td></tr>'
if(len(identification) > 0): # print an identification row if values specified
html = f'{html}<tr><td colspan="{len(attributes)}"><table border="0" cellspacing="0" cellpadding="0" cellborder="0"><tr>'
for attrib in identification:
html = f'{html}<td>{attrib}</td>'
html = f'{html}</tr></table></td></tr>' # end identification row
html = f'{html}<tr>' # attribute row
for attrib in attributes:
html = f'{html}<td>{attrib}</td>'
html = f'{html}</tr>' # attribute row
html = f'{html}</table></td></tr>' # name+attributes table
html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer between attributes and wires
html = f'{html}<tr><td><table border="0" cellspacing="0" cellborder="0">' # conductor table
for i, connection in enumerate(cable.colors, 1):
p = []
p.append(f'<!-- {i}_in -->')
p.append(wv_colors.translate_color(connection, self.color_mode))
p.append(f'<!-- {i}_out -->')
html = f'{html}<tr>'
for bla in p:
html = f'{html}<td>{bla}</td>'
html = f'{html}</tr>'
bgcolor = wv_colors.translate_color(connection, 'hex')
bgcolor = bgcolor if bgcolor != '' else '#ffffff'
html = f'{html}<tr><td colspan="{len(p)}" cellpadding="0" height="6" bgcolor="{bgcolor}" border="2" sides="tb" port="w{i}"></td></tr>'
if(cable.category == 'bundle'): # for bundles individual wires can have part information
# create a list of wire parameters
wireidentification = []
if isinstance(cable.manufacturer, list):
wireidentification.append(cable.manufacturer[i - 1])
if isinstance(cable.manufacturer_part_number, list):
wireidentification.append(f'MPN: {cable.manufacturer_part_number[i - 1]}')
if isinstance(cable.internal_part_number, list):
wireidentification.append(f'IPN: {cable.internal_part_number[i - 1]}')
# print parameters into a table row under the wire
if(len(wireidentification) > 0):
html = f'{html}<tr><td colspan="{len(p)}"><table border="0" cellspacing="0" cellborder="0"><tr>'
for attrib in wireidentification:
html = f'{html}<td>{attrib}</td>'
html = f'{html}</tr></table></td></tr>'
if cable.shield:
p = ['<!-- s_in -->', 'Shield', '<!-- s_out -->']
html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer
html = f'{html}<tr>'
for bla in p:
html = html + f'<td>{bla}</td>'
html = f'{html}</tr>'
html = f'{html}<tr><td colspan="{len(p)}" cellpadding="0" height="6" border="2" sides="b" port="ws"></td></tr>'
html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer at the end
html = f'{html}</table>' # conductor table
html = f'{html}</td></tr>' # main table
if cable.notes:
html = f'{html}<tr><td cellpadding="3">{cable.notes}</td></tr>' # notes table
html = f'{html}<tr><td>&nbsp;</td></tr>' # spacer at the end
html = f'{html}</table>' # main table
# connections
for connection in cable.connections:
if isinstance(connection.via_port, int): # check if it's an actual wire and not a shield
search_color = cable.colors[connection.via_port - 1]
if search_color in wv_colors.color_hex:
dot.attr('edge', color=f'#000000:{wv_colors.color_hex[search_color]}:#000000')
else: # color name not found
dot.attr('edge', color='#000000:#ffffff:#000000')
else: # it's a shield connection
dot.attr('edge', color='#000000')
if connection.from_port is not None: # connect to left
from_ferrule = self.connectors[connection.from_name].category == 'ferrule'
port = f':p{connection.from_port}r' if not from_ferrule else ''
code_left_1 = f'{connection.from_name}{port}:e'
code_left_2 = f'{cable.name}:w{connection.via_port}:w'
dot.edge(code_left_1, code_left_2)
from_string = f'{connection.from_name}:{connection.from_port}' if not from_ferrule else ''
html = html.replace(f'<!-- {connection.via_port}_in -->', from_string)
if connection.to_port is not None: # connect to right
to_ferrule = self.connectors[connection.to_name].category == 'ferrule'
code_right_1 = f'{cable.name}:w{connection.via_port}:e'
to_port = f':p{connection.to_port}l' if not to_ferrule else ''
code_right_2 = f'{connection.to_name}{to_port}:w'
dot.edge(code_right_1, code_right_2)
to_string = f'{connection.to_name}:{connection.to_port}' if not to_ferrule else ''
html = html.replace(f'<!-- {connection.via_port}_out -->', to_string)
dot.node(cable.name, label=f'<{html}>', shape='box',
style='filled,dashed' if cable.category == 'bundle' else '', margin='0', fillcolor='white')
return dot
def output(self, filename, directory='_output', view=False, cleanup=True, fmt='pdf', gen_bom=False):
# graphical output
graph = self.create_graph()
for f in fmt:
graph.format = f
graph.render(filename=filename, directory=directory, view=view, cleanup=cleanup)
graph.save(filename=f'{filename}.gv', directory=directory)
# bom output
bom_list = self.bom_list()
with open(f'{filename}.bom.tsv', 'w') as file:
file.write(tuplelist2tsv(bom_list))
# HTML output
with open(f'{filename}.html', 'w') as file:
file.write('<html><body style="font-family:Arial">')
file.write('<h1>Diagram</h1>')
with open(f'{filename}.svg') as svg:
for svgdata in svg:
file.write(svgdata)
file.write('<h1>Bill of Materials</h1>')
listy = flatten2d(bom_list)
file.write('<table style="border:1px solid #000000; font-size: 14pt; border-spacing: 0px">')
file.write('<tr>')
for item in listy[0]:
file.write(f'<th align="left" style="border:1px solid #000000; padding: 8px">{item}</th>')
file.write('</tr>')
for row in listy[1:]:
file.write('<tr>')
for i, item in enumerate(row):
align = 'align="right"' if listy[0][i] == 'Qty' else ''
file.write(f'<td {align} style="border:1px solid #000000; padding: 4px">{item}</td>')
file.write('</tr>')
file.write('</table>')
file.write('</body></html>')
def bom(self):
bom = []
bom_connectors = []
bom_cables = []
# connectors
types = Counter([(v.type, v.subtype, v.pincount, v.manufacturer, v.manufacturer_part_number, v.internal_part_number) for v in self.connectors.values()])
for maintype in types:
items = {k: v for k, v in self.connectors.items() if (v.type, v.subtype, v.pincount, v.manufacturer, v.manufacturer_part_number, v.internal_part_number) == maintype}
shared = next(iter(items.values()))
designators = list(items.keys())
designators.sort()
conn_type = f', {shared.type}' if shared.type else ''
conn_subtype = f', {shared.subtype}' if shared.subtype else ''
conn_pincount = f', {shared.pincount} pins' if shared.category != 'ferrule' 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.category != 'ferrule' else ''}
if shared.manufacturer is not None: # set manufacturer only if it exists
item['manufacturer'] = shared.manufacturer
if shared.manufacturer_part_number is not None: # set part number only if it exists
item['manufacturer part number'] = shared.manufacturer_part_number
if shared.internal_part_number is not None: # set part number only if it exists
item['internal part number'] = shared.internal_part_number
bom_connectors.append(item)
bom_connectors = sorted(bom_connectors, key=lambda k: k['item']) # https://stackoverflow.com/a/73050
bom.extend(bom_connectors)
# cables
types = Counter([(v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield,
v.manufacturer if not isinstance(v.manufacturer, list) else None,
v.manufacturer_part_number if not isinstance(v.manufacturer_part_number, list) else None,
v.internal_part_number if not isinstance(v.manufacturer_part_number, list) else None
) for v in self.cables.values()])
for maintype in types:
items = {k: v for k, v in self.cables.items() if (
v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield,
v.manufacturer if not isinstance(v.manufacturer, list) else None,
v.manufacturer_part_number if not isinstance(v.manufacturer_part_number, list) else None,
v.internal_part_number if not isinstance(v.manufacturer_part_number, list) else None) == maintype}
shared = next(iter(items.values()))
if shared.category != 'bundle':
designators = list(items.keys())
designators.sort()
total_length = sum(i.length for i in items.values())
gauge_name = f' x {shared.gauge} {shared.gauge_unit}'if shared.gauge else ' wires'
shield_name = ' shielded' if shared.shield else ''
name = f'Cable, {shared.wirecount}{gauge_name}{shield_name}'
item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators}
if shared.manufacturer is not None: # set manufacturer only if it exists
item['manufacturer'] = shared.manufacturer
if shared.manufacturer_part_number is not None: # set part number only if it exists
item['manufacturer part number'] = shared.manufacturer_part_number
if shared.internal_part_number is not None: # set part number only if it exists
item['internal part number'] = shared.internal_part_number
bom_cables.append(item)
# bundles (ignores wirecount)
wirelist = []
# list all cables again, since bundles are represented as wires internally, with the category='bundle' set
types = Counter([(v.category, v.gauge, v.gauge_unit, v.length) for v in self.cables.values()])
for maintype in types:
items = {k: v for k, v in self.cables.items() if (v.category, v.gauge, v.gauge_unit, v.length) == maintype}
shared = next(iter(items.values()))
# filter out cables that are not bundles
if shared.category == 'bundle':
for bundle in items.values():
# add each wire from each bundle to the wirelist
for index, color in enumerate(bundle.colors, 0):
wireinfo = {'gauge': shared.gauge, 'gauge_unit': shared.gauge_unit, 'length': shared.length, 'color': color, 'designator': bundle.name}
wireinfo['manufacturer'] = bundle.manufacturer[index] if isinstance(bundle.manufacturer, list) else None
wireinfo['manufacturer part number'] = bundle.manufacturer_part_number[index] if isinstance(bundle.manufacturer_part_number, list) else None
wireinfo['internal part number'] = bundle.internal_part_number[index] if isinstance(bundle.internal_part_number, list) else None
wirelist.append(wireinfo)
# join similar wires from all the bundles to a single BOM item
types = Counter([(v['gauge'], v['gauge_unit'], v['color'],
v['manufacturer'], v['manufacturer part number'], v['internal part number']) for v in wirelist])
for maintype in types:
items = [v for v in wirelist if (
v['gauge'], v['gauge_unit'], v['color'],
v['manufacturer'], v['manufacturer part number'], v['internal part number']) == maintype]
shared = items[0]
designators = [i['designator'] for i in items]
# remove duplicates
designators = list(dict.fromkeys(designators))
designators.sort()
total_length = sum(i['length'] for i in items)
gauge_name = f', {shared["gauge"]} {shared["gauge_unit"]}' if shared['gauge'] else ''
gauge_color = f', {shared["color"]}' if shared['color'] != '' else ''
name = f'Wire{gauge_name}{gauge_color}'
item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators}
if shared['manufacturer'] is not None: # set manufacturer only if it exists
item['manufacturer'] = shared['manufacturer']
if shared['manufacturer part number'] is not None: # set part number only if it exists
item['manufacturer part number'] = shared['manufacturer part number']
if shared['internal part number'] is not None: # set part number only if it exists
item['internal part number'] = shared['internal part number']
bom_cables.append(item)
bom_cables = sorted(bom_cables, key=lambda k: k['item']) # https://stackoverflow.com/a/73050
bom.extend(bom_cables)
return bom
def bom_list(self):
bom = self.bom()
keys = ['item', 'qty', 'unit', 'designators']
# check if any optional fields are set and add to keys if they are
for fieldname in ["manufacturer", "manufacturer part number", "internal part number"]:
if any(fieldname in x for x in bom):
keys.append(fieldname)
bom_list = []
bom_list.append([k.capitalize() for k in keys]) # create header row with keys
for item in bom:
item_list = [item.get(key, '') for key in keys] # fill missing values with blanks
for i, subitem in enumerate(item_list):
if isinstance(subitem, List): # convert any lists into comma separated strings
item_list[i] = ', '.join(subitem)
bom_list.append(item_list)
return bom_list

View File

@ -1,4 +1,5 @@
#!/usr/bin/python3 #!/usr/bin/python3
# -*- coding: utf-8 -*-
import os import os
import sys import sys

View File

@ -1,540 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse import argparse
from collections import Counter
from dataclasses import dataclass, field
from graphviz import Graph
import os import os
import sys import sys
from typing import Any, List
import yaml import yaml
if __name__== '__main__': if __name__ == '__main__':
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from wireviz import wv_colors
from wireviz.wv_helper import nested, int2tuple, awg_equiv, flatten2d, tuplelist2tsv
class Harness: from wireviz.Harness import Harness
def __init__(self):
self.color_mode = 'SHORT'
self.connectors = {}
self.cables = {}
def add_connector(self, name, *args, **kwargs):
self.connectors[name] = Connector(name, *args, **kwargs)
def add_cable(self, name, *args, **kwargs):
self.cables[name] = Cable(name, *args, **kwargs)
def loop(self, connector_name, from_pin, to_pin):
self.connectors[connector_name].loop(from_pin, to_pin)
def connect(self, from_name, from_pin, via_name, via_pin, to_name, to_pin):
self.cables[via_name].connect(from_name, from_pin, via_pin, to_name, to_pin)
if from_name in self.connectors:
self.connectors[from_name].activate_pin(from_pin)
if to_name in self.connectors:
self.connectors[to_name].activate_pin(to_pin)
def create_graph(self):
dot = Graph()
dot.body.append('// Graph generated by WireViz')
dot.body.append('// https://github.com/formatc1702/WireViz')
font = 'arial'
dot.attr('graph', rankdir='LR',
ranksep='2',
bgcolor='white',
nodesep='0.33',
fontname=font)
dot.attr('node', shape='record',
style='filled',
fillcolor='white',
fontname=font)
dot.attr('edge', style='bold',
fontname=font)
# prepare ports on connectors depending on which side they will connect
for k, c in self.cables.items():
for x in c.connections:
if x.from_port is not None: # connect to left
self.connectors[x.from_name].ports_right = True
if x.to_port is not None: # connect to right
self.connectors[x.to_name].ports_left = True
for k, n in self.connectors.items():
if n.category == 'ferrule':
infostring = '{type}{subtype} {color}'.format(type=n.type,
subtype=', {}'.format(n.subtype) if n.subtype else '',
color=wv_colors.translate_color(n.color, self.color_mode) if n.color else '')
# id = identification
id = [n.manufacturer,
'MPN: {}'.format(n.manufacturer_part_number) if n.manufacturer_part_number else '',
'IPN: {}'.format(n.internal_part_number) if n.internal_part_number else '']
id = list(filter(None, id))
if(len(id) > 0):
infostring = infostring + '<br/>'
for attrib in id:
infostring = infostring + '{attrib}, '.format(attrib=attrib)
infostring = infostring[:-2] # remove trainling comma and space
infostring_l = infostring if n.ports_right else ''
infostring_r = infostring if n.ports_left else ''
dot.node(k, shape='none',
style='filled',
margin='0',
orientation = '0' if n.ports_left else '180',
label='''<
<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0" CELLPADDING="2"><TR>
<TD PORT="p1l"> {infostring_l} </TD>
{colorbar}
<TD PORT="p1r"> {infostring_r} </TD>
</TR></TABLE>
>'''.format(infostring_l=infostring_l,
infostring_r=infostring_r,
colorbar='<TD BGCOLOR="{}" BORDER="1" SIDES="LR" WIDTH="4"></TD>'.format(wv_colors.translate_color(n.color, 'HEX')) if n.color else ''))
else: # not a ferrule
# id = identification
id = [n.manufacturer,
'MPN: {}'.format(n.manufacturer_part_number) if n.manufacturer_part_number else '',
'IPN: {}'.format(n.internal_part_number) if n.internal_part_number else '']
# a = attributes
a = [n.type,
n.subtype,
'{}-pin'.format(n.pincount) if n.show_pincount else '']
# p = pinout
p = [[],[],[]]
for pinnumber, pinname in zip(n.pinnumbers, n.pinout):
if n.hide_disconnected_pins and not n.visible_pins.get(pinnumber, False):
continue
p[1].append(pinname)
if n.ports_left:
p[0].append('<p{portno}l>{portno}'.format(portno=pinnumber))
if n.ports_right:
p[2].append('<p{portno}r>{portno}'.format(portno=pinnumber))
# l = label
l = [n.name if n.show_name else '', id, a, p, n.notes]
dot.node(k, label=nested(l))
if len(n.loops) > 0:
dot.attr('edge',color='#000000:#ffffff:#000000')
if n.ports_left:
loop_side = 'l'
loop_dir = 'w'
elif n.ports_right:
loop_side = 'r'
loop_dir = 'e'
else:
raise Exception('No side for loops')
for loop in n.loops:
dot.edge('{name}:p{port_from}{loop_side}:{loop_dir}'.format(name=n.name, port_from=loop[0], port_to=loop[1], loop_side=loop_side, loop_dir=loop_dir),
'{name}:p{port_to}{loop_side}:{loop_dir}'.format(name=n.name, port_from=loop[0], port_to=loop[1], loop_side=loop_side, loop_dir=loop_dir))
for k, c in self.cables.items():
# id = identification
id = [c.manufacturer if not isinstance(c.manufacturer, list) else '',
'MPN: {}'.format(c.manufacturer_part_number) if (c.manufacturer_part_number and not isinstance(c.manufacturer_part_number, list)) else '',
'IPN: {}'.format(c.internal_part_number) if (c.internal_part_number and not isinstance(c.internal_part_number, list)) else '']
id = list(filter(None, id))
# a = attributes
a = ['{}x'.format(len(c.colors)) if c.show_wirecount else '',
'{} {}{}'.format(c.gauge, c.gauge_unit, ' ({} AWG)'.format(awg_equiv(c.gauge)) if c.gauge_unit == 'mm\u00B2' and c.show_equiv else '') if c.gauge else '', # TODO: show equiv
'+ S' if c.shield else '',
'{} m'.format(c.length) if c.length > 0 else '']
a = list(filter(None, a))
html = '<table border="0" cellspacing="0" cellpadding="0"><tr><td>' # main table
html = html + '<table border="0" cellspacing="0" cellpadding="3" cellborder="1">' # name+attributes table
if c.show_name:
html = html + '<tr><td colspan="{colspan}">{name}</td></tr>'.format(colspan=len(a), name=c.name)
if(len(id) > 0): # print an identification row if values specified
html = html + '<tr><td colspan="{colspan}"><table border="0" cellspacing="0" cellpadding="0" cellborder="0"><tr>'.format(colspan=len(a))
for attrib in id:
html = html + '<td>{attrib}</td>'.format(attrib=attrib)
html = html + '</tr></table></td></tr>' # end identification row
html = html + '<tr>' # attribute row
for attrib in a:
html = html + '<td>{attrib}</td>'.format(attrib=attrib)
html = html + '</tr>' # attribute row
html = html + '</table></td></tr>' # name+attributes table
html = html + '<tr><td>&nbsp;</td></tr>' # spacer between attributes and wires
html = html + '<tr><td><table border="0" cellspacing="0" cellborder="0">' # conductor table
for i, x in enumerate(c.colors,1):
p = []
p.append('<!-- {}_in -->'.format(i))
p.append(wv_colors.translate_color(x, self.color_mode))
p.append('<!-- {}_out -->'.format(i))
html = html + '<tr>'
for bla in p:
html = html + '<td>{}</td>'.format(bla)
html = html + '</tr>'
bgcolor = wv_colors.translate_color(x, 'hex')
html = html + '<tr><td colspan="{colspan}" cellpadding="0" height="6" bgcolor="{bgcolor}" border="2" sides="tb" port="{port}"></td></tr>'.format(colspan=len(p), bgcolor=bgcolor if bgcolor != '' else '#ffffff', port='w{}'.format(i))
if(c.category == 'bundle'): # for bundles individual wires can have part information
# create a list of wire parameters
wireid = []
if isinstance(c.manufacturer, list):
wireid.append(c.manufacturer[i - 1])
if isinstance(c.manufacturer_part_number, list):
wireid.append('MPN: {}'.format(c.manufacturer_part_number[i - 1]))
if isinstance(c.internal_part_number, list):
wireid.append('IPN: {}'.format(c.internal_part_number[i - 1]))
# print parameters into a table row under the wire
if(len(wireid) > 0):
html = html + '<tr><td colspan="{colspan}"><table border="0" cellspacing="0" cellborder="0"><tr>'.format(colspan=len(a))
for attrib in wireid:
html = html + '<td>{attrib}</td>'.format(attrib=attrib)
html = html + '</tr></table></td></tr>'
if c.shield:
p = ['<!-- s_in -->', 'Shield', '<!-- s_out -->']
html = html + '<tr><td>&nbsp;</td></tr>' # spacer
html = html + '<tr>'
for bla in p:
html = html + '<td>{}</td>'.format(bla)
html = html + '</tr>'
html = html + '<tr><td colspan="{colspan}" cellpadding="0" height="6" border="2" sides="b" port="{port}"></td></tr>'.format(colspan=len(p), bgcolor=wv_colors.translate_color(x, 'hex'), port='ws')
html = html + '<tr><td>&nbsp;</td></tr>' # spacer at the end
html = html + '</table>' # conductor table
html = html + '</td></tr>' # main table
if c.notes:
html = html + '<tr><td cellpadding="3">{}</td></tr>'.format(c.notes) # notes table
html = html + '<tr><td>&nbsp;</td></tr>' # spacer at the end
html = html + '</table>' # main table
# connections
for x in c.connections:
if isinstance(x.via_port, int): # check if it's an actual wire and not a shield
search_color = c.colors[x.via_port-1]
if search_color in wv_colors.color_hex:
dot.attr('edge',color='#000000:{wire_color}:#000000'.format(wire_color=wv_colors.color_hex[search_color]))
else: # color name not found
dot.attr('edge',color='#000000:#ffffff:#000000')
else: # it's a shield connection
dot.attr('edge',color='#000000')
if x.from_port is not None: # connect to left
from_ferrule = self.connectors[x.from_name].category == 'ferrule'
code_left_1 = '{from_name}{from_port}:e'.format(from_name=x.from_name, from_port=':p{}r'.format(x.from_port) if not from_ferrule else '')
code_left_2 = '{via_name}:w{via_wire}:w'.format(via_name=c.name, via_wire=x.via_port, via_subport='i' if c.show_pinout else '')
dot.edge(code_left_1, code_left_2)
from_string = '{}:{}'.format(x.from_name, x.from_port) if not from_ferrule else ''
html = html.replace('<!-- {}_in -->'.format(x.via_port), from_string)
if x.to_port is not None: # connect to right
to_ferrule = self.connectors[x.to_name].category == 'ferrule'
code_right_1 = '{via_name}:w{via_wire}:e'.format(via_name=c.name, via_wire=x.via_port, via_subport='o' if c.show_pinout else '')
code_right_2 = '{to_name}{to_port}:w'.format(to_name=x.to_name, to_port=':p{}l'.format(x.to_port) if not to_ferrule else '')
dot.edge(code_right_1, code_right_2)
to_string = '{}:{}'.format(x.to_name, x.to_port) if not to_ferrule else ''
html = html.replace('<!-- {}_out -->'.format(x.via_port), to_string)
dot.node(c.name, label='<{html}>'.format(html=html), shape='box', style='filled,dashed' if c.category=='bundle' else '', margin='0', fillcolor='white')
return dot
def output(self, filename, directory='_output', view=False, cleanup=True, format='pdf', gen_bom=False):
# graphical output
d = self.create_graph()
for f in format:
d.format = f
d.render(filename=filename, directory=directory, view=view, cleanup=cleanup)
d.save(filename='{}.gv'.format(filename), directory=directory)
# bom output
bom_list = self.bom_list()
with open('{}.bom.tsv'.format(filename),'w') as file:
file.write(tuplelist2tsv(bom_list))
# HTML output
with open('{}.html'.format(filename),'w') as file:
file.write('<html><body style="font-family:Arial">')
file.write('<h1>Diagram</h1>')
with open('{}.svg'.format(filename),'r') as svg:
for l in svg:
file.write(l)
file.write('<h1>Bill of Materials</h1>')
listy = flatten2d(bom_list)
file.write('<table style="border:1px solid #000000; font-size: 14pt; border-spacing: 0px">')
file.write('<tr>')
for item in listy[0]:
file.write('<th align="left" style="border:1px solid #000000; padding: 8px">{}</th>'.format(item))
file.write('</tr>')
for row in listy[1:]:
file.write('<tr>')
for i, item in enumerate(row):
file.write('<td {align} style="border:1px solid #000000; padding: 4px">{content}</td>'.format(content=item, align='align="right"' if listy[0][i] == 'Qty' else ''))
file.write('</tr>')
file.write('</table>')
file.write('</body></html>')
def bom(self):
bom = []
bom_connectors = []
bom_cables = []
# connectors
types = Counter([(v.type, v.subtype, v.pincount, v.manufacturer, v.manufacturer_part_number, v.internal_part_number) for v in self.connectors.values()])
for type in types:
items = {k: v for k, v in self.connectors.items() if (v.type, v.subtype, v.pincount, v.manufacturer, v.manufacturer_part_number, v.internal_part_number) == type}
shared = next(iter(items.values()))
designators = list(items.keys())
designators.sort()
name = 'Connector{type}{subtype}{pincount}{color}'.format(type = ', {}'.format(shared.type) if shared.type else '',
subtype = ', {}'.format(shared.subtype) if shared.subtype else '',
pincount = ', {} pins'.format(shared.pincount) if shared.category != 'ferrule' else '',
color = ', {}'.format(shared.color) if shared.color else '')
item = {'item': name, 'qty': len(designators), 'unit': '', 'designators': designators if shared.category != 'ferrule' else ''}
if shared.manufacturer is not None: # set manufacturer only if it exists
item['manufacturer'] = shared.manufacturer
if shared.manufacturer_part_number is not None: # set part number only if it exists
item['manufacturer part number'] = shared.manufacturer_part_number
if shared.internal_part_number is not None: # set part number only if it exists
item['internal part number'] = shared.internal_part_number
bom_connectors.append(item)
bom_connectors = sorted(bom_connectors, key=lambda k: k['item']) # https://stackoverflow.com/a/73050
bom.extend(bom_connectors)
# cables
types = Counter([(v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield,
v.manufacturer if not isinstance(v.manufacturer, list) else None,
v.manufacturer_part_number if not isinstance(v.manufacturer_part_number, list) else None,
v.internal_part_number if not isinstance(v.manufacturer_part_number, list) else None
) for v in self.cables.values()])
for type in types:
items = {k: v for k, v in self.cables.items() if (v.category, v.gauge, v.gauge_unit, v.wirecount, v.shield,
v.manufacturer if not isinstance(v.manufacturer, list) else None,
v.manufacturer_part_number if not isinstance(v.manufacturer_part_number, list) else None,
v.internal_part_number if not isinstance(v.manufacturer_part_number, list) else None
) == type}
shared = next(iter(items.values()))
if shared.category != 'bundle':
designators = list(items.keys())
designators.sort()
total_length = sum(i.length for i in items.values())
name = 'Cable, {wirecount}{gauge}{shield}'.format(wirecount = shared.wirecount,
gauge = ' x {} {}'.format(shared.gauge, shared.gauge_unit) if shared.gauge else ' wires',
shield = ' shielded' if shared.shield else '')
item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators}
if shared.manufacturer is not None: # set manufacturer only if it exists
item['manufacturer'] = shared.manufacturer
if shared.manufacturer_part_number is not None: # set part number only if it exists
item['manufacturer part number'] = shared.manufacturer_part_number
if shared.internal_part_number is not None: # set part number only if it exists
item['internal part number'] = shared.internal_part_number
bom_cables.append(item)
# bundles (ignores wirecount)
wirelist = []
# list all cables again, since bundles are represented as wires internally, with the category='bundle' set
types = Counter([(v.category, v.gauge, v.gauge_unit, v.length) for v in self.cables.values()])
for type in types:
items = {k: v for k, v in self.cables.items() if (v.category, v.gauge, v.gauge_unit, v.length) == type}
shared = next(iter(items.values()))
# filter out cables that are not bundles
if shared.category == 'bundle':
for bundle in items.values():
# add each wire from each bundle to the wirelist
for index, color in enumerate(bundle.colors, 0):
wireinfo = {'gauge': shared.gauge, 'gauge_unit': shared.gauge_unit, 'length': shared.length, 'color': color, 'designator': bundle.name}
wireinfo['manufacturer'] = bundle.manufacturer[index] if isinstance(bundle.manufacturer, list) else None
wireinfo['manufacturer part number'] = bundle.manufacturer_part_number[index] if isinstance(bundle.manufacturer_part_number, list) else None
wireinfo['internal part number'] = bundle.internal_part_number[index] if isinstance(bundle.internal_part_number, list) else None
wirelist.append(wireinfo)
# join similar wires from all the bundles to a single BOM item
types = Counter([(v['gauge'], v['gauge_unit'], v['color'], v['manufacturer'], v['manufacturer part number'], v['internal part number']) for v in wirelist])
for type in types:
items = [v for v in wirelist if (v['gauge'], v['gauge_unit'], v['color'], v['manufacturer'], v['manufacturer part number'], v['internal part number']) == type]
shared = items[0]
designators = [i['designator'] for i in items]
# remove duplicates
designators = list(dict.fromkeys(designators))
designators.sort()
total_length = sum(i['length'] for i in items)
name = 'Wire{gauge}{color}'.format(gauge=', {} {}'.format(shared['gauge'], shared['gauge_unit']) if shared['gauge'] else '',
color=', {}'.format(shared['color']) if shared['color'] != '' else '')
item = {'item': name, 'qty': round(total_length, 3), 'unit': 'm', 'designators': designators}
if shared['manufacturer'] is not None: # set manufacturer only if it exists
item['manufacturer'] = shared['manufacturer']
if shared['manufacturer part number'] is not None: # set part number only if it exists
item['manufacturer part number'] = shared['manufacturer part number']
if shared['internal part number'] is not None: # set part number only if it exists
item['internal part number'] = shared['internal part number']
bom_cables.append(item)
bom_cables = sorted(bom_cables, key=lambda k: k['item']) # https://stackoverflow.com/a/73050
bom.extend(bom_cables)
return bom
def bom_list(self):
bom = self.bom()
keys = ['item', 'qty', 'unit', 'designators']
# check if any optional fields are set and add to keys if they are
for fieldname in ["manufacturer", "manufacturer part number", "internal part number"]:
if any(fieldname in x for x in bom):
keys.append(fieldname)
bom_list = []
bom_list.append([k.capitalize() for k in keys]) # create header row with keys
for item in bom:
item_list = [item.get(key, '') for key in keys] # fill missing values with blanks
for i, subitem in enumerate(item_list):
if isinstance(subitem, List): # convert any lists into comma separated strings
item_list[i] = ', '.join(subitem)
bom_list.append(item_list)
return bom_list
@dataclass
class Connector:
name: str
manufacturer: str = None
manufacturer_part_number: str = None
internal_part_number: str = None
category: str = None
type: str = None
subtype: str = None
pincount: int = None
notes: str = None
pinout: List[Any] = field(default_factory=list)
pinnumbers: List[Any] = field(default_factory=list)
color: str = None
show_name: bool = True
show_pincount: bool = True
hide_disconnected_pins: bool = False
def __post_init__(self):
self.ports_left = False
self.ports_right = False
self.loops = []
self.visible_pins = {}
if self.pincount is None:
if self.pinout:
self.pincount = len(self.pinout)
elif self.pinnumbers:
self.pincount = len(self.pinnumbers)
elif self.category == 'ferrule':
self.pincount = 1
else:
raise Exception('You need to specify at least one, pincount, pinout or pinnumbers')
if self.pinout and self.pinnumbers:
if len(self.pinout) != len(self.pinnumbers):
raise Exception('Given pinout and pinnumbers size mismatch')
# create default lists for pinnumbers (sequential) and pinouts (blank) if not specified
if not self.pinnumbers:
self.pinnumbers = list(range(1,self.pincount + 1))
if not self.pinout:
self.pinout = [''] * self.pincount
def loop(self, from_pin, to_pin):
self.loops.append((from_pin, to_pin))
if self.hide_disconnected_pins:
self.visible_pins[from_pin] = True
self.visible_pins[to_pin] = True
def activate_pin(self, pin):
self.visible_pins[pin] = True
@dataclass
class Cable:
name: str
manufacturer: str = None
manufacturer_part_number: str = None
internal_part_number: str = None
category : str = None
type: str = None
gauge: float = None
gauge_unit : str = None
show_equiv: bool = False
length: float = 0
wirecount: int = None
shield: bool = False
notes: str = None
colors: List[Any] = field(default_factory=list)
color_code: str = None
show_name: bool = True
show_pinout: bool = False
show_wirecount: bool = True
def __post_init__(self):
if isinstance(self.gauge, str): # gauge and unit specified
try:
g, u = self.gauge.split(' ')
except:
raise Exception('Gauge must be a number, or number and unit separated by a space')
self.gauge = g
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'
else:
pass # gauge not specified
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 wv_colors.COLOR_CODES:
raise Exception('Unknown color code')
self.colors = wv_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)
# if lists of part numbers are provided check this is a bundle and that it matches the wirecount.
for idfield in [self.manufacturer, self.manufacturer_part_number, self.internal_part_number]:
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')
# for BOM generation
self.wirecount_and_shield = (self.wirecount, self.shield)
def connect(self, from_name, from_pin, via_pin, to_name, to_pin):
from_pin = int2tuple(from_pin)
via_pin = int2tuple(via_pin)
to_pin = int2tuple(to_pin)
if len(from_pin) != len(to_pin):
raise Exception('from_pin must have the same number of elements as to_pin')
for i, x in enumerate(from_pin):
# 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]))
@dataclass
class Connection:
from_name: Any
from_port: Any
via_port: Any
to_name: Any
to_port: Any
def parse(yaml_input, file_out=None, generate_bom=False): def parse(yaml_input, file_out=None, generate_bom=False):
@ -547,23 +25,23 @@ def parse(yaml_input, file_out=None, generate_bom=False):
# if str is of the format '#-#', it is treated as a range (inclusive) and expanded # if str is of the format '#-#', it is treated as a range (inclusive) and expanded
output = [] output = []
if not isinstance(yaml_data, list): if not isinstance(yaml_data, list):
yaml_data = [yaml_data,] yaml_data = [yaml_data]
for e in yaml_data: for e in yaml_data:
e = str(e) e = str(e)
if '-' in e: # list of pins if '-' in e: # list of pins
a, b = tuple(map(int, e.split('-'))) a, b = tuple(map(int, e.split('-')))
if a < b: if a < b:
for x in range(a,b+1): for x in range(a, b + 1):
output.append(x) output.append(x)
elif a > b: elif a > b:
for x in range(a,b-1,-1): for x in range(a, b - 1, -1):
output.append(x) output.append(x)
elif a == b: elif a == b:
output.append(a) output.append(a)
else: else:
try: try:
x = int(e) x = int(e)
except: except Exception:
x = e x = e
output.append(x) output.append(x)
return output return output
@ -574,25 +52,25 @@ def parse(yaml_input, file_out=None, generate_bom=False):
return False return False
return True return True
h = Harness() harness = Harness()
# add items # add items
sections = ['connectors','cables','ferrules','connections'] sections = ['connectors', 'cables', 'ferrules', 'connections']
types = [dict, dict, dict, list] types = [dict, dict, dict, list]
for sec, ty in zip(sections, types): for sec, ty in zip(sections, types):
if sec in yaml_data and type(yaml_data[sec]) == ty: if sec in yaml_data and type(yaml_data[sec]) == ty:
if len(yaml_data[sec]) > 0: if len(yaml_data[sec]) > 0:
if ty == dict: if ty == dict:
for k, o in yaml_data[sec].items(): for key, o in yaml_data[sec].items():
if sec == 'connectors': if sec == 'connectors':
h.add_connector(name=k, **o) harness.add_connector(name=key, **o)
elif sec == 'cables': elif sec == 'cables':
h.add_cable(name=k, **o) harness.add_cable(name=key, **o)
elif sec == 'ferrules': elif sec == 'ferrules':
pass pass
else: else:
pass # section exists but is empty pass # section exists but is empty
else: # section does not exist, create empty section else: # section does not exist, create empty section
if ty == dict: if ty == dict:
yaml_data[sec] = {} yaml_data[sec] = {}
elif ty == list: elif ty == list:
@ -600,64 +78,63 @@ def parse(yaml_input, file_out=None, generate_bom=False):
# add connections # add connections
ferrule_counter = 0 ferrule_counter = 0
for con in yaml_data['connections']: for connections in yaml_data['connections']:
if len(con) == 3: # format: connector -- cable -- connector if len(connections) == 3: # format: connector -- cable -- connector
for c in con: for connection in connections:
if len(list(c.keys())) != 1: # check that each entry in con has only one key, which is the designator if len(list(connection.keys())) != 1: # check that each entry in con has only one key, which is the designator
raise Exception('Too many keys') raise Exception('Too many keys')
from_name = list(con[0].keys())[0] from_name = list(connections[0].keys())[0]
via_name = list(con[1].keys())[0] via_name = list(connections[1].keys())[0]
to_name = list(con[2].keys())[0] to_name = list(connections[2].keys())[0]
if not check_designators([from_name,via_name,to_name],('connectors','cables','connectors')): if not check_designators([from_name, via_name, to_name], ('connectors', 'cables', 'connectors')):
print([from_name,via_name,to_name]) print([from_name, via_name, to_name])
raise Exception('Bad connection definition (3)') raise Exception('Bad connection definition (3)')
from_pins = expand(con[0][from_name]) from_pins = expand(connections[0][from_name])
via_pins = expand(con[1][via_name]) via_pins = expand(connections[1][via_name])
to_pins = expand(con[2][to_name]) to_pins = expand(connections[2][to_name])
if len(from_pins) != len(via_pins) or len(via_pins) != len(to_pins): if len(from_pins) != len(via_pins) or len(via_pins) != len(to_pins):
raise Exception('List length mismatch') raise Exception('List length mismatch')
for (from_pin, via_pin, to_pin) in zip(from_pins, via_pins, to_pins): for (from_pin, via_pin, to_pin) in zip(from_pins, via_pins, to_pins):
h.connect(from_name, from_pin, via_name, via_pin, to_name, to_pin) harness.connect(from_name, from_pin, via_name, via_pin, to_name, to_pin)
elif len(con) == 2: elif len(connections) == 2:
for c in con: for connection in connections:
if type(c) is dict: if type(connection) is dict:
if len(list(c.keys())) != 1: # check that each entry in con has only one key, which is the designator if len(list(connection.keys())) != 1: # check that each entry in con has only one key, which is the designator
raise Exception('Too many keys') raise Exception('Too many keys')
# hack to make the format for ferrules compatible with the formats for connectors and cables # hack to make the format for ferrules compatible with the formats for connectors and cables
if type(con[0]) == str: if type(connections[0]) == str:
name = con[0] name = connections[0]
con[0] = {} connections[0] = {}
con[0][name] = name connections[0][name] = name
if type(con[1]) == str: if type(connections[1]) == str:
name = con[1] name = connections[1]
con[1] = {} connections[1] = {}
con[1][name] = name connections[1][name] = name
from_name = list(con[0].keys())[0] from_name = list(connections[0].keys())[0]
to_name = list(con[1].keys())[0] to_name = list(connections[1].keys())[0]
con_cbl = check_designators([from_name, to_name],('connectors','cables')) con_cbl = check_designators([from_name, to_name], ('connectors', 'cables'))
cbl_con = check_designators([from_name, to_name],('cables','connectors')) cbl_con = check_designators([from_name, to_name], ('cables', 'connectors'))
con_con = check_designators([from_name, to_name],('connectors','connectors')) con_con = check_designators([from_name, to_name], ('connectors', 'connectors'))
fer_cbl = check_designators([from_name, to_name], ('ferrules', 'cables'))
fer_cbl = check_designators([from_name, to_name],('ferrules','cables')) cbl_fer = check_designators([from_name, to_name], ('cables', 'ferrules'))
cbl_fer = check_designators([from_name, to_name],('cables','ferrules'))
if not con_cbl and not cbl_con and not con_con and not fer_cbl and not cbl_fer: if not con_cbl and not cbl_con and not con_con and not fer_cbl and not cbl_fer:
raise Exception('Wrong designators') raise Exception('Wrong designators')
from_pins = expand(con[0][from_name]) from_pins = expand(connections[0][from_name])
to_pins = expand(con[1][to_name]) to_pins = expand(connections[1][to_name])
if con_cbl or cbl_con or con_con: if con_cbl or cbl_con or con_con:
if len(from_pins) != len(to_pins): if len(from_pins) != len(to_pins):
@ -666,19 +143,19 @@ def parse(yaml_input, file_out=None, generate_bom=False):
if con_cbl or cbl_con: if con_cbl or cbl_con:
for (from_pin, to_pin) in zip(from_pins, to_pins): for (from_pin, to_pin) in zip(from_pins, to_pins):
if con_cbl: if con_cbl:
h.connect(from_name, from_pin, to_name, to_pin, None, None) harness.connect(from_name, from_pin, to_name, to_pin, None, None)
else: # cbl_con else: # cbl_con
h.connect(None, None, from_name, from_pin, to_name, to_pin) harness.connect(None, None, from_name, from_pin, to_name, to_pin)
elif con_con: elif con_con:
cocon_coname = list(con[0].keys())[0] cocon_coname = list(connections[0].keys())[0]
from_pins = expand(con[0][from_name]) from_pins = expand(connections[0][from_name])
to_pins = expand(con[1][to_name]) to_pins = expand(connections[1][to_name])
for (from_pin, to_pin) in zip(from_pins, to_pins): for (from_pin, to_pin) in zip(from_pins, to_pins):
h.loop(cocon_coname, from_pin, to_pin) harness.loop(cocon_coname, from_pin, to_pin)
if fer_cbl or cbl_fer: if fer_cbl or cbl_fer:
from_pins = expand(con[0][from_name]) from_pins = expand(connections[0][from_name])
to_pins = expand(con[1][to_name]) to_pins = expand(connections[1][to_name])
if fer_cbl: if fer_cbl:
ferrule_name = from_name ferrule_name = from_name
@ -692,19 +169,19 @@ def parse(yaml_input, file_out=None, generate_bom=False):
ferrule_params = yaml_data['ferrules'][ferrule_name] ferrule_params = yaml_data['ferrules'][ferrule_name]
for cable_pin in cable_pins: for cable_pin in cable_pins:
ferrule_counter = ferrule_counter + 1 ferrule_counter = ferrule_counter + 1
ferrule_id = '_F{}'.format(ferrule_counter) ferrule_id = f'_F{ferrule_counter}'
h.add_connector(ferrule_id, category='ferrule', **ferrule_params) harness.add_connector(ferrule_id, category='ferrule', **ferrule_params)
if fer_cbl: if fer_cbl:
h.connect(ferrule_id, 1, cable_name, cable_pin, None, None) harness.connect(ferrule_id, 1, cable_name, cable_pin, None, None)
else: else:
h.connect(None, None, cable_name, cable_pin, ferrule_id, 1) harness.connect(None, None, cable_name, cable_pin, ferrule_id, 1)
else: else:
raise Exception('Wrong number of connection parameters') raise Exception('Wrong number of connection parameters')
h.output(filename=file_out, format=('png','svg'), gen_bom=generate_bom, view=False) harness.output(filename=file_out, fmt=('png', 'svg'), gen_bom=generate_bom, view=False)
def parse_file(yaml_file, file_out=None, generate_bom=False): def parse_file(yaml_file, file_out=None, generate_bom=False):
with open(yaml_file, 'r') as file: with open(yaml_file, 'r') as file:
@ -720,27 +197,21 @@ def parse_file(yaml_file, file_out=None, generate_bom=False):
def parse_cmdline(): def parse_cmdline():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Generate cable and wiring harness documentation from YAML descriptions' description='Generate cable and wiring harness documentation from YAML descriptions',
) )
parser.add_argument('input_file', action='store', type=str, metavar='YAML_FILE') parser.add_argument('input_file', action='store', type=str, metavar='YAML_FILE')
parser.add_argument('-o', '--output_file', action='store', type=str, metavar='OUTPUT')
parser.add_argument('-o', '--output_file', action='store', type=str, metavar='OUTPUT')
parser.add_argument('--generate-bom', action='store_true', default=True) parser.add_argument('--generate-bom', action='store_true', default=True)
parser.add_argument('--prepend-file', action='store', type=str, metavar='YAML_FILE') parser.add_argument('--prepend-file', action='store', type=str, metavar='YAML_FILE')
return parser.parse_args()
args = parser.parse_args()
return args
def main(): def main():
args = parse_cmdline() args = parse_cmdline()
if not os.path.exists(args.input_file): if not os.path.exists(args.input_file):
print('Error: input file {} inaccessible or does not exist, check path'.format(args.input_file)) print(f'Error: input file {args.input_file} inaccessible or does not exist, check path')
sys.exit(1) sys.exit(1)
with open(args.input_file) as fh: with open(args.input_file) as fh:
@ -748,7 +219,7 @@ def main():
if args.prepend_file: if args.prepend_file:
if not os.path.exists(args.prepend_file): if not os.path.exists(args.prepend_file):
print('Error: prepend input file {} inaccessible or does not exist, check path'.format(args.prepend_file)) print(f'Error: prepend input file {args.prepend_file} inaccessible or does not exist, check path')
sys.exit(1) sys.exit(1)
with open(args.prepend_file) as fh: with open(args.prepend_file) as fh:
prepend = fh.read() prepend = fh.read()
@ -757,12 +228,13 @@ def main():
if not args.output_file: if not args.output_file:
file_out = args.input_file file_out = args.input_file
pre, _ = os.path.splitext(file_out) pre, _ = os.path.splitext(file_out)
file_out = pre # extension will be added by graphviz output function file_out = pre # extension will be added by graphviz output function
else: else:
file_out = args.output_file file_out = args.output_file
file_out = os.path.abspath(file_out) file_out = os.path.abspath(file_out)
parse(yaml_input, file_out=file_out, generate_bom=args.generate_bom) parse(yaml_input, file_out=file_out, generate_bom=args.generate_bom)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -1,74 +1,78 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
COLOR_CODES = { COLOR_CODES = {
'DIN': ['WH','BN','GN','YE','GY','PK','BU','RD','BK','VT'], # ,'GYPK','RDBU','WHGN','BNGN','WHYE','YEBN','WHGY','GYBN','WHPK','PKBN'], 'DIN': ['WH', 'BN', 'GN', 'YE', 'GY', 'PK', 'BU', 'RD', 'BK', 'VT'], # ,'GYPK','RDBU','WHGN','BNGN','WHYE','YEBN','WHGY','GYBN','WHPK','PKBN'],
'IEC': ['BN','RD','OG','YE','GN','BU','VT','GY','WH','BK'], 'IEC': ['BN', 'RD', 'OG', 'YE', 'GN', 'BU', 'VT', 'GY', 'WH', 'BK'],
'BW': ['BK','WH'] 'BW': ['BK', 'WH'],
} }
color_hex = { color_hex = {
'BK': '#000000', 'BK': '#000000',
'WH': '#ffffff', 'WH': '#ffffff',
'GY': '#999999', 'GY': '#999999',
'PK': '#ff66cc', 'PK': '#ff66cc',
'RD': '#ff0000', 'RD': '#ff0000',
'OG': '#ff8000', 'OG': '#ff8000',
'YE': '#ffff00', 'YE': '#ffff00',
'GN': '#00ff00', 'GN': '#00ff00',
'TQ': '#00ffff', 'TQ': '#00ffff',
'BU': '#0066ff', 'BU': '#0066ff',
'VT': '#8000ff', 'VT': '#8000ff',
'BN': '#666600', 'BN': '#666600',
} }
color_full = { color_full = {
'BK': 'black', 'BK': 'black',
'WH': 'white', 'WH': 'white',
'GY': 'grey', 'GY': 'grey',
'PK': 'pink', 'PK': 'pink',
'RD': 'red', 'RD': 'red',
'OG': 'orange', 'OG': 'orange',
'YE': 'yellow', 'YE': 'yellow',
'GN': 'green', 'GN': 'green',
'TQ': 'turquoise', 'TQ': 'turquoise',
'BU': 'blue', 'BU': 'blue',
'VT': 'violet', 'VT': 'violet',
'BN': 'brown', 'BN': 'brown',
} }
color_ger = { color_ger = {
'BK': 'sw', 'BK': 'sw',
'WH': 'ws', 'WH': 'ws',
'GY': 'gr', 'GY': 'gr',
'PK': 'rs', 'PK': 'rs',
'RD': 'rt', 'RD': 'rt',
'OG': 'or', 'OG': 'or',
'YE': 'ge', 'YE': 'ge',
'GN': 'gn', 'GN': 'gn',
'TQ': 'tk', 'TQ': 'tk',
'BU': 'bl', 'BU': 'bl',
'VT': 'vi', 'VT': 'vi',
'BN': 'br', 'BN': 'br',
} }
def translate_color(input, color_mode):
if input == '': def translate_color(inp, color_mode):
if inp == '':
output = '' output = ''
else: else:
if color_mode == 'full': if color_mode == 'full':
output = color_full[input].lower() output = color_full[inp].lower()
elif color_mode == 'FULL': elif color_mode == 'FULL':
output = color_full[input].upper() output = color_full[inp].upper()
elif color_mode == 'hex': elif color_mode == 'hex':
output = color_hex[input].lower() output = color_hex[inp].lower()
elif color_mode == 'HEX': elif color_mode == 'HEX':
output = color_hex[input].upper() output = color_hex[inp].upper()
elif color_mode == 'ger': elif color_mode == 'ger':
output = color_ger[input].lower() output = color_ger[inp].lower()
elif color_mode == 'GER': elif color_mode == 'GER':
output = color_ger[input].upper() output = color_ger[inp].upper()
elif color_mode == 'short': elif color_mode == 'short':
output = input.lower() output = inp.lower()
elif color_mode == 'SHORT': elif color_mode == 'SHORT':
output = input.upper() output = inp.upper()
else: else:
raise Exception('Unknown color mode') raise Exception('Unknown color mode')
return output return output

View File

@ -1,33 +1,38 @@
from typing import Any, List #!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import List
awg_equiv_table = {
'0.09': '28',
'0.14': '26',
'0.25': '24',
'0.34': '22',
'0.5': '21',
'0.75': '20',
'1': '18',
'1.5': '16',
'2.5': '14',
'4': '12',
'6': '10',
'10': '8',
'16': '6',
'25': '4',
'35': '2',
'50': '1',
}
mm2_equiv_table = {v:k for k,v in awg_equiv_table.items()}
def awg_equiv(mm2): def awg_equiv(mm2):
awg_equiv_table = { return awg_equiv_table.get(str(mm2), 'Unknown')
'0.09': 28,
'0.14': 26,
'0.25': 24,
'0.34': 22,
'0.5': 21,
'0.75': 20,
'1': 18,
'1.5': 16,
'2.5': 14,
'4': 12,
'6': 10,
'10': 8,
'16': 6,
'25': 4,
'35': 2,
'50': 1,
}
k = str(mm2)
if k in awg_equiv_table:
return awg_equiv_table[k]
else:
return 'unknown'
def nested(input): def mm2_equiv(awg):
return mm2_equiv_table.get(str(awg), 'Unknown')
def nested(inp):
l = [] l = []
for x in input: for x in inp:
if isinstance(x, list): if isinstance(x, list):
if len(x) > 0: if len(x) > 0:
n = nested(x) n = nested(x)
@ -37,25 +42,26 @@ def nested(input):
if x is not None: if x is not None:
if x != '': if x != '':
l.append(str(x)) l.append(str(x))
s = '|'.join(l) return '|'.join(l)
return s
def int2tuple(input):
if isinstance(input, tuple): def int2tuple(inp):
output = input if isinstance(inp, tuple):
output = inp
else: else:
output = (input,) output = (inp,)
return output return output
def flatten2d(input):
output = [[str(item) if not isinstance(item, List) else ', '.join(item) for item in row] for row in input]
return output
def tuplelist2tsv(input, header=None): def flatten2d(inp):
return [[str(item) if not isinstance(item, List) else ', '.join(item) for item in row] for row in inp]
def tuplelist2tsv(inp, header=None):
output = '' output = ''
if header is not None: if header is not None:
input.insert(0, header) inp.insert(0, header)
input = flatten2d(input) inp = flatten2d(inp)
for row in input: for row in inp:
output = output + '\t'.join(str(item) for item in row) + '\n' output = output + '\t'.join(str(item) for item in row) + '\n'
return output return output