diff --git a/.gitattributes b/.gitattributes index deb5a71c8..11fb9d82a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,8 +2,8 @@ docs/* linguist-documentation examples/* linguist-documentation tutorial/* linguist-documentation -**/*.bom.tsv linguist-generated -**/*.bom.csv linguist-generated +**/*.tsv linguist-generated +**/*.csv linguist-generated **/*.gv linguist-generated **/*.html linguist-generated **/*.png linguist-generated diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 301ac18ad..533b74fb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,25 +8,25 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.7, 3.8] + python-version: ["3.8", "3.10"] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Setup Graphviz - uses: ts-graphviz/setup-graphviz@v1 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install . - - name: Create Examples - run: PYTHONPATH=$(pwd)/src:$PYTHONPATH cd src/wireviz/ && python build_examples.py - - name: Upload examples, demos, and tutorials - uses: actions/upload-artifact@v2 - with: - name: examples-and-tutorials - path: | - examples/ - tutorial/ \ No newline at end of file + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Setup Graphviz + uses: ts-graphviz/setup-graphviz@v1 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . + - name: Create Examples + run: PYTHONPATH=$(pwd)/src:$PYTHONPATH python src/wireviz/tools/build_examples.py + - name: Upload examples, demos, and tutorials + uses: actions/upload-artifact@v2 + with: + name: examples-and-tutorials + path: | + examples/ + tutorial/ diff --git a/.gitignore b/.gitignore index 7484ecbd7..dc58d27e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,21 @@ +# OS-specific files .DS_Store +desktop.ini +Thumbs.db + +# Development aids .idea/ -.eggs -__pycache__ -.*.swp -*.egg-info -*.pyc -build -data -dist +.vscode/ +temp/ venv/ .venv/ -desktop.ini -thumbs.db -temp/ + +# Build/compile/release artifacts +build/ +dist/ +*.egg-info +*.pyc + +# Other temporary files +__pycache__ +.*.swp diff --git a/cleanup.sh b/cleanup.sh new file mode 100755 index 000000000..9c774be4b --- /dev/null +++ b/cleanup.sh @@ -0,0 +1,5 @@ +#!/bin/zsh + +autoflake -i --remove-all-unused-imports src/wireviz/*.py +isort src/wireviz/*py +black src/wireviz/*.py diff --git a/devtools.txt b/devtools.txt new file mode 100644 index 000000000..adf89cad0 --- /dev/null +++ b/devtools.txt @@ -0,0 +1,12 @@ +# The following tools have proven useful during development +# Feel free to install while inside the WireViz virtualenv, using: +# pip install -r devtools.txt + +# Code formatting +black # black src/wireviz/*.py +isort # isort src/wireviz/*py + +# Development aids +pudb # import pudb; pudb.set_trace() +autoflake # autoflake -i --remove-all-unused-imports src/wireviz/*.py +pyan # pyan3 src/wireviz/*.py -uncge --html > temp/pyan.html diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 28f8ba14c..3303aa037 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,18 +1,51 @@ # Change Log -## [0.4](https://github.com/formatc1702/WireViz/tree/v0.4) (20XX-XX-XX) +## [0.5](https://github.com/wireviz/WireViz/tree/v0.5) (20XX-XX-XX) + +TODO + + +## [0.4.1](https://github.com/wireviz/WireViz/tree/v0.4.1) (2024-07-13) + +### Improvements to help reported issues + +- Print Python & OS versions when raising unexpected OSError related to #346 & #392 (bugfixes below) +- Explain unexpeced top-level type ([#342](https://github.com/wireviz/WireViz/issues/342), [#383](https://github.com/wireviz/WireViz/pull/383)) +- Add non-empty label to reduce over-sized loops ([#286](https://github.com/wireviz/WireViz/issues/286), [#381](https://github.com/wireviz/WireViz/pull/381)) +- Improve placeholder name consistency ([#377](https://github.com/wireviz/WireViz/issues/377), [#380](https://github.com/wireviz/WireViz/pull/380)) +- Add work-around for Graphviz SVG bug ([#175](https://github.com/wireviz/WireViz/issues/175), [#371](https://github.com/wireviz/WireViz/pull/371)) + +### Bugfixes + +- Avoid ResourceWarning: unclosed file ([#309 (comment)](https://github.com/wireviz/WireViz/pull/309#issuecomment-2170988381), [#395](https://github.com/wireviz/WireViz/pull/395)) +- Catch ValueError and OSError(errno=None) ([#318 (review)](https://github.com/wireviz/WireViz/pull/318#pullrequestreview-1457016602), [#391](https://github.com/wireviz/WireViz/issues/391), [#392](https://github.com/wireviz/WireViz/pull/392)) +- Add minor missing doc entry ([#186 (comment)](https://github.com/wireviz/WireViz/pull/186#issuecomment-2139037434), [#186 (comment)](https://github.com/wireviz/WireViz/pull/186#issuecomment-2155032522)) +- Avoid Graphviz error when hiding all pins ([#257](https://github.com/wireviz/WireViz/issues/257), [#375](https://github.com/wireviz/WireViz/pull/375)) +- Avoid decimal point and trailing zero for integer BOM quantities ([#340](https://github.com/wireviz/WireViz/issues/340), [#374](https://github.com/wireviz/WireViz/pull/374)) +- Update project URL references ([#316 (comment)](https://github.com/wireviz/WireViz/issues/316#issuecomment-1568748914), [#364](https://github.com/wireviz/WireViz/pull/364)) +- Add missing import of embed_svg_images ([#363](https://github.com/wireviz/WireViz/pull/363)) +- Use correct default title ([#360](https://github.com/wireviz/WireViz/issues/360), [#361](https://github.com/wireviz/WireViz/pull/361)) +- Fix bugs in mate processing ([#355](https://github.com/wireviz/WireViz/issues/355), [#358](https://github.com/wireviz/WireViz/pull/358)) +- Include missing files in published package ([#345](https://github.com/wireviz/WireViz/issues/345), [#347](https://github.com/wireviz/WireViz/pull/347)) +- Catch OSError(errno=EINVAL) ([#344](https://github.com/wireviz/WireViz/issues/344), [#346](https://github.com/wireviz/WireViz/pull/346)) + + +## [0.4](https://github.com/wireviz/WireViz/tree/v0.4) (2024-05-12) ### Backward-incompatible changes - New syntax for autogenerated components ([#184](https://github.com/wireviz/WireViz/issues/184), [#186](https://github.com/wireviz/WireViz/pull/186)) -- New command line interface ([#244](https://github.com/wireviz/WireViz/pull/244)) - + - Components that are not referenced in any connection set will not be rendered. Instead, a warning will be output in the console. ([#328](https://github.com/wireviz/WireViz/issues/328), [#332](https://github.com/wireviz/WireViz/pull/332)) +- New command line interface ([#244](https://github.com/wireviz/WireViz/pull/244)). Run `wireviz --help` for details + - The path specified with the `-o`/`--output-dir` option no longer includes the filename (without extension) of the generated files. Use the `-O`/`--output-name` option to specify a different filename for the generated files. +- The `.gv` file is no longer included as a default output format (only as an intermediate file during processing) unless specified with the new `-f` option described below. ### New features - Allow mates between connectors ([#134](https://github.com/wireviz/WireViz/issues/134), [#186](https://github.com/wireviz/WireViz/pull/186)) - Improve technical drawing output ([#74](https://github.com/wireviz/WireViz/pull/74), [#32](https://github.com/wireviz/WireViz/issues/32), [#239](https://github.com/wireviz/WireViz/pull/239)) - Embed images in SVG output ([#189](https://github.com/wireviz/WireViz/pull/189)) - +- Add ability to choose output formats using the `-f`/`--format` command line option ([#60](https://github.com/wireviz/WireViz/issues/60)) +- Add option to multiply additional component quantity by number of unpopulated positions on connector ([#298](https://github.com/wireviz/WireViz/pull/298)) ### Misc. fixes - Use `isort` and `black` for cleaner code and easier merging ([#248](https://github.com/wireviz/WireViz/pull/248)) @@ -21,93 +54,89 @@ - Minor adjustments ([#256](https://github.com/wireviz/WireViz/pull/256)) -## [0.3.2](https://github.com/formatc1702/WireViz/tree/v0.3.2) (2021-11-27) +## [0.3.2](https://github.com/wireviz/WireViz/tree/v0.3.2) (2021-11-27) ### Hotfix -- Adjust GraphViz generation code for compatibility with v0.18 of the `graphviz` Python package ([#258](https://github.com/formatc1702/WireViz/issues/258), [#261](https://github.com/formatc1702/WireViz/pull/261)) +- Adjust GraphViz generation code for compatibility with v0.18 of the `graphviz` Python package ([#258](https://github.com/wireviz/WireViz/issues/258), [#261](https://github.com/wireviz/WireViz/pull/261)) -## [0.3.1](https://github.com/formatc1702/WireViz/tree/v0.3.1) (2021-10-25) +## [0.3.1](https://github.com/wireviz/WireViz/tree/v0.3.1) (2021-10-25) ### Hotfix -- Assign generic harness title when using WireViz as a module and not specifying an output file name ([#253](https://github.com/formatc1702/WireViz/issues/253), [#254](https://github.com/formatc1702/WireViz/pull/254)) +- Assign generic harness title when using WireViz as a module and not specifying an output file name ([#253](https://github.com/wireviz/WireViz/issues/253), [#254](https://github.com/wireviz/WireViz/pull/254)) -## [0.3](https://github.com/formatc1702/WireViz/tree/v0.3) (2021-10-11) +## [0.3](https://github.com/wireviz/WireViz/tree/v0.3) (2021-10-11) ### New features -- Allow referencing a cable's/bundle's wires by color or by label ([#70](https://github.com/formatc1702/WireViz/issues/70), [#169](https://github.com/formatc1702/WireViz/issues/169), [#193](https://github.com/formatc1702/WireViz/issues/193), [#194](https://github.com/formatc1702/WireViz/pull/194)) -- Allow additional BOM items within components ([#50](https://github.com/formatc1702/WireViz/issues/50), [#115](https://github.com/formatc1702/WireViz/pull/115)) -- Add support for length units in cables and wires ([#7](https://github.com/formatc1702/WireViz/issues/7), [#196](https://github.com/formatc1702/WireViz/pull/196) (with work from [#161](https://github.com/formatc1702/WireViz/pull/161), [#162](https://github.com/formatc1702/WireViz/pull/162), [#171](https://github.com/formatc1702/WireViz/pull/171)), [#198](https://github.com/formatc1702/WireViz/pull/198), [#205](https://github.com/formatc1702/WireViz/issues/205). [#206](https://github.com/formatc1702/WireViz/pull/206)) -- Add option to define connector pin colors ([#53](https://github.com/formatc1702/WireViz/issues/53), [#141](https://github.com/formatc1702/WireViz/pull/141)) -- Remove HTML links from the input attributes ([#164](https://github.com/formatc1702/WireViz/pull/164)) -- Add harness metadata section ([#158](https://github.com/formatc1702/WireViz/issues/158), [#214](https://github.com/formatc1702/WireViz/pull/214)) -- Add support for supplier and supplier part number information ([#240](https://github.com/formatc1702/WireViz/issues/240), [#241](https://github.com/formatc1702/WireViz/pull/241/)) -- Add graph rendering options (colors, font, color name display style, ...) ([#158](https://github.com/formatc1702/WireViz/issues/158), [#214](https://github.com/formatc1702/WireViz/pull/214)) -- Add support for supplier and supplier part number information ([#240](https://github.com/formatc1702/WireViz/issues/240), [#241](https://github.com/formatc1702/WireViz/pull/241/)) -- Add graph rendering options (colors, font, color name display style, ...) ([#158](https://github.com/formatc1702/WireViz/issues/158), [#214](https://github.com/formatc1702/WireViz/pull/214)) -- Add support for background colors for cables and connectors, as well as for some individual cells ([#210](https://github.com/formatc1702/WireViz/issues/210), [#219](https://github.com/formatc1702/WireViz/pull/219)) -- Add optional tweaking of the .gv output ([#215](https://github.com/formatc1702/WireViz/pull/215)) (experimental) - +- Allow referencing a cable's/bundle's wires by color or by label ([#70](https://github.com/wireviz/WireViz/issues/70), [#169](https://github.com/wireviz/WireViz/issues/169), [#193](https://github.com/wireviz/WireViz/issues/193), [#194](https://github.com/wireviz/WireViz/pull/194)) +- Allow additional BOM items within components ([#50](https://github.com/wireviz/WireViz/issues/50), [#115](https://github.com/wireviz/WireViz/pull/115)) +- Add support for length units in cables and wires ([#7](https://github.com/wireviz/WireViz/issues/7), [#196](https://github.com/wireviz/WireViz/pull/196) (with work from [#161](https://github.com/wireviz/WireViz/pull/161), [#162](https://github.com/wireviz/WireViz/pull/162), [#171](https://github.com/wireviz/WireViz/pull/171)), [#198](https://github.com/wireviz/WireViz/pull/198), [#205](https://github.com/wireviz/WireViz/issues/205). [#206](https://github.com/wireviz/WireViz/pull/206)) +- Add option to define connector pin colors ([#53](https://github.com/wireviz/WireViz/issues/53), [#141](https://github.com/wireviz/WireViz/pull/141)) +- Remove HTML links from the input attributes ([#164](https://github.com/wireviz/WireViz/pull/164)) +- Add harness metadata section ([#158](https://github.com/wireviz/WireViz/issues/158), [#214](https://github.com/wireviz/WireViz/pull/214)) +- Add support for supplier and supplier part number information ([#240](https://github.com/wireviz/WireViz/issues/240), [#241](https://github.com/wireviz/WireViz/pull/241/)) +- Add graph rendering options (background colors, fontname, color name display style, ...) ([#158](https://github.com/wireviz/WireViz/issues/158), [#214](https://github.com/wireviz/WireViz/pull/214)) +- Add support for background colors for cables and connectors, as well as for some individual cells ([#210](https://github.com/wireviz/WireViz/issues/210), [#219](https://github.com/wireviz/WireViz/pull/219)) +- Add optional tweaking of the .gv output ([#215](https://github.com/wireviz/WireViz/pull/215)) (experimental) ### Misc. fixes -- Remove case-sensitivity issues with pin names and labels ([#160](https://github.com/formatc1702/WireViz/issues/160), [#229](https://github.com/formatc1702/WireViz/pull/229)) -- Improve type hinting ([#156](https://github.com/formatc1702/WireViz/issues/156), [#163](https://github.com/formatc1702/WireViz/pull/163)) -- Move BOM management and HTML functions to separate modules ([#151](https://github.com/formatc1702/WireViz/issues/151), [#192](https://github.com/formatc1702/WireViz/pull/192)) -- Simplify BOM code ([#197](https://github.com/formatc1702/WireViz/pull/197)) -- Bug fixes ([#218](https://github.com/formatc1702/WireViz/pull/218), [#221](https://github.com/formatc1702/WireViz/pull/221)) +- Remove case-sensitivity issues with pin names and labels ([#160](https://github.com/wireviz/WireViz/issues/160), [#229](https://github.com/wireviz/WireViz/pull/229)) +- Improve type hinting ([#156](https://github.com/wireviz/WireViz/issues/156), [#163](https://github.com/wireviz/WireViz/pull/163)) +- Move BOM management and HTML functions to separate modules ([#151](https://github.com/wireviz/WireViz/issues/151), [#192](https://github.com/wireviz/WireViz/pull/192)) +- Simplify BOM code ([#197](https://github.com/wireviz/WireViz/pull/197)) +- Bug fixes ([#218](https://github.com/wireviz/WireViz/pull/218), [#221](https://github.com/wireviz/WireViz/pull/221)) ### Known issues -- Including images in the harness may lead to issues in the following cases: ([#189](https://github.com/formatc1702/WireViz/pull/189), [#220](https://github.com/formatc1702/WireViz/issues/220)) +- Including images in the harness may lead to issues in the following cases: ([#189](https://github.com/wireviz/WireViz/pull/189), [#220](https://github.com/wireviz/WireViz/issues/220)) - When using the `-o`/`--output_file` CLI option, specifying an output path in a different directory from the input file - When using the `--prepend-file` CLI option, specifying a prepend file in a different directory from the mail input file -## [0.2](https://github.com/formatc1702/WireViz/tree/v0.2) (2020-10-17) + +## [0.2](https://github.com/wireviz/WireViz/tree/v0.2) (2020-10-17) ### Backward incompatible changes -- Change names of connector attributes ([#77](https://github.com/formatc1702/WireViz/issues/77), [#105](https://github.com/formatc1702/WireViz/pull/105)) +- Change names of connector attributes ([#77](https://github.com/wireviz/WireViz/issues/77), [#105](https://github.com/wireviz/WireViz/pull/105)) - `pinnumbers` is now `pins` - `pinout` is now `pinlabels` -- Remove ferrules as a separate connector type ([#78](https://github.com/formatc1702/WireViz/issues/78), [#102](https://github.com/formatc1702/WireViz/pull/102)) +- Remove ferrules as a separate connector type ([#78](https://github.com/wireviz/WireViz/issues/78), [#102](https://github.com/wireviz/WireViz/pull/102)) - Simple connectors like ferrules are now defined using the `style: simple` attribute -- Change the way loops are defined ([#79](https://github.com/formatc1702/WireViz/issues/79), [#75](https://github.com/formatc1702/WireViz/pull/75)) +- Change the way loops are defined ([#79](https://github.com/wireviz/WireViz/issues/79), [#75](https://github.com/wireviz/WireViz/pull/75)) - Wires looping between two pins of the same connector are now handled via the connector's `loops` attribute. See the [syntax description](syntax.md) for details. - ### New features -- Add bidirectional AWG/mm2 conversion ([#40](https://github.com/formatc1702/WireViz/issues/40), [#41](https://github.com/formatc1702/WireViz/pull/41)) -- Add support for part numbers ([#11](https://github.com/formatc1702/WireViz/pull/11), [#114](https://github.com/formatc1702/WireViz/issues/114), [#121](https://github.com/formatc1702/WireViz/pull/121)) -- Add support for multicolored wires ([#12](https://github.com/formatc1702/WireViz/issues/12), [#17](https://github.com/formatc1702/WireViz/pull/17), [#96](https://github.com/formatc1702/WireViz/pull/96), [#131](https://github.com/formatc1702/WireViz/issues/131), [#132](https://github.com/formatc1702/WireViz/pull/132)) -- Add support for images ([#27](https://github.com/formatc1702/WireViz/issues/27), [#153](https://github.com/formatc1702/WireViz/pull/153)) -- Add ability to export data directly to other programs ([#55](https://github.com/formatc1702/WireViz/pull/55)) -- Add support for line breaks in various fields ([#49](https://github.com/formatc1702/WireViz/issues/49), [#64](https://github.com/formatc1702/WireViz/pull/64)) -- Allow using connector pin names to define connections ([#72](https://github.com/formatc1702/WireViz/issues/72), [#139](https://github.com/formatc1702/WireViz/issues/139), [#140](https://github.com/formatc1702/WireViz/pull/140)) -- Make defining connection sets easier and more flexible ([#67](https://github.com/formatc1702/WireViz/issues/67), [#75](https://github.com/formatc1702/WireViz/pull/75)) -- Add new command line options ([#167](https://github.com/formatc1702/WireViz/issues/167), [#173](https://github.com/formatc1702/WireViz/pull/173)) -- Add new features to `build_examples.py` ([#118](https://github.com/formatc1702/WireViz/pull/118)) -- Add new colors ([#103](https://github.com/formatc1702/WireViz/pull/103), [#113](https://github.com/formatc1702/WireViz/pull/113), [#144](https://github.com/formatc1702/WireViz/issues/144), [#145](https://github.com/formatc1702/WireViz/pull/145)) -- Improve documentation ([#107](https://github.com/formatc1702/WireViz/issues/107), [#111](https://github.com/formatc1702/WireViz/pull/111)) - +- Add bidirectional AWG/mm2 conversion ([#40](https://github.com/wireviz/WireViz/issues/40), [#41](https://github.com/wireviz/WireViz/pull/41)) +- Add support for part numbers ([#11](https://github.com/wireviz/WireViz/pull/11), [#114](https://github.com/wireviz/WireViz/issues/114), [#121](https://github.com/wireviz/WireViz/pull/121)) +- Add support for multicolored wires ([#12](https://github.com/wireviz/WireViz/issues/12), [#17](https://github.com/wireviz/WireViz/pull/17), [#96](https://github.com/wireviz/WireViz/pull/96), [#131](https://github.com/wireviz/WireViz/issues/131), [#132](https://github.com/wireviz/WireViz/pull/132)) +- Add support for images ([#27](https://github.com/wireviz/WireViz/issues/27), [#153](https://github.com/wireviz/WireViz/pull/153)) +- Add ability to export data directly to other programs ([#55](https://github.com/wireviz/WireViz/pull/55)) +- Add support for line breaks in various fields ([#49](https://github.com/wireviz/WireViz/issues/49), [#64](https://github.com/wireviz/WireViz/pull/64)) +- Allow using connector pin names to define connections ([#72](https://github.com/wireviz/WireViz/issues/72), [#139](https://github.com/wireviz/WireViz/issues/139), [#140](https://github.com/wireviz/WireViz/pull/140)) +- Make defining connection sets easier and more flexible ([#67](https://github.com/wireviz/WireViz/issues/67), [#75](https://github.com/wireviz/WireViz/pull/75)) +- Add new command line options ([#167](https://github.com/wireviz/WireViz/issues/167), [#173](https://github.com/wireviz/WireViz/pull/173)) +- Add new features to `build_examples.py` ([#118](https://github.com/wireviz/WireViz/pull/118)) +- Add new colors ([#103](https://github.com/wireviz/WireViz/pull/103), [#113](https://github.com/wireviz/WireViz/pull/113), [#144](https://github.com/wireviz/WireViz/issues/144), [#145](https://github.com/wireviz/WireViz/pull/145)) +- Improve documentation ([#107](https://github.com/wireviz/WireViz/issues/107), [#111](https://github.com/wireviz/WireViz/pull/111)) ### Misc. fixes - Improve BOM generation - Add various input sanity checks -- Improve HTML output ([#66](https://github.com/formatc1702/WireViz/issues/66), [#136](https://github.com/formatc1702/WireViz/pull/136), [#95](https://github.com/formatc1702/WireViz/pull/95), [#177](https://github.com/formatc1702/WireViz/pull/177)) -- Fix node rendering bug ([#69](https://github.com/formatc1702/WireViz/issues/69), [#104](https://github.com/formatc1702/WireViz/pull/104)) -- Improve shield rendering ([#125](https://github.com/formatc1702/WireViz/issues/125), [#126](https://github.com/formatc1702/WireViz/pull/126)) -- Add GitHub Linguist overrides ([#146](https://github.com/formatc1702/WireViz/issues/146), [#154](https://github.com/formatc1702/WireViz/pull/154)) +- Improve HTML output ([#66](https://github.com/wireviz/WireViz/issues/66), [#136](https://github.com/wireviz/WireViz/pull/136), [#95](https://github.com/wireviz/WireViz/pull/95), [#177](https://github.com/wireviz/WireViz/pull/177)) +- Fix node rendering bug ([#69](https://github.com/wireviz/WireViz/issues/69), [#104](https://github.com/wireviz/WireViz/pull/104)) +- Improve shield rendering ([#125](https://github.com/wireviz/WireViz/issues/125), [#126](https://github.com/wireviz/WireViz/pull/126)) +- Add GitHub Linguist overrides ([#146](https://github.com/wireviz/WireViz/issues/146), [#154](https://github.com/wireviz/WireViz/pull/154)) -## [0.1](https://github.com/formatc1702/WireViz/tree/v0.1) (2020-06-29) +## [0.1](https://github.com/wireviz/WireViz/tree/v0.1) (2020-06-29) - Initial release diff --git a/docs/README.md b/docs/README.md index 8045f85dd..4fa515931 100644 --- a/docs/README.md +++ b/docs/README.md @@ -74,13 +74,13 @@ Output file: ![Sample output diagram](../examples/demo01.png) -[Bill of Materials](../examples/demo01.bom.tsv) (auto-generated) +[Bill of Materials](../examples/demo01.tsv) (auto-generated) ### Demo 02 ![](../examples/demo02.png) -[Source](../examples/demo02.yml) - [Bill of Materials](../examples/demo02.bom.tsv) +[Source](../examples/demo02.yml) - [Bill of Materials](../examples/demo02.tsv) ### Syntax, tutorial and example gallery @@ -133,7 +133,7 @@ Depending on the options specified, this will output some or all of the followin mywire.gv GraphViz output mywire.svg Wiring diagram as vector image mywire.png Wiring diagram as raster image -mywire.bom.tsv BOM (bill of materials) as tab-separated text file +mywire.tsv BOM (bill of materials) as tab-separated text file mywire.html HTML page with wiring diagram and BOM embedded ``` diff --git a/docs/buildscript.md b/docs/buildscript.md index 2655770e8..d39eab416 100644 --- a/docs/buildscript.md +++ b/docs/buildscript.md @@ -26,7 +26,7 @@ Possible group names: - `tutorial` to process`tutorial/{readme.md,tutorial*.*}` - `demos` to process`examples/demo*.*` - Affected filetypes: `.gv`, `.bom.tsv`, `.png`, `.svg`, `.html` + Affected filetypes: `.gv`, `.tsv`, `.png`, `.svg`, `.html` ## Usage hints diff --git a/examples/demo01.gv b/examples/demo01.gv index 492611687..56dc5f703 100644 --- a/examples/demo01.gv +++ b/examples/demo01.gv @@ -60,6 +60,8 @@ graph { > fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + X1:p7r:e -- X1:p8r:e X2 [label=<
diff --git a/examples/demo01.html b/examples/demo01.html index f192c77dd..8dcb55017 100644 --- a/examples/demo01.html +++ b/examples/demo01.html @@ -1,13 +1,36 @@ - - - demo01 - + + + demo01 + +

demo01

Diagram

- - + + +
+ + Diagram X1 + X1 @@ -63,6 +87,13 @@

Diagram

9 + + +X1:e--X1:e + + + + W1 @@ -103,28 +134,28 @@

Diagram

 
- + X1:e--W1:w - + X1:e--W1:w - + X1:e--W1:w - + X1:e--W1:w @@ -132,6 +163,7 @@

Diagram

X2 + X2 @@ -154,21 +186,21 @@

Diagram

TX
- + W1:e--X2:w - + W1:e--X2:w - + W1:e--X2:w @@ -176,35 +208,47 @@

Diagram

+ +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 3 x 0.25 mm² shielded0.2mW1
2Connector, D-Sub, female, 9 pins1X1
3Connector, Molex KK 254, female, 3 pins1X2
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 3 x 0.25 mm² shielded0.2mW1
2Connector, D-Sub, female, 9 pins1X1
3Connector, Molex KK 254, female, 3 pins1X2
+ + + diff --git a/examples/demo01.png b/examples/demo01.png index f8a95a317..179147d40 100644 Binary files a/examples/demo01.png and b/examples/demo01.png differ diff --git a/examples/demo01.svg b/examples/demo01.svg index bd454446c..03b2a4bf3 100644 --- a/examples/demo01.svg +++ b/examples/demo01.svg @@ -1,7 +1,7 @@ - X1 + X1 @@ -57,6 +58,13 @@ 9 + + +X1:e--X1:e + + + + W1 @@ -97,28 +105,28 @@   - + X1:e--W1:w - + X1:e--W1:w - + X1:e--W1:w - + X1:e--W1:w @@ -126,6 +134,7 @@ X2 + X2 @@ -148,21 +157,21 @@ TX - + W1:e--X2:w - + W1:e--X2:w - + W1:e--X2:w diff --git a/examples/demo01.yml b/examples/demo01.yml index 56c77c11b..d5ed3ef23 100644 --- a/examples/demo01.yml +++ b/examples/demo01.yml @@ -1,8 +1,13 @@ +metadata: + title: demo01 + connectors: X1: type: D-Sub subtype: female pinlabels: [DCD, RX, TX, DTR, GND, DSR, RTS, CTS, RI] + loops: + - [7,8] X2: type: Molex KK 254 subtype: female diff --git a/examples/demo02.gv b/examples/demo02.gv index 48f53817d..ed8dd8117 100644 --- a/examples/demo02.gv +++ b/examples/demo02.gv @@ -168,7 +168,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_1 [label=< + AUTOGENERATED_F_1 [label=<
@@ -180,7 +180,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_2 [label=< + AUTOGENERATED_F_2 [label=<
@@ -487,10 +487,10 @@ graph {
> fillcolor="#FFFFFF" shape=box style="filled,dashed"] edge [color="#000000:#000000:#000000"] - _ferrule_crimp_1:e -- W4:w1:w + AUTOGENERATED_F_1:e -- W4:w1:w W4:w1:e -- X4:p1l:w edge [color="#000000:#ff0000:#000000"] - _ferrule_crimp_2:e -- W4:w2:w + AUTOGENERATED_F_2:e -- W4:w2:w W4:w2:e -- X4:p2l:w W4 [label=< diff --git a/examples/demo02.html b/examples/demo02.html index cc726ae35..8fe7c4721 100644 --- a/examples/demo02.html +++ b/examples/demo02.html @@ -1,13 +1,194 @@ - - - - demo02 - -

demo02

-

Diagram

- - + + + +Diagram X1 + X1 @@ -264,6 +446,7 @@

Diagram

X2 + X2 @@ -293,6 +476,7 @@

Diagram

X3 + X3 @@ -322,6 +506,7 @@

Diagram

X4 + X4 @@ -351,10 +536,11 @@

Diagram

SCK
- + -_ferrule_crimp_1 +AUTOGENERATED_F_1 + Crimp ferrule @@ -389,17 +575,18 @@

Diagram

 
- + -_ferrule_crimp_1:e--W4:w +AUTOGENERATED_F_1:e--W4:w - + -_ferrule_crimp_2 +AUTOGENERATED_F_2 + Crimp ferrule @@ -409,9 +596,9 @@

Diagram

- + -_ferrule_crimp_2:e--W4:w +AUTOGENERATED_F_2:e--W4:w @@ -516,98 +703,193 @@

Diagram

-

Bill of Materials

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +
+ +
+ + + +
+
IdDescriptionQtyUnitDesignators
1Cable, 2 x 0.25 mm²0.3mW4
2Connector, Crimp ferrule, 0.25 mm², YE2
3Connector, Molex KK 254, female, 4 pins2X2, X3
4Connector, Molex KK 254, female, 5 pins1X4
5Connector, Molex KK 254, female, 8 pins1X1
6Wire, 0.14 mm², BK0.9mW1, W2, W3
7Wire, 0.14 mm², BU0.3mW3
8Wire, 0.14 mm², GN0.6mW1, W2
9Wire, 0.14 mm², OG0.3mW3
10Wire, 0.14 mm², RD0.6mW1, W2
11Wire, 0.14 mm², VT0.3mW3
12Wire, 0.14 mm², YE0.6mW1, W2
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
12Wire, 0.14 mm², YE0.6mW1, W2
11Wire, 0.14 mm², VT0.3mW3
10Wire, 0.14 mm², RD0.6mW1, W2
9Wire, 0.14 mm², OG0.3mW3
8Wire, 0.14 mm², GN0.6mW1, W2
7Wire, 0.14 mm², BU0.3mW3
6Wire, 0.14 mm², BK0.9mW1, W2, W3
5Connector, Molex KK 254, female, 8 pins1X1
4Connector, Molex KK 254, female, 5 pins1X4
3Connector, Molex KK 254, female, 4 pins2X2, X3
2Connector, Crimp ferrule, 0.25 mm², YE2
1Cable, 2 x 0.25 mm²0.3mW4
IdDescriptionQtyUnitDesignators
- + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateNameWireViz Demo 2
Created2020-05-20D. Rojas
Approved2020-05-20D. Rojas
WV-DEMO-02Sheet
1
AWireViz 0.2 release2020-10-17D. Rojasof 1
RevChangelogDateName
+
+ + + + + diff --git a/examples/demo02.png b/examples/demo02.png index 667267704..f7dd69188 100644 Binary files a/examples/demo02.png and b/examples/demo02.png differ diff --git a/examples/demo02.svg b/examples/demo02.svg index 06320f89d..130922bef 100644 --- a/examples/demo02.svg +++ b/examples/demo02.svg @@ -1,7 +1,7 @@ - X1 + X1 @@ -258,6 +259,7 @@ X2 + X2 @@ -287,6 +289,7 @@ X3 + X3 @@ -316,6 +319,7 @@ X4 + X4 @@ -345,10 +349,11 @@ SCK - + -_ferrule_crimp_1 +AUTOGENERATED_F_1 + Crimp ferrule @@ -383,17 +388,18 @@   - + -_ferrule_crimp_1:e--W4:w +AUTOGENERATED_F_1:e--W4:w - + -_ferrule_crimp_2 +AUTOGENERATED_F_2 + Crimp ferrule @@ -403,9 +409,9 @@ - + -_ferrule_crimp_2:e--W4:w +AUTOGENERATED_F_2:e--W4:w diff --git a/examples/ex01.html b/examples/ex01.html index 63558a535..c11a3a6f9 100644 --- a/examples/ex01.html +++ b/examples/ex01.html @@ -1,13 +1,36 @@ - - - ex01 - + + + ex01 + +

ex01

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, Serial, 4 x 0.25 mm² shielded0.2mW1
2Connector, Molex KK 254, female, 4 pins2X1, X2
+ + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, Serial, 4 x 0.25 mm² shielded0.2mW1
2Connector, Molex KK 254, female, 4 pins2X1, X2
+ + + diff --git a/examples/ex01.svg b/examples/ex01.svg index d72503b51..4667ae539 100644 --- a/examples/ex01.svg +++ b/examples/ex01.svg @@ -1,7 +1,7 @@ - - - - ex02 - + + + ex02 + +

ex02

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 2 x 0.25 mm²0.4mW1, W2
2Cable, 2 x 20 AWG0.2mW3
3Connector, Molex Micro-Fit, female, 2 pins3X2, X3, X4
4Connector, Molex Micro-Fit, male, 2 pins1X1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 2 x 0.25 mm²0.4mW1, W2
2Cable, 2 x 20 AWG0.2mW3
3Connector, Molex Micro-Fit, female, 2 pins3X2, X3, X4
4Connector, Molex Micro-Fit, male, 2 pins1X1
+ + + diff --git a/examples/ex02.svg b/examples/ex02.svg index fe8f153d1..cb86958c3 100644 --- a/examples/ex02.svg +++ b/examples/ex02.svg @@ -1,7 +1,7 @@ - - - - ex03 - + + + ex03 + +

ex03

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Connector, Molex Micro-Fit, female, 2 pins3X2, X3, X4
2Connector, Molex Micro-Fit, male, 2 pins1X1
3Wire, 0.25 mm², BK0.6mW1
4Wire, 0.25 mm², RD0.6mW1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Molex Micro-Fit, female, 2 pins3X2, X3, X4
2Connector, Molex Micro-Fit, male, 2 pins1X1
3Wire, 0.25 mm², BK0.6mW1
4Wire, 0.25 mm², RD0.6mW1
+ + + diff --git a/examples/ex03.svg b/examples/ex03.svg index 50fd1352c..a13b91849 100644 --- a/examples/ex03.svg +++ b/examples/ex03.svg @@ -1,7 +1,7 @@ -
@@ -13,7 +13,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_2 [label=< + AUTOGENERATED_F_2 [label=<
@@ -22,7 +22,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_3 [label=< + AUTOGENERATED_F_3 [label=<
@@ -31,7 +31,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_4 [label=< + AUTOGENERATED_F_4 [label=<
@@ -40,7 +40,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_5 [label=< + AUTOGENERATED_F_5 [label=<
@@ -49,7 +49,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_6 [label=< + AUTOGENERATED_F_6 [label=<
@@ -58,7 +58,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_7 [label=< + AUTOGENERATED_F_7 [label=<
@@ -67,7 +67,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_8 [label=< + AUTOGENERATED_F_8 [label=<
@@ -76,7 +76,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_9 [label=< + AUTOGENERATED_F_9 [label=<
@@ -85,7 +85,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_10 [label=< + AUTOGENERATED_F_10 [label=<
@@ -94,7 +94,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_11 [label=< + AUTOGENERATED_F_11 [label=<
@@ -103,7 +103,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _ferrule_crimp_12 [label=< + AUTOGENERATED_F_12 [label=< - -
@@ -113,23 +113,23 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] edge [color="#000000:#895956:#000000"] - _ferrule_crimp_1:e -- W1:w1:w - W1:w1:e -- _ferrule_crimp_7:w + AUTOGENERATED_F_1:e -- W1:w1:w + W1:w1:e -- AUTOGENERATED_F_7:w edge [color="#000000:#ff0000:#000000"] - _ferrule_crimp_2:e -- W1:w2:w - W1:w2:e -- _ferrule_crimp_8:w + AUTOGENERATED_F_2:e -- W1:w2:w + W1:w2:e -- AUTOGENERATED_F_8:w edge [color="#000000:#ff8000:#000000"] - _ferrule_crimp_3:e -- W1:w3:w - W1:w3:e -- _ferrule_crimp_9:w + AUTOGENERATED_F_3:e -- W1:w3:w + W1:w3:e -- AUTOGENERATED_F_9:w edge [color="#000000:#ffff00:#000000"] - _ferrule_crimp_4:e -- W1:w4:w - W1:w4:e -- _ferrule_crimp_10:w + AUTOGENERATED_F_4:e -- W1:w4:w + W1:w4:e -- AUTOGENERATED_F_10:w edge [color="#000000:#00ff00:#000000"] - _ferrule_crimp_5:e -- W1:w5:w - W1:w5:e -- _ferrule_crimp_11:w + AUTOGENERATED_F_5:e -- W1:w5:w + W1:w5:e -- AUTOGENERATED_F_11:w edge [color="#000000:#0066ff:#000000"] - _ferrule_crimp_6:e -- W1:w6:w - W1:w6:e -- _ferrule_crimp_12:w + AUTOGENERATED_F_6:e -- W1:w6:w + W1:w6:e -- AUTOGENERATED_F_12:w W1 [label=< +
diff --git a/examples/ex04.html b/examples/ex04.html index a1e3e39f4..cd0d4a41c 100644 --- a/examples/ex04.html +++ b/examples/ex04.html @@ -1,22 +1,45 @@ - - - ex04 - + + + ex04 + +

ex04

Diagram

- - + + +
+ + - + -_ferrule_crimp_1 +AUTOGENERATED_F_1 Crimp ferrule @@ -60,226 +83,238 @@

Diagram

  - + -_ferrule_crimp_1:e--W1:w +AUTOGENERATED_F_1:e--W1:w - + -_ferrule_crimp_2 +AUTOGENERATED_F_2 Crimp ferrule - + -_ferrule_crimp_2:e--W1:w +AUTOGENERATED_F_2:e--W1:w - + -_ferrule_crimp_3 +AUTOGENERATED_F_3 Crimp ferrule - + -_ferrule_crimp_3:e--W1:w +AUTOGENERATED_F_3:e--W1:w - + -_ferrule_crimp_4 +AUTOGENERATED_F_4 Crimp ferrule - + -_ferrule_crimp_4:e--W1:w +AUTOGENERATED_F_4:e--W1:w - + -_ferrule_crimp_5 +AUTOGENERATED_F_5 Crimp ferrule - + -_ferrule_crimp_5:e--W1:w +AUTOGENERATED_F_5:e--W1:w - + -_ferrule_crimp_6 +AUTOGENERATED_F_6 Crimp ferrule - + -_ferrule_crimp_6:e--W1:w +AUTOGENERATED_F_6:e--W1:w - + -_ferrule_crimp_7 +AUTOGENERATED_F_7 Crimp ferrule - + -_ferrule_crimp_8 +AUTOGENERATED_F_8 Crimp ferrule - + -_ferrule_crimp_9 +AUTOGENERATED_F_9 Crimp ferrule - + -_ferrule_crimp_10 +AUTOGENERATED_F_10 Crimp ferrule - + -_ferrule_crimp_11 +AUTOGENERATED_F_11 Crimp ferrule - + -_ferrule_crimp_12 +AUTOGENERATED_F_12 Crimp ferrule - + -W1:e--_ferrule_crimp_7:w +W1:e--AUTOGENERATED_F_7:w - + -W1:e--_ferrule_crimp_8:w +W1:e--AUTOGENERATED_F_8:w - + -W1:e--_ferrule_crimp_9:w +W1:e--AUTOGENERATED_F_9:w - + -W1:e--_ferrule_crimp_10:w +W1:e--AUTOGENERATED_F_10:w - + -W1:e--_ferrule_crimp_11:w +W1:e--AUTOGENERATED_F_11:w - + -W1:e--_ferrule_crimp_12:w +W1:e--AUTOGENERATED_F_12:w + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Connector, Crimp ferrule12
2Wire, 0.25 mm², BN0.2mW1
3Wire, 0.25 mm², BU0.2mW1
4Wire, 0.25 mm², GN0.2mW1
5Wire, 0.25 mm², OG0.2mW1
6Wire, 0.25 mm², RD0.2mW1
7Wire, 0.25 mm², YE0.2mW1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Crimp ferrule12
2Wire, 0.25 mm², BN0.2mW1
3Wire, 0.25 mm², BU0.2mW1
4Wire, 0.25 mm², GN0.2mW1
5Wire, 0.25 mm², OG0.2mW1
6Wire, 0.25 mm², RD0.2mW1
7Wire, 0.25 mm², YE0.2mW1
+ + + diff --git a/examples/ex04.svg b/examples/ex04.svg index 19174c3d3..fe8bb9326 100644 --- a/examples/ex04.svg +++ b/examples/ex04.svg @@ -1,16 +1,16 @@ - - + -_ferrule_crimp_1 +AUTOGENERATED_F_1 Crimp ferrule @@ -54,163 +54,163 @@   - + -_ferrule_crimp_1:e--W1:w +AUTOGENERATED_F_1:e--W1:w - + -_ferrule_crimp_2 +AUTOGENERATED_F_2 Crimp ferrule - + -_ferrule_crimp_2:e--W1:w +AUTOGENERATED_F_2:e--W1:w - + -_ferrule_crimp_3 +AUTOGENERATED_F_3 Crimp ferrule - + -_ferrule_crimp_3:e--W1:w +AUTOGENERATED_F_3:e--W1:w - + -_ferrule_crimp_4 +AUTOGENERATED_F_4 Crimp ferrule - + -_ferrule_crimp_4:e--W1:w +AUTOGENERATED_F_4:e--W1:w - + -_ferrule_crimp_5 +AUTOGENERATED_F_5 Crimp ferrule - + -_ferrule_crimp_5:e--W1:w +AUTOGENERATED_F_5:e--W1:w - + -_ferrule_crimp_6 +AUTOGENERATED_F_6 Crimp ferrule - + -_ferrule_crimp_6:e--W1:w +AUTOGENERATED_F_6:e--W1:w - + -_ferrule_crimp_7 +AUTOGENERATED_F_7 Crimp ferrule - + -_ferrule_crimp_8 +AUTOGENERATED_F_8 Crimp ferrule - + -_ferrule_crimp_9 +AUTOGENERATED_F_9 Crimp ferrule - + -_ferrule_crimp_10 +AUTOGENERATED_F_10 Crimp ferrule - + -_ferrule_crimp_11 +AUTOGENERATED_F_11 Crimp ferrule - + -_ferrule_crimp_12 +AUTOGENERATED_F_12 Crimp ferrule - + -W1:e--_ferrule_crimp_7:w +W1:e--AUTOGENERATED_F_7:w - + -W1:e--_ferrule_crimp_8:w +W1:e--AUTOGENERATED_F_8:w - + -W1:e--_ferrule_crimp_9:w +W1:e--AUTOGENERATED_F_9:w - + -W1:e--_ferrule_crimp_10:w +W1:e--AUTOGENERATED_F_10:w - + -W1:e--_ferrule_crimp_11:w +W1:e--AUTOGENERATED_F_11:w - + -W1:e--_ferrule_crimp_12:w +W1:e--AUTOGENERATED_F_12:w diff --git a/examples/ex05.html b/examples/ex05.html index 7865b8863..5be09bd3c 100644 --- a/examples/ex05.html +++ b/examples/ex05.html @@ -1,13 +1,36 @@ - - - ex05 - + + + ex05 + +

ex05

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Connector, Molex KK 254, female, 4 pins3X1, X2, X3
2Wire, I2C, 0.25 mm², PK0.4mW1, W2
3Wire, I2C, 0.25 mm², TQ0.4mW1, W2
4Wire, I2C, 0.25 mm², VT0.4mW1, W2
5Wire, I2C, 0.25 mm², YE0.4mW1, W2
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Molex KK 254, female, 4 pins3X1, X2, X3
2Wire, I2C, 0.25 mm², PK0.4mW1, W2
3Wire, I2C, 0.25 mm², TQ0.4mW1, W2
4Wire, I2C, 0.25 mm², VT0.4mW1, W2
5Wire, I2C, 0.25 mm², YE0.4mW1, W2
+ + + diff --git a/examples/ex05.svg b/examples/ex05.svg index abfa6ec76..6d4e75f7e 100644 --- a/examples/ex05.svg +++ b/examples/ex05.svg @@ -1,7 +1,7 @@ - - - - ex06 - + + + ex06 + +

ex06

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Connector, Molex KK 254, female, 4 pins6X1, X2, X3, X4, X5, X6
2Wire, 0.25 mm², PK1.0mW1, W2, W3, W4, W5
3Wire, 0.25 mm², TQ1.0mW1, W2, W3, W4, W5
4Wire, 0.25 mm², VT1.0mW1, W2, W3, W4, W5
5Wire, 0.25 mm², YE1.0mW1, W2, W3, W4, W5
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Molex KK 254, female, 4 pins6X1, X2, X3, X4, X5, X6
2Wire, 0.25 mm², PK1.0mW1, W2, W3, W4, W5
3Wire, 0.25 mm², TQ1.0mW1, W2, W3, W4, W5
4Wire, 0.25 mm², VT1.0mW1, W2, W3, W4, W5
5Wire, 0.25 mm², YE1.0mW1, W2, W3, W4, W5
+ + + diff --git a/examples/ex06.svg b/examples/ex06.svg index 1dc1a5c67..fa1c37850 100644 --- a/examples/ex06.svg +++ b/examples/ex06.svg @@ -1,7 +1,7 @@ - - - - ex07 - + + + ex07 + +

ex07

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 2 x 20 AWG1mC1
2Connector, D-Sub, female, 9 pins1X2
3Connector, TE 776164-1, female, 35 pins1X1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 2 x 20 AWG1mC1
2Connector, D-Sub, female, 9 pins1X2
3Connector, TE 776164-1, female, 35 pins1X1
+ + + diff --git a/examples/ex07.svg b/examples/ex07.svg index 02d8378f5..2c92ead02 100644 --- a/examples/ex07.svg +++ b/examples/ex07.svg @@ -1,7 +1,7 @@ -
- +
@@ -135,7 +135,7 @@ graph {
- +
diff --git a/examples/ex08.html b/examples/ex08.html index b23a1a952..cd62b4af5 100644 --- a/examples/ex08.html +++ b/examples/ex08.html @@ -1,13 +1,36 @@ - - - ex08 - + + + ex08 + +

ex08

Diagram

- - + + +
+ + Diagram S - + Tip, Ring, and Sleeve @@ -83,7 +106,7 @@

Diagram

  - + Cross-section @@ -117,28 +140,40 @@

Diagram

+ +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 3 x 24 AWG shielded, BK0.2mW1
2Connector, Phone Connector, male 3.51Key
+ + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 3 x 24 AWG shielded, BK0.2mW1
2Connector, Phone Connector, male 3.51Key
+ + + diff --git a/examples/ex08.svg b/examples/ex08.svg index 3355f0829..78ab558e6 100644 --- a/examples/ex08.svg +++ b/examples/ex08.svg @@ -1,7 +1,7 @@ - S - + Tip, Ring, and Sleeve @@ -77,7 +77,7 @@   - + Cross-section diff --git a/examples/ex09.html b/examples/ex09.html index e0fe77b50..8c6a6fe06 100644 --- a/examples/ex09.html +++ b/examples/ex09.html @@ -1,13 +1,36 @@ - - - ex09 - + + + ex09 + +

ex09

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 12 x 0.25 mm² shielded0.2mW1
2Connector, D-Sub, male, 25 pins1X1
3Connector, F48, female, 48 pins1X2
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 12 x 0.25 mm² shielded0.2mW1
2Connector, D-Sub, male, 25 pins1X1
3Connector, F48, female, 48 pins1X2
+ + + diff --git a/examples/ex09.svg b/examples/ex09.svg index 607a69352..e5e5c7cca 100644 --- a/examples/ex09.svg +++ b/examples/ex09.svg @@ -1,7 +1,7 @@ - - - - ex10 - + + + ex10 + +

ex10

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, CAT5e, 8 x 24 AWG1mW1
2Connector, Stewart Connector SS-37000-002, male, 8 pins2X1, X2
+ + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, CAT5e, 8 x 24 AWG1mW1
2Connector, Stewart Connector SS-37000-002, male, 8 pins2X1, X2
+ + + diff --git a/examples/ex10.svg b/examples/ex10.svg index d0e302f1b..abc7168f6 100644 --- a/examples/ex10.svg +++ b/examples/ex10.svg @@ -1,7 +1,7 @@ - +
+
+ + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_F_2 [label=< + + +
+ + + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_F_3 [label=< + + +
+ + + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_F_4 [label=< + + +
+ + + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + X1 [label=< + + + + +
+ + +
X1
+
+ + + + + + +
Screw connectormale4-pinGN
+
+ + + + + + + + + + + + + + + + + +
1A
2B
3C
4D
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#000000:#000000"] + W1:w1:e -- AUTOGENERATED_F_1:w + edge [color="#000000:#ffffff:#000000"] + W1:w2:e -- AUTOGENERATED_F_2:w + edge [color="#000000:#0066ff:#000000"] + W1:w3:e -- AUTOGENERATED_F_3:w + edge [color="#000000:#895956:#000000"] + W1:w4:e -- AUTOGENERATED_F_4:w + W1 [label=< + + + + +
+ + +
W1
+
+ + + + +
4xBK
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ 1:BK +
+ + + + +
+
+ 2:WH +
+ + + + +
+
+ 3:BU +
+ + + + +
+
+ 4:BN +
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_1:e -- X1:p1l:w + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_2:e -- X1:p2l:w + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_3:e -- X1:p3l:w + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_4:e -- X1:p4l:w +} diff --git a/examples/ex11.html b/examples/ex11.html new file mode 100644 index 000000000..9f2a3e7d5 --- /dev/null +++ b/examples/ex11.html @@ -0,0 +1,245 @@ + + + + + ex11 + + +

ex11

+

Diagram

+ +
+ +
+ +
+ + + + + + + + +AUTOGENERATED_F_1 + + +Ferrule + +GY + + + + + +X1 + + +X1 + +Screw connector + +male + +4-pin + +GN + + + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +AUTOGENERATED_F_1:e--X1:w + + + + + +AUTOGENERATED_F_2 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_2:e--X1:w + + + + + +AUTOGENERATED_F_3 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_3:e--X1:w + + + + + +AUTOGENERATED_F_4 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_4:e--X1:w + + + + + +W1 + + +W1 + +4x + +BK + + +  +     1:BK     + + + +     2:WH     + + + +     3:BU     + + + +     4:BN     + + + +  + + + +W1:e--AUTOGENERATED_F_1:w + + + + + + +W1:e--AUTOGENERATED_F_2:w + + + + + + +W1:e--AUTOGENERATED_F_3:w + + + + + + +W1:e--AUTOGENERATED_F_4:w + + + + + + + +
+ +
+ +
+ +

Bill of Materials

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 4 wires, BK0mW1
2Connector, Ferrule, GY4
3Connector, Screw connector, male, 4 pins, GN1X1
+ +
+ + diff --git a/examples/ex11.png b/examples/ex11.png new file mode 100644 index 000000000..1f572f08d Binary files /dev/null and b/examples/ex11.png differ diff --git a/examples/ex11.svg b/examples/ex11.svg new file mode 100644 index 000000000..466cb758f --- /dev/null +++ b/examples/ex11.svg @@ -0,0 +1,172 @@ + + + + + + + + + +AUTOGENERATED_F_1 + + +Ferrule + +GY + + + + + +X1 + + +X1 + +Screw connector + +male + +4-pin + +GN + + + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +AUTOGENERATED_F_1:e--X1:w + + + + + +AUTOGENERATED_F_2 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_2:e--X1:w + + + + + +AUTOGENERATED_F_3 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_3:e--X1:w + + + + + +AUTOGENERATED_F_4 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_4:e--X1:w + + + + + +W1 + + +W1 + +4x + +BK + + +  +     1:BK     + + + +     2:WH     + + + +     3:BU     + + + +     4:BN     + + + +  + + + +W1:e--AUTOGENERATED_F_1:w + + + + + + +W1:e--AUTOGENERATED_F_2:w + + + + + + +W1:e--AUTOGENERATED_F_3:w + + + + + + +W1:e--AUTOGENERATED_F_4:w + + + + + + diff --git a/examples/ex12.bom.tsv b/examples/ex12.bom.tsv new file mode 100644 index 000000000..b7242cb50 --- /dev/null +++ b/examples/ex12.bom.tsv @@ -0,0 +1,7 @@ +Id Description Qty Unit Designators +1 Connector, Dupont 2.54mm, female, 5 pins, BK 1 X2 +2 Connector, Dupont 2.54mm, male, 5 pins, BK 1 X1 +3 Wire, BK 0.4 m W1, W2 +4 Wire, BU 0.4 m W1, W2 +5 Wire, GN 0.4 m W1, W2 +6 Wire, RD 0.4 m W1, W2 diff --git a/examples/ex12.gv b/examples/ex12.gv new file mode 100644 index 000000000..c5429861e --- /dev/null +++ b/examples/ex12.gv @@ -0,0 +1,269 @@ +graph { +// Graph generated by WireViz 0.4-dev +// https://github.com/formatc1702/WireViz + graph [bgcolor="#FFFFFF" fontname=arial nodesep=0.33 rankdir=LR ranksep=2] + node [fillcolor="#FFFFFF" fontname=arial height=0 margin=0 shape=none style=filled width=0] + edge [fontname=arial style=bold] + X1 [label=< + + + + +
+ + +
X1
+
+ + + + + + +
Dupont 2.54mmmale5-pinBK
+
+ + + + + + + + + + + + + + + + +
1
2
3
4
5
+
+> fillcolor="#FFFFFF" shape=box style=filled] + X2 [label=< + + + + +
+ + +
X2
+
+ + + + + + +
Dupont 2.54mmfemale5-pinBK
+
+ + + + + + + + + + + + + + + + +
1
2
3
4
5
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ff0000:#000000"] + W1:w1:e -- X1:p1l:w + edge [color="#000000:#000000:#000000"] + W1:w2:e -- X1:p2l:w + edge [color="#000000:#0066ff:#000000"] + W1:w3:e -- X1:p3l:w + edge [color="#000000:#00ff00:#000000"] + W1:w4:e -- X1:p4l:w + W1 [label=< + + + + +
+ + +
W1
+
+ + + +
4x0.2 m
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ RD + X1:1
+ + + + +
+
+ BK + X1:2
+ + + + +
+
+ BU + X1:3
+ + + + +
+
+ GN + X1:4
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style="filled,dashed"] + edge [color="#000000:#ff0000:#000000"] + X2:p1r:e -- W2:w1:w + edge [color="#000000:#000000:#000000"] + X2:p2r:e -- W2:w2:w + edge [color="#000000:#0066ff:#000000"] + X2:p3r:e -- W2:w3:w + edge [color="#000000:#00ff00:#000000"] + X2:p4r:e -- W2:w4:w + W2 [label=< + + + + +
+ + +
W2
+
+ + + +
4x0.2 m
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
X2:1 + RD +
+ + + + +
+
X2:2 + BK +
+ + + + +
+
X2:3 + BU +
+ + + + +
+
X2:4 + GN +
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style="filled,dashed"] + edge [color="#000000:#000000" dir=forward style=dashed] + X1:e -- X2:w +} diff --git a/examples/ex12.html b/examples/ex12.html new file mode 100644 index 000000000..e3d983cbb --- /dev/null +++ b/examples/ex12.html @@ -0,0 +1,289 @@ + + + + + ex12 + + +

ex12

+

Diagram

+ +
+ +
+ +
+ + + + + + + + +X1 + + +X1 + +Dupont 2.54mm + +male + +5-pin + +BK + + + +1 + +2 + +3 + +4 + +5 + + + +X2 + + +X2 + +Dupont 2.54mm + +female + +5-pin + +BK + + + +1 + +2 + +3 + +4 + +5 + + + +X1:e--X2:w + + + + + + +W2 + + +W2 + +4x + +0.2 m +  +X2:1 +     RD     + + + +X2:2 +     BK     + + + +X2:3 +     BU     + + + +X2:4 +     GN     + + + +  + + + +X2:e--W2:w + + + + + + +X2:e--W2:w + + + + + + +X2:e--W2:w + + + + + + +X2:e--W2:w + + + + + + +W1 + + +W1 + +4x + +0.2 m +  +     RD     +X1:1 + + + +     BK     +X1:2 + + + +     BU     +X1:3 + + + +     GN     +X1:4 + + + +  + + + +W1:e--X1:w + + + + + + +W1:e--X1:w + + + + + + +W1:e--X1:w + + + + + + +W1:e--X1:w + + + + + + + +
+ +
+ +
+ +

Bill of Materials

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Dupont 2.54mm, female, 5 pins, BK1X2
2Connector, Dupont 2.54mm, male, 5 pins, BK1X1
3Wire, BK0.4mW1, W2
4Wire, BU0.4mW1, W2
5Wire, GN0.4mW1, W2
6Wire, RD0.4mW1, W2
+ +
+ + diff --git a/examples/ex12.png b/examples/ex12.png new file mode 100644 index 000000000..bc0fce6c5 Binary files /dev/null and b/examples/ex12.png differ diff --git a/examples/ex12.svg b/examples/ex12.svg new file mode 100644 index 000000000..1f9f645ba --- /dev/null +++ b/examples/ex12.svg @@ -0,0 +1,195 @@ + + + + + + + + + +X1 + + +X1 + +Dupont 2.54mm + +male + +5-pin + +BK + + + +1 + +2 + +3 + +4 + +5 + + + +X2 + + +X2 + +Dupont 2.54mm + +female + +5-pin + +BK + + + +1 + +2 + +3 + +4 + +5 + + + +X1:e--X2:w + + + + + + +W2 + + +W2 + +4x + +0.2 m +  +X2:1 +     RD     + + + +X2:2 +     BK     + + + +X2:3 +     BU     + + + +X2:4 +     GN     + + + +  + + + +X2:e--W2:w + + + + + + +X2:e--W2:w + + + + + + +X2:e--W2:w + + + + + + +X2:e--W2:w + + + + + + +W1 + + +W1 + +4x + +0.2 m +  +     RD     +X1:1 + + + +     BK     +X1:2 + + + +     BU     +X1:3 + + + +     GN     +X1:4 + + + +  + + + +W1:e--X1:w + + + + + + +W1:e--X1:w + + + + + + +W1:e--X1:w + + + + + + +W1:e--X1:w + + + + + + diff --git a/examples/ex13.bom.tsv b/examples/ex13.bom.tsv new file mode 100644 index 000000000..b5936d262 --- /dev/null +++ b/examples/ex13.bom.tsv @@ -0,0 +1,4 @@ +Id Description Qty Unit Designators +1 Cable, 4 wires 0 m C1, C2, C3 +2 Connector, 4 pins 3 X1, X2, X3 +3 Connector, ferrule 4 diff --git a/examples/ex13.gv b/examples/ex13.gv new file mode 100644 index 000000000..b26897ac7 --- /dev/null +++ b/examples/ex13.gv @@ -0,0 +1,433 @@ +graph { +// Graph generated by WireViz 0.4-dev +// https://github.com/formatc1702/WireViz + graph [bgcolor="#FFFFFF" fontname=arial nodesep=0.33 rankdir=LR ranksep=2] + node [fillcolor="#FFFFFF" fontname=arial height=0 margin=0 shape=none style=filled width=0] + edge [fontname=arial style=bold] + X1 [label=< + + + + +
+ + +
X1
+
+ + +
4-pin
+
+ + + + + + + + + + + + + + + + + +
A1
B2
C3
D4
+
+> fillcolor="#FFFFFF" shape=box style=filled] + F1 [label=< + + +
+ + +
ferrule
+
+> fillcolor="#FFFFFF" shape=box style=filled] + F2 [label=< + + +
+ + +
ferrule
+
+> fillcolor="#FFFFFF" shape=box style=filled] + F3 [label=< + + +
+ + +
ferrule
+
+> fillcolor="#FFFFFF" shape=box style=filled] + F4 [label=< + + +
+ + +
ferrule
+
+> fillcolor="#FFFFFF" shape=box style=filled] + X2 [label=< + + + + +
+ + +
X2
+
+ + +
4-pin
+
+ + + + + + + + + + + + + + + + + +
1A
2B
3C
4D
+
+> fillcolor="#FFFFFF" shape=box style=filled] + X3 [label=< + + + + +
+ + +
X3
+
+ + +
4-pin
+
+ + + + + + + + + + + + + + + + + +
1A
2B
3C
4D
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + X1:p1r:e -- C1:w1:w + C1:w1:e -- F1:w + edge [color="#000000:#895956:#000000"] + X1:p2r:e -- C1:w2:w + C1:w2:e -- F2:w + edge [color="#000000:#00ff00:#000000"] + X1:p3r:e -- C1:w3:w + C1:w3:e -- F3:w + edge [color="#000000:#ffff00:#000000"] + X1:p4r:e -- C1:w4:w + C1:w4:e -- F4:w + C1 [label=< + + + + +
+ + +
C1
+
+ + +
4x
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
X1:1:A + 1:WH +
+ + + + +
+
X1:2:B + 2:BN +
+ + + + +
+
X1:3:C + 3:GN +
+ + + + +
+
X1:4:D + 4:YE +
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + F1:e -- C2:w1:w + C2:w1:e -- X2:p1l:w + edge [color="#000000:#895956:#000000"] + F2:e -- C2:w2:w + C2:w2:e -- X2:p2l:w + edge [color="#000000:#00ff00:#000000"] + F3:e -- C2:w3:w + C2:w3:e -- X2:p3l:w + edge [color="#000000:#ffff00:#000000"] + F4:e -- C2:w4:w + C2:w4:e -- X2:p4l:w + C2 [label=< + + + + +
+ + +
C2
+
+ + +
4x
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ 1:WH + X2:1:A
+ + + + +
+
+ 2:BN + X2:2:B
+ + + + +
+
+ 3:GN + X2:3:C
+ + + + +
+
+ 4:YE + X2:4:D
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + F1:e -- C3:w1:w + C3:w1:e -- X3:p1l:w + edge [color="#000000:#895956:#000000"] + F2:e -- C3:w2:w + C3:w2:e -- X3:p2l:w + edge [color="#000000:#00ff00:#000000"] + F3:e -- C3:w3:w + C3:w3:e -- X3:p3l:w + edge [color="#000000:#ffff00:#000000"] + F4:e -- C3:w4:w + C3:w4:e -- X3:p4l:w + C3 [label=< + + + + +
+ + +
C3
+
+ + +
4x
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ 1:WH + X3:1:A
+ + + + +
+
+ 2:BN + X3:2:B
+ + + + +
+
+ 3:GN + X3:3:C
+ + + + +
+
+ 4:YE + X3:4:D
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] +} diff --git a/examples/ex13.html b/examples/ex13.html new file mode 100644 index 000000000..1e7bbe55a --- /dev/null +++ b/examples/ex13.html @@ -0,0 +1,449 @@ + + + + + ex13 + + +

ex13

+

Diagram

+ +
+ +
+ +
+ + + + + + + + +X1 + + +X1 + +4-pin + +A + +1 + +B + +2 + +C + +3 + +D + +4 + + + +C1 + + +C1 + +4x +  +X1:1:A +     1:WH     + + + +X1:2:B +     2:BN     + + + +X1:3:C +     3:GN     + + + +X1:4:D +     4:YE     + + + +  + + + +X1:e--C1:w + + + + + + +X1:e--C1:w + + + + + + +X1:e--C1:w + + + + + + +X1:e--C1:w + + + + + + +F1 + + +ferrule + + + +C2 + + +C2 + +4x +  +     1:WH     +X2:1:A + + + +     2:BN     +X2:2:B + + + +     3:GN     +X2:3:C + + + +     4:YE     +X2:4:D + + + +  + + + +F1:e--C2:w + + + + + + +C3 + + +C3 + +4x +  +     1:WH     +X3:1:A + + + +     2:BN     +X3:2:B + + + +     3:GN     +X3:3:C + + + +     4:YE     +X3:4:D + + + +  + + + +F1:e--C3:w + + + + + + +F2 + + +ferrule + + + +F2:e--C2:w + + + + + + +F2:e--C3:w + + + + + + +F3 + + +ferrule + + + +F3:e--C2:w + + + + + + +F3:e--C3:w + + + + + + +F4 + + +ferrule + + + +F4:e--C2:w + + + + + + +F4:e--C3:w + + + + + + +X2 + + +X2 + +4-pin + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +X3 + + +X3 + +4-pin + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +C1:e--F1:w + + + + + + +C1:e--F2:w + + + + + + +C1:e--F3:w + + + + + + +C1:e--F4:w + + + + + + +C2:e--X2:w + + + + + + +C2:e--X2:w + + + + + + +C2:e--X2:w + + + + + + +C2:e--X2:w + + + + + + +C3:e--X3:w + + + + + + +C3:e--X3:w + + + + + + +C3:e--X3:w + + + + + + +C3:e--X3:w + + + + + + + +
+ +
+ +
+ +

Bill of Materials

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 4 wires0mC1, C2, C3
2Connector, 4 pins3X1, X2, X3
3Connector, ferrule4
+ +
+ + diff --git a/examples/ex13.png b/examples/ex13.png new file mode 100644 index 000000000..9b24caff7 Binary files /dev/null and b/examples/ex13.png differ diff --git a/examples/ex13.svg b/examples/ex13.svg new file mode 100644 index 000000000..cf6f37934 --- /dev/null +++ b/examples/ex13.svg @@ -0,0 +1,376 @@ + + + + + + + + + +X1 + + +X1 + +4-pin + +A + +1 + +B + +2 + +C + +3 + +D + +4 + + + +C1 + + +C1 + +4x +  +X1:1:A +     1:WH     + + + +X1:2:B +     2:BN     + + + +X1:3:C +     3:GN     + + + +X1:4:D +     4:YE     + + + +  + + + +X1:e--C1:w + + + + + + +X1:e--C1:w + + + + + + +X1:e--C1:w + + + + + + +X1:e--C1:w + + + + + + +F1 + + +ferrule + + + +C2 + + +C2 + +4x +  +     1:WH     +X2:1:A + + + +     2:BN     +X2:2:B + + + +     3:GN     +X2:3:C + + + +     4:YE     +X2:4:D + + + +  + + + +F1:e--C2:w + + + + + + +C3 + + +C3 + +4x +  +     1:WH     +X3:1:A + + + +     2:BN     +X3:2:B + + + +     3:GN     +X3:3:C + + + +     4:YE     +X3:4:D + + + +  + + + +F1:e--C3:w + + + + + + +F2 + + +ferrule + + + +F2:e--C2:w + + + + + + +F2:e--C3:w + + + + + + +F3 + + +ferrule + + + +F3:e--C2:w + + + + + + +F3:e--C3:w + + + + + + +F4 + + +ferrule + + + +F4:e--C2:w + + + + + + +F4:e--C3:w + + + + + + +X2 + + +X2 + +4-pin + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +X3 + + +X3 + +4-pin + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +C1:e--F1:w + + + + + + +C1:e--F2:w + + + + + + +C1:e--F3:w + + + + + + +C1:e--F4:w + + + + + + +C2:e--X2:w + + + + + + +C2:e--X2:w + + + + + + +C2:e--X2:w + + + + + + +C2:e--X2:w + + + + + + +C3:e--X3:w + + + + + + +C3:e--X3:w + + + + + + +C3:e--X3:w + + + + + + +C3:e--X3:w + + + + + + diff --git a/examples/ex14.bom.tsv b/examples/ex14.bom.tsv new file mode 100644 index 000000000..c0e530747 --- /dev/null +++ b/examples/ex14.bom.tsv @@ -0,0 +1,8 @@ +Id Description Qty Unit Designators +1 Cable, 1 wires 0.1 m +2 Cable, 4 wires 0.4 m W1, W2, W21, W3 +3 Connector, Ferrule, GY 4 +4 Connector, JST SM, female, 4 pins 1 X2 +5 Connector, JST SM, male, 4 pins 2 X1, X3 +6 Connector, Screw terminal connector, 4 pins, GN 1 X4 +7 Connector, Splice, CU 8 diff --git a/examples/ex14.gv b/examples/ex14.gv new file mode 100644 index 000000000..4f24330e6 --- /dev/null +++ b/examples/ex14.gv @@ -0,0 +1,717 @@ +graph { +// Graph generated by WireViz 0.4-dev +// https://github.com/formatc1702/WireViz + graph [bgcolor="#FFFFFF" fontname=arial nodesep=0.33 rankdir=LR ranksep=2] + node [fillcolor="#FFFFFF" fontname=arial height=0 margin=0 shape=none style=filled width=0] + edge [fontname=arial style=bold] + X1 [label=< + + + + +
+ + +
X1
+
+ + + + +
JST SMmale4-pin
+
+ + + + + + + + + + + + + + + + + +
A1
B2
C3
D4
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_S_1 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_S_2 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + S1 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_S_3 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_S_4 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_S_5 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_S_6 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_S_7 [label=< + + +
+ + + + +
SpliceCU
+
+> fillcolor="#FFFFFF" shape=box style=filled] + X2 [label=< + + + + +
+ + +
X2
+
+ + + + +
JST SMfemale4-pin
+
+ + + + + + + + + + + + + + + + + +
1A
2B
3C
4D
+
+> fillcolor="#FFFFFF" shape=box style=filled] + X3 [label=< + + + + +
+ + +
X3
+
+ + + + +
JST SMmale4-pin
+
+ + + + + + + + + + + + + + + + + +
A1
B2
C3
D4
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_F_1 [label=< + + +
+ + + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_F_2 [label=< + + +
+ + + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_F_3 [label=< + + +
+ + + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + AUTOGENERATED_F_4 [label=< + + +
+ + + + +
FerruleGY
+
+> fillcolor="#FFFFFF" shape=box style=filled] + X4 [label=< + + + + +
+ + +
X4
+
+ + + + + +
Screw terminal connector4-pinGN
+
+ + + + + + + + + + + + + + + + + +
1W
2X
3Y
4Z
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + X1:p4r:e -- W1:w1:w + W1:w1:e -- AUTOGENERATED_S_1:w + edge [color="#000000:#895956:#000000"] + X1:p3r:e -- W1:w2:w + W1:w2:e -- AUTOGENERATED_S_2:w + edge [color="#000000:#00ff00:#000000"] + X1:p2r:e -- W1:w3:w + W1:w3:e -- S1:w + edge [color="#000000:#ffff00:#000000"] + X1:p1r:e -- W1:w4:w + W1:w4:e -- AUTOGENERATED_S_3:w + W1 [label=< + + + + +
+ + +
W1
+
+ + + +
4x0.1 m
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
X1:4:D + 1:WH +
+ + + + +
+
X1:3:C + 2:BN +
+ + + + +
+
X1:2:B + 3:GN +
+ + + + +
+
X1:1:A + 4:YE +
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + AUTOGENERATED_S_1:e -- W2:w1:w + W2:w1:e -- AUTOGENERATED_S_4:w + edge [color="#000000:#895956:#000000"] + AUTOGENERATED_S_2:e -- W2:w2:w + W2:w2:e -- AUTOGENERATED_S_5:w + edge [color="#000000:#00ff00:#000000"] + S1:e -- W2:w3:w + W2:w3:e -- AUTOGENERATED_S_6:w + edge [color="#000000:#ffff00:#000000"] + AUTOGENERATED_S_3:e -- W2:w4:w + W2:w4:e -- AUTOGENERATED_S_7:w + W2 [label=< + + + + +
+ + +
W2
+
+ + + +
4x0.1 m
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ 1:WH +
+ + + + +
+
+ 2:BN +
+ + + + +
+
+ 3:GN +
+ + + + +
+
+ 4:YE +
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + AUTOGENERATED_S_4:e -- W21:w1:w + W21:w1:e -- X2:p1l:w + edge [color="#000000:#895956:#000000"] + AUTOGENERATED_S_5:e -- W21:w2:w + W21:w2:e -- X2:p2l:w + edge [color="#000000:#00ff00:#000000"] + AUTOGENERATED_S_6:e -- W21:w3:w + W21:w3:e -- X2:p3l:w + edge [color="#000000:#ffff00:#000000"] + AUTOGENERATED_S_7:e -- W21:w4:w + W21:w4:e -- X2:p4l:w + W21 [label=< + + + + +
+ + +
W21
+
+ + + +
4x0.1 m
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ 1:WH + X2:1:A
+ + + + +
+
+ 2:BN + X2:2:B
+ + + + +
+
+ 3:GN + X2:3:C
+ + + + +
+
+ 4:YE + X2:4:D
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#ffffff:#000000"] + X3:p1r:e -- W3:w1:w + W3:w1:e -- AUTOGENERATED_F_1:w + edge [color="#000000:#895956:#000000"] + X3:p2r:e -- W3:w2:w + W3:w2:e -- AUTOGENERATED_F_2:w + edge [color="#000000:#00ff00:#000000"] + X3:p3r:e -- W3:w3:w + W3:w3:e -- AUTOGENERATED_F_3:w + edge [color="#000000:#ffff00:#000000"] + X3:p4r:e -- W3:w4:w + W3:w4:e -- AUTOGENERATED_F_4:w + W3 [label=< + + + + +
+ + +
W3
+
+ + + +
4x0.1 m
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
X3:1:A + 1:WH +
+ + + + +
+
X3:2:B + 2:BN +
+ + + + +
+
X3:3:C + 3:GN +
+ + + + +
+
X3:4:D + 4:YE +
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#000000:#000000"] + S1:e -- AUTOGENERATED_WIRE_1:w1:w + AUTOGENERATED_WIRE_1:w1:e -- X2:p4l:w + AUTOGENERATED_WIRE_1 [label=< + + + +
+ + + +
1x0.1 m
+
+ + + + + + + + + + + +
 
+ 1:BK + X2:4:D
+ + + + +
+
 
+
+> fillcolor="#FFFFFF" shape=box style=filled] + edge [color="#000000:#000000" dir=both style=dashed] + X2:e -- X3:w + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_1:e -- X4:p2l:w + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_2:e -- X4:p1l:w + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_3:e -- X4:p4l:w + edge [color="#000000" dir=forward style=dashed] + AUTOGENERATED_F_4:e -- X4:p3l:w +} diff --git a/examples/ex14.html b/examples/ex14.html new file mode 100644 index 000000000..e20c23261 --- /dev/null +++ b/examples/ex14.html @@ -0,0 +1,777 @@ + + + + + ex14 + + +

ex14

+

Diagram

+ +
+ +
+ +
+ + + + + + + + +X1 + + +X1 + +JST SM + +male + +4-pin + +A + +1 + +B + +2 + +C + +3 + +D + +4 + + + +W1 + + +W1 + +4x + +0.1 m +  +X1:4:D +     1:WH     + + + +X1:3:C +     2:BN     + + + +X1:2:B +     3:GN     + + + +X1:1:A +     4:YE     + + + +  + + + +X1:e--W1:w + + + + + + +X1:e--W1:w + + + + + + +X1:e--W1:w + + + + + + +X1:e--W1:w + + + + + + +AUTOGENERATED_S_1 + + +Splice + +CU + + + + + +W2 + + +W2 + +4x + +0.1 m +  +     1:WH     + + + +     2:BN     + + + +     3:GN     + + + +     4:YE     + + + +  + + + +AUTOGENERATED_S_1:e--W2:w + + + + + + +AUTOGENERATED_S_2 + + +Splice + +CU + + + + + +AUTOGENERATED_S_2:e--W2:w + + + + + + +S1 + + +Splice + +CU + + + + + +S1:e--W2:w + + + + + + +AUTOGENERATED_WIRE_1 + + +1x + +0.1 m +  +     1:BK     +X2:4:D + + + +  + + + +S1:e--AUTOGENERATED_WIRE_1:w + + + + + + +AUTOGENERATED_S_3 + + +Splice + +CU + + + + + +AUTOGENERATED_S_3:e--W2:w + + + + + + +AUTOGENERATED_S_4 + + +Splice + +CU + + + + + +W21 + + +W21 + +4x + +0.1 m +  +     1:WH     +X2:1:A + + + +     2:BN     +X2:2:B + + + +     3:GN     +X2:3:C + + + +     4:YE     +X2:4:D + + + +  + + + +AUTOGENERATED_S_4:e--W21:w + + + + + + +AUTOGENERATED_S_5 + + +Splice + +CU + + + + + +AUTOGENERATED_S_5:e--W21:w + + + + + + +AUTOGENERATED_S_6 + + +Splice + +CU + + + + + +AUTOGENERATED_S_6:e--W21:w + + + + + + +AUTOGENERATED_S_7 + + +Splice + +CU + + + + + +AUTOGENERATED_S_7:e--W21:w + + + + + + +X2 + + +X2 + +JST SM + +female + +4-pin + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +X3 + + +X3 + +JST SM + +male + +4-pin + +A + +1 + +B + +2 + +C + +3 + +D + +4 + + + +X2:e--X3:w + + + + + + + +W3 + + +W3 + +4x + +0.1 m +  +X3:1:A +     1:WH     + + + +X3:2:B +     2:BN     + + + +X3:3:C +     3:GN     + + + +X3:4:D +     4:YE     + + + +  + + + +X3:e--W3:w + + + + + + +X3:e--W3:w + + + + + + +X3:e--W3:w + + + + + + +X3:e--W3:w + + + + + + +AUTOGENERATED_F_1 + + +Ferrule + +GY + + + + + +X4 + + +X4 + +Screw terminal connector + +4-pin + +GN + + + +1 + +W + +2 + +X + +3 + +Y + +4 + +Z + + + +AUTOGENERATED_F_1:e--X4:w + + + + + +AUTOGENERATED_F_2 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_2:e--X4:w + + + + + +AUTOGENERATED_F_3 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_3:e--X4:w + + + + + +AUTOGENERATED_F_4 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_4:e--X4:w + + + + + +W1:e--AUTOGENERATED_S_1:w + + + + + + +W1:e--AUTOGENERATED_S_2:w + + + + + + +W1:e--S1:w + + + + + + +W1:e--AUTOGENERATED_S_3:w + + + + + + +W2:e--AUTOGENERATED_S_4:w + + + + + + +W2:e--AUTOGENERATED_S_5:w + + + + + + +W2:e--AUTOGENERATED_S_6:w + + + + + + +W2:e--AUTOGENERATED_S_7:w + + + + + + +W21:e--X2:w + + + + + + +W21:e--X2:w + + + + + + +W21:e--X2:w + + + + + + +W21:e--X2:w + + + + + + +W3:e--AUTOGENERATED_F_1:w + + + + + + +W3:e--AUTOGENERATED_F_2:w + + + + + + +W3:e--AUTOGENERATED_F_3:w + + + + + + +W3:e--AUTOGENERATED_F_4:w + + + + + + +AUTOGENERATED_WIRE_1:e--X2:w + + + + + + + +
+ +
+ +
+ +

Bill of Materials

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 1 wires0.1m
2Cable, 4 wires0.4mW1, W2, W21, W3
3Connector, Ferrule, GY4
4Connector, JST SM, female, 4 pins1X2
5Connector, JST SM, male, 4 pins2X1, X3
6Connector, Screw terminal connector, 4 pins, GN1X4
7Connector, Splice, CU8
+ +
+ + diff --git a/examples/ex14.png b/examples/ex14.png new file mode 100644 index 000000000..bae9a15d3 Binary files /dev/null and b/examples/ex14.png differ diff --git a/examples/ex14.svg b/examples/ex14.svg new file mode 100644 index 000000000..3f06f5a0f --- /dev/null +++ b/examples/ex14.svg @@ -0,0 +1,676 @@ + + + + + + + + + +X1 + + +X1 + +JST SM + +male + +4-pin + +A + +1 + +B + +2 + +C + +3 + +D + +4 + + + +W1 + + +W1 + +4x + +0.1 m +  +X1:4:D +     1:WH     + + + +X1:3:C +     2:BN     + + + +X1:2:B +     3:GN     + + + +X1:1:A +     4:YE     + + + +  + + + +X1:e--W1:w + + + + + + +X1:e--W1:w + + + + + + +X1:e--W1:w + + + + + + +X1:e--W1:w + + + + + + +AUTOGENERATED_S_1 + + +Splice + +CU + + + + + +W2 + + +W2 + +4x + +0.1 m +  +     1:WH     + + + +     2:BN     + + + +     3:GN     + + + +     4:YE     + + + +  + + + +AUTOGENERATED_S_1:e--W2:w + + + + + + +AUTOGENERATED_S_2 + + +Splice + +CU + + + + + +AUTOGENERATED_S_2:e--W2:w + + + + + + +S1 + + +Splice + +CU + + + + + +S1:e--W2:w + + + + + + +AUTOGENERATED_WIRE_1 + + +1x + +0.1 m +  +     1:BK     +X2:4:D + + + +  + + + +S1:e--AUTOGENERATED_WIRE_1:w + + + + + + +AUTOGENERATED_S_3 + + +Splice + +CU + + + + + +AUTOGENERATED_S_3:e--W2:w + + + + + + +AUTOGENERATED_S_4 + + +Splice + +CU + + + + + +W21 + + +W21 + +4x + +0.1 m +  +     1:WH     +X2:1:A + + + +     2:BN     +X2:2:B + + + +     3:GN     +X2:3:C + + + +     4:YE     +X2:4:D + + + +  + + + +AUTOGENERATED_S_4:e--W21:w + + + + + + +AUTOGENERATED_S_5 + + +Splice + +CU + + + + + +AUTOGENERATED_S_5:e--W21:w + + + + + + +AUTOGENERATED_S_6 + + +Splice + +CU + + + + + +AUTOGENERATED_S_6:e--W21:w + + + + + + +AUTOGENERATED_S_7 + + +Splice + +CU + + + + + +AUTOGENERATED_S_7:e--W21:w + + + + + + +X2 + + +X2 + +JST SM + +female + +4-pin + +1 + +A + +2 + +B + +3 + +C + +4 + +D + + + +X3 + + +X3 + +JST SM + +male + +4-pin + +A + +1 + +B + +2 + +C + +3 + +D + +4 + + + +X2:e--X3:w + + + + + + + +W3 + + +W3 + +4x + +0.1 m +  +X3:1:A +     1:WH     + + + +X3:2:B +     2:BN     + + + +X3:3:C +     3:GN     + + + +X3:4:D +     4:YE     + + + +  + + + +X3:e--W3:w + + + + + + +X3:e--W3:w + + + + + + +X3:e--W3:w + + + + + + +X3:e--W3:w + + + + + + +AUTOGENERATED_F_1 + + +Ferrule + +GY + + + + + +X4 + + +X4 + +Screw terminal connector + +4-pin + +GN + + + +1 + +W + +2 + +X + +3 + +Y + +4 + +Z + + + +AUTOGENERATED_F_1:e--X4:w + + + + + +AUTOGENERATED_F_2 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_2:e--X4:w + + + + + +AUTOGENERATED_F_3 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_3:e--X4:w + + + + + +AUTOGENERATED_F_4 + + +Ferrule + +GY + + + + + +AUTOGENERATED_F_4:e--X4:w + + + + + +W1:e--AUTOGENERATED_S_1:w + + + + + + +W1:e--AUTOGENERATED_S_2:w + + + + + + +W1:e--S1:w + + + + + + +W1:e--AUTOGENERATED_S_3:w + + + + + + +W2:e--AUTOGENERATED_S_4:w + + + + + + +W2:e--AUTOGENERATED_S_5:w + + + + + + +W2:e--AUTOGENERATED_S_6:w + + + + + + +W2:e--AUTOGENERATED_S_7:w + + + + + + +W21:e--X2:w + + + + + + +W21:e--X2:w + + + + + + +W21:e--X2:w + + + + + + +W21:e--X2:w + + + + + + +W3:e--AUTOGENERATED_F_1:w + + + + + + +W3:e--AUTOGENERATED_F_2:w + + + + + + +W3:e--AUTOGENERATED_F_3:w + + + + + + +W3:e--AUTOGENERATED_F_4:w + + + + + + +AUTOGENERATED_WIRE_1:e--X2:w + + + + + + diff --git a/examples/readme.md b/examples/readme.md index 145ee5a9f..88a4389ca 100644 --- a/examples/readme.md +++ b/examples/readme.md @@ -60,3 +60,27 @@ [Source](ex10.yml) - [Bill of Materials](ex10.bom.tsv) +## Example 11 +![](ex11.png) + +[Source](ex11.yml) - [Bill of Materials](ex11.bom.tsv) + + +## Example 12 +![](ex12.png) + +[Source](ex12.yml) - [Bill of Materials](ex12.bom.tsv) + + +## Example 13 +![](ex13.png) + +[Source](ex13.yml) - [Bill of Materials](ex13.bom.tsv) + + +## Example 14 +![](ex14.png) + +[Source](ex14.yml) - [Bill of Materials](ex14.bom.tsv) + + diff --git a/requirements.txt b/requirements.txt index 07564c32e..9405dd187 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ graphviz pillow pyyaml setuptools +tabulate diff --git a/setup.py b/setup.py index 8cdd48641..8ef73a62d 100644 --- a/setup.py +++ b/setup.py @@ -15,13 +15,14 @@ author="Daniel Rojas", # author_email='', description="Easily document cables and wiring harnesses", - long_description=open(README_PATH).read(), + long_description=README_PATH.read_text(), long_description_content_type="text/markdown", install_requires=[ "click", - "pyyaml", - "pillow", "graphviz", + "pillow", + "pyyaml", + "tabulate", ], license="GPLv3", keywords="cable connector hardware harness wiring wiring-diagram wiring-harness", @@ -37,8 +38,9 @@ "Development Status :: 4 - Beta", "Environment :: Console", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Utilities", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", ], diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py deleted file mode 100644 index 74a9dc427..000000000 --- a/src/wireviz/DataClasses.py +++ /dev/null @@ -1,433 +0,0 @@ -# -*- coding: utf-8 -*- - -from dataclasses import InitVar, dataclass, field -from enum import Enum, auto -from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union - -from wireviz.wv_colors import COLOR_CODES, Color, ColorMode, Colors, ColorScheme -from wireviz.wv_helper import aspect_ratio, int2tuple - -# Each type alias have their legal values described in comments - validation might be implemented in the future -PlainText = str # Text not containing HTML tags nor newlines -Hypertext = str # Text possibly including HTML hyperlinks that are removed in all outputs except HTML output -MultilineHypertext = ( - str # Hypertext possibly also including newlines to break lines in diagram output -) - -Designator = PlainText # Case insensitive unique name of connector or cable - -# Literal type aliases below are commented to avoid requiring python 3.8 -ConnectorMultiplier = PlainText # = Literal['pincount', 'populated'] -CableMultiplier = ( - PlainText # = Literal['wirecount', 'terminations', 'length', 'total_length'] -) -ImageScale = PlainText # = Literal['false', 'true', 'width', 'height', 'both'] - -# Type combinations -Pin = Union[int, PlainText] # Pin identifier -PinIndex = int # Zero-based pin index -Wire = Union[int, PlainText] # Wire number or Literal['s'] for shield -NoneOrMorePins = Union[ - Pin, Tuple[Pin, ...], None -] # None, one, or a tuple of pin identifiers -NoneOrMorePinIndices = Union[ - PinIndex, Tuple[PinIndex, ...], None -] # None, one, or a tuple of zero-based pin indices -OneOrMoreWires = Union[Wire, Tuple[Wire, ...]] # One or a tuple of wires - -# Metadata can contain whatever is needed by the HTML generation/template. -MetadataKeys = PlainText # Literal['title', 'description', 'notes', ...] - - -Side = Enum("Side", "LEFT RIGHT") - - -class Metadata(dict): - pass - - -@dataclass -class Options: - fontname: PlainText = "arial" - bgcolor: Color = "WH" - bgcolor_node: Optional[Color] = "WH" - bgcolor_connector: Optional[Color] = None - bgcolor_cable: Optional[Color] = None - bgcolor_bundle: Optional[Color] = None - color_mode: ColorMode = "SHORT" - mini_bom_mode: bool = True - template_separator: str = "." - - def __post_init__(self): - if not self.bgcolor_node: - self.bgcolor_node = self.bgcolor - if not self.bgcolor_connector: - self.bgcolor_connector = self.bgcolor_node - if not self.bgcolor_cable: - self.bgcolor_cable = self.bgcolor_node - if not self.bgcolor_bundle: - self.bgcolor_bundle = self.bgcolor_cable - - -@dataclass -class Tweak: - override: Optional[Dict[Designator, Dict[str, Optional[str]]]] = None - append: Union[str, List[str], None] = None - - -@dataclass -class Image: - # Attributes of the image object : - src: str - scale: Optional[ImageScale] = None - # Attributes of the image cell
containing the image: - width: Optional[int] = None - height: Optional[int] = None - fixedsize: Optional[bool] = None - bgcolor: Optional[Color] = None - # Contents of the text cell just below the image cell: - caption: Optional[MultilineHypertext] = None - # See also HTML doc at https://graphviz.org/doc/info/shapes.html#html - - def __post_init__(self): - - if self.fixedsize is None: - # Default True if any dimension specified unless self.scale also is specified. - self.fixedsize = (self.width or self.height) and self.scale is None - - if self.scale is None: - if not self.width and not self.height: - self.scale = "false" - elif self.width and self.height: - self.scale = "both" - else: - self.scale = "true" # When only one dimension is specified. - - if self.fixedsize: - # If only one dimension is specified, compute the other - # because Graphviz requires both when fixedsize=True. - if self.height: - if not self.width: - self.width = self.height * aspect_ratio(self.src) - else: - if self.width: - self.height = self.width / aspect_ratio(self.src) - - -@dataclass -class AdditionalComponent: - type: MultilineHypertext - subtype: Optional[MultilineHypertext] = None - manufacturer: Optional[MultilineHypertext] = None - mpn: Optional[MultilineHypertext] = None - supplier: Optional[MultilineHypertext] = None - spn: Optional[MultilineHypertext] = None - pn: Optional[Hypertext] = None - qty: float = 1 - unit: Optional[str] = None - qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None - bgcolor: Optional[Color] = None - - @property - def description(self) -> str: - s = self.type.rstrip() + f", {self.subtype.rstrip()}" if self.subtype else "" - return s - - -@dataclass -class Connector: - name: Designator - bgcolor: Optional[Color] = None - bgcolor_title: Optional[Color] = None - manufacturer: Optional[MultilineHypertext] = None - mpn: Optional[MultilineHypertext] = None - supplier: Optional[MultilineHypertext] = None - spn: Optional[MultilineHypertext] = None - pn: Optional[Hypertext] = None - style: Optional[str] = None - category: Optional[str] = None - type: Optional[MultilineHypertext] = None - subtype: Optional[MultilineHypertext] = None - pincount: Optional[int] = None - image: Optional[Image] = None - notes: Optional[MultilineHypertext] = None - pins: List[Pin] = field(default_factory=list) - pinlabels: List[Pin] = field(default_factory=list) - pincolors: List[Color] = field(default_factory=list) - color: Optional[Color] = None - show_name: Optional[bool] = None - show_pincount: Optional[bool] = None - hide_disconnected_pins: bool = False - loops: List[List[Pin]] = field(default_factory=list) - ignore_in_bom: bool = False - additional_components: List[AdditionalComponent] = field(default_factory=list) - - def __post_init__(self) -> None: - - if isinstance(self.image, dict): - self.image = Image(**self.image) - - self.ports_left = False - self.ports_right = False - self.visible_pins = {} - - if self.style == "simple": - if self.pincount and self.pincount > 1: - raise Exception( - "Connectors with style set to simple may only have one pin" - ) - self.pincount = 1 - - if not self.pincount: - self.pincount = max( - len(self.pins), len(self.pinlabels), len(self.pincolors) - ) - if not self.pincount: - raise Exception( - "You need to specify at least one, pincount, pins, pinlabels, or pincolors" - ) - - # create default list for pins (sequential) if not specified - if not self.pins: - self.pins = list(range(1, self.pincount + 1)) - - if len(self.pins) != len(set(self.pins)): - raise Exception("Pins are not unique") - - if self.show_name is None: - # hide designators for simple and for auto-generated connectors by default - self.show_name = self.style != "simple" and self.name[0:2] != "__" - - if self.show_pincount is None: - # hide pincount for simple (1 pin) connectors by default - self.show_pincount = self.style != "simple" - - for loop in self.loops: - # TODO: allow using pin labels in addition to pin numbers, just like when defining regular connections - # TODO: include properties of wire used to create the loop - if len(loop) != 2: - raise Exception("Loops must be between exactly two pins!") - for pin in loop: - if pin not in self.pins: - raise Exception(f'Unknown loop pin "{pin}" for connector "{self.name}"!') - # Make sure loop connected pins are not hidden. - self.activate_pin(pin) - - for i, item in enumerate(self.additional_components): - if isinstance(item, dict): - self.additional_components[i] = AdditionalComponent(**item) - - def activate_pin(self, pin: Pin, side: Side) -> None: - self.visible_pins[pin] = True - if side == Side.LEFT: - self.ports_left = True - elif side == Side.RIGHT: - self.ports_right = True - - def get_qty_multiplier(self, qty_multiplier: Optional[ConnectorMultiplier]) -> int: - if not qty_multiplier: - return 1 - elif qty_multiplier == "pincount": - return self.pincount - elif qty_multiplier == "populated": - return sum(self.visible_pins.values()) - else: - raise ValueError( - f"invalid qty multiplier parameter for connector {qty_multiplier}" - ) - - -@dataclass -class Cable: - name: Designator - bgcolor: Optional[Color] = None - bgcolor_title: Optional[Color] = None - manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None - mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None - supplier: Union[MultilineHypertext, List[MultilineHypertext], None] = None - spn: Union[MultilineHypertext, List[MultilineHypertext], None] = None - pn: Union[Hypertext, List[Hypertext], None] = None - category: Optional[str] = None - type: Optional[MultilineHypertext] = None - gauge: Optional[float] = None - gauge_unit: Optional[str] = None - show_equiv: bool = False - length: float = 0 - length_unit: Optional[str] = None - color: Optional[Color] = None - wirecount: Optional[int] = None - shield: Union[bool, Color] = False - image: Optional[Image] = None - notes: Optional[MultilineHypertext] = None - colors: List[Colors] = field(default_factory=list) - wirelabels: List[Wire] = field(default_factory=list) - color_code: Optional[ColorScheme] = None - show_name: Optional[bool] = None - show_wirecount: bool = True - show_wirenumbers: Optional[bool] = None - ignore_in_bom: bool = False - additional_components: List[AdditionalComponent] = field(default_factory=list) - - def __post_init__(self) -> None: - - if isinstance(self.image, dict): - self.image = Image(**self.image) - - if isinstance(self.gauge, str): # gauge and unit specified - try: - g, u = self.gauge.split(" ") - except Exception: - raise Exception( - f"Cable {self.name} gauge={self.gauge} - Gauge must be a number, or number and unit separated by a space" - ) - self.gauge = g - - if self.gauge_unit is not None: - print( - f"Warning: Cable {self.name} gauge_unit={self.gauge_unit} is ignored because its gauge contains {u}" - ) - 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 - - if isinstance(self.length, str): # length and unit specified - try: - L, u = self.length.split(" ") - L = float(L) - except Exception: - raise Exception( - f"Cable {self.name} length={self.length} - Length must be a number, or number and unit separated by a space" - ) - self.length = L - if self.length_unit is not None: - print( - f"Warning: Cable {self.name} length_unit={self.length_unit} is ignored because its length contains {u}" - ) - self.length_unit = u - elif not any(isinstance(self.length, t) for t in [int, float]): - raise Exception(f"Cable {self.name} length has a non-numeric value") - elif self.length_unit is None: - self.length_unit = "m" - - self.connections = [] - - if self.wirecount: # number of wires explicitly defined - if self.colors: # use custom color palette (partly or looped if needed) - pass - elif self.color_code: - # use standard color palette (partly or looped if needed) - if self.color_code not in COLOR_CODES: - raise Exception("Unknown color code") - self.colors = COLOR_CODES[self.color_code] - else: # no colors defined, add dummy colors - self.colors = [""] * self.wirecount - - # make color code loop around if more wires than colors - if self.wirecount > len(self.colors): - m = self.wirecount // len(self.colors) + 1 - self.colors = self.colors * int(m) - # cut off excess after looping - self.colors = self.colors[: self.wirecount] - else: # wirecount implicit in length of color list - if not self.colors: - raise Exception( - "Unknown number of wires. Must specify wirecount or colors (implicit length)" - ) - self.wirecount = len(self.colors) - - if self.wirelabels: - if self.shield and "s" in self.wirelabels: - raise Exception( - '"s" may not be used as a wire label for a shielded cable.' - ) - - # if lists of part numbers are provided check this is a bundle and that it matches the wirecount. - for idfield in [self.manufacturer, self.mpn, self.supplier, self.spn, self.pn]: - if isinstance(idfield, list): - if self.category == "bundle": - # check the length - if len(idfield) != self.wirecount: - raise Exception("lists of part data must match wirecount") - else: - raise Exception("lists of part data are only supported for bundles") - - if self.show_name is None: - # hide designators for auto-generated cables by default - self.show_name = self.name[0:2] != "__" - - if self.show_wirenumbers is None: - # by default, show wire numbers for cables, hide for bundles - self.show_wirenumbers = self.category != "bundle" - - for i, item in enumerate(self.additional_components): - if isinstance(item, dict): - self.additional_components[i] = AdditionalComponent(**item) - - # The *_pin arguments accept a tuple, but it seems not in use with the current code. - def connect( - self, - from_name: Optional[Designator], - from_pin: NoneOrMorePinIndices, - via_wire: OneOrMoreWires, - to_name: Optional[Designator], - to_pin: NoneOrMorePinIndices, - ) -> None: - - from_pin = int2tuple(from_pin) - via_wire = int2tuple(via_wire) - 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( - Connection(from_name, from_pin[i], via_wire[i], to_name, to_pin[i]) - ) - - def get_qty_multiplier(self, qty_multiplier: Optional[CableMultiplier]) -> float: - if not qty_multiplier: - return 1 - elif qty_multiplier == "wirecount": - return self.wirecount - elif qty_multiplier == "terminations": - return len(self.connections) - elif qty_multiplier == "length": - return self.length - elif qty_multiplier == "total_length": - return self.length * self.wirecount - else: - raise ValueError( - f"invalid qty multiplier parameter for cable {qty_multiplier}" - ) - - -@dataclass -class Connection: - from_name: Optional[Designator] - from_pin: Optional[Pin] - via_port: Wire - to_name: Optional[Designator] - to_pin: Optional[Pin] - - -@dataclass -class MatePin: - from_name: Designator - from_pin: Pin - to_name: Designator - to_pin: Pin - shape: str - - -@dataclass -class MateComponent: - from_name: Designator - to_name: Designator - shape: str diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py deleted file mode 100644 index da4f2bfdf..000000000 --- a/src/wireviz/Harness.py +++ /dev/null @@ -1,706 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -from collections import Counter -from dataclasses import dataclass -from itertools import zip_longest -from pathlib import Path -from typing import Any, List, Union - -from graphviz import Graph - -from wireviz import APP_NAME, APP_URL, __version__, wv_colors -from wireviz.DataClasses import ( - Cable, - Connector, - MateComponent, - MatePin, - Metadata, - Options, - Tweak, - Side, -) -from wireviz.svgembed import embed_svg_images_file -from wireviz.wv_bom import ( - HEADER_MPN, - HEADER_PN, - HEADER_SPN, - bom_list, - component_table_entry, - generate_bom, - get_additional_component_table, - pn_info_string, -) -from wireviz.wv_colors import get_color_hex, translate_color -from wireviz.wv_gv_html import ( - html_bgcolor, - html_bgcolor_attr, - html_caption, - html_colorbar, - html_image, - html_line_breaks, - nested_html_table, - remove_links, -) -from wireviz.wv_helper import ( - awg_equiv, - flatten2d, - is_arrow, - mm2_equiv, - open_file_read, - open_file_write, - tuplelist2tsv, -) -from wireviz.wv_html import generate_html_output - - -@dataclass -class Harness: - metadata: Metadata - options: Options - tweak: Tweak - - def __post_init__(self): - self.connectors = {} - self.cables = {} - self.mates = [] - self._bom = [] # Internal Cache for generated bom - self.additional_bom_items = [] - - def add_connector(self, name: str, *args, **kwargs) -> None: - self.connectors[name] = Connector(name, *args, **kwargs) - - def add_cable(self, name: str, *args, **kwargs) -> None: - self.cables[name] = Cable(name, *args, **kwargs) - - def add_mate_pin(self, from_name, from_pin, to_name, to_pin, arrow_type) -> None: - self.mates.append(MatePin(from_name, from_pin, to_name, to_pin, arrow_type)) - self.connectors[from_name].activate_pin(from_pin, Side.RIGHT) - self.connectors[to_name].activate_pin(to_pin, Side.LEFT) - - def add_mate_component(self, from_name, to_name, arrow_type) -> None: - self.mates.append(MateComponent(from_name, to_name, arrow_type)) - - def add_bom_item(self, item: dict) -> None: - self.additional_bom_items.append(item) - - def connect( - self, - from_name: str, - from_pin: (int, str), - via_name: str, - via_wire: (int, str), - to_name: str, - to_pin: (int, str), - ) -> None: - # check from and to connectors - for (name, pin) in zip([from_name, to_name], [from_pin, to_pin]): - if name is not None and name in self.connectors: - connector = self.connectors[name] - # check if provided name is ambiguous - if pin in connector.pins and pin in connector.pinlabels: - if connector.pins.index(pin) != connector.pinlabels.index(pin): - raise Exception( - f"{name}:{pin} is defined both in pinlabels and pins, for different pins." - ) - # TODO: Maybe issue a warning if present in both lists but referencing the same pin? - if pin in connector.pinlabels: - if connector.pinlabels.count(pin) > 1: - raise Exception(f"{name}:{pin} is defined more than once.") - index = connector.pinlabels.index(pin) - pin = connector.pins[index] # map pin name to pin number - if name == from_name: - from_pin = pin - if name == to_name: - to_pin = pin - if not pin in connector.pins: - raise Exception(f"{name}:{pin} not found.") - - # check via cable - if via_name in self.cables: - cable = self.cables[via_name] - # check if provided name is ambiguous - if via_wire in cable.colors and via_wire in cable.wirelabels: - if cable.colors.index(via_wire) != cable.wirelabels.index(via_wire): - raise Exception( - f"{via_name}:{via_wire} is defined both in colors and wirelabels, for different wires." - ) - # TODO: Maybe issue a warning if present in both lists but referencing the same wire? - if via_wire in cable.colors: - if cable.colors.count(via_wire) > 1: - raise Exception( - f"{via_name}:{via_wire} is used for more than one wire." - ) - # list index starts at 0, wire IDs start at 1 - via_wire = cable.colors.index(via_wire) + 1 - elif via_wire in cable.wirelabels: - if cable.wirelabels.count(via_wire) > 1: - raise Exception( - f"{via_name}:{via_wire} is used for more than one wire." - ) - via_wire = ( - cable.wirelabels.index(via_wire) + 1 - ) # list index starts at 0, wire IDs start at 1 - - # perform the actual connection - self.cables[via_name].connect(from_name, from_pin, via_wire, to_name, to_pin) - if from_name in self.connectors: - self.connectors[from_name].activate_pin(from_pin, Side.RIGHT) - if to_name in self.connectors: - self.connectors[to_name].activate_pin(to_pin, Side.LEFT) - - def create_graph(self) -> Graph: - dot = Graph() - dot.body.append(f"// Graph generated by {APP_NAME} {__version__}\n") - dot.body.append(f"// {APP_URL}\n") - dot.attr( - "graph", - rankdir="LR", - ranksep="2", - bgcolor=wv_colors.translate_color(self.options.bgcolor, "HEX"), - nodesep="0.33", - fontname=self.options.fontname, - ) - dot.attr( - "node", - shape="none", - width="0", - height="0", - margin="0", # Actual size of the node is entirely determined by the label. - style="filled", - fillcolor=wv_colors.translate_color(self.options.bgcolor_node, "HEX"), - fontname=self.options.fontname, - ) - dot.attr("edge", style="bold", fontname=self.options.fontname) - - for connector in self.connectors.values(): - - # If no wires connected (except maybe loop wires)? - if not (connector.ports_left or connector.ports_right): - connector.ports_left = True # Use left side pins. - - html = [] - # fmt: off - rows = [[f'{html_bgcolor(connector.bgcolor_title)}{remove_links(connector.name)}' - if connector.show_name else None], - [pn_info_string(HEADER_PN, None, remove_links(connector.pn)), - html_line_breaks(pn_info_string(HEADER_MPN, connector.manufacturer, connector.mpn)), - html_line_breaks(pn_info_string(HEADER_SPN, connector.supplier, connector.spn))], - [html_line_breaks(connector.type), - html_line_breaks(connector.subtype), - f'{connector.pincount}-pin' if connector.show_pincount else None, - translate_color(connector.color, self.options.color_mode) if connector.color else None, - html_colorbar(connector.color)], - '' if connector.style != 'simple' else None, - [html_image(connector.image)], - [html_caption(connector.image)]] - # fmt: on - - rows.extend(get_additional_component_table(self, connector)) - rows.append([html_line_breaks(connector.notes)]) - html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor))) - - if connector.style != "simple": - pinhtml = [] - pinhtml.append( - '' - ) - - for pinindex, (pinname, pinlabel, pincolor) in enumerate( - zip_longest( - connector.pins, connector.pinlabels, connector.pincolors - ) - ): - if ( - connector.hide_disconnected_pins - and not connector.visible_pins.get(pinname, False) - ): - continue - - pinhtml.append(" ") - if connector.ports_left: - pinhtml.append(f' ') - if pinlabel: - pinhtml.append(f" ") - if connector.pincolors: - if pincolor in wv_colors._color_hex.keys(): - # fmt: off - pinhtml.append(f' ') - pinhtml.append( ' ') - # fmt: on - else: - pinhtml.append(' ') - - if connector.ports_right: - pinhtml.append(f' ') - pinhtml.append(" ") - - pinhtml.append("
{pinname}{pinlabel}{translate_color(pincolor, self.options.color_mode)}') - pinhtml.append( ' ') - pinhtml.append(f' ') - pinhtml.append( '
') - pinhtml.append( '
{pinname}
") - - html = [ - row.replace("", "\n".join(pinhtml)) - for row in html - ] - - html = "\n".join(html) - dot.node( - connector.name, - label=f"<\n{html}\n>", - shape="box", - style="filled", - fillcolor=translate_color(self.options.bgcolor_connector, "HEX"), - ) - - 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}", - ) - - # determine if there are double- or triple-colored wires in the harness; - # if so, pad single-color wires to make all wires of equal thickness - pad = any( - len(colorstr) > 2 - for cable in self.cables.values() - for colorstr in cable.colors - ) - - for cable in self.cables.values(): - - html = [] - - 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)" - - # fmt: off - rows = [[f'{html_bgcolor(cable.bgcolor_title)}{remove_links(cable.name)}' - if cable.show_name else None], - [pn_info_string(HEADER_PN, None, - remove_links(cable.pn)) if not isinstance(cable.pn, list) else None, - html_line_breaks(pn_info_string(HEADER_MPN, - cable.manufacturer if not isinstance(cable.manufacturer, list) else None, - cable.mpn if not isinstance(cable.mpn, list) else None)), - html_line_breaks(pn_info_string(HEADER_SPN, - cable.supplier if not isinstance(cable.supplier, list) else None, - cable.spn if not isinstance(cable.spn, list) else None))], - [html_line_breaks(cable.type), - f'{cable.wirecount}x' if cable.show_wirecount else None, - f'{cable.gauge} {cable.gauge_unit}{awg_fmt}' if cable.gauge else None, - '+ S' if cable.shield else None, - f'{cable.length} {cable.length_unit}' if cable.length > 0 else None, - translate_color(cable.color, self.options.color_mode) if cable.color else None, - html_colorbar(cable.color)], - '', - [html_image(cable.image)], - [html_caption(cable.image)]] - # fmt: on - - rows.extend(get_additional_component_table(self, cable)) - rows.append([html_line_breaks(cable.notes)]) - html.extend(nested_html_table(rows, html_bgcolor_attr(cable.bgcolor))) - - wirehtml = [] - # conductor table - wirehtml.append('') - wirehtml.append(" ") - - for i, (connection_color, wirelabel) in enumerate( - zip_longest(cable.colors, cable.wirelabels), 1 - ): - wirehtml.append(" ") - wirehtml.append(f" ") - wirehtml.append(f" ") - wirehtml.append(f" ") - wirehtml.append(" ") - - # fmt: off - bgcolors = ['#000000'] + get_color_hex(connection_color, pad=pad) + ['#000000'] - wirehtml.append(f" ") - wirehtml.append(f' ") - wirehtml.append(" ") - # fmt: on - - # for bundles, individual wires can have part information - if cable.category == "bundle": - # create a list of wire parameters - wireidentification = [] - if isinstance(cable.pn, list): - wireidentification.append( - pn_info_string( - HEADER_PN, None, remove_links(cable.pn[i - 1]) - ) - ) - manufacturer_info = pn_info_string( - HEADER_MPN, - cable.manufacturer[i - 1] - if isinstance(cable.manufacturer, list) - else None, - cable.mpn[i - 1] if isinstance(cable.mpn, list) else None, - ) - supplier_info = pn_info_string( - HEADER_SPN, - cable.supplier[i - 1] - if isinstance(cable.supplier, list) - else None, - cable.spn[i - 1] if isinstance(cable.spn, list) else None, - ) - if manufacturer_info: - wireidentification.append(html_line_breaks(manufacturer_info)) - if supplier_info: - wireidentification.append(html_line_breaks(supplier_info)) - # print parameters into a table row under the wire - if len(wireidentification) > 0: - # fmt: off - wirehtml.append(' ") - # fmt: on - - if cable.shield: - wirehtml.append(" ") # spacer - wirehtml.append(" ") - wirehtml.append(" ") - wirehtml.append(" ") - wirehtml.append(" ") - wirehtml.append(" ") - if isinstance(cable.shield, str): - # shield is shown with specified color and black borders - shield_color_hex = wv_colors.get_color_hex(cable.shield)[0] - attributes = ( - f'height="6" bgcolor="{shield_color_hex}" border="2" sides="tb"' - ) - else: - # shield is shown as a thin black wire - attributes = f'height="2" bgcolor="#000000" border="0"' - # fmt: off - wirehtml.append(f' ') - # fmt: on - - wirehtml.append(" ") - wirehtml.append("
 
") - - wireinfo = [] - if cable.show_wirenumbers: - wireinfo.append(str(i)) - colorstr = wv_colors.translate_color( - connection_color, self.options.color_mode - ) - if colorstr: - wireinfo.append(colorstr) - if cable.wirelabels: - wireinfo.append(wirelabel if wirelabel is not None else "") - wirehtml.append(f' {":".join(wireinfo)}') - - wirehtml.append(f"
') - wirehtml.append(' ') - for j, bgcolor in enumerate(bgcolors[::-1]): # Reverse to match the curved wires when more than 2 colors - wirehtml.append(f' ') - wirehtml.append("
") - wirehtml.append("
') - wirehtml.append(' ') - for attrib in wireidentification: - wirehtml.append(f" ") - wirehtml.append("
{attrib}
") - wirehtml.append("
 
Shield
 
") - - html = [ - row.replace("", "\n".join(wirehtml)) for row in html - ] - - # connections - for connection in cable.connections: - if isinstance(connection.via_port, int): - # check if it's an actual wire and not a shield - dot.attr( - "edge", - color=":".join( - ["#000000"] - + wv_colors.get_color_hex( - cable.colors[connection.via_port - 1], pad=pad - ) - + ["#000000"] - ), - ) - else: # it's a shield connection - # shield is shown with specified color and black borders, or as a thin black wire otherwise - dot.attr( - "edge", - color=":".join(["#000000", shield_color_hex, "#000000"]) - if isinstance(cable.shield, str) - else "#000000", - ) - if connection.from_pin is not None: # connect to left - from_connector = self.connectors[connection.from_name] - from_pin_index = from_connector.pins.index(connection.from_pin) - from_port_str = ( - f":p{from_pin_index+1}r" - if from_connector.style != "simple" - else "" - ) - code_left_1 = f"{connection.from_name}{from_port_str}:e" - code_left_2 = f"{cable.name}:w{connection.via_port}:w" - dot.edge(code_left_1, code_left_2) - if from_connector.show_name: - from_info = [ - str(connection.from_name), - str(connection.from_pin), - ] - if from_connector.pinlabels: - pinlabel = from_connector.pinlabels[from_pin_index] - if pinlabel != "": - from_info.append(pinlabel) - from_string = ":".join(from_info) - else: - from_string = "" - html = [ - row.replace(f"", from_string) - for row in html - ] - if connection.to_pin is not None: # connect to right - to_connector = self.connectors[connection.to_name] - to_pin_index = to_connector.pins.index(connection.to_pin) - to_port_str = ( - f":p{to_pin_index+1}l" if to_connector.style != "simple" else "" - ) - code_right_1 = f"{cable.name}:w{connection.via_port}:e" - code_right_2 = f"{connection.to_name}{to_port_str}:w" - dot.edge(code_right_1, code_right_2) - if to_connector.show_name: - to_info = [str(connection.to_name), str(connection.to_pin)] - if to_connector.pinlabels: - pinlabel = to_connector.pinlabels[to_pin_index] - if pinlabel != "": - to_info.append(pinlabel) - to_string = ":".join(to_info) - else: - to_string = "" - html = [ - row.replace(f"", to_string) - for row in html - ] - - style, bgcolor = ( - ("filled,dashed", self.options.bgcolor_bundle) - if cable.category == "bundle" - else ("filled", self.options.bgcolor_cable) - ) - html = "\n".join(html) - dot.node( - cable.name, - label=f"<\n{html}\n>", - shape="box", - style=style, - fillcolor=translate_color(bgcolor, "HEX"), - ) - - def typecheck(name: str, value: Any, expect: type) -> None: - if not isinstance(value, expect): - raise Exception( - f"Unexpected value type of {name}: Expected {expect}, got {type(value)}\n{value}" - ) - - # TODO?: Differ between override attributes and HTML? - if self.tweak.override is not None: - typecheck("tweak.override", self.tweak.override, dict) - for k, d in self.tweak.override.items(): - typecheck(f"tweak.override.{k} key", k, str) - typecheck(f"tweak.override.{k} value", d, dict) - for a, v in d.items(): - typecheck(f"tweak.override.{k}.{a} key", a, str) - typecheck(f"tweak.override.{k}.{a} value", v, (str, type(None))) - - # Override generated attributes of selected entries matching tweak.override. - for i, entry in enumerate(dot.body): - if isinstance(entry, str): - # Find a possibly quoted keyword after leading TAB(s) and followed by [ ]. - match = re.match( - r'^\t*(")?((?(1)[^"]|[^ "])+)(?(1)") \[.*\]$', entry, re.S - ) - keyword = match and match[2] - if keyword in self.tweak.override.keys(): - for attr, value in self.tweak.override[keyword].items(): - if value is None: - entry, n_subs = re.subn( - f'( +)?{attr}=("[^"]*"|[^] ]*)(?(1)| *)', "", entry - ) - if n_subs < 1: - print( - f"Harness.create_graph() warning: {attr} not found in {keyword}!" - ) - elif n_subs > 1: - print( - f"Harness.create_graph() warning: {attr} removed {n_subs} times in {keyword}!" - ) - continue - - if len(value) == 0 or " " in value: - value = value.replace('"', r"\"") - value = f'"{value}"' - entry, n_subs = re.subn( - f'{attr}=("[^"]*"|[^] ]*)', f"{attr}={value}", entry - ) - if n_subs < 1: - # If attr not found, then append it - entry = re.sub(r"\]$", f" {attr}={value}]", entry) - elif n_subs > 1: - print( - f"Harness.create_graph() warning: {attr} overridden {n_subs} times in {keyword}!" - ) - - dot.body[i] = entry - - if self.tweak.append is not None: - if isinstance(self.tweak.append, list): - for i, element in enumerate(self.tweak.append, 1): - typecheck(f"tweak.append[{i}]", element, str) - dot.body.extend(self.tweak.append) - else: - typecheck("tweak.append", self.tweak.append, str) - dot.body.append(self.tweak.append) - - for mate in self.mates: - if mate.shape[0] == "<" and mate.shape[-1] == ">": - dir = "both" - elif mate.shape[0] == "<": - dir = "back" - elif mate.shape[-1] == ">": - dir = "forward" - else: - dir = "none" - - if isinstance(mate, MatePin): - color = "#000000" - elif isinstance(mate, MateComponent): - color = "#000000:#000000" - else: - raise Exception(f"{mate} is an unknown mate") - - from_connector = self.connectors[mate.from_name] - if ( - isinstance(mate, MatePin) - and self.connectors[mate.from_name].style != "simple" - ): - from_pin_index = from_connector.pins.index(mate.from_pin) - from_port_str = f":p{from_pin_index+1}r" - else: # MateComponent or style == 'simple' - from_port_str = "" - if ( - isinstance(mate, MatePin) - and self.connectors[mate.to_name].style != "simple" - ): - to_pin_index = to_connector.pins.index(mate.to_pin) - to_port_str = ( - f":p{to_pin_index+1}l" - if isinstance(mate, MatePin) - and self.connectors[mate.to_name].style != "simple" - else "" - ) - else: # MateComponent or style == 'simple' - to_port_str = "" - code_from = f"{mate.from_name}{from_port_str}:e" - to_connector = self.connectors[mate.to_name] - code_to = f"{mate.to_name}{to_port_str}:w" - - dot.attr("edge", color=color, style="dashed", dir=dir) - dot.edge(code_from, code_to) - - return dot - - # cache for the GraphViz Graph object - # do not access directly, use self.graph instead - _graph = None - - @property - def graph(self): - if not self._graph: # no cached graph exists, generate one - self._graph = self.create_graph() - return self._graph # return cached graph - - @property - def png(self): - from io import BytesIO - - graph = self.graph - data = BytesIO() - data.write(graph.pipe(format="png")) - data.seek(0) - return data.read() - - @property - def svg(self): - graph = self.graph - return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd()) - - - def output( - self, - filename: (str, Path), - view: bool = False, - cleanup: bool = True, - fmt: tuple = ("html", "png", "svg", "tsv"), - ) -> None: - # graphical output - graph = self.graph - svg_already_exists = Path( - f"{filename}.svg" - ).exists() # if SVG already exists, do not delete later - # graphical output - for f in fmt: - if f in ("png", "svg", "html"): - if f == "html": # if HTML format is specified, - f = "svg" # generate SVG for embedding into HTML - # SVG file will be renamed/deleted later - _filename = f"{filename}.tmp" if f == "svg" else filename - # TODO: prevent rendering SVG twice when both SVG and HTML are specified - graph.format = f - graph.render(filename=_filename, view=view, cleanup=cleanup) - # embed images into SVG output - if "svg" in fmt or "html" in fmt: - embed_svg_images_file(f"{filename}.tmp.svg") - # GraphViz output - if "gv" in fmt: - graph.save(filename=f"{filename}.gv") - # BOM output - bomlist = bom_list(self.bom()) - if "tsv" in fmt: - open_file_write(f"{filename}.bom.tsv").write(tuplelist2tsv(bomlist)) - if "csv" in fmt: - # TODO: implement CSV output (preferrably using CSV library) - print("CSV output is not yet supported") - # HTML output - if "html" in fmt: - generate_html_output(filename, bomlist, self.metadata, self.options) - # PDF output - if "pdf" in fmt: - # TODO: implement PDF output - print("PDF output is not yet supported") - # delete SVG if not needed - if "html" in fmt and not "svg" in fmt: - # SVG file was just needed to generate HTML - Path(f"{filename}.tmp.svg").unlink() - elif "svg" in fmt: - Path(f"{filename}.tmp.svg").replace(f"{filename}.svg") - - def bom(self): - if not self._bom: - self._bom = generate_bom(self) - return self._bom diff --git a/src/wireviz/__init__.py b/src/wireviz/__init__.py index 08f7167a8..937f136c9 100644 --- a/src/wireviz/__init__.py +++ b/src/wireviz/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Please don't import anything in this file to avoid issues when it is imported in setup.py -__version__ = "0.4-dev" +__version__ = "0.4-dev251" CMD_NAME = "wireviz" # Lower case command and module name APP_NAME = "WireViz" # Application name in texts meant to be human readable diff --git a/src/wireviz/svgembed.py b/src/wireviz/svgembed.py deleted file mode 100644 index ab6b9f1ed..000000000 --- a/src/wireviz/svgembed.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -import base64 -import re -from pathlib import Path -from typing import Union - -mime_subtype_replacements = {"jpg": "jpeg", "tif": "tiff"} - - -def embed_svg_images(svg_in: str, base_path: Union[str, Path] = Path.cwd()) -> str: - images_b64 = {} # cache of base64-encoded images - - def image_tag(pre: str, url: str, post: str) -> str: - return f'' - - def replace(match: re.Match) -> str: - imgurl = match["URL"] - if not imgurl in images_b64: # only encode/cache every unique URL once - imgurl_abs = (Path(base_path) / imgurl).resolve() - image = imgurl_abs.read_bytes() - images_b64[imgurl] = base64.b64encode(image).decode("utf-8") - return image_tag( - match["PRE"] or "", - f"data:image/{get_mime_subtype(imgurl)};base64, {images_b64[imgurl]}", - match["POST"] or "", - ) - - pattern = re.compile( - image_tag(r"(?P
 [^>]*?)?", r'(?P[^"]*?)', r"(?P [^>]*?)?"),
-        re.IGNORECASE,
-    )
-    return pattern.sub(replace, svg_in)
-
-
-def get_mime_subtype(filename: Union[str, Path]) -> str:
-    mime_subtype = Path(filename).suffix.lstrip(".").lower()
-    if mime_subtype in mime_subtype_replacements:
-        mime_subtype = mime_subtype_replacements[mime_subtype]
-    return mime_subtype
-
-
-def embed_svg_images_file(
-    filename_in: Union[str, Path], overwrite: bool = True
-) -> None:
-    filename_in = Path(filename_in).resolve()
-    filename_out = filename_in.with_suffix(".b64.svg")
-    filename_out.write_text(
-        embed_svg_images(filename_in.read_text(), filename_in.parent)
-    )
-    if overwrite:
-        filename_out.replace(filename_in)
diff --git a/src/wireviz/build_examples.py b/src/wireviz/tools/build_examples.py
similarity index 92%
rename from src/wireviz/build_examples.py
rename to src/wireviz/tools/build_examples.py
index e54d0f5cc..7ad295a0c 100755
--- a/src/wireviz/build_examples.py
+++ b/src/wireviz/tools/build_examples.py
@@ -7,13 +7,12 @@
 from pathlib import Path
 
 script_path = Path(__file__).absolute()
-
-sys.path.insert(0, str(script_path.parent.parent))  # to find wireviz module
-from wv_helper import open_file_append, open_file_read, open_file_write
+sys.path.insert(0, str(script_path.parent.parent.parent))  # to find wireviz module
 
 from wireviz import APP_NAME, __version__, wireviz
+from wireviz.wv_utils import open_file_append, open_file_read, open_file_write
 
-dir = script_path.parent.parent.parent
+dir = script_path.parent.parent.parent.parent
 readme = "readme.md"
 groups = {
     "examples": {
@@ -32,10 +31,11 @@
         "path": dir / "examples",
         "prefix": "demo",
     },
+    **{p.stem: {"path": p} for p in (dir / "tests").glob("**")},
 }
 
 input_extensions = [".yml"]
-extensions_not_containing_graphviz_output = [".gv", ".bom.tsv"]
+extensions_not_containing_graphviz_output = [".gv", ".tsv"]
 extensions_containing_graphviz_output = [".png", ".svg", ".html"]
 generated_extensions = (
     extensions_not_containing_graphviz_output + extensions_containing_graphviz_output
@@ -44,7 +44,7 @@
 
 def collect_filenames(description, groupkey, ext_list):
     path = groups[groupkey]["path"]
-    patterns = [f"{groups[groupkey]['prefix']}*{ext}" for ext in ext_list]
+    patterns = [f"{groups[groupkey].get('prefix', '')}*{ext}" for ext in ext_list]
     if ext_list != input_extensions and readme in groups[groupkey]:
         patterns.append(readme)
     print(f'{description} {groupkey} in "{path}"')
@@ -88,7 +88,7 @@ def build_generated(groupkeys):
 
                     out.write(f"![]({yaml_file.stem}.png)\n\n")
                     out.write(
-                        f"[Source]({yaml_file.name}) - [Bill of Materials]({yaml_file.stem}.bom.tsv)\n\n\n"
+                        f"[Source]({yaml_file.name}) - [Bill of Materials]({yaml_file.stem}.tsv)\n\n\n"
                     )
 
 
@@ -98,7 +98,7 @@ def clean_generated(groupkeys):
         for filename in collect_filenames("Cleaning", key, generated_extensions):
             if filename.is_file():
                 print(f'  rm "{filename}"')
-                Path(filename).unlink()
+                filename.unlink()
 
 
 def compare_generated(groupkeys, branch="", include_graphviz_output=False):
diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py
index 163be954a..dce05ad95 100755
--- a/src/wireviz/wireviz.py
+++ b/src/wireviz/wireviz.py
@@ -10,9 +10,9 @@
 if __name__ == "__main__":
     sys.path.insert(0, str(Path(__file__).parent.parent))  # add src/wireviz to PATH
 
-from wireviz.DataClasses import Metadata, Options, Tweak
-from wireviz.Harness import Harness
-from wireviz.wv_helper import (
+from wireviz.wv_dataclasses import AUTOGENERATED_PREFIX, Metadata, Options, Tweak
+from wireviz.wv_harness import Harness
+from wireviz.wv_utils import (
     expand,
     get_single_key_and_value,
     is_arrow,
@@ -34,7 +34,7 @@ def parse(
     and outputs the result as one or more files and/or as a function return value
 
     Accepted inputs:
-        * A path to a YAML source file to parse
+        * A Path object or a path-like string pointing to a YAML source file to parse
         * A string containing the YAML data to parse
         * A Python Dict containing the pre-parsed YAML data
 
@@ -93,7 +93,8 @@ def parse(
         output_file = output_dir / output_name
 
     if yaml_file:
-        # if reading from file, ensure that input file's parent directory is included in image_paths
+        # if reading from file, ensure that input file's parent directory
+        # is included in image_paths
         default_image_path = yaml_file.parent.resolve()
         if not default_image_path in [Path(x).resolve() for x in image_paths]:
             image_paths.append(default_image_path)
@@ -128,7 +129,8 @@ def parse(
             if len(yaml_data[sec]) > 0:  # section has contents
                 if ty == dict:
                     for key, attribs in yaml_data[sec].items():
-                        # The Image dataclass might need to open an image file with a relative path.
+                        # The Image dataclass might need to open
+                        # an image file with a relative path.
                         image = attribs.get("image")
                         if isinstance(image, dict):
                             image_path = image["src"]
@@ -164,12 +166,16 @@ def resolve_designator(inp, separator):
                 autogenerated_designators[template] = (
                     autogenerated_designators.get(template, 0) + 1
                 )
-                designator = f"__{template}_{autogenerated_designators[template]}"
+                designator = (
+                    f"{AUTOGENERATED_PREFIX}"
+                    f"{template}_{autogenerated_designators[template]}"
+                )
             # check if redefining existing component to different template
             if designator in designators_and_templates:
                 if designators_and_templates[designator] != template:
                     raise Exception(
-                        f"Trying to redefine {designator} from {designators_and_templates[designator]} to {template}"
+                        f"Trying to redefine {designator}"
+                        f" from {designators_and_templates[designator]} to {template}"
                     )
             else:
                 designators_and_templates[designator] = template
@@ -201,7 +207,6 @@ def alternate_type():  # flip between connector and cable/arrow
         expected_type = alternating_types[1 - alternating_types.index(expected_type)]
 
     for connection_set in connection_sets:
-
         # figure out number of parallel connections within this set
         connectioncount = []
         for entry in connection_set:
@@ -282,7 +287,7 @@ def alternate_type():  # flip between connector and cable/arrow
                     # generate new connector instance from template
                     check_type(designator, template, "connector")
                     harness.add_connector(
-                        name=designator, **template_connectors[template]
+                        designator=designator, **template_connectors[template]
                     )
 
                 elif designator in harness.cables:  # existing cable instance
@@ -290,7 +295,9 @@ def alternate_type():  # flip between connector and cable/arrow
                 elif template in template_cables.keys():
                     # generate new cable instance from template
                     check_type(designator, template, "cable/arrow")
-                    harness.add_cable(name=designator, **template_cables[template])
+                    harness.add_cable(
+                        designator=designator, **template_cables[template]
+                    )
 
                 elif is_arrow(designator):
                     check_type(designator, template, "cable/arrow")
@@ -300,7 +307,8 @@ def alternate_type():  # flip between connector and cable/arrow
                         f"{template} is an unknown template/designator/arrow."
                     )
 
-            alternate_type()  # entries in connection set must alternate between connectors and cables/arrows
+            # entries in connection set must alternate between connectors and cables/arrows
+            alternate_type()
 
         # transpose connection set list
         # before: one item per component, one subitem per connection in set
@@ -355,11 +363,13 @@ def alternate_type():  # flip between connector and cable/arrow
                         # mate two connectors as a whole
                         harness.add_mate_component(from_name, to_name, designator)
 
-    # harness population completed =============================================
-
     if "additional_bom_items" in yaml_data:
         for line in yaml_data["additional_bom_items"]:
-            harness.add_bom_item(line)
+            harness.add_additional_bom_item(line)
+
+    # harness population completed =============================================
+
+    harness.populate_bom()
 
     if output_formats:
         harness.output(filename=output_file, fmt=output_formats, view=False)
@@ -382,23 +392,15 @@ def alternate_type():  # flip between connector and cable/arrow
         return tuple(returns) if len(returns) != 1 else returns[0]
 
 
-def _get_yaml_data_and_path(inp: Union[str, Path, Dict]) -> (Dict, Path):
+def _get_yaml_data_and_path(inp: Union[str, Path, Dict]) -> Tuple[Dict, Path]:
     # determine whether inp is a file path, a YAML string, or a Dict
     if not isinstance(inp, Dict):  # received a str or a Path
-        try:
+        if isinstance(inp, Path) or (isinstance(inp, str) and not "\n" in inp):
             yaml_path = Path(inp).expanduser().resolve(strict=True)
-            # if no FileNotFoundError exception happens, get file contents
             yaml_str = open_file_read(yaml_path).read()
-        except (FileNotFoundError, OSError) as e:
-            # if inp is a long YAML string, Pathlib will raise OSError: [errno.ENAMETOOLONG]
-            # when trying to expand and resolve it as a path.
-            # Catch this error, but raise any others
-            from errno import ENAMETOOLONG
-            if type(e) is OSError and e.errno != ENAMETOOLONG:
-                raise e
-            # file does not exist; assume inp is a YAML string
-            yaml_str = inp
+        else:
             yaml_path = None
+            yaml_str = inp
         yaml_data = yaml.safe_load(yaml_str)
     else:
         # received a Dict, use as-is
diff --git a/src/wireviz/wv_bom.py b/src/wireviz/wv_bom.py
index 6689d79a1..01fbac344 100644
--- a/src/wireviz/wv_bom.py
+++ b/src/wireviz/wv_bom.py
@@ -1,266 +1,87 @@
 # -*- coding: utf-8 -*-
 
-from dataclasses import asdict
-from itertools import groupby
-from typing import Any, Dict, List, Optional, Tuple, Union
+from collections import namedtuple
+from dataclasses import dataclass
+from enum import Enum, IntEnum
+from typing import List, Optional, Union
 
-from wireviz.DataClasses import AdditionalComponent, Cable, Color, Connector
-from wireviz.wv_colors import translate_color
-from wireviz.wv_gv_html import html_bgcolor_attr, html_line_breaks
-from wireviz.wv_helper import clean_whitespace
+import tabulate as tabulate_module
 
-BOM_COLUMNS_ALWAYS = ("id", "description", "qty", "unit", "designators")
-BOM_COLUMNS_OPTIONAL = ("pn", "manufacturer", "mpn", "supplier", "spn")
-BOM_COLUMNS_IN_KEY = ("description", "unit") + BOM_COLUMNS_OPTIONAL
+from wireviz.wv_utils import html_line_breaks
 
-HEADER_PN = "P/N"
-HEADER_MPN = "MPN"
-HEADER_SPN = "SPN"
+BOM_HASH_FIELDS = "description qty_unit amount partnumbers"
 
-BOMKey = Tuple[str, ...]
-BOMColumn = str  # = Literal[*BOM_COLUMNS_ALWAYS, *BOM_COLUMNS_OPTIONAL]
-BOMEntry = Dict[BOMColumn, Union[str, int, float, List[str], None]]
 
+BomEntry = namedtuple("BomEntry", "category qty designators")
+BomHash = namedtuple("BomHash", BOM_HASH_FIELDS)
+BomHashList = namedtuple("BomHashList", BOM_HASH_FIELDS)
+PartNumberInfo = namedtuple("PartNumberInfo", "pn manufacturer mpn supplier spn")
 
-def optional_fields(part: Union[Connector, Cable, AdditionalComponent]) -> BOMEntry:
-    """Return part field values for the optional BOM columns as a dict."""
-    part = asdict(part)
-    return {field: part.get(field) for field in BOM_COLUMNS_OPTIONAL}
+# TODO: different BOM modes
+# BomMode
+# "normal"  # no bubbles, full PN info in GV node
+# "bubbles"  # = "full" -> maximum info in GV node
+# "hide PN info"
+# "PN crossref" = "PN bubbles" + "hide PN info"
+# "additionally: BOM table in GV graph label (#227)"
+# "title block in GV graph label"
 
 
-def get_additional_component_table(
-    harness: "Harness", component: Union[Connector, Cable]
-) -> List[str]:
-    """Return a list of diagram node table row strings with additional components."""
-    rows = []
-    if component.additional_components:
-        rows.append(["Additional components"])
-        for part in component.additional_components:
-            common_args = {
-                "qty": part.qty * component.get_qty_multiplier(part.qty_multiplier),
-                "unit": part.unit,
-                "bgcolor": part.bgcolor,
-            }
-            if harness.options.mini_bom_mode:
-                id = get_bom_index(
-                    harness.bom(),
-                    bom_entry_key({**asdict(part), "description": part.description}),
-                )
-                rows.append(
-                    component_table_entry(
-                        f"#{id} ({part.type.rstrip()})", **common_args
-                    )
-                )
-            else:
-                rows.append(
-                    component_table_entry(
-                        part.description, **common_args, **optional_fields(part)
-                    )
-                )
-    return rows
-
-
-def get_additional_component_bom(component: Union[Connector, Cable]) -> List[BOMEntry]:
-    """Return a list of BOM entries with additional components."""
-    bom_entries = []
-    for part in component.additional_components:
-        bom_entries.append(
-            {
-                "description": part.description,
-                "qty": part.qty * component.get_qty_multiplier(part.qty_multiplier),
-                "unit": part.unit,
-                "designators": component.name if component.show_name else None,
-                **optional_fields(part),
-            }
-        )
-    return bom_entries
-
-
-def bom_entry_key(entry: BOMEntry) -> BOMKey:
-    """Return a tuple of string values from the dict that must be equal to join BOM entries."""
-    if "key" not in entry:
-        entry["key"] = tuple(
-            clean_whitespace(make_str(entry.get(c))) for c in BOM_COLUMNS_IN_KEY
-        )
-    return entry["key"]
-
+BomCategory = IntEnum(  # to enforce ordering in BOM
+    "BomEntry", "CONNECTOR CABLE WIRE ADDITIONAL_INSIDE ADDITIONAL_OUTSIDE"
+)
+QtyMultiplierConnector = Enum(
+    "QtyMultiplierConnector", "PINCOUNT POPULATED CONNECTIONS"
+)
+QtyMultiplierCable = Enum(
+    "QtyMultiplierCable", "WIRECOUNT TERMINATION LENGTH TOTAL_LENGTH"
+)
 
-def generate_bom(harness: "Harness") -> List[BOMEntry]:
-    """Return a list of BOM entries generated from the harness."""
-    from wireviz.Harness import Harness  # Local import to avoid circular imports
+PART_NUMBER_HEADERS = PartNumberInfo(
+    pn="P/N", manufacturer=None, mpn="MPN", supplier=None, spn="SPN"
+)
 
-    bom_entries = []
-    # connectors
-    for connector in harness.connectors.values():
-        if not connector.ignore_in_bom:
-            description = (
-                "Connector"
-                + (f", {connector.type}" if connector.type else "")
-                + (f", {connector.subtype}" if connector.subtype else "")
-                + (f", {connector.pincount} pins" if connector.show_pincount else "")
-                + (
-                    f", {translate_color(connector.color, harness.options.color_mode)}"
-                    if connector.color
-                    else ""
-                )
-            )
-            bom_entries.append(
-                {
-                    "description": description,
-                    "designators": connector.name if connector.show_name else None,
-                    **optional_fields(connector),
-                }
-            )
-
-        # add connectors aditional components to bom
-        bom_entries.extend(get_additional_component_bom(connector))
-
-    # cables
-    # TODO: If category can have other non-empty values than 'bundle', maybe it should be part of description?
-    for cable in harness.cables.values():
-        if not cable.ignore_in_bom:
-            if cable.category != "bundle":
-                # process cable as a single entity
-                description = (
-                    "Cable"
-                    + (f", {cable.type}" if cable.type else "")
-                    + (f", {cable.wirecount}")
-                    + (
-                        f" x {cable.gauge} {cable.gauge_unit}"
-                        if cable.gauge
-                        else " wires"
-                    )
-                    + (" shielded" if cable.shield else "")
-                    + (
-                        f", {translate_color(cable.color, harness.options.color_mode)}"
-                        if cable.color
-                        else ""
-                    )
-                )
-                bom_entries.append(
-                    {
-                        "description": description,
-                        "qty": cable.length,
-                        "unit": cable.length_unit,
-                        "designators": cable.name if cable.show_name else None,
-                        **optional_fields(cable),
-                    }
-                )
-            else:
-                # add each wire from the bundle to the bom
-                for index, color in enumerate(cable.colors):
-                    description = (
-                        "Wire"
-                        + (f", {cable.type}" if cable.type else "")
-                        + (f", {cable.gauge} {cable.gauge_unit}" if cable.gauge else "")
-                        + (
-                            f", {translate_color(color, harness.options.color_mode)}"
-                            if color
-                            else ""
-                        )
-                    )
-                    bom_entries.append(
-                        {
-                            "description": description,
-                            "qty": cable.length,
-                            "unit": cable.length_unit,
-                            "designators": cable.name if cable.show_name else None,
-                            **{
-                                k: index_if_list(v, index)
-                                for k, v in optional_fields(cable).items()
-                            },
-                        }
-                    )
-
-        # add cable/bundles aditional components to bom
-        bom_entries.extend(get_additional_component_bom(cable))
 
-    # add harness aditional components to bom directly, as they both are List[BOMEntry]
-    bom_entries.extend(harness.additional_bom_items)
+def partnumbers2list(
+    partnumbers: PartNumberInfo, parent_partnumbers: PartNumberInfo = None
+) -> List[str]:
+    if parent_partnumbers is None:
+        _is_toplevel = True
+        parent_partnumbers = partnumbers
+    else:
+        _is_toplevel = False
 
-    # remove line breaks if present and cleanup any resulting whitespace issues
-    bom_entries = [
-        {k: clean_whitespace(v) for k, v in entry.items()} for entry in bom_entries
-    ]
+    # Note: != operator used as XOR in the following section (https://stackoverflow.com/a/433161)
 
-    # deduplicate bom
-    bom = []
-    for _, group in groupby(sorted(bom_entries, key=bom_entry_key), key=bom_entry_key):
-        group_entries = list(group)
-        designators = sum(
-            (make_list(entry.get("designators")) for entry in group_entries), []
-        )
-        total_qty = sum(entry.get("qty", 1) for entry in group_entries)
-        bom.append(
-            {
-                **group_entries[0],
-                "qty": round(total_qty, 3),
-                "designators": sorted(set(designators)),
-            }
+    if _is_toplevel != isinstance(parent_partnumbers.pn, List):
+        # top level and not a list, or wire level and list
+        cell_pn = pn_info_string(PART_NUMBER_HEADERS.pn, None, partnumbers.pn)
+    else:
+        # top level and list -> do per wire later
+        # wire level and not list -> already done at top level
+        cell_pn = None
+
+    if _is_toplevel != isinstance(parent_partnumbers.mpn, List):
+        # TODO: edge case: different manufacturers, but same MPN?
+        cell_mpn = pn_info_string(
+            PART_NUMBER_HEADERS.mpn, partnumbers.manufacturer, partnumbers.mpn
         )
+    else:
+        cell_mpn = None
 
-    # add an incrementing id to each bom entry
-    return [{**entry, "id": index} for index, entry in enumerate(bom, 1)]
-
-
-def get_bom_index(bom: List[BOMEntry], target: BOMKey) -> int:
-    """Return id of BOM entry or raise exception if not found."""
-    for entry in bom:
-        if bom_entry_key(entry) == target:
-            return entry["id"]
-    raise Exception("Internal error: No BOM entry found matching: " + "|".join(target))
-
-
-def bom_list(bom: List[BOMEntry]) -> List[List[str]]:
-    """Return list of BOM rows as lists of column strings with headings in top row."""
-    keys = list(BOM_COLUMNS_ALWAYS)  # Always include this fixed set of BOM columns.
-    for fieldname in BOM_COLUMNS_OPTIONAL:
-        # Include only those optional BOM columns that are in use.
-        if any(entry.get(fieldname) for entry in bom):
-            keys.append(fieldname)
-    # Custom mapping from internal name to BOM column headers.
-    # Headers not specified here are generated by capitilising the internal name.
-    bom_headings = {
-        "pn": HEADER_PN,
-        "mpn": HEADER_MPN,
-        "spn": HEADER_SPN,
-    }
-    return [
-        [bom_headings.get(k, k.capitalize()) for k in keys]
-    ] + [  # Create header row with key names
-        [make_str(entry.get(k)) for k in keys] for entry in bom
-    ]  # Create string list for each entry row
-
+    if _is_toplevel != isinstance(parent_partnumbers.spn, List):
+        # TODO: edge case: different suppliers, but same SPN?
+        cell_spn = pn_info_string(
+            PART_NUMBER_HEADERS.spn, partnumbers.supplier, partnumbers.spn
+        )
+    else:
+        cell_spn = None
 
-def component_table_entry(
-    type: str,
-    qty: Union[int, float],
-    unit: Optional[str] = None,
-    bgcolor: Optional[Color] = None,
-    pn: Optional[str] = None,
-    manufacturer: Optional[str] = None,
-    mpn: Optional[str] = None,
-    supplier: Optional[str] = None,
-    spn: Optional[str] = None,
-) -> str:
-    """Return a diagram node table row string with an additional component."""
-    part_number_list = [
-        pn_info_string(HEADER_PN, None, pn),
-        pn_info_string(HEADER_MPN, manufacturer, mpn),
-        pn_info_string(HEADER_SPN, supplier, spn),
-    ]
-    output = (
-        f"{qty}"
-        + (f" {unit}" if unit else "")
-        + f" x {type}"
-        + ("
" if any(part_number_list) else "") - + (", ".join([pn for pn in part_number_list if pn])) - ) - # format the above output as left aligned text in a single visible cell - # indent is set to two to match the indent in the generated html table - return f""" - -
{html_line_breaks(output)}
""" + cell_contents = [cell_pn, cell_mpn, cell_spn] + if any(cell_contents): + return [html_line_breaks(cell) for cell in cell_contents] + else: + return None def pn_info_string( @@ -274,16 +95,51 @@ def pn_info_string( return None -def index_if_list(value: Any, index: int) -> Any: - """Return the value indexed if it is a list, or simply the value otherwise.""" - return value[index] if isinstance(value, list) else value - - -def make_list(value: Any) -> list: - """Return value if a list, empty list if None, or single element list otherwise.""" - return value if isinstance(value, list) else [] if value is None else [value] +def bom_list(bom): + headers = ( + "# Qty Unit Description Amount Unit Designators " + "P/N Manufacturer MPN Supplier SPN Category".split(" ") + ) + rows = [] + rows.append(headers) + # fill rows + for hash, entry in bom.items(): + cells = [ + entry["id"], + entry["qty"], + hash.qty_unit, + hash.description, + hash.amount.number if hash.amount else None, + hash.amount.unit if hash.amount else None, + ", ".join(sorted(entry["designators"])), + ] + if hash.partnumbers: + cells.extend( + [ + hash.partnumbers.pn, + hash.partnumbers.manufacturer, + hash.partnumbers.mpn, + hash.partnumbers.supplier, + hash.partnumbers.spn, + ] + ) + else: + cells.extend([None, None, None, None, None]) + # cells.extend([f"{entry['category']} ({entry['category'].name})"]) # for debugging + rows.append(cells) + # remove empty columns + transposed = list(map(list, zip(*rows))) + transposed = [ + column + for column in transposed + if any([cell is not None for cell in column[1:]]) + # ^ ignore header cell in check + ] + rows = list(map(list, zip(*transposed))) + return rows -def make_str(value: Any) -> str: - """Return comma separated elements if a list, empty string if None, or value as a string otherwise.""" - return ", ".join(str(element) for element in make_list(value)) +def print_bom_table(bom): + print() + print(tabulate_module.tabulate(bom_list(bom), headers="firstrow")) + print() diff --git a/src/wireviz/wv_cli.py b/src/wireviz/wv_cli.py index 731507200..7acff8a17 100644 --- a/src/wireviz/wv_cli.py +++ b/src/wireviz/wv_cli.py @@ -11,7 +11,7 @@ import wireviz.wireviz as wv from wireviz import APP_NAME, __version__ -from wireviz.wv_helper import open_file_read +from wireviz.wv_utils import open_file_read format_codes = { "c": "csv", @@ -23,9 +23,12 @@ "t": "tsv", } -epilog = "The -f or --format option accepts a string containing one or more of the " -epilog += "following characters to specify which file types to output:\n" -epilog += ", ".join([f"{key} ({value.upper()})" for key, value in format_codes.items()]) + +epilog = ( + "The -f or --format option accepts a string containing one or more of the " + "following characters to specify which file types to output:\n" + + f", ".join([f"{key} ({value.upper()})" for key, value in format_codes.items()]) +) @click.command(epilog=epilog, no_args_is_help=True) @@ -58,7 +61,10 @@ "--output-name", default=None, type=str, - help="File name (without extension) to use for output files, if different from input file name.", + help=( + "File name (without extension) to use for output files, " + "if different from input file name." + ), ) @click.option( "-V", @@ -71,7 +77,7 @@ def wireviz(file, format, prepend, output_dir, output_name, version): """ Parses the provided FILE and generates the specified outputs. """ - print() + print() # blank line before execution print(f"{APP_NAME} {__version__}") if version: return # print version number only and exit @@ -105,6 +111,8 @@ def wireviz(file, format, prepend, output_dir, output_name, version): prepend_file = Path(prepend_file) if not prepend_file.exists(): raise Exception(f"File does not exist:\n{prepend_file}") + if not prepend_file.is_file(): + raise Exception(f"Path is not a file:\n{prepend_file}") print("Prepend file:", prepend_file) prepend_input += open_file_read(prepend_file).read() + "\n" @@ -116,6 +124,8 @@ def wireviz(file, format, prepend, output_dir, output_name, version): file = Path(file) if not file.exists(): raise Exception(f"File does not exist:\n{file}") + if not file.is_file(): + raise Exception(f"Path is not a file:\n{file}") # file_out = file.with_suffix("") if not output_file else output_file _output_dir = file.parent if not output_dir else output_dir @@ -142,7 +152,7 @@ def wireviz(file, format, prepend, output_dir, output_name, version): image_paths=list(image_paths), ) - print() + print() # blank line after execution if __name__ == "__main__": diff --git a/src/wireviz/wv_colors.py b/src/wireviz/wv_colors.py index 857f30779..876a03019 100644 --- a/src/wireviz/wv_colors.py +++ b/src/wireviz/wv_colors.py @@ -1,6 +1,205 @@ # -*- coding: utf-8 -*- -from typing import Dict, List +from collections import namedtuple +from dataclasses import dataclass, field +from enum import Enum +from typing import List + +padding_amount = 1 + +ColorOutputMode = Enum( + "ColorOutputMode", "EN_LOWER EN_UPPER DE_LOWER DE_UPPER HTML_LOWER HTML_UPPER" +) + +color_output_mode = ColorOutputMode.EN_UPPER + +KnownColor = namedtuple("KnownColor", "html code_de full_en full_de") + +known_colors = { # v--------v--------- for future use + "BK": KnownColor("#000000", "sw", "black", "schwarz"), + "WH": KnownColor("#ffffff", "ws", "white", "weiß"), + "GY": KnownColor("#999999", "gr", "grey", "grau"), + "PK": KnownColor("#ff66cc", "rs", "pink", "rosa"), + "RD": KnownColor("#ff0000", "rt", "red", "rot"), + "OG": KnownColor("#ff8000", "or", "orange", "orange"), + "YE": KnownColor("#ffff00", "ge", "yellow", "gelb"), + "OL": KnownColor("#708000", "ol", "olive green", "olivgrün"), + "GN": KnownColor("#00aa00", "gn", "green", "grün"), + "TQ": KnownColor("#00ffff", "tk", "turquoise", "türkis"), + "LB": KnownColor("#a0dfff", "hb", "light blue", "hellblau"), + "BU": KnownColor("#0066ff", "bl", "blue", "blau"), + "VT": KnownColor("#8000ff", "vi", "violet", "violett"), + "BN": KnownColor("#895956", "br", "brown", "braun"), + "BG": KnownColor("#ceb673", "bg", "beige", "beige"), + "IV": KnownColor("#f5f0d0", "eb", "ivory", "elfenbein"), + "SL": KnownColor("#708090", "si", "slate", "schiefer"), + "CU": KnownColor("#d6775e", "ku", "copper", "Kupfer"), + "SN": KnownColor("#aaaaaa", "vz", "tin", "verzinkt"), + "SR": KnownColor("#84878c", "ag", "silver", "Silber"), + "GD": KnownColor("#ffcf80", "au", "gold", "Gold"), +} + + +def convert_case(inp): + if "_LOWER" in color_output_mode.name: + return inp.lower() + elif "_UPPER" in color_output_mode.name: + return inp.upper() + else: # currently not used + return inp + + +def get_color_by_colorcode_index(color_code: str, index: int) -> str: + num_colors_in_code = len(COLOR_CODES[color_code]) + actual_index = index % num_colors_in_code # wrap around if index is out of bounds + return COLOR_CODES[color_code][actual_index] + + +@dataclass +class SingleColor: + _code_en: str + _html: str + + @property + def code_en(self): + return convert_case(self._code_en) if self._code_en else None + + @property + def code_de(self): + return ( + convert_case(known_colors[self._code_en.upper()].code_de) + if self._code_en + else None + ) + + @property + def html(self): + return convert_case(self._html) if self._code_en else None + + @property + def known(self): + # treat None as a known color + return self.code_en.upper() in known_colors.keys() if self._code_en else True + + def __init__(self, inp): + if inp is None: + self._html = None + self._code_en = None + elif isinstance(inp, int): + hex_str = f"#{inp:06x}" + self._html = hex_str + self._code_en = hex_str # do not perform reverse lookup - why not? + elif not isinstance(inp, str): + raise Exception(f"Unknown single color {inp}!") + else: + inp_upper = inp.upper() + if inp_upper in known_colors.keys(): + self._code_en = inp_upper + self._html = known_colors[inp_upper].html + else: + try: # Maybe inp is an int as string? + inp = f"#{int(inp, 0):06x}" + except ValueError: + pass # assume it's a valid HTML color name + self._html = inp + self._code_en = inp + + @property + def html_padded(self): + return ":".join([self.html] * padding_amount) + + def __bool__(self): + return self._code_en is not None + + def __str__(self): + if self._html is None: + return "" + elif self.known and "EN_" in color_output_mode.name: + return self.code_en + elif self.known and "DE_" in color_output_mode.name: + return self.code_de + else: + return self.html + + +@dataclass +class MultiColor: + colors: List[SingleColor] = field(default_factory=list) + + def __init__(self, inp): + self.colors = [] + if inp is None: + pass + elif isinstance(inp, List): # input is already a list + for item in inp: + if item is None: + pass + elif isinstance(item, SingleColor): + self.colors.append(item) + else: # string or integer (type check done inside) + self.colors.append(SingleColor(item)) + elif isinstance(inp, SingleColor): # single color + self.colors = [inp] + elif isinstance(inp, int): + self.colors = [SingleColor(inp)] + elif not isinstance(inp, str): + raise Exception(f"Unknown multi-color {inp}!") + elif ":" in inp: # split input into list + self.colors = [SingleColor(item) for item in inp.split(":")] + else: + if len(inp) % 2 == 0: + items = [inp[i : i + 2] for i in range(0, len(inp), 2)] + known = [item.upper() in known_colors.keys() for item in items] + if all(known): + self.colors = [SingleColor(item) for item in items] + return + # assume it's a valid HTML color name + self.colors = [SingleColor(inp)] + + def __len__(self): + return len(self.colors) + + def __bool__(self): + return len(self.colors) >= 1 + + def __str__(self): + if "EN_" in color_output_mode.name or "DE_" in color_output_mode.name: + joiner = "" if self.all_known else ":" + elif "HTML_" in color_output_mode.name: + joiner = ":" + else: + joiner = "???" + return joiner.join([str(color) for color in self.colors]) + + @property + def all_known(self): + return all([color.known for color in self.colors]) + + @property + def html(self): + return ":".join([color.html for color in self.colors]) + + @property + def html_padded_list(self): + # padding only properly works for padding_amount 1 or 3 + if padding_amount == 1: + out = [color.html for color in self.colors] + elif len(self) == 0: + out = [] + elif len(self) == 1: + out = [self.colors[0].html for i in range(3)] + elif len(self) == 2: + out = [self.colors[0].html, self.colors[1].html, self.colors[0].html] + elif len(self) == 3: + out = [color.html for color in self.colors] + else: + raise Exception(f"Padding not supported for len {len(self)}") + return [str(color) for color in out] + + @property + def html_padded(self): + return ":".join(self.html_padded_list) + COLOR_CODES = { # fmt: off @@ -38,164 +237,3 @@ "T568A": ["WHGN", "GN", "WHOG", "BU", "WHBU", "OG", "WHBN", "BN"], "T568B": ["WHOG", "OG", "WHGN", "BU", "WHBU", "GN", "WHBN", "BN"], } - -# Convention: Color names should be 2 letters long, to allow for multicolored wires - -_color_hex = { - "BK": "#000000", - "WH": "#ffffff", - "GY": "#999999", - "PK": "#ff66cc", - "RD": "#ff0000", - "OG": "#ff8000", - "YE": "#ffff00", - "OL": "#708000", # olive green - "GN": "#00ff00", - "TQ": "#00ffff", - "LB": "#a0dfff", # light blue - "BU": "#0066ff", - "VT": "#8000ff", - "BN": "#895956", - "BG": "#ceb673", # beige - "IV": "#f5f0d0", # ivory - "SL": "#708090", - "CU": "#d6775e", # Faux-copper look, for bare CU wire - "SN": "#aaaaaa", # Silvery look for tinned bare wire - "SR": "#84878c", # Darker silver for silvered wire - "GD": "#ffcf80", # Golden color for gold -} - -_color_full = { - "BK": "black", - "WH": "white", - "GY": "grey", - "PK": "pink", - "RD": "red", - "OG": "orange", - "YE": "yellow", - "OL": "olive green", - "GN": "green", - "TQ": "turquoise", - "LB": "light blue", - "BU": "blue", - "VT": "violet", - "BN": "brown", - "BG": "beige", - "IV": "ivory", - "SL": "slate", - "CU": "copper", - "SN": "tin", - "SR": "silver", - "GD": "gold", -} - -_color_ger = { - "BK": "sw", - "WH": "ws", - "GY": "gr", - "PK": "rs", - "RD": "rt", - "OG": "or", - "YE": "ge", - "OL": "ol", # olivgrün - "GN": "gn", - "TQ": "tk", - "LB": "hb", # hellblau - "BU": "bl", - "VT": "vi", - "BN": "br", - "BG": "bg", # beige - "IV": "eb", # elfenbeinfarben - "SL": "si", # Schiefer - "CU": "ku", # Kupfer - "SN": "vz", # verzinkt - "SR": "ag", # Silber - "GD": "au", # Gold -} - - -color_default = "#ffffff" - -_hex_digits = set("0123456789abcdefABCDEF") - - -# Literal type aliases below are commented to avoid requiring python 3.8 -Color = str # Two-letter color name = Literal[_color_hex.keys()] -Colors = str # One or more two-letter color names (Color) concatenated into one string -ColorMode = ( - str # = Literal['full', 'FULL', 'hex', 'HEX', 'short', 'SHORT', 'ger', 'GER'] -) -ColorScheme = str # Color scheme name = Literal[COLOR_CODES.keys()] - - -def get_color_hex(input: Colors, pad: bool = False) -> List[str]: - """Return list of hex colors from either a string of color names or :-separated hex colors.""" - if input is None or input == "": - return [color_default] - elif input[0] == "#": # Hex color(s) - output = input.split(":") - for i, c in enumerate(output): - if c[0] != "#" or not all(d in _hex_digits for d in c[1:]): - if c != input: - c += f" in input: {input}" - print(f"Invalid hex color: {c}") - output[i] = color_default - else: # Color name(s) - - def lookup(c: str) -> str: - try: - return _color_hex[c] - except KeyError: - if c != input: - c += f" in input: {input}" - print(f"Unknown color name: {c}") - return color_default - - output = [lookup(input[i : i + 2]) for i in range(0, len(input), 2)] - - if len(output) == 2: # Give wires with EXACTLY 2 colors that striped look. - output += output[:1] - elif pad and len(output) == 1: # Hacky style fix: Give single color wires - output *= 3 # a triple-up so that wires are the same size. - - return output - - -def get_color_translation(translate: Dict[Color, str], input: Colors) -> List[str]: - """Return list of colors translations from either a string of color names or :-separated hex colors.""" - - def from_hex(hex_input: str) -> str: - for color, hex in _color_hex.items(): - if hex == hex_input: - return translate[color] - return f'({",".join(str(int(hex_input[i:i+2], 16)) for i in range(1, 6, 2))})' - - return ( - [from_hex(h) for h in input.lower().split(":")] - if input[0] == "#" - else [translate.get(input[i : i + 2], "??") for i in range(0, len(input), 2)] - ) - - -def translate_color(input: Colors, color_mode: ColorMode) -> str: - if input == "" or input is None: - return "" - upper = color_mode.isupper() - if not (color_mode.isupper() or color_mode.islower()): - raise Exception("Unknown color mode capitalization") - - color_mode = color_mode.lower() - if color_mode == "full": - output = "/".join(get_color_translation(_color_full, input)) - elif color_mode == "hex": - output = ":".join(get_color_hex(input, pad=False)) - elif color_mode == "ger": - output = "".join(get_color_translation(_color_ger, input)) - elif color_mode == "short": - output = input - else: - raise Exception("Unknown color mode") - if upper: - return output.upper() - else: - return output.lower() diff --git a/src/wireviz/wv_dataclasses.py b/src/wireviz/wv_dataclasses.py new file mode 100644 index 000000000..ded141a56 --- /dev/null +++ b/src/wireviz/wv_dataclasses.py @@ -0,0 +1,826 @@ +# -*- coding: utf-8 -*- + +from collections import namedtuple +from dataclasses import dataclass, field +from enum import Enum +from itertools import zip_longest +from typing import Any, Dict, List, Optional, Tuple, Union + +from wireviz.wv_bom import ( + BomHash, + BomHashList, + PartNumberInfo, + QtyMultiplierCable, + QtyMultiplierConnector, +) +from wireviz.wv_colors import ( + COLOR_CODES, + ColorOutputMode, + MultiColor, + SingleColor, + get_color_by_colorcode_index, +) +from wireviz.wv_utils import ( + NumberAndUnit, + awg_equiv, + aspect_ratio, + mm2_equiv, + parse_number_and_unit, + remove_links, +) + +# Each type alias have their legal values described in comments +# - validation might be implemented in the future +PlainText = str # Text not containing HTML tags nor newlines +Hypertext = str # Text possibly including HTML hyperlinks that are removed in all outputs except HTML output +MultilineHypertext = ( + str # Hypertext possibly also including newlines to break lines in diagram output +) + +Designator = PlainText # Case insensitive unique name of connector or cable + +# Literal type aliases below are commented to avoid requiring python 3.8 +ImageScale = PlainText # = Literal['false', 'true', 'width', 'height', 'both'] + +# Type combinations +Pin = Union[int, PlainText] # Pin identifier +PinIndex = int # Zero-based pin index +Wire = Union[int, PlainText] # Wire number or Literal['s'] for shield +NoneOrMorePins = Union[ + Pin, Tuple[Pin, ...], None +] # None, one, or a tuple of pin identifiers +NoneOrMorePinIndices = Union[ + PinIndex, Tuple[PinIndex, ...], None +] # None, one, or a tuple of zero-based pin indices +OneOrMoreWires = Union[Wire, Tuple[Wire, ...]] # One or a tuple of wires + +# Metadata can contain whatever is needed by the HTML generation/template. +MetadataKeys = PlainText # Literal['title', 'description', 'notes', ...] + + +Side = Enum("Side", "LEFT RIGHT") +ArrowDirection = Enum("ArrowDirection", "NONE BACK FORWARD BOTH") +ArrowWeight = Enum("ArrowWeight", "SINGLE DOUBLE") + +AUTOGENERATED_PREFIX = "AUTOGENERATED_" + + +@dataclass +class Arrow: + direction: ArrowDirection + weight: ArrowWeight + + +class Metadata(dict): + pass + + +@dataclass +class Options: + fontname: PlainText = "arial" + bgcolor: SingleColor = "WH" # will be converted to SingleColor in __post_init__ + bgcolor_node: SingleColor = "WH" + bgcolor_connector: SingleColor = None + bgcolor_cable: SingleColor = None + bgcolor_bundle: SingleColor = None + color_output_mode: ColorOutputMode = ColorOutputMode.EN_UPPER + mini_bom_mode: bool = True + template_separator: str = "." + _pad: int = 0 + # TODO: resolve template and image paths during rendering, not during YAML parsing + _template_paths: List = field(default_factory=list) + _image_paths: List = field(default_factory=list) + + def __post_init__(self): + self.bgcolor = SingleColor(self.bgcolor) + self.bgcolor_node = SingleColor(self.bgcolor_node) + self.bgcolor_connector = SingleColor(self.bgcolor_connector) + self.bgcolor_cable = SingleColor(self.bgcolor_cable) + self.bgcolor_bundle = SingleColor(self.bgcolor_bundle) + + if not self.bgcolor_node: + self.bgcolor_node = self.bgcolor + if not self.bgcolor_connector: + self.bgcolor_connector = self.bgcolor_node + if not self.bgcolor_cable: + self.bgcolor_cable = self.bgcolor_node + if not self.bgcolor_bundle: + self.bgcolor_bundle = self.bgcolor_cable + + +@dataclass +class Tweak: + override: Optional[Dict[Designator, Dict[str, Optional[str]]]] = None + append: Union[str, List[str], None] = None + + +@dataclass +class Image: + # Attributes of the image object : + src: str + scale: Optional[ImageScale] = None + # Attributes of the image cell
containing the image: + width: Optional[int] = None + height: Optional[int] = None + fixedsize: Optional[bool] = None + bgcolor: SingleColor = None + # Contents of the text cell just below the image cell: + caption: Optional[MultilineHypertext] = None + # See also HTML doc at https://graphviz.org/doc/info/shapes.html#html + + def __post_init__(self): + self.bgcolor = SingleColor(self.bgcolor) + + if self.fixedsize is None: + # Default True if any dimension specified unless self.scale also is specified. + self.fixedsize = (self.width or self.height) and self.scale is None + + if self.scale is None: + if not self.width and not self.height: + self.scale = "false" + elif self.width and self.height: + self.scale = "both" + else: + self.scale = "true" # When only one dimension is specified. + + if self.fixedsize: + # If only one dimension is specified, compute the other + # because Graphviz requires both when fixedsize=True. + if self.height: + if not self.width: + self.width = self.height * aspect_ratio(self.src) + else: + if self.width: + self.height = self.width / aspect_ratio(self.src) + + +@dataclass +class PinClass: + index: int + id: str + label: str + color: MultiColor + parent: str # designator of parent connector + _num_connections = 0 # incremented in Connector.connect() + _anonymous: bool = False # true for pins on autogenerated connectors + _simple: bool = False # true for simple connector + + def __str__(self): + snippets = [ # use str() for each in case they are int or other non-str + str(self.parent) if not self._anonymous else "", + str(self.id) if not self._anonymous and not self._simple else "", + str(self.label) if self.label else "", + ] + return ":".join([snip for snip in snippets if snip != ""]) + + +@dataclass +class Component: + category: Optional[str] = None # currently only used by cables, to define bundles + type: Union[MultilineHypertext, List[MultilineHypertext]] = None + subtype: Union[MultilineHypertext, List[MultilineHypertext]] = None + + # part number + partnumbers: PartNumberInfo = None # filled by __post_init__() + # the following are provided for user convenience and should not be accessed later. + # their contents are loaded into partnumbers during the child class __post_init__() + pn: str = None + manufacturer: str = None + mpn: str = None + supplier: str = None + spn: str = None + # BOM info + qty: Optional[Union[None, int, float]] = None + amount: Optional[NumberAndUnit] = None + sum_amounts_in_bom: bool = True + ignore_in_bom: bool = False + bom_id: Optional[str] = None # to be filled after harness is built + + def __post_init__(self): + partnos = [self.pn, self.manufacturer, self.mpn, self.supplier, self.spn] + partnos = [remove_links(entry) for entry in partnos] + partnos = tuple(partnos) + self.partnumbers = PartNumberInfo(*partnos) + self.amount = parse_number_and_unit(self.amount, None) + + @property + def bom_hash(self) -> BomHash: + if isinstance(self, AdditionalComponent): + _amount = self.amount_computed + else: + _amount = self.amount + + if self.sum_amounts_in_bom: + _hash = BomHash( + description=self.description, + qty_unit=_amount.unit if _amount else None, + amount=None, + partnumbers=self.partnumbers, + ) + else: + _hash = BomHash( + description=self.description, + qty_unit=None, + amount=_amount, + partnumbers=self.partnumbers, + ) + return _hash + + @property + def has_pn_info(self) -> bool: + return any([self.pn, self.manufacturer, self.mpn, self.supplier, self.spn]) + + @property + def description(self) -> str: + return f"{self.type}{', ' + self.subtype if self.subtype else ''}" + + +@dataclass +class AdditionalBomItem(Component): + designators: Optional[str] = None + + @property + def additional_components(self): + # An additional item may not have further nested additional comonents. + # This property is currently needed for objects in the same list as + # TopLevelGraphicalComponent objects in a Harness method. + return [] + + +@dataclass +class GraphicalComponent(Component): # abstract class + bgcolor: Optional[SingleColor] = None + + def __post_init__(self): + super().__post_init__() + self.bgcolor = SingleColor(self.bgcolor) + + +@dataclass +class AdditionalComponent(GraphicalComponent): + qty_multiplier: Union[QtyMultiplierConnector, QtyMultiplierCable, int] = 1 + qty_computed: Optional[int] = None + explicit_qty: bool = True + amount_computed: Optional[NumberAndUnit] = None + note: str = None + + def __post_init__(self): + super().__post_init__() + + if isinstance(self.qty_multiplier, float) or isinstance( + self.qty_multiplier, int + ): + pass + else: + self.qty_multiplier = self.qty_multiplier.upper() + if self.qty_multiplier in QtyMultiplierConnector.__members__.keys(): + self.qty_multiplier = QtyMultiplierConnector[self.qty_multiplier] + elif self.qty_multiplier in QtyMultiplierCable.__members__.keys(): + self.qty_multiplier = QtyMultiplierCable[self.qty_multiplier] + else: + raise Exception(f"Unknown qty multiplier: {self.qty_multiplier}") + + if self.qty is None and self.qty_multiplier in [ + QtyMultiplierCable.TOTAL_LENGTH, + QtyMultiplierCable.LENGTH, + 1, + ]: # simplify add.comp. table in parent node for implicit qty 1 + self.qty = 1 + self.explicit_qty = False + + +@dataclass +class TopLevelGraphicalComponent(GraphicalComponent): # abstract class + # component properties + designator: Designator = None + color: Optional[MultiColor] = None + image: Optional[Image] = None + additional_parameters: Optional[Dict] = None + additional_components: List[AdditionalComponent] = field(default_factory=list) + notes: Optional[MultilineHypertext] = None + # rendering options + bgcolor_title: Optional[SingleColor] = None + show_name: Optional[bool] = None + + +@dataclass +class Connector(TopLevelGraphicalComponent): + # connector-specific properties + style: Optional[str] = None + loops: List[List[Pin]] = field(default_factory=list) + # pin information in particular + pincount: Optional[int] = None + pins: List[Pin] = field(default_factory=list) # legacy + pinlabels: List[Pin] = field(default_factory=list) # legacy + pincolors: List[str] = field(default_factory=list) # legacy + pin_objects: Dict[Any, PinClass] = field(default_factory=dict) # new + # rendering option + show_pincount: Optional[bool] = None + hide_disconnected_pins: bool = False + + @property + def is_autogenerated(self): + return self.designator.startswith(AUTOGENERATED_PREFIX) + + @property + def description(self) -> str: + substrs = [ + "Connector", + self.type, + self.subtype, + f"{self.pincount} pins" if self.show_pincount else None, + str(self.color) if self.color else None, + ] + return ", ".join([str(s) for s in substrs if s is not None and s != ""]) + + def should_show_pin(self, pin_id): + return ( + not self.hide_disconnected_pins + or self.pin_objects[pin_id]._num_connections > 0 + ) + + @property + def unit(self): # for compatibility with BOM hashing + return None # connectors do not support units. + + def __post_init__(self) -> None: + super().__post_init__() + + self.bgcolor_title = SingleColor(self.bgcolor_title) + self.color = MultiColor(self.color) + + # connectors do not support custom qty or amount + if self.qty is None: + self.qty = 1 + if self.qty != 1: + raise Exception("Connector qty != 1 not supported") + if self.amount is not None: + raise Exception("Connector amount not supported") + + if isinstance(self.image, dict): + self.image = Image(**self.image) + + self.ports_left = False + self.ports_right = False + self.visible_pins = {} + + if self.style == "simple": + if self.pincount and self.pincount > 1: + raise Exception( + "Connectors with style set to simple may only have one pin" + ) + self.pincount = 1 + + if not self.pincount: + self.pincount = max( + len(self.pins), len(self.pinlabels), len(self.pincolors) + ) + if not self.pincount: + raise Exception( + "You need to specify at least one: " + "pincount, pins, pinlabels, or pincolors" + ) + + # create default list for pins (sequential) if not specified + if not self.pins: + self.pins = list(range(1, self.pincount + 1)) + + if len(self.pins) != len(set(self.pins)): + raise Exception("Pins are not unique") + + # all checks have passed + pin_tuples = zip_longest( + self.pins, + self.pinlabels, + self.pincolors, + ) + for pin_index, (pin_id, pin_label, pin_color) in enumerate(pin_tuples): + self.pin_objects[pin_id] = PinClass( + index=pin_index, + id=pin_id, + label=pin_label, + color=MultiColor(pin_color), + parent=self.designator, + _anonymous=self.is_autogenerated, + _simple=self.style == "simple", + ) + + if self.show_name is None: + self.show_name = self.style != "simple" and not self.is_autogenerated + + if self.show_pincount is None: + # hide pincount for simple (1 pin) connectors by default + self.show_pincount = self.style != "simple" + + for loop in self.loops: + # TODO: allow using pin labels in addition to pin numbers, + # just like when defining regular connections + # TODO: include properties of wire used to create the loop + if len(loop) != 2: + raise Exception("Loops must be between exactly two pins!") + for pin in loop: + if pin not in self.pins: + raise Exception( + f'Unknown loop pin "{pin}" for connector "{self.name}"!' + ) + # Make sure loop connected pins are not hidden. + # side=None, determine side to show loops during rendering + self.activate_pin(pin, side=None, is_connection=True) + + for i, item in enumerate(self.additional_components): + if isinstance(item, dict): + self.additional_components[i] = AdditionalComponent(**item) + + def activate_pin(self, pin_id, side: Side = None, is_connection=True) -> None: + if is_connection: + self.pin_objects[pin_id]._num_connections += 1 + if side == Side.LEFT: + self.ports_left = True + elif side == Side.RIGHT: + self.ports_right = True + + def compute_qty_multipliers(self): + # do not run before all connections in harness have been made! + num_populated_pins = len( + [pin for pin in self.pin_objects.values() if pin._num_connections > 0] + ) + num_connections = sum( + [pin._num_connections for pin in self.pin_objects.values()] + ) + qty_multipliers_computed = { + "PINCOUNT": self.pincount, + "POPULATED": num_populated_pins, + "CONNECTIONS": num_connections, + } + for subitem in self.additional_components: + if isinstance(subitem.qty_multiplier, QtyMultiplierConnector): + computed_factor = qty_multipliers_computed[subitem.qty_multiplier.name] + elif isinstance(subitem.qty_multiplier, QtyMultiplierCable): + raise Exception("Used a cable multiplier in a connector!") + else: # int or float + computed_factor = subitem.qty_multiplier + + if subitem.qty is not None: + subitem.qty_computed = subitem.qty * computed_factor + else: + subitem.qty_computed = computed_factor + subitem.amount_computed = subitem.amount + + +@dataclass +class WireClass: + parent: str # designator of parent cable/bundle + # wire-specific properties + index: int + id: str + label: str + color: MultiColor + # ... + bom_id: Optional[str] = None # to be filled after harness is built + # inheritable from parent cable + type: Union[MultilineHypertext, List[MultilineHypertext]] = None + subtype: Union[MultilineHypertext, List[MultilineHypertext]] = None + gauge: Optional[NumberAndUnit] = None + length: Optional[NumberAndUnit] = None + ignore_in_bom: Optional[bool] = False + sum_amounts_in_bom: bool = True + partnumbers: PartNumberInfo = None + + @property + def bom_hash(self) -> BomHash: + if self.sum_amounts_in_bom: + _hash = BomHash( + description=self.description, + qty_unit=self.length.unit if self.length else None, + amount=None, + partnumbers=self.partnumbers, + ) + else: + _hash = BomHash( + description=self.description, + qty_unit=None, + amount=self.length, + partnumbers=self.partnumbers, + ) + return _hash + + @property + def gauge_str(self): + if not self.gauge: + return None + actual_gauge = f"{self.gauge.number} {self.gauge.unit}" + actual_gauge = actual_gauge.replace("mm2", "mm\u00B2") + return actual_gauge + + @property + def description(self) -> str: + substrs = [ + "Wire", + self.type, + self.subtype, + self.gauge_str, + str(self.color) if self.color else None, + ] + desc = ", ".join([s for s in substrs if s is not None and s != ""]) + return desc + + +@dataclass +class ShieldClass(WireClass): + pass # TODO, for wires with multiple shields more shield details, ... + + +@dataclass +class Connection: + from_: PinClass = None + via: Union[WireClass, ShieldClass] = None + to: PinClass = None + + +@dataclass +class Cable(TopLevelGraphicalComponent): + # cable-specific properties + gauge: Optional[NumberAndUnit] = None + length: Optional[NumberAndUnit] = None + color_code: Optional[str] = None + # wire information in particular + wirecount: Optional[int] = None + shield: Union[bool, MultiColor] = False + colors: List[str] = field(default_factory=list) # legacy + wirelabels: List[Wire] = field(default_factory=list) # legacy + wire_objects: Dict[Any, WireClass] = field(default_factory=dict) # new + # internal + _connections: List[Connection] = field(default_factory=list) + # rendering options + show_name: Optional[bool] = None + show_equiv: bool = False + show_wirecount: bool = True + show_wirenumbers: Optional[bool] = None + + @property + def is_autogenerated(self): + return self.designator.startswith(AUTOGENERATED_PREFIX) + + @property + def unit(self): # for compatibility with parent class + return self.length + + @property + def gauge_str(self): + if not self.gauge: + return None + actual_gauge = f"{self.gauge.number} {self.gauge.unit}" + actual_gauge = actual_gauge.replace("mm2", "mm\u00B2") + return actual_gauge + + @property + def gauge_str_with_equiv(self): + if not self.gauge: + return None + actual_gauge = self.gauge_str + equivalent_gauge = "" + if self.show_equiv: + # convert unit if known + if self.gauge.unit == "mm2": + equivalent_gauge = f" ({awg_equiv(self.gauge.number)} AWG)" + elif self.gauge.unit.upper() == "AWG": + equivalent_gauge = f" ({mm2_equiv(self.gauge.number)} mm2)" + out = f"{actual_gauge}{equivalent_gauge}" + out = out.replace("mm2", "mm\u00B2") + return out + + @property + def length_str(self): + if not self.length: + return None + out = f"{self.length.number} {self.length.unit}" + return out + + @property + def bom_hash(self): + if self.category == "bundle": + # This line should never be reached, since caller checks + # whether item is a bundle and if so, calls bom_hash + # for each individual wire instead + raise Exception("Do this at the wire level!") + else: + return super().bom_hash + + @property + def description(self) -> str: + if self.category == "bundle": + raise Exception("Do this at the wire level!") + else: + substrs = [ + ("", "Cable"), + (", ", self.type), + (", ", self.subtype), + (", ", self.wirecount), + (" ", f"x {self.gauge_str}" if self.gauge else "wires"), + (" ", "shielded" if self.shield else None), + (", ", str(self.color) if self.color else None), + ] + desc = "".join( + [f"{s[0]}{s[1]}" for s in substrs if s[1] is not None and s[1] != ""] + ) + return desc + + def _get_wire_partnumber(self, idx) -> PartNumberInfo: + def _get_correct_element(inp, idx): + return inp[idx] if isinstance(inp, List) else inp + + # TODO: possibly make more robust/elegant + if self.category == "bundle": + return PartNumberInfo( + _get_correct_element(self.partnumbers.pn, idx), + _get_correct_element(self.partnumbers.manufacturer, idx), + _get_correct_element(self.partnumbers.mpn, idx), + _get_correct_element(self.partnumbers.supplier, idx), + _get_correct_element(self.partnumbers.spn, idx), + ) + else: + return None # non-bundles do not support lists of part data + + def __post_init__(self) -> None: + super().__post_init__() + + self.bgcolor_title = SingleColor(self.bgcolor_title) + self.color = MultiColor(self.color) + + # cables do not support custom qty or amount + if self.qty is None: + self.qty = 1 + if self.qty != 1: + raise Exception("Cable qty != 1 not supported") + + if isinstance(self.image, dict): + self.image = Image(**self.image) + + # TODO: + # allow gauge, length, and other fields to be lists too (like part numbers), + # and assign them the same way to bundles. + + self.gauge = parse_number_and_unit(self.gauge, "mm2") + self.length = parse_number_and_unit(self.length, "m") + self.amount = self.length # for BOM + + if self.wirecount: # number of wires explicitly defined + if self.colors: # use custom color palette (partly or looped if needed) + self.colors = [ + self.colors[i % len(self.colors)] for i in range(self.wirecount) + ] + elif self.color_code: + # use standard color palette (partly or looped if needed) + if self.color_code not in COLOR_CODES: + raise Exception("Unknown color code") + self.colors = [ + get_color_by_colorcode_index(self.color_code, i) + for i in range(self.wirecount) + ] + else: # no colors defined, add dummy colors + self.colors = [""] * self.wirecount + + else: # wirecount implicit in length of color list + if not self.colors: + raise Exception( + "Unknown number of wires. " + "Must specify wirecount or colors (implicit length)" + ) + self.wirecount = len(self.colors) + + if self.wirelabels: + if self.shield and "s" in self.wirelabels: + raise Exception( + '"s" may not be used as a wire label for a shielded cable.' + ) + + # if lists of part numbers are provided, + # check this is a bundle and that it matches the wirecount. + for idfield in [self.manufacturer, self.mpn, self.supplier, self.spn, self.pn]: + if isinstance(idfield, list): + if self.category == "bundle": + # check the length + if len(idfield) != self.wirecount: + raise Exception("lists of part data must match wirecount") + else: + raise Exception("lists of part data are only supported for bundles") + + # all checks have passed + wire_tuples = zip_longest( + # TODO: self.wire_ids + self.colors, + self.wirelabels, + ) + for wire_index, (wire_color, wire_label) in enumerate(wire_tuples): + id = wire_index + 1 + self.wire_objects[id] = WireClass( + parent=self.designator, + # wire-specific properties + index=wire_index, # TODO: wire_id + id=id, # TODO: wire_id + label=wire_label, + color=MultiColor(wire_color), + # inheritable from parent cable + type=self.type, + subtype=self.subtype, + gauge=self.gauge, + length=self.length, + sum_amounts_in_bom=self.sum_amounts_in_bom, + ignore_in_bom=self.ignore_in_bom, + partnumbers=self._get_wire_partnumber(wire_index), + ) + + if self.shield: + index_offset = len(self.wire_objects) + # TODO: add support for multiple shields + id = "s" + self.wire_objects[id] = ShieldClass( + index=index_offset, + id=id, + label="Shield", + color=( + MultiColor(self.shield) + if isinstance(self.shield, str) + else MultiColor(None) + ), + parent=self.designator, + ) + + if self.show_name is None: + self.show_name = not self.is_autogenerated + + if self.show_wirenumbers is None: + # by default, show wire numbers for cables, hide for bundles + self.show_wirenumbers = self.category != "bundle" + + for i, item in enumerate(self.additional_components): + if isinstance(item, dict): + self.additional_components[i] = AdditionalComponent(**item) + + def _connect( + self, + from_pin_obj: List[PinClass], + via_wire_id: str, + to_pin_obj: List[PinClass], + ) -> None: + via_wire_obj = self.wire_objects[via_wire_id] + self._connections.append(Connection(from_pin_obj, via_wire_obj, to_pin_obj)) + + def compute_qty_multipliers(self): + # do not run before all connections in harness have been made! + total_length = sum( + [ + wire.length.number if wire.length else 0 + for wire in self.wire_objects.values() + ] + ) + qty_multipliers_computed = { + "WIRECOUNT": len(self.wire_objects), + # "TERMINATIONS": ___, # TODO + "LENGTH": self.length.number if self.length else 0, + "TOTAL_LENGTH": total_length, + } + for subitem in self.additional_components: + if isinstance(subitem.qty_multiplier, QtyMultiplierCable): + computed_factor = qty_multipliers_computed[subitem.qty_multiplier.name] + if subitem.qty_multiplier.name in ["LENGTH", "TOTAL_LENGTH"]: + # since length can have a unit, use amount fields to hold + if subitem.amount is not None: + raise Exception( + f"No amount may be specified when using " + f"{subitem.qty_multiplier.name} as a multiplier." + ) + subitem.qty_computed = subitem.qty if subitem.qty else 1 + subitem.amount_computed = NumberAndUnit( + computed_factor, self.length.unit + ) + else: + # multiplier unrelated to length, therefore no unit + if subitem.qty is not None: + subitem.qty_computed = subitem.qty * computed_factor + else: + subitem.qty_computed = computed_factor + subitem.amount_computed = subitem.amount + + elif isinstance(subitem.qty_multiplier, QtyMultiplierConnector): + raise Exception("Used a connector multiplier in a cable!") + else: # int or float + if subitem.qty is not None: + subitem.qty_computed = subitem.qty * subitem.qty_multiplier + else: + subitem.qty_computed = subitem.qty_multiplier + subitem.amount_computed = subitem.amount + + +@dataclass +class MatePin: + from_: PinClass + to: PinClass + arrow: Arrow + + +@dataclass +class MateComponent: + from_: str # Designator + to: str # Designator + arrow: Arrow diff --git a/src/wireviz/wv_graphviz.py b/src/wireviz/wv_graphviz.py new file mode 100644 index 000000000..d42dcacca --- /dev/null +++ b/src/wireviz/wv_graphviz.py @@ -0,0 +1,632 @@ +# -*- coding: utf-8 -*- + +import re +import warnings +from itertools import zip_longest +from typing import Any, List, Optional, Tuple, Union + +from wireviz import APP_NAME, APP_URL, __version__ +from wireviz.wv_bom import partnumbers2list +from wireviz.wv_colors import MultiColor +from wireviz.wv_dataclasses import ( + ArrowDirection, + ArrowWeight, + Cable, + Component, + Connector, + MateComponent, + MatePin, + Options, + PartNumberInfo, + ShieldClass, + WireClass, +) +from wireviz.wv_html import Img, Table, Td, Tr +from wireviz.wv_utils import html_line_breaks, remove_links + + +def gv_node_component(component: Component) -> Table: + # If no wires connected (except maybe loop wires)? + if isinstance(component, Connector): + if not (component.ports_left or component.ports_right): + component.ports_left = True # Use left side pins by default + + # generate all rows to be shown in the node + if component.show_name: + str_name = f"{remove_links(component.designator)}" + line_name = Td(str_name, bgcolor=component.bgcolor_title.html) + else: + line_name = None + + line_pn = partnumbers2list(component.partnumbers) + + is_simple_connector = ( + isinstance(component, Connector) and component.style == "simple" + ) + + if isinstance(component, Connector): + line_info = [ + bom_bubble(component.bom_id), + html_line_breaks(component.type), + html_line_breaks(component.subtype), + f"{component.pincount}-pin" if component.show_pincount else None, + str(component.color) if component.color else None, + ] + elif isinstance(component, Cable): + line_info = [ + bom_bubble(component.bom_id) if component.category != "bundle" else None, + html_line_breaks(component.type), + f"{component.wirecount}x" if component.show_wirecount else None, + component.gauge_str_with_equiv, + "+ S" if component.shield else None, + component.length_str, + str(component.color) if component.color else None, + ] + + if component.additional_parameters: + line_additional_parameters = nested_table_dict(component.additional_parameters) + else: + line_additional_parameters = [] + + if component.color: + line_info.extend(colorbar_cells(component.color)) + + line_image, line_image_caption = image_and_caption_cells(component) + line_additional_component_table = gv_additional_component_table(component) + line_notes = [Td(html_line_breaks(component.notes), balign="left")] + + if isinstance(component, Connector): + if component.style != "simple": + line_ports = gv_pin_table(component) + else: + line_ports = None + elif isinstance(component, Cable): + line_ports = gv_conductor_table(component) + + lines = [ + line_name, + line_pn, + line_info, + line_additional_parameters, + line_ports, + line_image, + line_image_caption, + line_additional_component_table, + line_notes, + ] + + tbl = nested_table(lines) + if is_simple_connector: + # Simple connectors have no pin table, and therefore, no ports to attach wires to. + # Manually assign left and right ports here if required. + # Use table itself for right port, and the first cell for left port. + # Even if the table only has one cell, two separate ports can still be assigned. + tbl.update_attribs(port="p1r") + first_cell_in_tbl = tbl.contents[0].contents + first_cell_in_tbl.update_attribs(port="p1l") + + return tbl + + +def gv_additional_component_table(component): + if not component.additional_components: + return None + + rows = [] + for subitem in component.additional_components: + + if subitem.explicit_qty: + text_qty, unit_qty = subitem.qty_computed, "x" + if subitem.amount_computed is not None: + text_desc = f"{subitem.amount_computed.number} {subitem.amount_computed.unit} {subitem.description}" + else: + text_desc = f"{subitem.description}" + else: + if subitem.amount_computed is not None: + text_qty, unit_qty = ( + subitem.amount_computed.number, + subitem.amount_computed.unit, + ) + else: + text_qty, unit_qty = "1", "x" + text_desc = subitem.description + + firstline = [ + Td(bom_bubble(subitem.bom_id)), + Td(text_qty, align="right"), + Td(unit_qty, align="left"), + Td(text_desc, align="left"), + Td(f"{subitem.note if subitem.note else ''}", align="left"), + ] + rows.append(Tr(firstline)) + + if subitem.has_pn_info: + pn_list = partnumbers2list(subitem.partnumbers) + secondline = [ + Td("", colspan=3), + Td(", ".join(pn for pn in pn_list if pn), align="left"), + Td(""), + ] + rows.append(Tr(secondline)) + + return Table(rows, border=1, cellborder=0, cellpadding=3, cellspacing=0) + + +def calculate_node_bgcolor(component, harness_options): + # assign component node bgcolor at the GraphViz node level + # instead of at the HTML table level for better rendering of node outline + if component.bgcolor: + return component.bgcolor.html + elif isinstance(component, Connector) and harness_options.bgcolor_connector: + return harness_options.bgcolor_connector.html + elif ( + isinstance(component, Cable) + and component.category == "bundle" + and harness_options.bgcolor_bundle + ): + return harness_options.bgcolor_bundle.html + elif isinstance(component, Cable) and harness_options.bgcolor_cable: + return harness_options.bgcolor_cable.html + + +def bom_bubble(id) -> Table: + if id is None: + return None + else: + # TODO: activate BOM bubbles + return None + # size and style of BOM bubble is optimized to be a rounded square, + # big enough to hold any two-digit ID without GraphViz warnings + text = id + # text = f'{id}' + return Table( + Tr( + Td( + text, + border=1, + cellpadding=0, + fixedsize="true", + style="rounded", + height=20, + width=20, + # bgcolor="#000000", + ) + ), + border=0, + ) + + +def make_list_of_cells(inp) -> List[Td]: + # inp may be List, + if isinstance(inp, List): + # ensure all list items are Td + list_out = [item if isinstance(item, Td) else Td(item) for item in inp] + return list_out + else: + if inp is None: + return [] + if isinstance(inp, Td): + return [inp] + else: + return [Td(inp)] + + +def nested_table(lines: List[Td]) -> Table: + cell_lists = [make_list_of_cells(line) for line in lines] + rows = [] + + for lst in cell_lists: + if len(lst) == 0: + continue # no cells in list + cells = [item for item in lst if item.contents is not None] + if len(cells) == 0: + continue # no cells in list, or all cells are None + if ( + len(cells) == 1 + and isinstance(cells[0].contents, Table) + and not "!" in cells[0].contents.attribs.get("id", "") + ): + # cell content is already a table, no need to re-wrap it; + # unless explicitly asked to by a "!" in the ID field + # as used by image_and_caption_cells() + inner_table = cells[0].contents + else: + # nest cell content inside a table + inner_table = Table( + Tr(cells), border=0, cellborder=1, cellpadding=3, cellspacing=0 + ) + rows.append(Tr(Td(inner_table))) + + if len(rows) == 0: # create dummy row to avoid GraphViz errors due to empty + inner_table = Table( + Tr(Td("")), border=0, cellborder=1, cellpadding=3, cellspacing=0 + ) + rows = [Tr(Td(inner_table))] + tbl = Table(rows, border=0, cellspacing=0, cellpadding=0) + return tbl + + +def nested_table_dict(d: dict) -> Table: + rows = [] + for k, v in d.items(): + rows.append( + Tr( + [ + Td(k, align="left", balign="left", valign="top"), + Td(html_line_breaks(v), align="left", balign="left"), + ] + ) + ) + return Table(rows, border=0, cellborder=1, cellpadding=3, cellspacing=0) + + +def gv_pin_table(component) -> Table: + pin_rows = [] + for pin in component.pin_objects.values(): + if component.should_show_pin(pin.id): + pin_rows.append(gv_pin_row(pin, component)) + if len(pin_rows) == 0: + # TODO: write test for empty pin tables, and for unconnected connectors that hide disconnected pins + pass + tbl = Table(pin_rows, border=0, cellborder=1, cellpadding=3, cellspacing=0) + return tbl + + +def gv_pin_row(pin, connector) -> Tr: + # ports in GraphViz are 1-indexed for more natural maping to pin/wire numbers + has_pincolors = any([_pin.color for _pin in connector.pin_objects.values()]) + cells = [ + Td(pin.id, port=f"p{pin.index+1}l") if connector.ports_left else None, + Td(pin.label, delete_if_empty=True), + Td(str(pin.color) if pin.color else "", sides="TBL") if has_pincolors else None, + Td(color_minitable(pin.color), sides="TBR") if has_pincolors else None, + Td(pin.id, port=f"p{pin.index+1}r") if connector.ports_right else None, + ] + return Tr(cells) + + +def gv_connector_loops(connector: Connector) -> List: + loop_edges = [] + 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: + head = f"{connector.designator}:p{loop[0]}{loop_side}:{loop_dir}" + tail = f"{connector.designator}:p{loop[1]}{loop_side}:{loop_dir}" + loop_edges.append((head, tail)) + return loop_edges + + +def gv_conductor_table(cable) -> Table: + rows = [] + rows.append(Tr(Td(" "))) # spacer row on top + + inserted_break_inbetween = False + for wire in cable.wire_objects.values(): + # insert blank space between wires and shields + if isinstance(wire, ShieldClass) and not inserted_break_inbetween: + rows.append(Tr(Td(" "))) # spacer row between wires and shields + inserted_break_inbetween = True + + # row above the wire + wireinfo = [] + if cable.show_wirenumbers and not isinstance(wire, ShieldClass): + wireinfo.append(str(wire.id)) + wireinfo.append(str(wire.color)) + wireinfo.append(wire.label) + + ins, outs = [], [] + for conn in cable._connections: + if conn.via.id == wire.id: + if conn.from_ is not None: + ins.append(str(conn.from_)) + if conn.to is not None: + outs.append(str(conn.to)) + + cells_above = [ + Td(" " + ", ".join(ins), align="left"), + Td(" "), # increase cell spacing here + Td(bom_bubble(wire.bom_id)) if cable.category == "bundle" else None, + Td(":".join([wi for wi in wireinfo if wi is not None and wi != ""])), + Td(" "), # increase cell spacing here + Td(", ".join(outs) + " ", align="right"), + ] + cells_above = [cell for cell in cells_above if cell is not None] + rows.append(Tr(cells_above)) + + # the wire itself + rows.append(Tr(gv_wire_cell(wire, len(cells_above)))) + + # row below the wire + if wire.partnumbers: + cells_below = partnumbers2list( + wire.partnumbers, parent_partnumbers=cable.partnumbers + ) + if cells_below is not None and len(cells_below) > 0: + table_below = ( + Table( + Tr([Td(cell) for cell in cells_below]), + border=0, + cellborder=0, + cellspacing=0, + ), + ) + rows.append(Tr(Td(table_below, colspan=len(cells_above)))) + + rows.append(Tr(Td(" "))) # spacer row on bottom + tbl = Table(rows, border=0, cellborder=0, cellspacing=0) + return tbl + + +def gv_wire_cell(wire: Union[WireClass, ShieldClass], colspan: int) -> Td: + if wire.color: + color_list = ["#000000"] + wire.color.html_padded_list + ["#000000"] + else: + color_list = ["#000000"] + + wire_inner_rows = [] + for j, bgcolor in enumerate(color_list[::-1]): + wire_inner_cell_attribs = { + "bgcolor": bgcolor if bgcolor != "" else "#000000", + "border": 0, + "cellpadding": 0, + "colspan": colspan, + "height": 2, + } + wire_inner_rows.append(Tr(Td("", **wire_inner_cell_attribs))) + wire_inner_table = Table(wire_inner_rows, border=0, cellborder=0, cellspacing=0) + wire_outer_cell_attribs = { + "border": 0, + "cellspacing": 0, + "cellpadding": 0, + "colspan": colspan, + "height": 2 * len(color_list), + "port": f"w{wire.index+1}", + } + # ports in GraphViz are 1-indexed for more natural maping to pin/wire numbers + wire_outer_cell = Td(wire_inner_table, **wire_outer_cell_attribs) + + return wire_outer_cell + + +def gv_edge_wire(harness, cable, connection) -> Tuple[str, str, str, str, str]: + if connection.via.color: + # check if it's an actual wire and not a shield + color = f"#000000:{connection.via.color.html_padded}:#000000" + else: # it's a shield connection + color = "#000000" + + if connection.from_ is not None: # connect to left + from_port_str = ( + f":p{connection.from_.index+1}r" + if harness.connectors[connection.from_.parent].style != "simple" + else "" + ) + code_left_1 = f"{connection.from_.parent}{from_port_str}:e" + code_left_2 = f"{connection.via.parent}:w{connection.via.index+1}:w" + # ports in GraphViz are 1-indexed for more natural maping to pin/wire numbers + else: + code_left_1, code_left_2 = None, None + + if connection.to is not None: # connect to right + to_port_str = ( + f":p{connection.to.index+1}l" + if harness.connectors[connection.to.parent].style != "simple" + else "" + ) + code_right_1 = f"{connection.via.parent}:w{connection.via.index+1}:e" + code_right_2 = f"{connection.to.parent}{to_port_str}:w" + else: + code_right_1, code_right_2 = None, None + + return color, code_left_1, code_left_2, code_right_1, code_right_2 + + +def parse_arrow_str(inp: str) -> ArrowDirection: + if inp[0] == "<" and inp[-1] == ">": + return ArrowDirection.BOTH + elif inp[0] == "<": + return ArrowDirection.BACK + elif inp[-1] == ">": + return ArrowDirection.FORWARD + else: + return ArrowDirection.NONE + + +def gv_edge_mate(mate) -> Tuple[str, str, str, str]: + if mate.arrow.weight == ArrowWeight.SINGLE: + color = "#000000" + elif mate.arrow.weight == ArrowWeight.DOUBLE: + color = "#000000:#000000" + + dir = mate.arrow.direction.name.lower() + + if isinstance(mate, MatePin): + from_pin_index = mate.from_.index + from_port_str = f":p{from_pin_index+1}r" + from_designator = mate.from_.parent + to_pin_index = mate.to.index + to_port_str = f":p{to_pin_index+1}l" + to_designator = mate.to.parent + elif isinstance(mate, MateComponent): + from_designator = mate.from_ + from_port_str = "" + to_designator = mate.to + to_port_str = "" + else: + raise Exception(f"Unknown type of mate:\n{mate}") + + code_from = f"{from_designator}{from_port_str}:e" + code_to = f"{to_designator}{to_port_str}:w" + + return color, dir, code_from, code_to + + +def colorbar_cells(color, mini=False) -> List[Td]: + cells = [] + mini = {"height": 8, "width": 8, "fixedsize": "true"} if mini else {} + for index, subcolor in enumerate(color.colors): + sides_l = "L" if index == 0 else "" + sides_r = "R" if index == len(color.colors) - 1 else "" + sides = "TB" + sides_l + sides_r + cells.append(Td("", bgcolor=subcolor.html, sides=sides, **mini)) + return cells + + +def color_minitable(color: Optional[MultiColor]) -> Union[Table, str]: + if color is None or len(color) == 0: + return "" + + cells = colorbar_cells(color, mini=True) + + return Table( + Tr(cells), + border=0, + cellborder=1, + cellspacing=0, + height=8, + width=8 * len(cells), + fixedsize="true", + ) + + +def image_and_caption_cells(component: Component) -> Tuple[Td, Td]: + if not component.image: + return (None, None) + + image_tag = Img(scale=component.image.scale, src=component.image.src) + image_cell_inner = Td(image_tag, flat=True) + if component.image.fixedsize: + # further nest the image in a table with width/height/fixedsize parameters, + # and place that table in a cell + image_cell_inner.update_attribs(**html_size_attr_dict(component.image)) + image_cell = Td( + Table(Tr(image_cell_inner), border=0, cellborder=0, cellspacing=0, id="!") + ) + else: + image_cell = image_cell_inner + + image_cell.update_attribs( + balign="left", + bgcolor=component.image.bgcolor.html, + sides="TLR" if component.image.caption else None, + ) + + if component.image.caption: + caption_cell = Td( + f"{html_line_breaks(component.image.caption)}", balign="left", sides="BLR" + ) + else: + caption_cell = None + return (image_cell, caption_cell) + + +def html_size_attr_dict(image): + # Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object + pass + + attr_dict = {} + if image: + if image.width: + attr_dict["width"] = image.width + if image.height: + attr_dict["height"] = image.height + if image.fixedsize: + attr_dict["fixedsize"] = "true" + return attr_dict + + +def set_dot_basics(dot, options): + dot.body.append(f"// Graph generated by {APP_NAME} {__version__}\n") + dot.body.append(f"// {APP_URL}\n") + dot.attr( + "graph", + rankdir="LR", + ranksep="2", + bgcolor=options.bgcolor.html, + nodesep="0.33", + fontname=options.fontname, + ) + dot.attr( + "node", + shape="none", + width="0", + height="0", + margin="0", # Actual size of the node is entirely determined by the label. + style="filled", + fillcolor=options.bgcolor_node.html, + fontname=options.fontname, + ) + dot.attr("edge", style="bold", fontname=options.fontname) + + +def apply_dot_tweaks(dot, tweak): + def typecheck(name: str, value: Any, expect: type) -> None: + if not isinstance(value, expect): + raise Exception( + f"Unexpected value type of {name}: " + f"Expected {expect}, got {type(value)}\n{value}" + ) + + # TODO?: Differ between override attributes and HTML? + if tweak.override is not None: + typecheck("tweak.override", tweak.override, dict) + for k, d in tweak.override.items(): + typecheck(f"tweak.override.{k} key", k, str) + typecheck(f"tweak.override.{k} value", d, dict) + for a, v in d.items(): + typecheck(f"tweak.override.{k}.{a} key", a, str) + typecheck(f"tweak.override.{k}.{a} value", v, (str, type(None))) + + # Override generated attributes of selected entries matching tweak.override. + for i, entry in enumerate(dot.body): + if not isinstance(entry, str): + continue + # Find a possibly quoted keyword after leading TAB(s) and followed by [ ]. + match = re.match(r'^\t*(")?((?(1)[^"]|[^ "])+)(?(1)") \[.*\]$', entry, re.S) + keyword = match and match[2] + if not keyword in tweak.override.keys(): + continue + + for attr, value in tweak.override[keyword].items(): + if value is None: + entry, n_subs = re.subn( + f'( +)?{attr}=("[^"]*"|[^] ]*)(?(1)| *)', "", entry + ) + if n_subs < 1: + warnings.warn(f"tweak: {attr} not found in {keyword}!") + elif n_subs > 1: + warnings.warn( + f"tweak: {attr} removed {n_subs} times in {keyword}!" + ) + continue + + if len(value) == 0 or " " in value: + value = value.replace('"', r"\"") + value = f'"{value}"' + entry, n_subs = re.subn( + f'{attr}=("[^"]*"|[^] ]*)', f"{attr}={value}", entry + ) + if n_subs < 1: + # If attr not found, then append it + entry = re.sub(r"\]$", f" {attr}={value}]", entry) + elif n_subs > 1: + warnings.warn( + f"tweak: {attr} overridden {n_subs} times in {keyword}!" + ) + + dot.body[i] = entry + + if tweak.append is not None: + if isinstance(tweak.append, list): + for i, element in enumerate(tweak.append, 1): + typecheck(f"tweak.append[{i}]", element, str) + dot.body.extend(tweak.append) + else: + typecheck("tweak.append", tweak.append, str) + dot.body.append(tweak.append) diff --git a/src/wireviz/wv_gv_html.py b/src/wireviz/wv_gv_html.py deleted file mode 100644 index ec80aa748..000000000 --- a/src/wireviz/wv_gv_html.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -from typing import List, Optional, Union - -from wireviz.DataClasses import Color -from wireviz.wv_colors import translate_color -from wireviz.wv_helper import remove_links - - -def nested_html_table( - rows: List[Union[str, List[Optional[str]], None]], table_attrs: str = "" -) -> str: - # input: list, each item may be scalar or list - # output: a parent table with one child table per parent item that is list, and one cell per parent item that is scalar - # purpose: create the appearance of one table, where cell widths are independent between rows - # attributes in any leading inside a list are injected into to the preceeding \n" - for item in bom[0]: - th_class = f"bom_col_{item.lower()}" - bom_header_html = f'{bom_header_html} \n' - bom_header_html = f"{bom_header_html} \n" - - # generate BOM contents - bom_contents = [] - for row in bom[1:]: - row_html = " \n" - for i, item in enumerate(row): - td_class = f"bom_col_{bom[0][i].lower()}" - row_html = f'{row_html} \n' - row_html = f"{row_html} \n" - bom_contents.append(row_html) - - bom_html = ( - '
tag - html = [] - html.append( - f'' - ) - - num_rows = 0 - for row in rows: - if isinstance(row, List): - if len(row) > 0 and any(row): - html.append(" ") - num_rows = num_rows + 1 - elif row is not None: - html.append(" ") - num_rows = num_rows + 1 - if num_rows == 0: # empty table - # generate empty cell to avoid GraphViz errors - html.append("") - html.append("
") - # fmt: off - html.append(' ') - # fmt: on - for cell in row: - if cell is not None: - # Inject attributes to the preceeding '.replace(">
tag where needed - # fmt: off - html.append(f' {cell}
") - html.append("
") - html.append(f" {row}") - html.append("
") - return html - - -def html_bgcolor_attr(color: Color) -> str: - """Return attributes for bgcolor or '' if no color.""" - return f' bgcolor="{translate_color(color, "HEX")}"' if color else "" - - -def html_bgcolor(color: Color, _extra_attr: str = "") -> str: - """Return
attributes prefix for bgcolor or '' if no color.""" - return f"" if color else "" - - -def html_colorbar(color: Color) -> str: - """Return attributes prefix for bgcolor and minimum width or None if no color.""" - return html_bgcolor(color, ' width="4"') if color else None - - -def html_image(image): - from wireviz.DataClasses import Image - - if not image: - return None - # The leading attributes belong to the preceeding tag. See where used below. - html = f'{html_size_attr(image)}>' - if image.fixedsize: - # Close the preceeding tag and enclose the image cell in a table without - # borders to avoid narrow borders when the fixed width < the node width. - html = f"""> - - -
- """ - return f"""{html_line_breaks(image.caption)}' - if image and image.caption - else None - ) - - -def html_size_attr(image): - from wireviz.DataClasses import Image - - # Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object - return ( - ( - (f' width="{image.width}"' if image.width else "") - + (f' height="{image.height}"' if image.height else "") - + (' fixedsize="true"' if image.fixedsize else "") - ) - if image - else "" - ) - - -def html_line_breaks(inp): - return remove_links(inp).replace("\n", "
") if isinstance(inp, str) else inp diff --git a/src/wireviz/wv_harness.py b/src/wireviz/wv_harness.py new file mode 100644 index 000000000..0c730acb4 --- /dev/null +++ b/src/wireviz/wv_harness.py @@ -0,0 +1,441 @@ +# -*- coding: utf-8 -*- + +from collections import defaultdict +from dataclasses import dataclass, field +from pathlib import Path +from typing import List, Union + +from graphviz import Graph + +import wireviz.wv_colors +from wireviz.wv_bom import BomCategory, BomEntry, bom_list, print_bom_table +from wireviz.wv_dataclasses import ( + AUTOGENERATED_PREFIX, + AdditionalBomItem, + Arrow, + ArrowWeight, + Cable, + Component, + Connector, + MateComponent, + MatePin, + Metadata, + Options, + Side, + TopLevelGraphicalComponent, + Tweak, +) +from wireviz.wv_graphviz import ( + apply_dot_tweaks, + calculate_node_bgcolor, + gv_connector_loops, + gv_edge_mate, + gv_edge_wire, + gv_node_component, + parse_arrow_str, + set_dot_basics, +) +from wireviz.wv_output import ( + embed_svg_images, + embed_svg_images_file, + generate_html_output, +) +from wireviz.wv_utils import bom2tsv, open_file_write + + +@dataclass +class Harness: + metadata: Metadata + options: Options + tweak: Tweak + additional_bom_items: List[AdditionalBomItem] = field(default_factory=list) + + def __post_init__(self): + self.connectors = {} + self.cables = {} + self.mates = [] + self.bom = defaultdict(dict) + self.additional_bom_items = [] + + def add_connector(self, designator: str, *args, **kwargs) -> None: + conn = Connector(designator=designator, *args, **kwargs) + self.connectors[designator] = conn + + def add_cable(self, designator: str, *args, **kwargs) -> None: + cbl = Cable(designator=designator, *args, **kwargs) + self.cables[designator] = cbl + + def add_additional_bom_item(self, item: dict) -> None: + new_item = AdditionalBomItem(**item) + self.additional_bom_items.append(new_item) + + def add_mate_pin(self, from_name, from_pin, to_name, to_pin, arrow_str) -> None: + from_con = self.connectors[from_name] + from_pin_obj = from_con.pin_objects[from_pin] + to_con = self.connectors[to_name] + to_pin_obj = to_con.pin_objects[to_pin] + arrow = Arrow(direction=parse_arrow_str(arrow_str), weight=ArrowWeight.SINGLE) + + self.mates.append(MatePin(from_pin_obj, to_pin_obj, arrow)) + self.connectors[from_name].activate_pin( + from_pin, Side.RIGHT, is_connection=False + ) + self.connectors[to_name].activate_pin(to_pin, Side.LEFT, is_connection=False) + + def add_mate_component(self, from_name, to_name, arrow_str) -> None: + arrow = Arrow(direction=parse_arrow_str(arrow_str), weight=ArrowWeight.SINGLE) + self.mates.append(MateComponent(from_name, to_name, arrow)) + + def populate_bom(self): # called once harness creation is complete + # helper lists + all_toplevel_items = ( + list(self.connectors.values()) + + list(self.cables.values()) + + self.additional_bom_items + ) + all_subitems = [ + subitem + for item in all_toplevel_items + for subitem in item.additional_components + ] + all_bom_relevant_items = ( + list(self.connectors.values()) + + [cable for cable in self.cables.values() if cable.category != "bundle"] + + [ + wire + for cable in self.cables.values() + if cable.category == "bundle" + for wire in cable.wire_objects.values() + ] + + all_subitems + ) + + # add items to BOM + for item in all_toplevel_items: + self._add_to_internal_bom(item) # nested subitems are also handled + # sort BOM by category first, then alphabetically by description within category + self.bom = dict( + sorted( + self.bom.items(), + key=lambda x: ( + x[1]["category"], + x[0].description, + ), # x[0] = key, x[1] = value + ) + ) + # assign BOM IDs + for id, key in enumerate(self.bom.keys(), 1): + self.bom[key]["id"] = id + # set BOM IDs within components (for BOM bubbles) + for item in all_bom_relevant_items: + if item.ignore_in_bom: + continue + if not item.bom_hash in self.bom: + print(f"{item}'s hash' not found in BOM dict.") # Should not happen + continue + item.bom_id = self.bom[item.bom_hash]["id"] + + def _add_to_internal_bom(self, item: Component): + if item.ignore_in_bom: + return + + def _add(hash, qty, designator=None, category=None): + bom_entry = self.bom[hash] + # initialize missing fields + if not "qty" in bom_entry: + bom_entry["qty"] = 0 + if not "designators" in bom_entry: + bom_entry["designators"] = set() + # update fields + bom_entry["qty"] += qty + if designator is None: + designator_list = [] + elif isinstance(designator, list): + designator_list = designator + else: + designator_list = [designator] + for des in designator_list: + if des and not des.startswith(AUTOGENERATED_PREFIX): + bom_entry["designators"].add(des) + bom_entry["category"] = category + + if isinstance(item, TopLevelGraphicalComponent): + if isinstance(item, Connector): + cat = BomCategory.CONNECTOR + elif isinstance(item, Cable): + if item.category == "bundle": + cat = BomCategory.WIRE + else: + cat = BomCategory.CABLE + else: + cat = "" + + if item.category == "bundle": + # wires of a bundle are added as individual BOM entries + for subitem in item.wire_objects.values(): + _add( + hash=subitem.bom_hash, + qty=item.qty, # should be 1 + designator=item.designator, # inherit from parent item + category=cat, + ) + else: + _add( + hash=item.bom_hash, + qty=item.qty, # should be 1 + designator=item.designator, + category=cat, + ) + + if item.additional_components: + item.compute_qty_multipliers() + + for comp in item.additional_components: + if comp.ignore_in_bom: + continue + + if comp.sum_amounts_in_bom: + if comp.amount_computed: + total_qty = comp.qty_computed * comp.amount_computed.number + else: + total_qty = comp.qty_computed + else: + total_qty = comp.qty_computed + _add( + hash=comp.bom_hash, + designator=item.designator, + qty=total_qty, + # no explicit qty specified; assume qty = 1 + # used to simplify add.comp. table within parent node + # e.g. show "10 mm Heatshrink" instead of "1x 10 mm Heatshrink" + category=BomCategory.ADDITIONAL_INSIDE, + ) + elif isinstance(item, AdditionalBomItem): + cat = BomCategory.ADDITIONAL_OUTSIDE + _add( + hash=item.bom_hash, + qty=item.qty, + designator=None, + category=cat, + ) + else: + raise Exception(f"Unknown type of item:\n{item}") + + def connect( + self, + from_name: str, + from_pin: Union[int, str], + via_name: str, + via_wire: Union[int, str], + to_name: str, + to_pin: Union[int, str], + ) -> None: + # check from and to connectors + for name, pin in zip([from_name, to_name], [from_pin, to_pin]): + if name is not None and name in self.connectors: + connector = self.connectors[name] + # check if provided name is ambiguous + if pin in connector.pins and pin in connector.pinlabels: + if connector.pins.index(pin) != connector.pinlabels.index(pin): + raise Exception( + f"{name}:{pin} is defined both in pinlabels and pins, " + "for different pins." + ) + # TODO: Maybe issue a warning if present in both lists + # but referencing the same pin? + if pin in connector.pinlabels: + if connector.pinlabels.count(pin) > 1: + raise Exception(f"{name}:{pin} is defined more than once.") + index = connector.pinlabels.index(pin) + pin = connector.pins[index] # map pin name to pin number + if name == from_name: + from_pin = pin + if name == to_name: + to_pin = pin + if not pin in connector.pins: + raise Exception(f"{name}:{pin} not found.") + + # check via cable + if via_name in self.cables: + cable = self.cables[via_name] + # check if provided name is ambiguous + if via_wire in cable.colors and via_wire in cable.wirelabels: + if cable.colors.index(via_wire) != cable.wirelabels.index(via_wire): + raise Exception( + f"{via_name}:{via_wire} is defined both in colors and wirelabels, " + "for different wires." + ) + # TODO: Maybe issue a warning if present in both lists + # but referencing the same wire? + if via_wire in cable.colors: + if cable.colors.count(via_wire) > 1: + raise Exception( + f"{via_name}:{via_wire} is used for more than one wire." + ) + # list index starts at 0, wire IDs start at 1 + via_wire = cable.colors.index(via_wire) + 1 + elif via_wire in cable.wirelabels: + if cable.wirelabels.count(via_wire) > 1: + raise Exception( + f"{via_name}:{via_wire} is used for more than one wire." + ) + via_wire = ( + cable.wirelabels.index(via_wire) + 1 + ) # list index starts at 0, wire IDs start at 1 + + # perform the actual connection + if from_name is not None: + from_con = self.connectors[from_name] + from_pin_obj = from_con.pin_objects[from_pin] + else: + from_pin_obj = None + if to_name is not None: + to_con = self.connectors[to_name] + to_pin_obj = to_con.pin_objects[to_pin] + else: + to_pin_obj = None + + self.cables[via_name]._connect(from_pin_obj, via_wire, to_pin_obj) + if from_name in self.connectors: + self.connectors[from_name].activate_pin(from_pin, Side.RIGHT) + if to_name in self.connectors: + self.connectors[to_name].activate_pin(to_pin, Side.LEFT) + + def create_graph(self) -> Graph: + dot = Graph() + set_dot_basics(dot, self.options) + + for connector in self.connectors.values(): + # generate connector node + gv_html = gv_node_component(connector) + gv_html.update_attribs( + bgcolor=calculate_node_bgcolor(connector, self.options) + ) + dot.node( + connector.designator, + label=f"<\n{gv_html}\n>", + shape="box", + style="filled", + ) + # generate edges for connector loops + if len(connector.loops) > 0: + dot.attr("edge", color="#000000") + loops = gv_connector_loops(connector) + for head, tail in loops: + dot.edge(head, tail) + + # determine if there are double- or triple-colored wires in the harness; + # if so, pad single-color wires to make all wires of equal thickness + wire_is_multicolor = [ + len(wire.color) > 1 + for cable in self.cables.values() + for wire in cable.wire_objects.values() + ] + if any(wire_is_multicolor): + wireviz.wv_colors.padding_amount = 3 + else: + wireviz.wv_colors.padding_amount = 1 + + for cable in self.cables.values(): + # generate cable node + # TODO: PN info for bundles (per wire) + gv_html = gv_node_component(cable) + gv_html.update_attribs(bgcolor=calculate_node_bgcolor(cable, self.options)) + style = "filled,dashed" if cable.category == "bundle" else "filled" + dot.node( + cable.designator, + label=f"<\n{gv_html}\n>", + shape="box", + style=style, + ) + + # generate wire edges between component nodes and cable nodes + for connection in cable._connections: + color, l1, l2, r1, r2 = gv_edge_wire(self, cable, connection) + dot.attr("edge", color=color) + if not (l1, l2) == (None, None): + dot.edge(l1, l2) + if not (r1, r2) == (None, None): + dot.edge(r1, r2) + + for mate in self.mates: + color, dir, code_from, code_to = gv_edge_mate(mate) + + dot.attr("edge", color=color, style="dashed", dir=dir) + dot.edge(code_from, code_to) + + apply_dot_tweaks(dot, self.tweak) + + return dot + + # cache for the GraphViz Graph object + # do not access directly, use self.graph instead + _graph = None + + @property + def graph(self): + if not self._graph: # no cached graph exists, generate one + self._graph = self.create_graph() + return self._graph # return cached graph + + @property + def png(self): + from io import BytesIO + + graph = self.graph + data = BytesIO() + data.write(graph.pipe(format="png")) + data.seek(0) + return data.read() + + @property + def svg(self): + graph = self.graph + return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd()) + + def output( + self, + filename: Union[str, Path], + view: bool = False, + cleanup: bool = True, + fmt: tuple = ("html", "png", "svg", "tsv"), + ) -> None: + # graphical output + graph = self.graph + for f in fmt: + if f in ("png", "svg", "html"): + if f == "html": # if HTML format is specified, + f = "svg" # generate SVG for embedding into HTML + # SVG file will be renamed/deleted later + _filename = f"{filename}.tmp" if f == "svg" else filename + # TODO: prevent rendering SVG twice when both SVG and HTML are specified + graph.format = f + graph.render(filename=_filename, view=view, cleanup=cleanup) + # embed images into SVG output + if "svg" in fmt or "html" in fmt: + embed_svg_images_file(f"{filename}.tmp.svg") + # GraphViz output + if "gv" in fmt: + graph.save(filename=f"{filename}.gv") + # BOM output + bomlist = bom_list(self.bom) + # bomlist = [[]] + if "tsv" in fmt: + tsv = bom2tsv(bomlist) + open_file_write(f"{filename}.tsv").write(tsv) + if "csv" in fmt: + # TODO: implement CSV output (preferrably using CSV library) + print("CSV output is not yet supported") + # HTML output + if "html" in fmt: + generate_html_output(filename, bomlist, self.metadata, self.options) + # PDF output + if "pdf" in fmt: + # TODO: implement PDF output + print("PDF output is not yet supported") + # delete SVG if not needed + if "html" in fmt and not "svg" in fmt: + # SVG file was just needed to generate HTML + Path(f"{filename}.tmp.svg").unlink() + elif "svg" in fmt: + Path(f"{filename}.tmp.svg").replace(f"{filename}.svg") diff --git a/src/wireviz/wv_html.py b/src/wireviz/wv_html.py index 153426689..1c4b750e0 100644 --- a/src/wireviz/wv_html.py +++ b/src/wireviz/wv_html.py @@ -1,119 +1,125 @@ # -*- coding: utf-8 -*- -import re -from pathlib import Path -from typing import Dict, List, Union - -from wireviz import APP_NAME, APP_URL, __version__, wv_colors -from wireviz.DataClasses import Metadata, Options -from wireviz.wv_gv_html import html_line_breaks -from wireviz.wv_helper import ( - flatten2d, - open_file_read, - open_file_write, - smart_file_resolve, -) - - -def generate_html_output( - filename: Union[str, Path], - bom_list: List[List[str]], - metadata: Metadata, - options: Options, -): - - # load HTML template - templatename = metadata.get("template", {}).get("name") - if templatename: - # if relative path to template was provided, check directory of YAML file first, fall back to built-in template directory - templatefile = smart_file_resolve( - f"{templatename}.html", - [Path(filename).parent, Path(__file__).parent / "templates"], - ) - else: - # fall back to built-in simple template if no template was provided - templatefile = Path(__file__).parent / "templates/simple.html" - - html = open_file_read(templatefile).read() - - # embed SVG diagram - with open_file_read(f"{filename}.tmp.svg") as file: - svgdata = re.sub( - "^<[?]xml [^?>]*[?]>[^<]*]*>", - "", - file.read(), - 1, - ) - - # generate BOM table - bom = flatten2d(bom_list) - - # generate BOM header (may be at the top or bottom of the table) - bom_header_html = "
{item}
{item}
\n' + bom_header_html + "".join(bom_contents) + "
\n" - ) - bom_html_reversed = ( - '\n' - + "".join(list(reversed(bom_contents))) - + bom_header_html - + "
\n" - ) - - # prepare simple replacements - replacements = { - "": f"{APP_NAME} {__version__} - {APP_URL}", - "": options.fontname, - "": wv_colors.translate_color(options.bgcolor, "hex"), - "": svgdata, - "": bom_html, - "": bom_html_reversed, - "": "1", # TODO: handle multi-page documents - "": "1", # TODO: handle multi-page documents - } - - # prepare metadata replacements - if metadata: - for item, contents in metadata.items(): - if isinstance(contents, (str, int, float)): - replacements[f""] = html_line_breaks(str(contents)) - elif isinstance(contents, Dict): # useful for authors, revisions - for index, (category, entry) in enumerate(contents.items()): - if isinstance(entry, Dict): - replacements[f""] = str(category) - for entry_key, entry_value in entry.items(): - replacements[ - f"" - ] = html_line_breaks(str(entry_value)) - - replacements['"sheetsize_default"'] = '"{}"'.format( - metadata.get("template", {}).get("sheetsize", "") - ) - # include quotes so no replacement happens within +

tutorial01

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 4 wires1mW1
2Connector, 4 pins2X1, X2
+ + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 4 wires1mW1
2Connector, 4 pins2X1, X2
+ + + diff --git a/tutorial/tutorial01.svg b/tutorial/tutorial01.svg index c96a7c393..ca1330aa6 100644 --- a/tutorial/tutorial01.svg +++ b/tutorial/tutorial01.svg @@ -1,7 +1,7 @@ - - - - tutorial02 - + + + tutorial02 + +

tutorial02

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 4 x 0.25 mm²1mW1
2Connector, Molex KK 254, female, 4 pins2X1, X2
+ + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 4 x 0.25 mm²1mW1
2Connector, Molex KK 254, female, 4 pins2X1, X2
+ + + diff --git a/tutorial/tutorial02.svg b/tutorial/tutorial02.svg index 30d0a32b6..6b546a1dc 100644 --- a/tutorial/tutorial02.svg +++ b/tutorial/tutorial02.svg @@ -1,7 +1,7 @@ - - - - tutorial03 - + + + tutorial03 + +

tutorial03

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 4 x 0.25 mm² shielded1mW1
2Connector, Molex KK 254, female, 4 pins2X1, X2
+ + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 4 x 0.25 mm² shielded1mW1
2Connector, Molex KK 254, female, 4 pins2X1, X2
+ + + diff --git a/tutorial/tutorial03.svg b/tutorial/tutorial03.svg index c49d74bdf..d6640e48f 100644 --- a/tutorial/tutorial03.svg +++ b/tutorial/tutorial03.svg @@ -1,7 +1,7 @@ - - - - tutorial04 - + + + tutorial04 + +

tutorial04

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Cable, 4 x 24 AWG0.4mW1, W2
2Connector, Molex KK 254, female, 4 pins2X2, X3
3Connector, Molex KK 254, male, 4 pins1X1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Cable, 4 x 24 AWG0.4mW1, W2
2Connector, Molex KK 254, female, 4 pins2X2, X3
3Connector, Molex KK 254, male, 4 pins1X1
+ + + diff --git a/tutorial/tutorial04.svg b/tutorial/tutorial04.svg index 42e089016..430a4b934 100644 --- a/tutorial/tutorial04.svg +++ b/tutorial/tutorial04.svg @@ -1,7 +1,7 @@ -
- -
X1
-
- - - - + + + +
Molex 8981female4-pinCrimp ferrule0.5 mm²OG
- - - - - - - - - - - - - - - - - -
1+12V
2GND
3GND
4+5V
-
> fillcolor="#FFFFFF" shape=box style=filled] - _F1_1 [label=< + AUTOGENERATED_F1_2 [label=<
@@ -52,7 +28,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _F1_2 [label=< + AUTOGENERATED_F1_3 [label=<
@@ -64,7 +40,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _F1_3 [label=< + AUTOGENERATED_F1_4 [label=<
@@ -76,29 +52,53 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _F1_4 [label=< + X1 [label=< + +
- - - - +
Crimp ferrule0.5 mm²OGX1
+ + + + +
Molex 8981female4-pin
+
+ + + + + + + + + + + + + + + + + +
1+12V
2GND
3GND
4+5V
+
> fillcolor="#FFFFFF" shape=box style=filled] edge [color="#000000:#ffff00:#000000"] - _F1_1:e -- W1:w1:w + AUTOGENERATED_F1_1:e -- W1:w1:w W1:w1:e -- X1:p1l:w edge [color="#000000:#000000:#000000"] - _F1_2:e -- W1:w2:w + AUTOGENERATED_F1_2:e -- W1:w2:w W1:w2:e -- X1:p2l:w edge [color="#000000:#000000:#000000"] - _F1_3:e -- W1:w3:w + AUTOGENERATED_F1_3:e -- W1:w3:w W1:w3:e -- X1:p3l:w edge [color="#000000:#ff0000:#000000"] - _F1_4:e -- W1:w4:w + AUTOGENERATED_F1_4:e -- W1:w4:w W1:w4:e -- X1:p4l:w W1 [label=< diff --git a/tutorial/tutorial05.html b/tutorial/tutorial05.html index f1ec2fc48..a906e4a20 100644 --- a/tutorial/tutorial05.html +++ b/tutorial/tutorial05.html @@ -1,51 +1,45 @@ - - - tutorial05 - + + + tutorial05 + +

tutorial05

Diagram

- - + + +
+ + - + -X1 - - -X1 - -Molex 8981 - -female - -4-pin - -1 - -+12V - -2 - -GND - -3 - -GND - -4 - -+5V - - - -_F1_1 +AUTOGENERATED_F1_1 Crimp ferrule @@ -91,16 +85,16 @@

Diagram

  - + -_F1_1:e--W1:w +AUTOGENERATED_F1_1:e--W1:w - - -_F1_2 + + +AUTOGENERATED_F1_2 Crimp ferrule @@ -111,16 +105,16 @@

Diagram

- + -_F1_2:e--W1:w +AUTOGENERATED_F1_2:e--W1:w - - -_F1_3 + + +AUTOGENERATED_F1_3 Crimp ferrule @@ -131,16 +125,16 @@

Diagram

- + -_F1_3:e--W1:w +AUTOGENERATED_F1_3:e--W1:w - - -_F1_4 + + +AUTOGENERATED_F1_4 Crimp ferrule @@ -151,13 +145,42 @@

Diagram

- + -_F1_4:e--W1:w +AUTOGENERATED_F1_4:e--W1:w + + +X1 + + +X1 + +Molex 8981 + +female + +4-pin + +1 + ++12V + +2 + +GND + +3 + +GND + +4 + ++5V + W1:e--X1:w @@ -188,49 +211,61 @@

Diagram

+ +
+ +
+ +
+

Bill of Materials

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Connector, Crimp ferrule, 0.5 mm², OG4
2Connector, Molex 8981, female, 4 pins1X1
3Wire, 0.5 mm², BK0.6mW1
4Wire, 0.5 mm², RD0.3mW1
5Wire, 0.5 mm², YE0.3mW1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Crimp ferrule, 0.5 mm², OG4
2Connector, Molex 8981, female, 4 pins1X1
3Wire, 0.5 mm², BK0.6mW1
4Wire, 0.5 mm², RD0.3mW1
5Wire, 0.5 mm², YE0.3mW1
+ + + diff --git a/tutorial/tutorial05.svg b/tutorial/tutorial05.svg index 7c485c22c..389147b27 100644 --- a/tutorial/tutorial05.svg +++ b/tutorial/tutorial05.svg @@ -1,45 +1,16 @@ - - + -X1 - - -X1 - -Molex 8981 - -female - -4-pin - -1 - -+12V - -2 - -GND - -3 - -GND - -4 - -+5V - - - -_F1_1 +AUTOGENERATED_F1_1 Crimp ferrule @@ -85,16 +56,16 @@   - + -_F1_1:e--W1:w +AUTOGENERATED_F1_1:e--W1:w - - -_F1_2 + + +AUTOGENERATED_F1_2 Crimp ferrule @@ -105,16 +76,16 @@ - + -_F1_2:e--W1:w +AUTOGENERATED_F1_2:e--W1:w - - -_F1_3 + + +AUTOGENERATED_F1_3 Crimp ferrule @@ -125,16 +96,16 @@ - + -_F1_3:e--W1:w +AUTOGENERATED_F1_3:e--W1:w - - -_F1_4 + + +AUTOGENERATED_F1_4 Crimp ferrule @@ -145,13 +116,42 @@ - + -_F1_4:e--W1:w +AUTOGENERATED_F1_4:e--W1:w + + +X1 + + +X1 + +Molex 8981 + +female + +4-pin + +1 + ++12V + +2 + +GND + +3 + +GND + +4 + ++5V + W1:e--X1:w diff --git a/tutorial/tutorial06.gv b/tutorial/tutorial06.gv index 3b0fb8e1f..472fa8b2e 100644 --- a/tutorial/tutorial06.gv +++ b/tutorial/tutorial06.gv @@ -4,43 +4,19 @@ graph { graph [bgcolor="#FFFFFF" fontname=arial nodesep=0.33 rankdir=LR ranksep=2] node [fillcolor="#FFFFFF" fontname=arial height=0 margin=0 shape=none style=filled width=0] edge [fontname=arial style=bold] - X1 [label=< + AUTOGENERATED_F_05_1 [label=< - -
- -
X1
-
- - - - + + + +
Molex 8981female4-pinCrimp ferrule0.5 mm²OG
- - - - - - - - - - - - - - - - - -
1+12V
2GND
3GND
4+5V
-
> fillcolor="#FFFFFF" shape=box style=filled] - F_10 [label=< + F1 [label=<
@@ -52,7 +28,7 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _F_05_1 [label=< + AUTOGENERATED_F_05_2 [label=<
@@ -64,29 +40,53 @@ graph {
> fillcolor="#FFFFFF" shape=box style=filled] - _F_05_2 [label=< + X1 [label=< + +
- - - - +
Crimp ferrule0.5 mm²OGX1
+ + + + +
Molex 8981female4-pin
+
+ + + + + + + + + + + + + + + + + +
1+12V
2GND
3GND
4+5V
+
> fillcolor="#FFFFFF" shape=box style=filled] edge [color="#000000:#ffff00:#000000"] - _F_05_1:e -- W1:w1:w + AUTOGENERATED_F_05_1:e -- W1:w1:w W1:w1:e -- X1:p1l:w edge [color="#000000:#000000:#000000"] - F_10:e -- W1:w2:w + F1:e -- W1:w2:w W1:w2:e -- X1:p2l:w edge [color="#000000:#000000:#000000"] - F_10:e -- W1:w3:w + F1:e -- W1:w3:w W1:w3:e -- X1:p3l:w edge [color="#000000:#ff0000:#000000"] - _F_05_2:e -- W1:w4:w + AUTOGENERATED_F_05_2:e -- W1:w4:w W1:w4:e -- X1:p4l:w W1 [label=< diff --git a/tutorial/tutorial06.html b/tutorial/tutorial06.html index 21df96aa2..8fba71334 100644 --- a/tutorial/tutorial06.html +++ b/tutorial/tutorial06.html @@ -1,60 +1,54 @@ - - - tutorial06 - + + + tutorial06 + +

tutorial06

Diagram

- - + + +
+ + - + -X1 - - -X1 - -Molex 8981 - -female - -4-pin - -1 - -+12V - -2 - -GND - -3 - -GND - -4 - -+5V - - - -F_10 - - -Crimp ferrule - -1.0 mm² - -YE - - +AUTOGENERATED_F_05_1 + + +Crimp ferrule + +0.5 mm² + +OG + + @@ -91,43 +85,43 @@

Diagram

  - + + +AUTOGENERATED_F_05_1:e--W1:w + + + + + + +F1 + + +Crimp ferrule + +1.0 mm² + +YE + + + + -F_10:e--W1:w +F1:e--W1:w - + -F_10:e--W1:w +F1:e--W1:w - + -_F_05_1 - - -Crimp ferrule - -0.5 mm² - -OG - - - - - -_F_05_1:e--W1:w - - - - - - -_F_05_2 +AUTOGENERATED_F_05_2 Crimp ferrule @@ -138,13 +132,42 @@

Diagram

- + -_F_05_2:e--W1:w +AUTOGENERATED_F_05_2:e--W1:w + + +X1 + + +X1 + +Molex 8981 + +female + +4-pin + +1 + ++12V + +2 + +GND + +3 + +GND + +4 + ++5V + W1:e--X1:w @@ -175,56 +198,68 @@

Diagram

+ +
+ +
+ +
+

Bill of Materials

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Connector, Crimp ferrule, 0.5 mm², OG2
2Connector, Crimp ferrule, 1.0 mm², YE1
3Connector, Molex 8981, female, 4 pins1X1
4Wire, 0.5 mm², BK0.6mW1
5Wire, 0.5 mm², RD0.3mW1
6Wire, 0.5 mm², YE0.3mW1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Crimp ferrule, 0.5 mm², OG2
2Connector, Crimp ferrule, 1.0 mm², YE1
3Connector, Molex 8981, female, 4 pins1X1
4Wire, 0.5 mm², BK0.6mW1
5Wire, 0.5 mm², RD0.3mW1
6Wire, 0.5 mm², YE0.3mW1
+ + + diff --git a/tutorial/tutorial06.svg b/tutorial/tutorial06.svg index 4eb4093a4..99f611929 100644 --- a/tutorial/tutorial06.svg +++ b/tutorial/tutorial06.svg @@ -1,54 +1,25 @@ - - + -X1 - - -X1 - -Molex 8981 - -female - -4-pin - -1 - -+12V - -2 - -GND - -3 - -GND - -4 - -+5V - - - -F_10 - - -Crimp ferrule - -1.0 mm² - -YE - - +AUTOGENERATED_F_05_1 + + +Crimp ferrule + +0.5 mm² + +OG + + @@ -85,43 +56,43 @@   - + + +AUTOGENERATED_F_05_1:e--W1:w + + + + + + +F1 + + +Crimp ferrule + +1.0 mm² + +YE + + + + -F_10:e--W1:w +F1:e--W1:w - + -F_10:e--W1:w +F1:e--W1:w - + -_F_05_1 - - -Crimp ferrule - -0.5 mm² - -OG - - - - - -_F_05_1:e--W1:w - - - - - - -_F_05_2 +AUTOGENERATED_F_05_2 Crimp ferrule @@ -132,13 +103,42 @@ - + -_F_05_2:e--W1:w +AUTOGENERATED_F_05_2:e--W1:w + + +X1 + + +X1 + +Molex 8981 + +female + +4-pin + +1 + ++12V + +2 + +GND + +3 + +GND + +4 + ++5V + W1:e--X1:w diff --git a/tutorial/tutorial07.html b/tutorial/tutorial07.html index fd226a10a..181abea82 100644 --- a/tutorial/tutorial07.html +++ b/tutorial/tutorial07.html @@ -1,13 +1,36 @@ - - - tutorial07 - + + + tutorial07 + +

tutorial07

Diagram

- - + + +
+ + Diagram + +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignators
1Connector, Molex KK 254, female, 4 pins6X1, X2, X3, X4, X5, X6
2Wire, 0.25 mm², PK1.0mW1, W2, W3, W4, W5
3Wire, 0.25 mm², TQ1.0mW1, W2, W3, W4, W5
4Wire, 0.25 mm², VT1.0mW1, W2, W3, W4, W5
5Wire, 0.25 mm², YE1.0mW1, W2, W3, W4, W5
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignators
1Connector, Molex KK 254, female, 4 pins6X1, X2, X3, X4, X5, X6
2Wire, 0.25 mm², PK1.0mW1, W2, W3, W4, W5
3Wire, 0.25 mm², TQ1.0mW1, W2, W3, W4, W5
4Wire, 0.25 mm², VT1.0mW1, W2, W3, W4, W5
5Wire, 0.25 mm², YE1.0mW1, W2, W3, W4, W5
+ + + diff --git a/tutorial/tutorial07.svg b/tutorial/tutorial07.svg index 7a18935bd..e5cf4314f 100644 --- a/tutorial/tutorial07.svg +++ b/tutorial/tutorial07.svg @@ -1,7 +1,7 @@ -
- +
1 x Test
P/N: ABC, Molex: 45454, Mousikey: 9999
1 x
P/N: ABC, Molex: 45454, Mousikey: 9999
@@ -106,7 +106,7 @@ graph {
- +
1 x Test
P/N: ABC, Molex: 45454, Mousikey: 9999
1 x
P/N: ABC, Molex: 45454, Mousikey: 9999
@@ -159,7 +159,7 @@ graph {
- +
1 x Test
P/N: ABC, Molex: 45454, Mousikey: 9999
1 x
P/N: ABC, Molex: 45454, Mousikey: 9999
diff --git a/tutorial/tutorial08.html b/tutorial/tutorial08.html index ce4f1aeec..09e560036 100644 --- a/tutorial/tutorial08.html +++ b/tutorial/tutorial08.html @@ -1,13 +1,36 @@ - - - tutorial08 - + + + tutorial08 + +

tutorial08

Diagram

- - + + +
+ + Diagram 4 x Crimp, Molex KK 254, 22-30 AWG Molex: 08500030 -1 x Test +1 x P/N: ABC, Molex: 45454, Mousikey: 9999 @@ -236,7 +259,7 @@

Diagram

4 x Crimp, Molex KK 254, 22-30 AWG Molex: 08500030 -1 x Test +1 x P/N: ABC, Molex: 45454, Mousikey: 9999 @@ -269,7 +292,7 @@

Diagram

4 x Crimp, Molex KK 254, 22-30 AWG Molex: 08500030 -1 x Test +1 x P/N: ABC, Molex: 45454, Mousikey: 9999 @@ -330,139 +353,151 @@

Diagram

+ +
+ +
+ +
+

Bill of Materials

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
IdDescriptionQtyUnitDesignatorsP/NManufacturerMPNSupplierSPN
1Cable, 4 x 0.25 mm²1mW1CAB1CablesCoABC123Cables R Us999-888-777
2Connector, Molex KK 254, female, 4 pins2X1, X3Molex22013047Digimouse1234
3Connector, Molex KK 254, female, 4 pins1X2CON4Molex22013047Digimouse1234
4Crimp, Molex KK 254, 22-30 AWG12X1, X2, X3Molex08500030
5Label, pinout information2X2, X3Label-ID-1BradyB-499
6Sleve, Braided nylon, black, 3mm1mW2SLV-1
7Test3X1, X2, X3ABCMolex45454Mousikey9999
8Wire, 0.25 mm², BK2mW2WIRE2WiresCoW1-BKWireShack1002
9Wire, 0.25 mm², RD1mW2WIRE3WiresCoW1-RDWireShack1009
10Wire, 0.25 mm², YE1mW2WIRE1WiresCoW1-YEWireShack1001
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdDescriptionQtyUnitDesignatorsP/NManufacturerMPNSupplierSPN
13X1, X2, X3ABCMolex45454Mousikey9999
2Cable, 4 x 0.25 mm²1mW1CAB1CablesCoABC123Cables R Us999-888-777
3Connector, Molex KK 254, female, 4 pins2X1, X3Molex22013047Digimouse1234
4Connector, Molex KK 254, female, 4 pins1X2CON4Molex22013047Digimouse1234
5Crimp, Molex KK 254, 22-30 AWG12X1, X2, X3Molex08500030
6Label, pinout information2X2, X3Label-ID-1BradyB-499
7Sleve, Braided nylon, black, 3mm1mW2SLV-1
8Wire, 0.25 mm², BK2mW2WIRE2WiresCoW1-BKWireShack1002
9Wire, 0.25 mm², RD1mW2WIRE3WiresCoW1-RDWireShack1009
10Wire, 0.25 mm², YE1mW2WIRE1WiresCoW1-YEWireShack1001
+ + + diff --git a/tutorial/tutorial08.png b/tutorial/tutorial08.png index d2b10be44..b6cefb930 100644 Binary files a/tutorial/tutorial08.png and b/tutorial/tutorial08.png differ diff --git a/tutorial/tutorial08.svg b/tutorial/tutorial08.svg index a6125f232..391897ae5 100644 --- a/tutorial/tutorial08.svg +++ b/tutorial/tutorial08.svg @@ -1,7 +1,7 @@ - 4 x Crimp, Molex KK 254, 22-30 AWG Molex: 08500030 -1 x Test +1 x P/N: ABC, Molex: 45454, Mousikey: 9999 @@ -230,7 +230,7 @@ 4 x Crimp, Molex KK 254, 22-30 AWG Molex: 08500030 -1 x Test +1 x P/N: ABC, Molex: 45454, Mousikey: 9999 @@ -263,7 +263,7 @@ 4 x Crimp, Molex KK 254, 22-30 AWG Molex: 08500030 -1 x Test +1 x P/N: ABC, Molex: 45454, Mousikey: 9999 diff --git a/tutorial/tutorial08.yml b/tutorial/tutorial08.yml index 1e5e4f20f..7de7e70f7 100644 --- a/tutorial/tutorial08.yml +++ b/tutorial/tutorial08.yml @@ -56,10 +56,9 @@ cables: # add a list of additional components to a part (shown in graph) additional_components: - - type: Sleve # short identifier used in graph + type: Sleeve # short identifier used in graph subtype: Braided nylon, black, 3mm # extra information added to type in bom qty_multiplier: length # multipier for quantity (length of cable) - unit: m pn: SLV-1 @@ -75,7 +74,7 @@ connections: additional_bom_items: - # define an additional item to add to the bill of materials (does not appear in graph) - description: Label, pinout information + type: Label, pinout information qty: 2 designators: - X2