Schematic Rendering¶
Render any Pulsim circuit to a publication-quality schematic SVG or PNG with one Python call. The default backend produces real analog symbols (resistor zig-zag, capacitor parallel lines, MOSFET vertical body, ground rails, etc.) — not labeled boxes — and routes wires orthogonally.
Quick start¶
import pulsim as ps
parser = ps.YamlParser(ps.YamlParserOptions())
circuit, _ = parser.load("examples/buck_converter.yaml")
# SVG — vector, scales to any resolution
ps.schematic.render(circuit, "buck.svg")
# PNG — handy for embedding in docs / chat
ps.schematic.render(circuit, "buck.png")
That's it. No layout configuration required for the common case.
What gets rendered¶
Every device introspected via Circuit.components() becomes a symbol on the canvas:
| Pulsim kind | Symbol |
|---|---|
resistor |
IEC rectangle (zig-zag with the alternative skin) |
capacitor |
Two parallel lines |
inductor |
Coiled spiral |
voltage_source / pwm_voltage_source / sine_voltage_source / pulse_voltage_source |
Circle with ±, sine, pulse, or DC indicator |
current_source |
Circle with arrow |
diode |
Triangle + cathode bar |
mosfet |
N-channel MOSFET (vertical channel, gate left, body-diode arrow) |
igbt |
MOSFET body + collector triangle |
vcswitch |
Open contacts + tilted switch arm + dashed control input |
transformer |
Two coupled coils |
| anything else | Labeled rectangle (graceful fallback) |
Each terminal connecting to ground gets its own gnd symbol — standard schematic convention.
Install¶
The default backend needs Node.js + netlistsvg (the production renderer used by the Yosys / openlane ecosystem, with a Pulsim-extended analog skin). PNG output additionally requires librsvg or cairosvg for the SVG→PNG conversion.
# Repo root
npm install netlistsvg elkjs
# macOS — for PNG output
brew install librsvg
# Debian/Ubuntu — for PNG output
sudo apt-get install librsvg2-bin
# Python side: nothing extra; pulsim.schematic is part of the core wheel.
If pulsim.schematic.render(...) is called without Node installed, an ImportError fires with the exact install command.
Backends and how to switch¶
PULSIM_SCHEMATIC_BACKEND selects the rendering backend:
| Value | When to use | Output |
|---|---|---|
netlistsvg (default) |
Default — publication quality | SVG (PNG/PDF/JPG via rsvg-convert) |
elk |
Need a Python SchematicLayout object (e.g. for GUI auto-place) |
SVG/PNG via schemdraw + ELK Sugiyama layered layout |
spring |
No Node.js available, or comparing against the legacy layout | SVG/PNG via schemdraw + force-directed |
PULSIM_SCHEMATIC_BACKEND=elk python my_script.py
GUI auto-placement¶
For a GUI that wants to read component coordinates and draw the canvas itself, use the ELK backend's SchematicLayout:
import os
os.environ["PULSIM_SCHEMATIC_BACKEND"] = "elk"
import pulsim as ps
parser = ps.YamlParser(ps.YamlParserOptions())
circuit, _ = parser.load("examples/buck_converter.yaml")
layout = ps.schematic.compute_layout(circuit)
payload = layout.to_json()
# payload is a dict with keys: components, wires, junctions, canvas, schema_version
# Coordinates in mm, origin top-left, +x right, +y down.
payload["components"] is a dict keyed by component name with {x, y, rotation, terminal_anchors, ...}. payload["wires"] is a list of {from, to, path}. The schema version is "schematic-v1" and stays stable for GUI consumers.
Custom output paths¶
Both SVG and raster formats infer from the path extension. Override explicitly with format=:
ps.schematic.render(circuit, "out.svg") # → SVG
ps.schematic.render(circuit, "out.png") # → PNG
ps.schematic.render(circuit, "out.pdf") # → PDF (rsvg-convert)
ps.schematic.render(circuit, "diagram", format="svg") # explicit
Known limits¶
- Layout is auto-computed. Component positions follow netlistsvg's ELK layered placement, which honors connectivity but doesn't reproduce a hand-drawn "textbook" layout for every topology. Tracked in
openspec/changes/add-schematic-position-hints— manual position hints are blocked by upstream netlistsvg bugs and require a fork or a Python re-implementation to land. - Switching-device symbols (
mosfet,igbt,vcswitch) ship in the Pulsim analog skin (python/pulsim/schematic/skin/pulsim_analog.svg). If you load a custom skin that drops them, they fall back to a labeledgenericrectangle. - Probes / control blocks (
voltage_probe,pi_controller, …) don't have analog skin symbols. They render as labeled boxes. - SPICE-style directives (
.tran,.ic, etc.) are not part of the rendered graphic — they live in the YAMLsimulation:block.
Programmatic circuits¶
The same render() works for circuits built in Python:
import pulsim as ps
ckt = ps.Circuit()
vin = ckt.add_node("vin")
vout = ckt.add_node("vout")
gnd = ckt.ground()
ckt.add_voltage_source("V1", vin, gnd, 12.0)
ckt.add_resistor("R1", vin, vout, 1_000.0)
ckt.add_capacitor("C1", vout, gnd, 1e-6, 0.0)
ps.schematic.render(ckt, "rc.svg")
Testing your schematic in code¶
pulsim.schematic.render(circuit, path) returns a pathlib.Path. A simple smoke test:
import pulsim as ps
import xml.etree.ElementTree as ET
def test_my_circuit_renders(tmp_path):
ckt = build_my_circuit()
out = ps.schematic.render(ckt, tmp_path / "my.svg")
assert out.exists() and out.stat().st_size > 0
ET.parse(out) # raises on malformed SVG
The schematic test suite in python/tests/test_schematic_render.py covers SVG/PNG output, the unknown-kind fallback, and the lazy-import gate — read that file for more patterns.