kicad-sch-api 0.1.7__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kicad_sch_api/__init__.py +6 -2
- kicad_sch_api/cli.py +119 -272
- kicad_sch_api/core/component_bounds.py +477 -0
- kicad_sch_api/core/components.py +22 -10
- kicad_sch_api/core/config.py +127 -0
- kicad_sch_api/core/formatter.py +183 -23
- kicad_sch_api/core/geometry.py +111 -0
- kicad_sch_api/core/ic_manager.py +43 -37
- kicad_sch_api/core/junctions.py +17 -22
- kicad_sch_api/core/manhattan_routing.py +430 -0
- kicad_sch_api/core/parser.py +495 -196
- kicad_sch_api/core/pin_utils.py +149 -0
- kicad_sch_api/core/schematic.py +630 -207
- kicad_sch_api/core/simple_manhattan.py +228 -0
- kicad_sch_api/core/types.py +9 -4
- kicad_sch_api/core/wire_routing.py +380 -0
- kicad_sch_api/core/wires.py +29 -25
- kicad_sch_api/discovery/__init__.py +1 -1
- kicad_sch_api/discovery/search_index.py +142 -107
- kicad_sch_api/library/cache.py +70 -62
- kicad_sch_api-0.2.1.dist-info/METADATA +483 -0
- kicad_sch_api-0.2.1.dist-info/RECORD +31 -0
- {kicad_sch_api-0.1.7.dist-info → kicad_sch_api-0.2.1.dist-info}/entry_points.txt +0 -1
- kicad_sch_api/mcp/__init__.py +0 -7
- kicad_sch_api/mcp/server.py +0 -1511
- kicad_sch_api-0.1.7.dist-info/METADATA +0 -322
- kicad_sch_api-0.1.7.dist-info/RECORD +0 -26
- {kicad_sch_api-0.1.7.dist-info → kicad_sch_api-0.2.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.1.7.dist-info → kicad_sch_api-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.1.7.dist-info → kicad_sch_api-0.2.1.dist-info}/top_level.txt +0 -0
kicad_sch_api/library/cache.py
CHANGED
|
@@ -41,7 +41,7 @@ class SymbolDefinition:
|
|
|
41
41
|
|
|
42
42
|
# Raw KiCAD data for exact format preservation
|
|
43
43
|
raw_kicad_data: Any = None
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
# Symbol inheritance
|
|
46
46
|
extends: Optional[str] = None # Parent symbol name if this symbol extends another
|
|
47
47
|
|
|
@@ -263,7 +263,7 @@ class SymbolLibraryCache:
|
|
|
263
263
|
Symbol definition if found, None otherwise
|
|
264
264
|
"""
|
|
265
265
|
logger.debug(f"🔧 CACHE: Requesting symbol: {lib_id}")
|
|
266
|
-
|
|
266
|
+
|
|
267
267
|
# Check cache first
|
|
268
268
|
if lib_id in self._symbols:
|
|
269
269
|
self._cache_hits += 1
|
|
@@ -357,7 +357,7 @@ class SymbolLibraryCache:
|
|
|
357
357
|
def _load_symbol(self, lib_id: str) -> Optional[SymbolDefinition]:
|
|
358
358
|
"""Load a single symbol from its library."""
|
|
359
359
|
logger.debug(f"🔧 LOAD: Loading symbol {lib_id}")
|
|
360
|
-
|
|
360
|
+
|
|
361
361
|
if ":" not in lib_id:
|
|
362
362
|
logger.warning(f"🔧 LOAD: Invalid lib_id format: {lib_id}")
|
|
363
363
|
return None
|
|
@@ -423,29 +423,29 @@ class SymbolLibraryCache:
|
|
|
423
423
|
try:
|
|
424
424
|
# Extract symbol name from lib_id
|
|
425
425
|
library_name, symbol_name = lib_id.split(":", 1)
|
|
426
|
-
|
|
427
|
-
with open(library_path,
|
|
426
|
+
|
|
427
|
+
with open(library_path, "r", encoding="utf-8") as f:
|
|
428
428
|
content = f.read()
|
|
429
429
|
|
|
430
430
|
# Parse the S-expression with symbol preservation
|
|
431
431
|
parsed = sexpdata.loads(content, true=None, false=None, nil=None)
|
|
432
432
|
logger.debug(f"🔧 PARSE: Parsed library file with {len(parsed)} top-level items")
|
|
433
|
-
|
|
433
|
+
|
|
434
434
|
# Find the symbol we're looking for
|
|
435
435
|
symbol_data = self._find_symbol_in_parsed_data(parsed, symbol_name)
|
|
436
436
|
if not symbol_data:
|
|
437
437
|
logger.debug(f"🔧 PARSE: Symbol {symbol_name} not found in {library_path}")
|
|
438
438
|
return None
|
|
439
|
-
|
|
439
|
+
|
|
440
440
|
logger.debug(f"🔧 PARSE: Found symbol {symbol_name} in library")
|
|
441
441
|
|
|
442
442
|
# Extract the library name and symbol name for resolution
|
|
443
443
|
library_name, symbol_name = lib_id.split(":", 1)
|
|
444
|
-
|
|
444
|
+
|
|
445
445
|
# Check if this symbol extends another symbol
|
|
446
446
|
extends_symbol = self._check_extends_directive(symbol_data)
|
|
447
447
|
logger.debug(f"🔧 CACHE: Symbol {lib_id} extends: {extends_symbol}")
|
|
448
|
-
|
|
448
|
+
|
|
449
449
|
# If this symbol extends another, we need to resolve it
|
|
450
450
|
if extends_symbol:
|
|
451
451
|
resolved_symbol_data = self._resolve_extends_relationship(
|
|
@@ -455,7 +455,7 @@ class SymbolLibraryCache:
|
|
|
455
455
|
symbol_data = resolved_symbol_data
|
|
456
456
|
extends_symbol = None # Clear extends after resolution
|
|
457
457
|
logger.debug(f"🔧 CACHE: Resolved extends for {lib_id}")
|
|
458
|
-
|
|
458
|
+
|
|
459
459
|
# Extract symbol information
|
|
460
460
|
result = {
|
|
461
461
|
"raw_data": symbol_data, # Store the raw parsed data
|
|
@@ -464,26 +464,26 @@ class SymbolLibraryCache:
|
|
|
464
464
|
"keywords": "",
|
|
465
465
|
"datasheet": "~",
|
|
466
466
|
"pins": [],
|
|
467
|
-
"extends": extends_symbol # Should be None after resolution
|
|
467
|
+
"extends": extends_symbol, # Should be None after resolution
|
|
468
468
|
}
|
|
469
469
|
|
|
470
470
|
# Extract properties from the symbol
|
|
471
471
|
for item in symbol_data[1:]:
|
|
472
472
|
if isinstance(item, list) and len(item) > 0:
|
|
473
|
-
if item[0] == sexpdata.Symbol(
|
|
473
|
+
if item[0] == sexpdata.Symbol("property"):
|
|
474
474
|
prop_name = item[1]
|
|
475
475
|
prop_value = item[2]
|
|
476
|
-
|
|
476
|
+
|
|
477
477
|
logger.debug(f"🔧 Processing property: {prop_name} = {prop_value}")
|
|
478
|
-
if prop_name == sexpdata.Symbol(
|
|
478
|
+
if prop_name == sexpdata.Symbol("Reference"):
|
|
479
479
|
result["reference_prefix"] = str(prop_value)
|
|
480
480
|
logger.debug(f"🔧 Set reference_prefix: {str(prop_value)}")
|
|
481
|
-
elif prop_name == sexpdata.Symbol(
|
|
481
|
+
elif prop_name == sexpdata.Symbol("Description"):
|
|
482
482
|
result["Description"] = str(prop_value) # Keep original case
|
|
483
483
|
logger.debug(f"🔧 Set Description: {str(prop_value)}")
|
|
484
|
-
elif prop_name == sexpdata.Symbol(
|
|
484
|
+
elif prop_name == sexpdata.Symbol("ki_keywords"):
|
|
485
485
|
result["keywords"] = str(prop_value)
|
|
486
|
-
elif prop_name == sexpdata.Symbol(
|
|
486
|
+
elif prop_name == sexpdata.Symbol("Datasheet"):
|
|
487
487
|
result["Datasheet"] = str(prop_value) # Keep original case
|
|
488
488
|
logger.debug(f"🔧 Set Datasheet: {str(prop_value)}")
|
|
489
489
|
|
|
@@ -500,7 +500,7 @@ class SymbolLibraryCache:
|
|
|
500
500
|
def _find_symbol_in_parsed_data(self, parsed_data: List, symbol_name: str) -> Optional[List]:
|
|
501
501
|
"""Find a specific symbol in parsed KiCAD library data."""
|
|
502
502
|
logger.debug(f"🔧 FIND: Looking for symbol '{symbol_name}' in parsed data")
|
|
503
|
-
|
|
503
|
+
|
|
504
504
|
if not isinstance(parsed_data, list):
|
|
505
505
|
logger.debug(f"🔧 FIND: Parsed data is not a list: {type(parsed_data)}")
|
|
506
506
|
return None
|
|
@@ -509,61 +509,66 @@ class SymbolLibraryCache:
|
|
|
509
509
|
available_symbols = []
|
|
510
510
|
for item in parsed_data:
|
|
511
511
|
if isinstance(item, list) and len(item) >= 2:
|
|
512
|
-
if item[0] == sexpdata.Symbol(
|
|
512
|
+
if item[0] == sexpdata.Symbol("symbol"):
|
|
513
513
|
available_symbols.append(str(item[1]).strip('"'))
|
|
514
|
-
|
|
515
|
-
logger.debug(
|
|
514
|
+
|
|
515
|
+
logger.debug(
|
|
516
|
+
f"🔧 FIND: Available symbols in library: {available_symbols[:10]}..."
|
|
517
|
+
) # Show first 10
|
|
516
518
|
|
|
517
519
|
# Search through the parsed data for the symbol
|
|
518
520
|
for item in parsed_data:
|
|
519
521
|
if isinstance(item, list) and len(item) >= 2:
|
|
520
|
-
if (
|
|
521
|
-
|
|
522
|
-
|
|
522
|
+
if (
|
|
523
|
+
item[0] == sexpdata.Symbol("symbol")
|
|
524
|
+
and len(item) > 1
|
|
525
|
+
and str(item[1]).strip('"') == symbol_name
|
|
526
|
+
):
|
|
523
527
|
logger.debug(f"🔧 FIND: Found symbol '{symbol_name}'")
|
|
524
528
|
return item
|
|
525
|
-
|
|
526
|
-
logger.debug(f"🔧 FIND: Symbol '{symbol_name}' not found in library")
|
|
529
|
+
|
|
530
|
+
logger.debug(f"🔧 FIND: Symbol '{symbol_name}' not found in library")
|
|
527
531
|
return None
|
|
528
532
|
|
|
529
533
|
def _check_extends_directive(self, symbol_data: List) -> Optional[str]:
|
|
530
534
|
"""Check if symbol has extends directive and return parent symbol name."""
|
|
531
535
|
if not isinstance(symbol_data, list):
|
|
532
536
|
return None
|
|
533
|
-
|
|
537
|
+
|
|
534
538
|
for item in symbol_data[1:]:
|
|
535
539
|
if isinstance(item, list) and len(item) >= 2:
|
|
536
|
-
if item[0] == sexpdata.Symbol(
|
|
540
|
+
if item[0] == sexpdata.Symbol("extends"):
|
|
537
541
|
parent_name = str(item[1]).strip('"')
|
|
538
542
|
logger.debug(f"Found extends directive: {parent_name}")
|
|
539
543
|
return parent_name
|
|
540
544
|
return None
|
|
541
545
|
|
|
542
|
-
def _resolve_extends_relationship(
|
|
543
|
-
|
|
546
|
+
def _resolve_extends_relationship(
|
|
547
|
+
self, child_symbol_data: List, parent_name: str, library_path: Path, library_name: str
|
|
548
|
+
) -> Optional[List]:
|
|
544
549
|
"""Resolve extends relationship by merging parent symbol into child."""
|
|
545
550
|
logger.debug(f"🔧 RESOLVE: Resolving extends {parent_name} for child symbol")
|
|
546
|
-
|
|
551
|
+
|
|
547
552
|
try:
|
|
548
553
|
# Load the parent symbol from the same library
|
|
549
|
-
with open(library_path,
|
|
554
|
+
with open(library_path, "r", encoding="utf-8") as f:
|
|
550
555
|
content = f.read()
|
|
551
|
-
|
|
556
|
+
|
|
552
557
|
parsed = sexpdata.loads(content, true=None, false=None, nil=None)
|
|
553
558
|
parent_symbol_data = self._find_symbol_in_parsed_data(parsed, parent_name)
|
|
554
|
-
|
|
559
|
+
|
|
555
560
|
if not parent_symbol_data:
|
|
556
561
|
logger.warning(f"🔧 RESOLVE: Parent symbol {parent_name} not found in library")
|
|
557
562
|
return None
|
|
558
|
-
|
|
563
|
+
|
|
559
564
|
logger.debug(f"🔧 RESOLVE: Found parent symbol {parent_name}")
|
|
560
|
-
|
|
565
|
+
|
|
561
566
|
# Merge parent into child (adapt from circuit-synth logic)
|
|
562
567
|
merged_symbol = self._merge_parent_into_child(child_symbol_data, parent_symbol_data)
|
|
563
568
|
logger.debug(f"🔧 RESOLVE: Merged parent into child symbol")
|
|
564
|
-
|
|
569
|
+
|
|
565
570
|
return merged_symbol
|
|
566
|
-
|
|
571
|
+
|
|
567
572
|
except Exception as e:
|
|
568
573
|
logger.error(f"🔧 RESOLVE: Error resolving extends: {e}")
|
|
569
574
|
return None
|
|
@@ -571,27 +576,30 @@ class SymbolLibraryCache:
|
|
|
571
576
|
def _merge_parent_into_child(self, child_data: List, parent_data: List) -> List:
|
|
572
577
|
"""Merge parent symbol graphics and pins into child symbol."""
|
|
573
578
|
import copy
|
|
574
|
-
|
|
579
|
+
|
|
575
580
|
# Get child and parent symbol names for unit renaming
|
|
576
581
|
child_name = str(child_data[1]).strip('"') if len(child_data) > 1 else "Child"
|
|
577
582
|
parent_name = str(parent_data[1]).strip('"') if len(parent_data) > 1 else "Parent"
|
|
578
|
-
|
|
583
|
+
|
|
579
584
|
logger.debug(f"🔧 MERGE: Merging {parent_name} into {child_name}")
|
|
580
|
-
|
|
585
|
+
|
|
581
586
|
# Start with child symbol structure
|
|
582
587
|
merged = copy.deepcopy(child_data)
|
|
583
|
-
|
|
588
|
+
|
|
584
589
|
# Remove the extends directive from child
|
|
585
|
-
merged = [
|
|
586
|
-
|
|
587
|
-
item
|
|
588
|
-
|
|
589
|
-
|
|
590
|
+
merged = [
|
|
591
|
+
item
|
|
592
|
+
for item in merged
|
|
593
|
+
if not (
|
|
594
|
+
isinstance(item, list) and len(item) >= 2 and item[0] == sexpdata.Symbol("extends")
|
|
595
|
+
)
|
|
596
|
+
]
|
|
597
|
+
|
|
590
598
|
# Copy all graphics and unit definitions from parent
|
|
591
599
|
for item in parent_data[1:]:
|
|
592
600
|
if isinstance(item, list) and len(item) > 0:
|
|
593
601
|
# Copy symbol unit definitions (contain graphics and pins)
|
|
594
|
-
if item[0] == sexpdata.Symbol(
|
|
602
|
+
if item[0] == sexpdata.Symbol("symbol"):
|
|
595
603
|
# Rename unit from parent name to child name
|
|
596
604
|
unit_item = copy.deepcopy(item)
|
|
597
605
|
if len(unit_item) > 1:
|
|
@@ -602,36 +610,36 @@ class SymbolLibraryCache:
|
|
|
602
610
|
logger.debug(f"🔧 MERGE: Renamed unit {old_unit_name} -> {new_unit_name}")
|
|
603
611
|
merged.append(unit_item)
|
|
604
612
|
# Copy other non-property elements (child properties override parent)
|
|
605
|
-
elif item[0] not in [sexpdata.Symbol(
|
|
613
|
+
elif item[0] not in [sexpdata.Symbol("property")]:
|
|
606
614
|
merged.append(copy.deepcopy(item))
|
|
607
|
-
|
|
615
|
+
|
|
608
616
|
logger.debug(f"🔧 MERGE: Merged symbol has {len(merged)} elements")
|
|
609
617
|
return merged
|
|
610
618
|
|
|
611
619
|
def _extract_pins_from_symbol(self, symbol_data: List) -> List[SchematicPin]:
|
|
612
620
|
"""Extract pins from symbol data."""
|
|
613
621
|
pins = []
|
|
614
|
-
|
|
622
|
+
|
|
615
623
|
# Look for symbol sub-definitions like "R_1_1" that contain pins
|
|
616
624
|
for item in symbol_data[1:]:
|
|
617
625
|
if isinstance(item, list) and len(item) > 0:
|
|
618
|
-
if item[0] == sexpdata.Symbol(
|
|
626
|
+
if item[0] == sexpdata.Symbol("symbol"):
|
|
619
627
|
# This is a symbol unit definition, look for pins
|
|
620
628
|
pins.extend(self._extract_pins_from_unit(item))
|
|
621
|
-
|
|
629
|
+
|
|
622
630
|
return pins
|
|
623
631
|
|
|
624
632
|
def _extract_pins_from_unit(self, unit_data: List) -> List[SchematicPin]:
|
|
625
633
|
"""Extract pins from a symbol unit definition."""
|
|
626
634
|
pins = []
|
|
627
|
-
|
|
635
|
+
|
|
628
636
|
for item in unit_data[1:]:
|
|
629
637
|
if isinstance(item, list) and len(item) > 0:
|
|
630
|
-
if item[0] == sexpdata.Symbol(
|
|
638
|
+
if item[0] == sexpdata.Symbol("pin"):
|
|
631
639
|
pin = self._parse_pin_definition(item)
|
|
632
640
|
if pin:
|
|
633
641
|
pins.append(pin)
|
|
634
|
-
|
|
642
|
+
|
|
635
643
|
return pins
|
|
636
644
|
|
|
637
645
|
def _parse_pin_definition(self, pin_data: List) -> Optional[SchematicPin]:
|
|
@@ -640,27 +648,27 @@ class SymbolLibraryCache:
|
|
|
640
648
|
# pin_data format: (pin passive line (at 0 3.81 270) (length 1.27) ...)
|
|
641
649
|
pin_type_str = str(pin_data[1]) if len(pin_data) > 1 else "passive"
|
|
642
650
|
pin_shape_str = str(pin_data[2]) if len(pin_data) > 2 else "line"
|
|
643
|
-
|
|
651
|
+
|
|
644
652
|
position = Point(0, 0)
|
|
645
653
|
length = 2.54
|
|
646
654
|
rotation = 0
|
|
647
655
|
name = "~"
|
|
648
656
|
number = "1"
|
|
649
|
-
|
|
657
|
+
|
|
650
658
|
# Parse pin attributes
|
|
651
659
|
for item in pin_data[3:]:
|
|
652
660
|
if isinstance(item, list) and len(item) > 0:
|
|
653
|
-
if item[0] == sexpdata.Symbol(
|
|
661
|
+
if item[0] == sexpdata.Symbol("at"):
|
|
654
662
|
# (at x y rotation)
|
|
655
663
|
if len(item) >= 3:
|
|
656
664
|
position = Point(float(item[1]), float(item[2]))
|
|
657
665
|
if len(item) >= 4:
|
|
658
666
|
rotation = float(item[3])
|
|
659
|
-
elif item[0] == sexpdata.Symbol(
|
|
667
|
+
elif item[0] == sexpdata.Symbol("length"):
|
|
660
668
|
length = float(item[1])
|
|
661
|
-
elif item[0] == sexpdata.Symbol(
|
|
669
|
+
elif item[0] == sexpdata.Symbol("name"):
|
|
662
670
|
name = str(item[1]).strip('"')
|
|
663
|
-
elif item[0] == sexpdata.Symbol(
|
|
671
|
+
elif item[0] == sexpdata.Symbol("number"):
|
|
664
672
|
number = str(item[1]).strip('"')
|
|
665
673
|
|
|
666
674
|
# Map pin type
|