Add verbose flag, WireViz dev dep, and rendered diagram examples
- Gate model/value heuristic output behind --verbose/-v flag (quiet by default) - Add wireviz>=0.4 as dev dependency for roundtrip render tests - Generate SVG/PNG diagram renders for inter_module, single_module, hierarchical - Embed rendered diagrams in README with layout optimization callout
This commit is contained in:
parent
fd822e07ce
commit
95ed08866c
24
README.md
24
README.md
@ -46,6 +46,28 @@ spice2wireviz design.net --bom
|
||||
spice2wireviz top_level.net --bom-wiring
|
||||
```
|
||||
|
||||
## Rendered diagrams
|
||||
|
||||
### Inter-module wiring
|
||||
|
||||
How multiple boards/modules connect to each other — power supply, amplifier, and I/O board with shared power and signal routing:
|
||||
|
||||

|
||||
|
||||
### Single-module interface
|
||||
|
||||
External interface of one subcircuit — the amplifier board's connectors and test points radiating from a central port header:
|
||||
|
||||

|
||||
|
||||
### Hierarchical system
|
||||
|
||||
A complex multi-level system with voltage regulator, main board, sensor module, and external connectors:
|
||||
|
||||

|
||||
|
||||
These diagrams are generated automatically by piping `spice2wireviz` output through WireViz (`--render` flag). The layout optimizer minimizes cable crossings — notice how parallel wires between the same module pair are bundled into multi-wire cables.
|
||||
|
||||
## Output examples
|
||||
|
||||
### Input: SPICE netlist
|
||||
@ -324,7 +346,7 @@ The parser extracts subcircuit definitions (`.subckt`), instances (`X*`), and bo
|
||||
|
||||
```bash
|
||||
uv sync --extra dev --extra asc
|
||||
uv run pytest # 188 tests
|
||||
uv run pytest # 194 tests
|
||||
uv run ruff check src/ tests/
|
||||
```
|
||||
|
||||
|
||||
160
docs/examples/hierarchical.yml
Normal file
160
docs/examples/hierarchical.yml
Normal file
@ -0,0 +1,160 @@
|
||||
metadata:
|
||||
title: 'Wiring diagram: hierarchical'
|
||||
source: tests/fixtures/hierarchical.net
|
||||
generator: spice2wireviz 2026.2.14
|
||||
connectors:
|
||||
J_PWR:
|
||||
type: BARREL_JACK
|
||||
pinlabels:
|
||||
- VIN_RAW
|
||||
- GND
|
||||
notes: 'SPICE ref: J_PWR'
|
||||
TP_3V3:
|
||||
type: TP
|
||||
style: simple
|
||||
pinlabels:
|
||||
- V3V3
|
||||
notes: 'SPICE ref: TP_3V3'
|
||||
J_USB:
|
||||
type: USB_B_CONN
|
||||
pinlabels:
|
||||
- GND
|
||||
- USB_DP
|
||||
- USB_DM
|
||||
notes: 'SPICE ref: J_USB'
|
||||
X_REG:
|
||||
type: regulator
|
||||
pinlabels:
|
||||
- VIN
|
||||
- GND
|
||||
- VOUT
|
||||
notes: 'SPICE instance: X_REG (regulator)'
|
||||
X_MAIN:
|
||||
type: main_board
|
||||
pinlabels:
|
||||
- USB_D+
|
||||
- USB_D-
|
||||
- VCC
|
||||
- GND
|
||||
- SDA
|
||||
- SCL
|
||||
- ALERT
|
||||
notes: 'SPICE instance: X_MAIN (main_board)'
|
||||
X_SENSOR:
|
||||
type: sensor_module
|
||||
pinlabels:
|
||||
- VCC
|
||||
- GND
|
||||
- SDA
|
||||
- SCL
|
||||
- ALERT
|
||||
notes: 'SPICE instance: X_SENSOR (sensor_module)'
|
||||
cables:
|
||||
W1:
|
||||
category: bundle
|
||||
colors:
|
||||
- BK
|
||||
- ''
|
||||
wirelabels:
|
||||
- GND
|
||||
- VIN_RAW
|
||||
notes: 'Nets: GND, VIN_RAW'
|
||||
W2:
|
||||
category: bundle
|
||||
wirecount: 2
|
||||
wirelabels:
|
||||
- USB_DM
|
||||
- USB_DP
|
||||
notes: 'Nets: USB_DM, USB_DP'
|
||||
W3:
|
||||
colors:
|
||||
- BK
|
||||
wirelabels:
|
||||
- GND
|
||||
notes: 'Net: GND'
|
||||
W5:
|
||||
wirecount: 1
|
||||
wirelabels:
|
||||
- V3V3
|
||||
notes: 'Net: V3V3'
|
||||
W7:
|
||||
category: bundle
|
||||
colors:
|
||||
- BK
|
||||
- ''
|
||||
wirelabels:
|
||||
- GND
|
||||
- V3V3
|
||||
notes: 'Nets: GND, V3V3'
|
||||
W8:
|
||||
category: bundle
|
||||
wirecount: 3
|
||||
wirelabels:
|
||||
- ALERT_SIG
|
||||
- I2C_SCL
|
||||
- I2C_SDA
|
||||
notes: 'Nets: ALERT_SIG, I2C_SCL, I2C_SDA'
|
||||
W9:
|
||||
category: bundle
|
||||
colors:
|
||||
- BK
|
||||
- ''
|
||||
wirelabels:
|
||||
- GND
|
||||
- V3V3
|
||||
notes: 'Nets: GND, V3V3'
|
||||
connections:
|
||||
- - J_USB: 1
|
||||
- W3: 1
|
||||
- X_REG: 2
|
||||
- - X_REG:
|
||||
- 2
|
||||
- 3
|
||||
- W7:
|
||||
- 1
|
||||
- 2
|
||||
- X_MAIN:
|
||||
- 4
|
||||
- 3
|
||||
- - X_MAIN:
|
||||
- 7
|
||||
- 6
|
||||
- 5
|
||||
- W8:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- X_SENSOR:
|
||||
- 5
|
||||
- 4
|
||||
- 3
|
||||
- - TP_3V3: 1
|
||||
- W5: 1
|
||||
- X_REG: 3
|
||||
- - J_USB:
|
||||
- 3
|
||||
- 2
|
||||
- W2:
|
||||
- 1
|
||||
- 2
|
||||
- X_MAIN:
|
||||
- 2
|
||||
- 1
|
||||
- - X_REG:
|
||||
- 2
|
||||
- 3
|
||||
- W9:
|
||||
- 1
|
||||
- 2
|
||||
- X_SENSOR:
|
||||
- 2
|
||||
- 1
|
||||
- - J_PWR:
|
||||
- 2
|
||||
- 1
|
||||
- W1:
|
||||
- 1
|
||||
- 2
|
||||
- X_REG:
|
||||
- 2
|
||||
- 1
|
||||
93
docs/examples/inter_module.yml
Normal file
93
docs/examples/inter_module.yml
Normal file
@ -0,0 +1,93 @@
|
||||
metadata:
|
||||
title: 'Wiring diagram: multi_board'
|
||||
source: tests/fixtures/multi_board.net
|
||||
generator: spice2wireviz 2026.2.14
|
||||
connectors:
|
||||
J_CHASSIS:
|
||||
type: chassis_gnd
|
||||
pinlabels:
|
||||
- EARTH
|
||||
- GND
|
||||
notes: 'SPICE ref: J_CHASSIS'
|
||||
TP_VCC:
|
||||
type: TP
|
||||
style: simple
|
||||
pinlabels:
|
||||
- VCC
|
||||
notes: 'SPICE ref: TP_VCC'
|
||||
X1:
|
||||
type: power_supply
|
||||
pinlabels:
|
||||
- VCC
|
||||
- GND
|
||||
notes: 'SPICE instance: X1 (power_supply)'
|
||||
X2:
|
||||
type: amplifier
|
||||
pinlabels:
|
||||
- VIN
|
||||
- GND
|
||||
- VOUT
|
||||
notes: 'SPICE instance: X2 (amplifier)'
|
||||
X3:
|
||||
type: io_board
|
||||
pinlabels:
|
||||
- GND
|
||||
- SIG_IN
|
||||
- SIG_OUT
|
||||
- CTRL
|
||||
notes: 'SPICE instance: X3 (io_board)'
|
||||
cables:
|
||||
W1:
|
||||
colors:
|
||||
- BK
|
||||
wirelabels:
|
||||
- GND
|
||||
notes: 'Net: GND'
|
||||
W3:
|
||||
colors:
|
||||
- RD
|
||||
wirelabels:
|
||||
- VCC
|
||||
notes: 'Net: VCC'
|
||||
W5:
|
||||
category: bundle
|
||||
colors:
|
||||
- BK
|
||||
- RD
|
||||
wirelabels:
|
||||
- GND
|
||||
- VCC
|
||||
notes: 'Nets: GND, VCC'
|
||||
W6:
|
||||
colors:
|
||||
- BK
|
||||
wirelabels:
|
||||
- GND
|
||||
notes: 'Net: GND'
|
||||
W8:
|
||||
wirecount: 1
|
||||
wirelabels:
|
||||
- AUDIO_OUT
|
||||
notes: 'Net: AUDIO_OUT'
|
||||
connections:
|
||||
- - TP_VCC: 1
|
||||
- W3: 1
|
||||
- X1: 1
|
||||
- - X1:
|
||||
- 2
|
||||
- 1
|
||||
- W5:
|
||||
- 1
|
||||
- 2
|
||||
- X2:
|
||||
- 2
|
||||
- 1
|
||||
- - X2: 3
|
||||
- W8: 1
|
||||
- X3: 2
|
||||
- - J_CHASSIS: 2
|
||||
- W1: 1
|
||||
- X1: 2
|
||||
- - X1: 2
|
||||
- W6: 1
|
||||
- X3: 1
|
||||
67
docs/examples/single_module.yml
Normal file
67
docs/examples/single_module.yml
Normal file
@ -0,0 +1,67 @@
|
||||
metadata:
|
||||
title: 'Wiring diagram: simple_board'
|
||||
source: tests/fixtures/simple_board.net
|
||||
generator: spice2wireviz 2026.2.14
|
||||
connectors:
|
||||
amplifier_board:
|
||||
type: Module Interface
|
||||
pinlabels:
|
||||
- VIN
|
||||
- GND
|
||||
- VOUT
|
||||
- SIGNAL_IN
|
||||
notes: 'SPICE subcircuit: .subckt amplifier_board'
|
||||
J1:
|
||||
type: PWR_CONN
|
||||
pinlabels:
|
||||
- VIN
|
||||
- GND
|
||||
notes: 'SPICE ref: J1, nets: VIN, GND'
|
||||
J2:
|
||||
type: SIG_CONN
|
||||
pinlabels:
|
||||
- VOUT
|
||||
- SIGNAL_IN
|
||||
notes: 'SPICE ref: J2, nets: SIGNAL_IN, VOUT'
|
||||
TP1:
|
||||
type: TP
|
||||
style: simple
|
||||
pinlabels:
|
||||
- N001
|
||||
notes: 'SPICE ref: TP1, nets: N001'
|
||||
cables:
|
||||
W_J1:
|
||||
category: bundle
|
||||
colors:
|
||||
- ''
|
||||
- BK
|
||||
wirelabels:
|
||||
- VIN
|
||||
- GND
|
||||
notes: 'Nets: VIN, GND'
|
||||
W_J2:
|
||||
category: bundle
|
||||
wirecount: 2
|
||||
wirelabels:
|
||||
- VOUT
|
||||
- SIGNAL_IN
|
||||
notes: 'Nets: VOUT, SIGNAL_IN'
|
||||
connections:
|
||||
- - amplifier_board:
|
||||
- 1
|
||||
- 2
|
||||
- W_J1:
|
||||
- 1
|
||||
- 2
|
||||
- J1:
|
||||
- 1
|
||||
- 2
|
||||
- - amplifier_board:
|
||||
- 3
|
||||
- 4
|
||||
- W_J2:
|
||||
- 1
|
||||
- 2
|
||||
- J2:
|
||||
- 1
|
||||
- 2
|
||||
@ -35,7 +35,7 @@ Issues = "https://git.supported.systems/warehack.ing/spice2wireviz/issues"
|
||||
|
||||
[project.optional-dependencies]
|
||||
asc = ["spicelib>=1.4.9"]
|
||||
dev = ["ruff", "pytest", "pytest-cov"]
|
||||
dev = ["ruff", "pytest", "pytest-cov", "wireviz>=0.4"]
|
||||
|
||||
[project.scripts]
|
||||
spice2wireviz = "spice2wireviz.cli:main"
|
||||
|
||||
@ -146,6 +146,12 @@ def _parse_comma_list(ctx: click.Context, param: click.Parameter, value: str | N
|
||||
is_flag=True,
|
||||
help="Emit wiring BOM as CSV instead of WireViz YAML (requires full mapping).",
|
||||
)
|
||||
@click.option(
|
||||
"-v",
|
||||
"--verbose",
|
||||
is_flag=True,
|
||||
help="Show detailed parser notes (model/value heuristic decisions, etc.).",
|
||||
)
|
||||
@click.version_option(version=__version__)
|
||||
def main(
|
||||
input_file: Path,
|
||||
@ -172,8 +178,14 @@ def main(
|
||||
ltspice_exe: Path | None,
|
||||
bom: bool,
|
||||
bom_wiring: bool,
|
||||
verbose: bool,
|
||||
) -> None:
|
||||
"""Convert SPICE netlist to WireViz YAML wiring diagram."""
|
||||
# --- Set parser verbosity ---
|
||||
from .parser import netlist as netlist_module
|
||||
|
||||
netlist_module.verbose = verbose
|
||||
|
||||
# --- Validate mutually exclusive BOM flags ---
|
||||
if bom and bom_wiring:
|
||||
click.echo("Error: --bom and --bom-wiring are mutually exclusive.", err=True)
|
||||
|
||||
@ -48,6 +48,11 @@ _KNOWN_NET_PATTERN = re.compile(
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Module-level verbosity flag — controls whether informational notes
|
||||
# (like model/value heuristic decisions) are printed to stderr.
|
||||
# Set by the CLI's --verbose flag; defaults to False (quiet).
|
||||
verbose = False
|
||||
|
||||
|
||||
def _strip_comment(line: str) -> str:
|
||||
"""Remove inline ; comments, respecting that ; inside quotes is literal."""
|
||||
@ -366,12 +371,13 @@ def _parse_boundary_component(tokens: list[str], prefix: str) -> SpiceComponent
|
||||
):
|
||||
value = last
|
||||
nodes = positional[:-1]
|
||||
print(
|
||||
f"Note: treating '{last}' as model/value for {ref} "
|
||||
f"(not as net name). If this is wrong, the net "
|
||||
f"connection to '{last}' will be missing.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if verbose:
|
||||
print(
|
||||
f"Note: treating '{last}' as model/value for {ref} "
|
||||
f"(not as net name). If this is wrong, the net "
|
||||
f"connection to '{last}' will be missing.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
pins = [
|
||||
SpicePin(
|
||||
|
||||
@ -209,16 +209,41 @@ X1 NET1 NET2 NET3 NET4 small
|
||||
assert "extra nodes" in captured.err
|
||||
|
||||
def test_model_value_heuristic_warning(self, capsys):
|
||||
"""I6: The model/value heuristic should warn when it triggers."""
|
||||
text = """\
|
||||
"""I6: The model/value heuristic should warn when verbose is on."""
|
||||
from spice2wireviz.parser import netlist as netlist_module
|
||||
|
||||
original = netlist_module.verbose
|
||||
try:
|
||||
netlist_module.verbose = True
|
||||
text = """\
|
||||
.subckt test A B
|
||||
J1 A B SOME_MODEL
|
||||
.ends test
|
||||
"""
|
||||
parse_netlist(text)
|
||||
captured = capsys.readouterr()
|
||||
assert "SOME_MODEL" in captured.err
|
||||
assert "model/value" in captured.err
|
||||
parse_netlist(text)
|
||||
captured = capsys.readouterr()
|
||||
assert "SOME_MODEL" in captured.err
|
||||
assert "model/value" in captured.err
|
||||
finally:
|
||||
netlist_module.verbose = original
|
||||
|
||||
def test_model_value_heuristic_quiet_by_default(self, capsys):
|
||||
"""The model/value heuristic should be silent when verbose is off."""
|
||||
from spice2wireviz.parser import netlist as netlist_module
|
||||
|
||||
original = netlist_module.verbose
|
||||
try:
|
||||
netlist_module.verbose = False
|
||||
text = """\
|
||||
.subckt test A B
|
||||
J1 A B SOME_MODEL
|
||||
.ends test
|
||||
"""
|
||||
parse_netlist(text)
|
||||
captured = capsys.readouterr()
|
||||
assert "model/value" not in captured.err
|
||||
finally:
|
||||
netlist_module.verbose = original
|
||||
|
||||
def test_duplicate_refs_both_parsed(self):
|
||||
"""S3: Duplicate reference designators should both be parsed."""
|
||||
|
||||
26
uv.lock
generated
26
uv.lock
generated
@ -285,6 +285,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphviz"
|
||||
version = "0.21"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
@ -1010,6 +1019,7 @@ dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "ruff" },
|
||||
{ name = "wireviz" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@ -1021,6 +1031,7 @@ requires-dist = [
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'" },
|
||||
{ name = "spicelib", marker = "extra == 'asc'", specifier = ">=1.4.9" },
|
||||
{ name = "wireviz", marker = "extra == 'dev'", specifier = ">=0.4" },
|
||||
]
|
||||
provides-extras = ["asc", "dev"]
|
||||
|
||||
@ -1114,3 +1125,18 @@ sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wireviz"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "graphviz" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/31/26e518535ae54f540dae0e83e583240d4bae9a883a6213d751b7b0c00304/wireviz-0.4.1.tar.gz", hash = "sha256:0e25ad8c2e3a269a7dd4a7f45e4e8d304db7e0432b1843a620acfa6be70570e1", size = 50281, upload-time = "2024-07-13T11:29:30.319Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/7a/5fc147e36159bdf7ff70cdd239e48512384ce32708ef8410df8fd62b4cf8/wireviz-0.4.1-py3-none-any.whl", hash = "sha256:5dfdfec91e5d26e4f27f0f9d031672da7fb3c133d127167d233f4742f01873a2", size = 52083, upload-time = "2024-07-13T11:29:28.972Z" },
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user