Add external .kicad_sym library search for custom symbol pin resolution
parse_lib_symbol_pins() now falls back to searching external .kicad_sym
library files when a symbol isn't embedded in the schematic's lib_symbols
section. Splits lib_id ("LibName:SymName") to locate the library file,
then parses pins using the bare symbol name.
Search order: schematic dir, libs/, ../libs/, project root, project
root/libs/, kicad/libs/, and sym-lib-table entries with ${KIPRJMOD}
substitution. Handles nonexistent directories gracefully.
Fixes add_power_symbol for script-generated schematics that reference
project library symbols without embedding them (e.g. D1/SMF5.0CA in
ESP32-P4-WIFI6-DEV-KIT library).
This commit is contained in:
parent
7525f3dcdc
commit
e88f75f567
@ -91,6 +91,11 @@ _PIN_RE = re.compile(
|
|||||||
def parse_lib_symbol_pins(filepath: str, lib_id: str) -> list[dict[str, Any]]:
|
def parse_lib_symbol_pins(filepath: str, lib_id: str) -> list[dict[str, Any]]:
|
||||||
"""Extract pin definitions for a symbol from the ``(lib_symbols ...)`` section.
|
"""Extract pin definitions for a symbol from the ``(lib_symbols ...)`` section.
|
||||||
|
|
||||||
|
Searches the embedded ``lib_symbols`` section first. If the symbol is not
|
||||||
|
found there (common for script-generated schematics that reference
|
||||||
|
external project libraries), falls back to searching external
|
||||||
|
``.kicad_sym`` library files.
|
||||||
|
|
||||||
Pins are returned in **local symbol coordinates** (not transformed to
|
Pins are returned in **local symbol coordinates** (not transformed to
|
||||||
schematic space). Use :func:`transform_pin_to_schematic` to convert
|
schematic space). Use :func:`transform_pin_to_schematic` to convert
|
||||||
them if the component's position and rotation are known.
|
them if the component's position and rotation are known.
|
||||||
@ -103,23 +108,35 @@ def parse_lib_symbol_pins(filepath: str, lib_id: str) -> list[dict[str, Any]]:
|
|||||||
List of pin dicts with ``number``, ``name``, ``type``, ``shape``,
|
List of pin dicts with ``number``, ``name``, ``type``, ``shape``,
|
||||||
``x``, ``y``, ``rotation``, and ``length`` fields.
|
``x``, ``y``, ``rotation``, and ``length`` fields.
|
||||||
"""
|
"""
|
||||||
|
pins = _parse_embedded_symbol_pins(filepath, lib_id)
|
||||||
|
if pins:
|
||||||
|
return pins
|
||||||
|
|
||||||
|
# Fallback: search external .kicad_sym library files
|
||||||
|
return _parse_external_lib_symbol_pins(filepath, lib_id)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_embedded_symbol_pins(filepath: str, lib_id: str) -> list[dict[str, Any]]:
|
||||||
|
"""Extract pins from the embedded ``(lib_symbols ...)`` section of a schematic."""
|
||||||
try:
|
try:
|
||||||
with open(filepath, encoding="utf-8") as f:
|
with open(filepath, encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 1. Find the (lib_symbols ...) section
|
|
||||||
lib_section = _extract_section(content, "lib_symbols")
|
lib_section = _extract_section(content, "lib_symbols")
|
||||||
if not lib_section:
|
if not lib_section:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 2. Find the top-level (symbol "LIB_ID" ...) within lib_symbols
|
|
||||||
symbol_section = _extract_named_section(lib_section, "symbol", lib_id)
|
symbol_section = _extract_named_section(lib_section, "symbol", lib_id)
|
||||||
if not symbol_section:
|
if not symbol_section:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 3. Extract all (pin ...) entries
|
return _extract_pins_from_section(symbol_section)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_pins_from_section(symbol_section: str) -> list[dict[str, Any]]:
|
||||||
|
"""Extract all ``(pin ...)`` entries from an s-expression symbol section."""
|
||||||
pins: list[dict[str, Any]] = []
|
pins: list[dict[str, Any]] = []
|
||||||
for match in _PIN_RE.finditer(symbol_section):
|
for match in _PIN_RE.finditer(symbol_section):
|
||||||
pins.append({
|
pins.append({
|
||||||
@ -132,7 +149,186 @@ def parse_lib_symbol_pins(filepath: str, lib_id: str) -> list[dict[str, Any]]:
|
|||||||
"rotation": float(match.group(5) or 0),
|
"rotation": float(match.group(5) or 0),
|
||||||
"length": float(match.group(6)),
|
"length": float(match.group(6)),
|
||||||
})
|
})
|
||||||
|
return pins
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# External library file search
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_root(start_path: str) -> str | None:
|
||||||
|
"""Walk up from a file path to find the directory containing a ``.kicad_pro`` file.
|
||||||
|
|
||||||
|
Returns the directory path, or None if no project root is found within
|
||||||
|
5 levels.
|
||||||
|
"""
|
||||||
|
current = os.path.dirname(os.path.abspath(start_path))
|
||||||
|
for _ in range(5):
|
||||||
|
if not current or current == os.path.dirname(current):
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
entries = os.listdir(current)
|
||||||
|
except OSError:
|
||||||
|
break
|
||||||
|
for entry in entries:
|
||||||
|
if entry.endswith(".kicad_pro"):
|
||||||
|
return current
|
||||||
|
current = os.path.dirname(current)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_library_file(schematic_path: str, library_name: str) -> str | None:
|
||||||
|
"""Search for a ``{library_name}.kicad_sym`` file in predictable locations.
|
||||||
|
|
||||||
|
Search order:
|
||||||
|
1. Same directory as the schematic
|
||||||
|
2. ``libs/`` relative to schematic directory
|
||||||
|
3. ``../libs/`` relative to schematic directory
|
||||||
|
4. Project root directory (directory containing ``.kicad_pro``)
|
||||||
|
5. ``libs/`` relative to project root
|
||||||
|
6. ``kicad/libs/`` relative to project root
|
||||||
|
7. Paths from ``sym-lib-table`` file in project root
|
||||||
|
|
||||||
|
Args:
|
||||||
|
schematic_path: Path to the ``.kicad_sch`` file.
|
||||||
|
library_name: Library name (the part before ``:`` in a lib_id).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Absolute path to the ``.kicad_sym`` file, or None.
|
||||||
|
"""
|
||||||
|
filename = f"{library_name}.kicad_sym"
|
||||||
|
sch_dir = os.path.dirname(os.path.abspath(schematic_path))
|
||||||
|
|
||||||
|
# Direct search in common locations
|
||||||
|
candidates = [
|
||||||
|
os.path.join(sch_dir, filename),
|
||||||
|
os.path.join(sch_dir, "libs", filename),
|
||||||
|
os.path.join(sch_dir, os.pardir, "libs", filename),
|
||||||
|
]
|
||||||
|
|
||||||
|
project_root = _find_project_root(schematic_path)
|
||||||
|
if project_root:
|
||||||
|
candidates.extend([
|
||||||
|
os.path.join(project_root, filename),
|
||||||
|
os.path.join(project_root, "libs", filename),
|
||||||
|
os.path.join(project_root, "kicad", "libs", filename),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Parse sym-lib-table for explicit library paths
|
||||||
|
sym_lib_table = os.path.join(project_root, "sym-lib-table")
|
||||||
|
if os.path.isfile(sym_lib_table):
|
||||||
|
resolved = _resolve_from_sym_lib_table(
|
||||||
|
sym_lib_table, library_name, project_root,
|
||||||
|
)
|
||||||
|
if resolved:
|
||||||
|
candidates.append(resolved)
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
resolved_path = os.path.normpath(candidate)
|
||||||
|
if os.path.isfile(resolved_path):
|
||||||
|
logger.debug("Found library file: %s", resolved_path)
|
||||||
|
return resolved_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Match: (lib (name "NAME") ... (uri "PATH") ...)
|
||||||
|
_SYM_LIB_TABLE_RE = re.compile(
|
||||||
|
r'\(lib\s+\(name\s+"([^"]+)"\)'
|
||||||
|
r'.*?'
|
||||||
|
r'\(uri\s+"([^"]+)"\)',
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_from_sym_lib_table(
|
||||||
|
table_path: str, library_name: str, project_root: str,
|
||||||
|
) -> str | None:
|
||||||
|
"""Parse a ``sym-lib-table`` file and resolve the URI for a library name.
|
||||||
|
|
||||||
|
Handles ``${KIPRJMOD}`` variable substitution (points to project root).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(table_path, encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for match in _SYM_LIB_TABLE_RE.finditer(content):
|
||||||
|
if match.group(1) == library_name:
|
||||||
|
uri = match.group(2)
|
||||||
|
# Substitute ${KIPRJMOD} with project root
|
||||||
|
uri = uri.replace("${KIPRJMOD}", project_root)
|
||||||
|
return os.path.normpath(uri)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_lib_file_symbol_pins(lib_file_path: str, symbol_name: str) -> list[dict[str, Any]]:
|
||||||
|
"""Extract pin definitions from a standalone ``.kicad_sym`` library file.
|
||||||
|
|
||||||
|
In ``.kicad_sym`` files, the top-level container is ``(kicad_symbol_lib ...)``
|
||||||
|
and symbols are named without the library prefix (e.g. ``"SMF5.0CA"``
|
||||||
|
rather than ``"ESP32-P4-WIFI6-DEV-KIT:SMF5.0CA"``).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lib_file_path: Path to a ``.kicad_sym`` file.
|
||||||
|
symbol_name: Symbol name without library prefix (e.g. ``SMF5.0CA``).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of pin dicts (same format as :func:`parse_lib_symbol_pins`).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(lib_file_path, encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# The whole file is the library — find the symbol directly
|
||||||
|
symbol_section = _extract_named_section(content, "symbol", symbol_name)
|
||||||
|
if not symbol_section:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return _extract_pins_from_section(symbol_section)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_external_lib_symbol_pins(
|
||||||
|
schematic_path: str, lib_id: str,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Search external ``.kicad_sym`` files for a symbol's pin definitions.
|
||||||
|
|
||||||
|
Splits the ``lib_id`` into library name and symbol name, searches for
|
||||||
|
the library file, and parses pins from it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
schematic_path: Path to the ``.kicad_sch`` file (search anchor).
|
||||||
|
lib_id: Full library identifier (e.g. ``ESP32-P4-WIFI6-DEV-KIT:SMF5.0CA``).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of pin dicts, or empty list if the library file isn't found.
|
||||||
|
"""
|
||||||
|
if ":" not in lib_id:
|
||||||
|
return []
|
||||||
|
|
||||||
|
library_name, symbol_name = lib_id.split(":", 1)
|
||||||
|
if not library_name or not symbol_name:
|
||||||
|
return []
|
||||||
|
|
||||||
|
lib_file = _find_library_file(schematic_path, library_name)
|
||||||
|
if lib_file is None:
|
||||||
|
logger.debug(
|
||||||
|
"External library file not found for %s (searched from %s)",
|
||||||
|
library_name, schematic_path,
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
pins = parse_lib_file_symbol_pins(lib_file, symbol_name)
|
||||||
|
if pins:
|
||||||
|
logger.debug(
|
||||||
|
"Resolved %d pins for %s from external library %s",
|
||||||
|
len(pins), lib_id, lib_file,
|
||||||
|
)
|
||||||
return pins
|
return pins
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from mckicad.utils.sexp_parser import (
|
|||||||
generate_label_sexp,
|
generate_label_sexp,
|
||||||
insert_sexp_before_close,
|
insert_sexp_before_close,
|
||||||
parse_global_labels,
|
parse_global_labels,
|
||||||
|
parse_lib_file_symbol_pins,
|
||||||
parse_lib_symbol_pins,
|
parse_lib_symbol_pins,
|
||||||
transform_pin_to_schematic,
|
transform_pin_to_schematic,
|
||||||
)
|
)
|
||||||
@ -474,3 +475,248 @@ class TestResolvePinPosition:
|
|||||||
# At component position (100, 100) with 0 rotation: (100, 103.81)
|
# At component position (100, 100) with 0 rotation: (100, 103.81)
|
||||||
assert result[0] == pytest.approx(100.0)
|
assert result[0] == pytest.approx(100.0)
|
||||||
assert result[1] == pytest.approx(103.81)
|
assert result[1] == pytest.approx(103.81)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# External library file tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Minimal .kicad_sym library file with a custom TVS diode symbol
|
||||||
|
SAMPLE_LIBRARY_FILE = """\
|
||||||
|
(kicad_symbol_lib
|
||||||
|
(version 20231120)
|
||||||
|
(generator "kicad_symbol_editor")
|
||||||
|
(symbol "SMF5.0CA"
|
||||||
|
(pin_names
|
||||||
|
(offset 1.016)
|
||||||
|
)
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(symbol "SMF5.0CA_0_1"
|
||||||
|
(pin passive line
|
||||||
|
(at -10.16 2.54 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "A"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "1"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 10.16 2.54 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "K"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Schematic that references the library symbol but doesn't embed it
|
||||||
|
SAMPLE_SCHEMATIC_NO_EMBED = """\
|
||||||
|
(kicad_sch
|
||||||
|
(version 20231120)
|
||||||
|
(generator "eeschema")
|
||||||
|
(uuid "abc123")
|
||||||
|
(paper "A4")
|
||||||
|
(lib_symbols
|
||||||
|
)
|
||||||
|
(symbol
|
||||||
|
(lib_id "MyProject:SMF5.0CA")
|
||||||
|
(at 100 100 0)
|
||||||
|
(unit 1)
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(uuid "d1-uuid")
|
||||||
|
(property "Reference" "D1"
|
||||||
|
(at 100 90 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def external_lib_project(tmp_path):
|
||||||
|
"""Create a project structure with an external library file.
|
||||||
|
|
||||||
|
tmp_path/
|
||||||
|
test_project.kicad_pro
|
||||||
|
libs/
|
||||||
|
MyProject.kicad_sym
|
||||||
|
sheets/
|
||||||
|
power.kicad_sch
|
||||||
|
"""
|
||||||
|
# Project file
|
||||||
|
pro_file = tmp_path / "test_project.kicad_pro"
|
||||||
|
pro_file.write_text('{"meta": {"filename": "test_project.kicad_pro"}}')
|
||||||
|
|
||||||
|
# Library directory
|
||||||
|
libs_dir = tmp_path / "libs"
|
||||||
|
libs_dir.mkdir()
|
||||||
|
lib_file = libs_dir / "MyProject.kicad_sym"
|
||||||
|
lib_file.write_text(SAMPLE_LIBRARY_FILE)
|
||||||
|
|
||||||
|
# Schematic in a subdirectory (common for multi-sheet projects)
|
||||||
|
sheets_dir = tmp_path / "sheets"
|
||||||
|
sheets_dir.mkdir()
|
||||||
|
sch_file = sheets_dir / "power.kicad_sch"
|
||||||
|
sch_file.write_text(SAMPLE_SCHEMATIC_NO_EMBED)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"project_root": str(tmp_path),
|
||||||
|
"lib_file": str(lib_file),
|
||||||
|
"schematic": str(sch_file),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestParseLibFileSymbolPins:
|
||||||
|
"""Tests for parsing pins from standalone .kicad_sym library files."""
|
||||||
|
|
||||||
|
def test_finds_pins_in_library_file(self, external_lib_project):
|
||||||
|
pins = parse_lib_file_symbol_pins(
|
||||||
|
external_lib_project["lib_file"], "SMF5.0CA",
|
||||||
|
)
|
||||||
|
assert len(pins) == 2
|
||||||
|
|
||||||
|
names = {p["name"] for p in pins}
|
||||||
|
assert names == {"A", "K"}
|
||||||
|
|
||||||
|
pin_a = next(p for p in pins if p["name"] == "A")
|
||||||
|
assert pin_a["number"] == "1"
|
||||||
|
assert pin_a["x"] == pytest.approx(-10.16)
|
||||||
|
assert pin_a["y"] == pytest.approx(2.54)
|
||||||
|
|
||||||
|
pin_k = next(p for p in pins if p["name"] == "K")
|
||||||
|
assert pin_k["number"] == "2"
|
||||||
|
assert pin_k["x"] == pytest.approx(10.16)
|
||||||
|
|
||||||
|
def test_nonexistent_symbol_returns_empty(self, external_lib_project):
|
||||||
|
pins = parse_lib_file_symbol_pins(
|
||||||
|
external_lib_project["lib_file"], "DOES_NOT_EXIST",
|
||||||
|
)
|
||||||
|
assert pins == []
|
||||||
|
|
||||||
|
def test_nonexistent_file_returns_empty(self):
|
||||||
|
pins = parse_lib_file_symbol_pins("/nonexistent/lib.kicad_sym", "X")
|
||||||
|
assert pins == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestExternalLibraryFallback:
|
||||||
|
"""Tests for parse_lib_symbol_pins falling back to external .kicad_sym files."""
|
||||||
|
|
||||||
|
def test_embedded_symbol_found_first(self, sample_schematic_file):
|
||||||
|
"""When symbol is embedded, don't search external files."""
|
||||||
|
pins = parse_lib_symbol_pins(sample_schematic_file, "Device:R")
|
||||||
|
assert len(pins) == 2 # Found in embedded lib_symbols
|
||||||
|
|
||||||
|
def test_external_library_fallback(self, external_lib_project):
|
||||||
|
"""When symbol not embedded, search external .kicad_sym files."""
|
||||||
|
pins = parse_lib_symbol_pins(
|
||||||
|
external_lib_project["schematic"], "MyProject:SMF5.0CA",
|
||||||
|
)
|
||||||
|
assert len(pins) == 2
|
||||||
|
|
||||||
|
names = {p["name"] for p in pins}
|
||||||
|
assert names == {"A", "K"}
|
||||||
|
|
||||||
|
def test_external_lib_not_found_returns_empty(self, tmp_path):
|
||||||
|
"""When no external library exists, return empty list."""
|
||||||
|
sch_file = tmp_path / "orphan.kicad_sch"
|
||||||
|
sch_file.write_text(
|
||||||
|
"(kicad_sch\n (version 20231120)\n (lib_symbols\n )\n)\n"
|
||||||
|
)
|
||||||
|
pins = parse_lib_symbol_pins(str(sch_file), "NoSuchLib:Missing")
|
||||||
|
assert pins == []
|
||||||
|
|
||||||
|
def test_resolve_pin_via_external_lib(self, external_lib_project):
|
||||||
|
"""resolve_pin_position should find pins via external library."""
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from mckicad.utils.sexp_parser import resolve_pin_position
|
||||||
|
|
||||||
|
sch = MagicMock()
|
||||||
|
sch.get_component_pin_position.return_value = None
|
||||||
|
|
||||||
|
comp = MagicMock()
|
||||||
|
comp.lib_id = "MyProject:SMF5.0CA"
|
||||||
|
comp.position = MagicMock()
|
||||||
|
comp.position.x = 100.0
|
||||||
|
comp.position.y = 100.0
|
||||||
|
comp.rotation = 0
|
||||||
|
comp.mirror = None
|
||||||
|
sch.components.get.return_value = comp
|
||||||
|
|
||||||
|
result = resolve_pin_position(
|
||||||
|
sch, external_lib_project["schematic"], "D1", "1",
|
||||||
|
)
|
||||||
|
assert result is not None
|
||||||
|
# Pin 1 (A) at (-10.16, 2.54) local, component at (100, 100), 0 rotation
|
||||||
|
assert result[0] == pytest.approx(100 - 10.16, abs=0.01)
|
||||||
|
assert result[1] == pytest.approx(100 + 2.54, abs=0.01)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSymLibTableParsing:
|
||||||
|
"""Tests for sym-lib-table resolution."""
|
||||||
|
|
||||||
|
def test_sym_lib_table_resolution(self, tmp_path):
|
||||||
|
"""Library paths from sym-lib-table should be found."""
|
||||||
|
from mckicad.utils.sexp_parser import _find_library_file
|
||||||
|
|
||||||
|
# Create project structure
|
||||||
|
pro_file = tmp_path / "project.kicad_pro"
|
||||||
|
pro_file.write_text('{}')
|
||||||
|
|
||||||
|
custom_libs = tmp_path / "custom" / "symbols"
|
||||||
|
custom_libs.mkdir(parents=True)
|
||||||
|
lib_file = custom_libs / "CustomLib.kicad_sym"
|
||||||
|
lib_file.write_text("(kicad_symbol_lib)")
|
||||||
|
|
||||||
|
sym_lib_table = tmp_path / "sym-lib-table"
|
||||||
|
sym_lib_table.write_text(
|
||||||
|
'(sym_lib_table\n'
|
||||||
|
' (version 7)\n'
|
||||||
|
' (lib (name "CustomLib")(type "KiCad")'
|
||||||
|
'(uri "${KIPRJMOD}/custom/symbols/CustomLib.kicad_sym")'
|
||||||
|
'(options "")(descr ""))\n'
|
||||||
|
')\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
sch_path = str(tmp_path / "test.kicad_sch")
|
||||||
|
result = _find_library_file(sch_path, "CustomLib")
|
||||||
|
assert result is not None
|
||||||
|
assert result == os.path.normpath(str(lib_file))
|
||||||
|
|
||||||
|
def test_libs_dir_search(self, external_lib_project):
|
||||||
|
"""Libraries in ../libs/ relative to schematic should be found."""
|
||||||
|
from mckicad.utils.sexp_parser import _find_library_file
|
||||||
|
|
||||||
|
result = _find_library_file(
|
||||||
|
external_lib_project["schematic"], "MyProject",
|
||||||
|
)
|
||||||
|
assert result is not None
|
||||||
|
assert result.endswith("MyProject.kicad_sym")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user