Python helper for KiCad symbol S-expressions
Project description
kicad-sym
Python helper for KiCad symbol S-expressions.
The library is intentionally low-level:
- forms are plain Python
listobjects - quoted strings are plain Python
str - bare atoms are
kicad_sym.Sym - integers and floats stay native
The core workflow is:
- read a
.kicad_symfile - modify the tree in Python
- write the whole file back out
There is no patch DSL and no heavy semantic object model.
Install
uv add kicad-sym
One-off script without editing your project dependencies:
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
print(ks.dumps(ks.library(ks.symbol("Demo"))))
PY
Basic Usage
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
lib = ks.load("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/Device.kicad_sym")
symbol = ks.get_symbol(lib, "R")
print(ks.symbol_name(symbol))
print(ks.nested_symbol_names(symbol))
print(ks.get_property(symbol, "Description"))
print(symbol)
PY
Parsed and built forms print as KiCad-style S-expressions, so print(node) and
REPL inspection are useful for quick debugging of intermediate states.
Read A Multi-Symbol Library
Use symbol_names() to inspect the top-level symbols, then get_symbol() to
pick the one you want.
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
lib = ks.load("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/Device.kicad_sym")
print(ks.symbol_names(lib)[:5])
resistor = ks.get_symbol(lib, "R")
capacitor = ks.get_symbol(lib, "C")
print(ks.symbol_name(resistor), ks.get_property(resistor, "Description"))
print(ks.symbol_name(capacitor), ks.get_property(capacitor, "Description"))
PY
For editing, the simplest workflow is still to target one top-level symbol at a time, modify it in memory, and write the whole library back out.
Query-Like Views In Python
The main pcb kq use cases translate directly into ordinary Python traversals.
The library handles tree access; Python handles filtering, sorting, and shaping
the result.
Metadata View
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
lib = ks.load("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/Interface_USB.kicad_sym")
sym = ks.get_symbol(lib, "ADUM4160")
print(ks.properties(sym))
PY
Electrical Signature
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
def pin_view(pin):
return {
"name": ks.child(pin, "name")[1],
"number": ks.child(pin, "number")[1],
"electrical": str(pin[1]),
"graphic": str(pin[2]),
}
lib = ks.load("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/Interface_USB.kicad_sym")
sym = ks.get_symbol(lib, "ADUM4160")
rows = [pin_view(pin) for pin in ks.pins(sym)]
rows.sort(key=lambda row: row["number"])
print(rows)
PY
Exact Unit In A Multi-Unit Symbol
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
def pin_view(pin):
return {
"name": ks.child(pin, "name")[1],
"number": ks.child(pin, "number")[1],
"electrical": str(pin[1]),
"graphic": str(pin[2]),
}
lib = ks.load("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/74xx.kicad_sym")
sym = ks.get_symbol(lib, "74LS00")
gate = ks.get_nested_symbol(sym, "74LS00_1_1")
power = ks.get_nested_symbol(sym, "74LS00_5_0")
print(ks.nested_symbol_names(sym)[:6])
print([pin_view(pin) for pin in ks.pins(gate)])
print([pin_view(pin) for pin in ks.pins(power)])
PY
Art And Geometry View
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
lib = ks.load("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/Interface_USB.kicad_sym")
sym = ks.get_symbol(lib, "ADUM4160")
text_art = ks.get_nested_symbol(sym, "ADUM4160_0_0")
body_art = ks.get_nested_symbol(sym, "ADUM4160_0_1")
pin_unit = ks.get_nested_symbol(sym, "ADUM4160_1_1")
labels = [node[1] for node in ks.walk(text_art, "text")]
rect = ks.find_one(body_art, "rectangle")
start = tuple(ks.child(rect, "start")[1:3])
end = tuple(ks.child(rect, "end")[1:3])
placements = [
{
"name": ks.child(pin, "name")[1],
"number": ks.child(pin, "number")[1],
"at": tuple(ks.child(pin, "at")[1:4]),
}
for pin in ks.pins(pin_unit)
]
print(labels)
print({"start": start, "end": end})
print(placements[:4])
PY
Explore Artwork In An Existing Symbol
Artwork-heavy symbols are easiest to explore by:
- loading the top-level symbol
- listing its nested unit/style symbols
- picking the art-bearing nested symbol
- walking only the draw nodes you care about
ADUM4160 is a good example because its body art and pin placement live in
different nested symbols.
uv run --with kicad-sym --no-project python - <<'PY'
from pathlib import Path
import kicad_sym as ks
DRAW = {"polyline", "rectangle", "circle", "arc", "bezier", "text", "text_box"}
root = Path("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols")
lib = ks.load(root / "Interface_USB.kicad_sym")
sym = ks.get_symbol(lib, "ADUM4160")
print(ks.nested_symbol_names(sym))
body_art = ks.get_nested_symbol(sym, "ADUM4160_0_1")
draw_nodes = [node for node in ks.walk(body_art) if ks.head(node) in DRAW]
print("draw nodes:", len(draw_nodes))
for node in draw_nodes[:3]:
print()
print(node)
PY
Because forms print as KiCad-style S-expressions, print(node) is usually
enough to inspect intermediate artwork state while you are iterating.
Write A Multi-Symbol Library
Build or load the top-level symbols you want, keep them in one library(...)
root, then save the entire file.
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
lib = ks.library(
ks.symbol(
"R_Custom",
ks.property("Reference", "R"),
ks.property("Value", "R_Custom"),
),
ks.symbol(
"C_Custom",
ks.property("Reference", "C"),
ks.property("Value", "C_Custom"),
),
)
resistor = ks.get_symbol(lib, "R_Custom")
ks.set_property(resistor, "Description", "Example resistor in a multi-symbol library")
lib.append(
ks.symbol(
"TP_Custom",
ks.property("Reference", "TP"),
ks.property("Value", "TP_Custom"),
)
)
path = "/tmp/custom-passives.kicad_sym"
ks.save(path, lib)
print(path)
print(ks.symbol_names(lib))
PY
Modify Artwork In An Existing Symbol
The usual pattern is:
- clone the symbol you want to modify
- rename the top-level symbol and any nested units you touched
- grab the nested art symbol
- replace or append draw nodes directly
uv run --with kicad-sym --no-project python - <<'PY'
from pathlib import Path
import kicad_sym as ks
root = Path("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols")
lib = ks.load(root / "Interface_USB.kicad_sym")
sym = ks.clone(ks.get_symbol(lib, "ADUM4160"))
sym[1] = "ADUM4160_Debug"
for nested in ks.nested_symbols(sym):
if isinstance(nested[1], str) and nested[1].startswith("ADUM4160_"):
nested[1] = nested[1].replace("ADUM4160", "ADUM4160_Debug", 1)
body_art = ks.get_nested_symbol(sym, "ADUM4160_Debug_0_1")
def replace_child(node, kind, new_child):
for i, child in enumerate(node[1:], start=1):
if isinstance(child, list) and ks.head(child) == kind:
node[i] = new_child
return
node.append(new_child)
rect = ks.find_one(body_art, "rectangle")
replace_child(rect, "stroke", ks.stroke(0.508, stroke_type="default"))
replace_child(rect, "fill", ks.fill("background"))
body_art.append(
ks.text(
"DBG",
at=(0, -9.5, 0),
effects_node=ks.effects(font_node=ks.font(1.0, 1.0)),
)
)
body_art.append(
ks.circle(
(0, 0),
2.0,
stroke_node=ks.stroke(0.254, stroke_type="default"),
fill_node=ks.fill("none"),
)
)
out = ks.library(sym)
path = Path("/tmp/ADUM4160_Debug.kicad_sym")
ks.save(path, out)
print(path)
PY
Update A Property In Place
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
lib = ks.load("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/Device.kicad_sym")
symbol = ks.clone(ks.get_symbol(lib, "R"))
ks.set_property(symbol, "Datasheet", "https://example.com/resistor")
ks.set_property(symbol, "Description", "Resistor with updated metadata")
ks.set_property(symbol, "Manufacturer", "Example Corp", hidden=True)
out = ks.library(symbol)
path = "/tmp/R_customized.kicad_sym"
ks.save(path, out)
print(path)
PY
Build A Minimal Single-Symbol Library
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
sym = ks.symbol(
"TestPoint_Pad",
ks.form("pin_numbers", ks.form("hide", ks.yesno(True))),
ks.form("pin_names", ks.form("offset", 0)),
ks.form("exclude_from_sim", ks.yesno(False)),
ks.form("in_bom", ks.yesno(True)),
ks.form("on_board", ks.yesno(True)),
ks.property("Reference", "TP", at=(0, 2.54, 0)),
ks.property("Value", "TestPoint_Pad", at=(0, -2.54, 0)),
ks.property("Footprint", "", at=(0, 0, 0), hidden=True),
ks.property("Datasheet", "", at=(0, 0, 0), hidden=True),
ks.property("Description", "One-pin test point", at=(0, 0, 0), hidden=True),
ks.unit_symbol(
"TestPoint_Pad",
1,
1,
ks.circle((0, 0), 1.27),
ks.pin("1", "TP", at=(-5.08, 0, 0)),
),
)
lib = ks.library(sym)
path = "/tmp/TestPoint_Pad.kicad_sym"
ks.save(path, lib)
print(path)
PY
Build Artwork From Scratch
The helpers cover the common draw forms directly: rectangle, polyline,
circle, and text. For forms that do not have a dedicated helper yet, such as
arc, just drop down to raw form(...).
uv run --with kicad-sym --no-project python - <<'PY'
from pathlib import Path
import kicad_sym as ks
badge = ks.symbol(
"Artwork_Debug_Badge",
ks.property("Reference", "SYM", hidden=True),
ks.property("Value", "Artwork_Debug_Badge", hidden=True),
ks.property("Footprint", "", hidden=True),
ks.property("Datasheet", "", hidden=True),
ks.property("Description", "Artwork example", hidden=True),
ks.unit_symbol(
"Artwork_Debug_Badge",
0,
1,
ks.circle(
(0, 0),
10,
stroke_node=ks.stroke(0.4, stroke_type="default"),
fill_node=ks.fill("none"),
),
ks.circle(
(0, 0),
7,
stroke_node=ks.stroke(0.25, stroke_type="default"),
fill_node=ks.fill("background"),
),
ks.form(
"arc",
ks.form("start", -6, 0),
ks.form("mid", 0, 6),
ks.form("end", 6, 0),
ks.stroke(0.35, stroke_type="default"),
ks.fill("none"),
),
ks.polyline(
[(-2.5, 4), (0.5, 0.5), (-1, 0.5), (2.5, -4), (0, -0.5), (1.2, -0.5)],
stroke_node=ks.stroke(0.35, stroke_type="default"),
fill_node=ks.fill("none"),
),
ks.text(
"DBG",
at=(0, -8.6, 0),
effects_node=ks.effects(font_node=ks.font(0.9, 0.9)),
),
),
)
out = ks.library(badge)
path = Path("/tmp/Artwork_Debug_Badge.kicad_sym")
ks.save(path, out)
print(path)
PY
Build A Multi-Unit Symbol
Nested KiCad unit/style symbols are direct child (symbol ...) nodes under the
top-level symbol, so multi-unit symbols fit the library naturally.
uv run --with kicad-sym --no-project python - <<'PY'
import kicad_sym as ks
dual = ks.symbol(
"Demo_Dual_Opamp",
ks.form("pin_names", ks.form("offset", 0.508)),
ks.form("exclude_from_sim", ks.yesno(False)),
ks.form("in_bom", ks.yesno(True)),
ks.form("on_board", ks.yesno(True)),
ks.property("Reference", "U", at=(0, 6.35, 0)),
ks.property("Value", "Demo_Dual_Opamp", at=(0, -6.35, 0)),
ks.property("Footprint", "", at=(0, 0, 0), hidden=True),
ks.property("Datasheet", "", at=(0, 0, 0), hidden=True),
ks.property("Description", "Example multi-unit symbol", at=(0, 0, 0), hidden=True),
ks.unit_symbol(
"Demo_Dual_Opamp",
1,
1,
ks.polyline([(-5.08, -3.81), (5.08, 0), (-5.08, 3.81), (-5.08, -3.81)]),
ks.pin("1", "-", electrical="input", at=(-7.62, 2.54, 0)),
ks.pin("2", "+", electrical="input", at=(-7.62, -2.54, 0)),
ks.pin("3", "~", electrical="output", at=(7.62, 0, 180)),
),
ks.unit_symbol(
"Demo_Dual_Opamp",
2,
1,
ks.polyline([(-5.08, -3.81), (5.08, 0), (-5.08, 3.81), (-5.08, -3.81)]),
ks.pin("5", "+", electrical="input", at=(-7.62, -2.54, 0)),
ks.pin("6", "-", electrical="input", at=(-7.62, 2.54, 0)),
ks.pin("7", "~", electrical="output", at=(7.62, 0, 180)),
),
ks.unit_symbol(
"Demo_Dual_Opamp",
3,
0,
ks.pin("4", "V-", electrical="power_in", at=(0, -7.62, 90)),
ks.pin("8", "V+", electrical="power_in", at=(0, 7.62, 270)),
),
)
lib = ks.library(dual)
path = "/tmp/Demo_Dual_Opamp.kicad_sym"
ks.save(path, lib)
print(path)
print(ks.nested_symbol_names(dual))
PY
KiCad Symbol Shape
A .kicad_sym file is an S-expression tree rooted at (kicad_symbol_lib ...).
(kicad_symbol_lib
(symbol "NAME"
SYMBOL_METADATA...
GRAPHICS...
PINS...
(symbol "NAME_0_1" ...)
(symbol "NAME_1_1" ...)
)
(symbol "ANOTHER_NAME" ...)
)
In practice:
- top-level
symbolnodes are the library entries - nested
symbolnodes are units or styles inside a parent symbol - metadata is usually
property,in_bom,on_board,pin_names,pin_numbers, and sometimesextends - drawing is built from forms like
rectangle,polyline,circle,arc, andtext - many real KiCad symbols put graphics and pins in different nested symbols
kicad-sym keeps that structure visible instead of wrapping it in a heavier object model.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kicad_sym-0.2.0.tar.gz.
File metadata
- Download URL: kicad_sym-0.2.0.tar.gz
- Upload date:
- Size: 11.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07930a137c041dd14edd58cb41d9eba4c549865be15d113be79bb82d958d940a
|
|
| MD5 |
4bea25bba24974fd3cfdad804fbeed5f
|
|
| BLAKE2b-256 |
293d9ed886454f30ba9f507d89e1b38b44e8df21eb2382f246542852377793e9
|
File details
Details for the file kicad_sym-0.2.0-py3-none-any.whl.
File metadata
- Download URL: kicad_sym-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ab262e55c2188cb14fda69ef1d742a11758257535768919a2dcc1b801214efe
|
|
| MD5 |
502e59c7eed93d6a35c1c535192cc505
|
|
| BLAKE2b-256 |
d2aca460134fc094a32cb79449ffabcab915fb209038ebc98c26b4b30066c7b8
|