Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ jobs:
- name: Install package + test dependencies
run: |
python -m pip install --upgrade pip
pip install .
sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
pip install ".[pdf]"
pip install pytest
- name: Run pytest
run: pytest -v
# WeasyPrint 70 needs Python 3.10+: there, the sheet PDF tests must
# run, not skip (a library missing on the runner fails them).
env:
WIREVIZ_REQUIRE_PDF: ${{ matrix.python-version != '3.9' && '1' || '' }}
run: pytest -v -rs
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ The pipeline is **YAML → Harness object graph → GraphViz `.gv` → rendered
- **`svgembed.py`** — inlines referenced raster images into the SVG so the SVG/HTML output is self-contained.
- **`wv_bom.py`** — BOM aggregation/dedup logic (`mini_bom_mode`, part-number handling, additional components). Reused for both the standalone `.bom.tsv` and the BOM table embedded in HTML.
- **`wv_colors.py`** — IEC 60757 color codes, color-scheme generators (DIN 47100, 25-pair, TIA/EIA 568), `ColorMode` (SHORT / FULL / HEX, upper/lower).
- **`wv_sheet.py`** — `sheet` output (`-f D`): the HTML page rendered to PDF with WeasyPrint (optional extra `wireviz[pdf]`, needs Pango). Its `url_fetcher` refuses every URL; everything in the HTML is inline. Page size comes from the template's named `@page` rules (`din-6771.html`: `.page_A3 { page: A3 }` etc.).
- **`wv_include.py`** — top-level `include:` (upstream #220): dict-level merge of `connectors`/`cables`/`additional_bom_items` from other files; main file wins, a duplicate between includes is an error; relative image paths are made absolute per included file. `parse()` refuses `include` when `untrusted=True` (it reads files).
- **`wv_images.py`** — turns `data:image/...;base64` URIs and `.webp` files into PNG files in the harness's private temp dir (`Harness.temp_dir()`, removed when the Harness is garbage-collected). Graphviz needs a file in a format its build can read.
- **`wv_helper.py`** — shared utilities: `awg_equiv`/`mm2_equiv` gauge conversion, `expand` (range syntax), `tuplelist2tsv`, `smart_file_resolve` (image-path resolution against the input dir + `--prepend` dirs).

Expand Down
13 changes: 12 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Change Log

## [Unreleased]
## [1.1.0] (2026-10-02)

Fixes for open issues in the original [wireviz/WireViz](https://github.com/wireviz/WireViz) repository. Triage: [docs/plans/2026-10-02-upstream-issue-triage.md](plans/2026-10-02-upstream-issue-triage.md).

Expand All @@ -23,8 +23,19 @@ Fixes for open issues in the original [wireviz/WireViz](https://github.com/wirev
- `image: file.png` works as a short form of `image: {src: file.png}` ([#292](https://github.com/wireviz/WireViz/issues/292)).
- Loops accept pin labels ([#432](https://github.com/wireviz/WireViz/issues/432)); loops on non-sequential pin numbers have a regression test ([#465](https://github.com/wireviz/WireViz/issues/465)).

### Behavior changes

- A cable named alone in a connection set (`- B1`) now uses wires 1 to n instead of wire 1 n times ([#508](https://github.com/wireviz/WireViz/issues/508)). Autogenerated cables (`- W.`) are unchanged.
- Errors in the input name the connection set (`connection set 2 (X1 → W1 → X2): ...`) and the CLI prints them as one line with exit code 1; `--debug` shows the traceback ([#505](https://github.com/wireviz/WireViz/issues/505), [#207](https://github.com/wireviz/WireViz/issues/207)). Library callers can catch `wireviz.wv_errors.WireVizError` (a `ValueError`).

### New features

- Print-ready sheet PDF: `-f D` / `output_formats="sheet"` writes `<name>.sheet.pdf`, the HTML page (frame, diagram, BOM, title block) on one page at the template's sheet size. Needs `pip install "wireviz[pdf]"` (WeasyPrint 70 or later, Python 3.10 or later); WeasyPrint may load only the inline `data:` images, never a file or URL, and in untrusted mode it runs in a child process with the render timeout. The `din-6771` template no longer lets the diagram overlap the BOM and title block, prints at the right page size, and defaults to A4; the simple template fits the diagram to the page when printed. New `<!-- %date% -->` placeholder ([#32](https://github.com/wireviz/WireViz/issues/32), [#304](https://github.com/wireviz/WireViz/issues/304)).
- Connector `shorts: [[1, 2, 3], {YE: [N, AUX]}]` shows internal shorts and jumpers as a bar in the pin table; shorted pins count as populated ([#350](https://github.com/wireviz/WireViz/issues/350)).
- Cable `twisted: [[RD, BK], {wires: [3, 4], rate: 20/m}]` shows twisted pairs, triads and groups as framed groups in the cable box ([#3](https://github.com/wireviz/WireViz/issues/3), [#353](https://github.com/wireviz/WireViz/issues/353)).
- `include:` merges shared connector/cable libraries from other files; `-I/--include-path` adds search directories ([#220](https://github.com/wireviz/WireViz/issues/220)). Not allowed in untrusted mode.
- A cable with no `wirecount` or `colors` takes its wire count from the wire numbers used in the connections ([#508](https://github.com/wireviz/WireViz/issues/508)).

- CSV BOM output: `-f c` / `output_formats="csv"` writes `<name>.bom.csv` ([#98](https://github.com/wireviz/WireViz/issues/98)).
- Loop colors: `loops: [{RD: [VCC, SENSE]}]` ([#457](https://github.com/wireviz/WireViz/issues/457)).
- `options.show_title: true` draws `metadata.title` above the diagram in PNG, SVG and PDF ([#460](https://github.com/wireviz/WireViz/issues/460)).
Expand Down
10 changes: 10 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ Wildcards in the file path are also supported to process multiple files at once,
$ wireviz ~/path/to/files/*.yml
```

### Print-ready PDF sheet

`wireviz -f D harness.yml` writes `harness.sheet.pdf`: the HTML page (diagram, BOM and, with the `din-6771` template, frame and title block) as a one-page PDF at the template's sheet size (`metadata.template.sheetsize`: A4, A3 or A2). It needs Python 3.10 or later and WeasyPrint:

```
$ pip install "wireviz[pdf]"
```

WeasyPrint also needs the Pango library (`apt install libpango-1.0-0 libpangoft2-1.0-0` on Debian/Ubuntu, `brew install pango` on macOS; if Python then cannot find it on macOS, set `DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib`). `-f P` still writes the diagram alone as PDF, without this dependency.

To see how to specify the output formats, as well as additional options, run:

```
Expand Down
137 changes: 137 additions & 0 deletions docs/plans/2026-10-02-batch-c-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Batch C — larger upstream features (design)

Date: 2026-10-02. Branch: `fix/upstream-batch-c`. Issues from
[the upstream triage](2026-10-02-upstream-issue-triage.md). Decisions marked
**Decided** were confirmed by Cole on 2026-10-02.

Upstream `dev` implements none of these. Open upstream PRs (#382, #455,
#506) are on `dev` or unreviewed, so only their ideas are used.

Implementation order: C1 (#505, #508), C2 (#220), C3 (#3/#353), C4
(#350), C5 (#32/#304). Each is one commit with tests in
`tests/test_upstream_issues.py`.

## C1a. Error messages (#505, #207)

Stage 1 only.

- New `wireviz.wv_errors.WireVizError(ValueError)`.
- `parse()` wraps the processing of each connection set. An error is
re-raised with the prefix `connection set N (X1 → W1 → X2): `, and keeps
its class when it is a `ValueError` or `TypeError`; any other exception
becomes `WireVizError`. `raise ... from exc` keeps the cause.
- The `style: simple` error names the connector.
- CLI: `WireVizError`, `ValueError`, `TypeError`, `yaml.YAMLError` and
`FileNotFoundError` from `parse()` print one line through
`click.ClickException` (exit 1). New `--debug` flag re-raises for a
traceback.
- Not now: YAML line numbers (stage 2, needs mark tracking in the loader).

## C1b. Infer the wire count (#508)

**Decided:** a bare named cable means wires 1..n.

- A pre-pass in `parse()` over all connection sets, before components
are created. For each cable designator with no `wirecount` and no
`colors`, the implied wire count is the largest of: the integer wire
numbers used for it, and the connection count of any set where it
appears as a bare name.
- A bare named cable (`- B1`, no template separator) in a set with n
parallel connections means wires 1..n (before: wire 1, n times). An
autogenerated cable (`- W.`) keeps its meaning: one new instance per
connection.
- Color or label references cannot imply a count: the existing error
stays, with a better message.
- CHANGELOG: list the bare-name change under "Behavior changes".

## C2. Include files (#220)

**Decided:** top-level `include:` list, merged at dict level.

```yaml
include:
- lib/connectors.yml
- lib/cables.yml
connectors: ...
```

- Paths resolve against the including file's directory (or `source_path`
for str/dict input), then against `include_paths` (new `parse()`
argument, CLI `-I/--include-path`, repeatable).
- Recursive. A cycle or a depth over 16 is an error naming the files.
- Merge `connectors`, `cables` and `additional_bom_items` (list append)
from includes into the main data. The main file wins on a duplicate
key. The same key in two included files is an error naming both
files. `metadata`, `options`, `tweak` and `connections` come from the
main file only; in an included file they are an error (say so, do not
drop them silently).
- `image.src` of an included component is made absolute against the
included file's directory before merging.
- YAML anchors and `<<:` do not cross files: each file is parsed on its
own. Documented.
- `untrusted=True` refuses `include` (it reads files).
- PNG embedding stores the merged YAML, so a PNG is self-contained.
- `--prepend` stays as it is, and is now documented in syntax.md.

## C3. Twisted pairs (#3, #353)

Syntax as kvid proposed in #353:

```yaml
cables:
W1:
colors: [RD, BK, WH, BU]
twisted: [[RD, BK], [WH, BU]] # or wire numbers / wire labels
# optional rate: twisted: [{wires: [1, 2], rate: 20/m}]
```

- Each group is 2 or more wires, referenced by number, color or wire
label (same resolution as connections). A wire in two groups, a group
under 2 wires, or an unknown wire is an error.
- Rendering in the wire table: the rows of a group are made contiguous
(the group starts at the row of its first wire) and wrapped in a
nested table with a thin **solid** border and a caption row
"Twisted pair" / "Twisted triad" / "Twisted group", plus `: <rate>`.
Solid, because a dashed border means a shield (IEC) and bundles
already use a dashed box. Ports keep the original wire number
(`w{i}`), so edges do not change.
- No drawn twist (split splines are a Graphviz limitation; out of scope).
- The grouping helper is generic, so shielded cores (#330) can reuse it.

## C4. Jumpers / internal shorts (#350)

**Decided:** `shorts:`, shorted pins count as populated.

```yaml
connectors:
TB1:
pincount: 6
shorts: [[1, 2, 3], {RD: [5, 6]}] # pin numbers or labels
```

- Each short is 2 or more pins, by number or label. Optional color as a
one-key mapping, like loops.
- Drawn in the pin table: one narrow column per short, a filled cell at
each member pin and a solid bar between the first and last member.
Plain HTML table cells; no gvpr/neato.
- Shorted pins are activated (visible with `hide_disconnected_pins`) and
count as `populated`.
- No automatic BOM entry; users add `additional_components`.
- `loops` keep their meaning (external wire loops).

## C5. Print-ready sheet PDF (#32, #304)

**Decided:** WeasyPrint as an optional extra, new format code.

1. Template fixes (`din-6771.html`): named CSS pages per sheet size, so
a browser and WeasyPrint print one page at the right size; the
diagram area scales the SVG to fit above the BOM and title block;
`%date%` placeholder.
2. New output format `sheet` (CLI `-f D`, file `<name>.sheet.pdf`): the
HTML output rendered to PDF with WeasyPrint. `pip install
"wireviz[pdf]"` adds it; without it a clear error says how to
install it. WeasyPrint gets a `url_fetcher` that refuses every URL
(all images are already inlined), so it reads no files or network.
`-f P` keeps its meaning (Graphviz diagram-only PDF).
3. Later (not this batch): BOM headings in `options.terminology`, page
orientation, BOM auto-scaling.
37 changes: 37 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## Main sections

```yaml
include: # optional list of files with shared definitions (see below)

connectors: # dictionary of all used connectors
<str> : # unique connector designator/name
... # connector attributes (see below)
Expand Down Expand Up @@ -89,6 +91,12 @@ tweak: # optional tweaking of .gv output
# pins may be given by number or by pin label, e.g. [VCC, SENSE];
# give a loop a wire color with a one-key mapping: {RD: [VCC, SENSE]}

# internal shorts / jumpers, e.g. on terminal blocks: each a list of 2+ pins
# (numbers or labels); optional color as a one-key mapping: {RD: [5, 6]}.
# Shorted pins count as populated and are always shown. At most 64 shorts
# per connector; not shown for style: simple.
shorts: <List>

# stripping lengths at this connector (shown in the diagram)
strip:
sleeve: <int/float/str> # numbers are taken as mm, e.g. 10 or '0.4 in'
Expand Down Expand Up @@ -164,6 +172,9 @@ tweak: # optional tweaking of .gv output
show_name: <bool> # defaults to true
show_wirecount: <bool> # defaults to true
show_wirenumbers: <bool> # defaults to true for cables; false for bundles
twisted: <List> # twisted groups, each a list of 2+ wires by number, color
# or wire label, e.g. [[RD, BK], [3, 4]]; a group may be
# {wires: [...], rate: 20/m}; shown framed in the cable box
show_box: <bool> # defaults to true; false hides the cable box and draws
# each wire straight from connector to connector
# (every wire then needs a connector at both ends)
Expand Down Expand Up @@ -266,6 +277,10 @@ connections:
- `<int>-<int>` auto-expands to a range.
- `<str>` to refer to a wire's label or color, if unambiguous.

- `<designator>` alone (no wire list) uses wires 1 to n, where n is the number of parallel connections in the set. An autogenerated cable (`<template>.`) instead creates one new instance per connection.

If a cable has neither `wirecount` nor `colors`, its wire count is taken from the highest wire number used for it in any connection set (or from n for a cable named alone). Wires referenced only by color or label cannot set the count.

### Arrows

Arrows may be used in place of wires to join two connectors. This can represent the mating of matching connectors.
Expand Down Expand Up @@ -364,6 +379,28 @@ connections:
If any component is defined in the `connectors` or `cables` sections but not referenced in `connections`, a warning is printed in the console.


## Include files

A harness file can pull shared connector and cable definitions from other files:

```yaml
include: # a file name or a list of file names
- lib/connectors.yml
- lib/cables.yml
```

- Paths are relative to the including file (for stdin or string input without a source path: to the working directory). The CLI option `-I/--include-path <dir>` (or `parse(include_paths=...)`) adds directories to search after that.
- `connectors` and `cables` are merged by name. A definition in the including file wins over the same name in an included file. The same name in two included files is an error that names both files.
- `additional_bom_items` from included files are added.
- `metadata`, `options`, `tweak` and `connections` are allowed only in the main file.
- Included files may include other files. A loop of includes is an error. A file reached through two includes is merged once. If one included file overrides a name from a shared file that another include also uses, that is a conflict: move the override to the main file.
- A relative `image: src:` in an included file is relative to that file.
- Each file is read on its own, so YAML anchors (`&name`, `*name`, `<<:`) do not work across files.
- A PNG output embeds the merged YAML, so it does not depend on the library files.

The older CLI option `-p/--prepend <file>` still works: it puts the text of the file in front of the main file, so anchors defined there can be used in the main file. Prefer `include:`: with `--prepend`, two files that both have a `connectors:` section silently lose the first one.


## Metadata entries

```yaml
Expand Down
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
"pillow>=10.3", # CVE-2023-4863, CVE-2023-44271, CVE-2024-28219
"graphviz>=0.20",
],
extras_require={
# print-ready sheet PDF (-f D); also needs the Pango system library
# (WeasyPrint 70 needs Python 3.10 or later)
"pdf": ["weasyprint>=70; python_version >= '3.10'"],
},
license="GPLv3",
keywords="cable connector hardware harness wiring wiring-diagram wiring-harness",
url=APP_URL,
Expand Down
Loading
Loading