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.
@@ -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, 'r', encoding='utf-8') as f:
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('property'):
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('Reference'):
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('Description'):
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('ki_keywords'):
484
+ elif prop_name == sexpdata.Symbol("ki_keywords"):
485
485
  result["keywords"] = str(prop_value)
486
- elif prop_name == sexpdata.Symbol('Datasheet'):
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('symbol'):
512
+ if item[0] == sexpdata.Symbol("symbol"):
513
513
  available_symbols.append(str(item[1]).strip('"'))
514
-
515
- logger.debug(f"🔧 FIND: Available symbols in library: {available_symbols[:10]}...") # Show first 10
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 (item[0] == sexpdata.Symbol('symbol') and
521
- len(item) > 1 and
522
- str(item[1]).strip('"') == symbol_name):
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('extends'):
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(self, child_symbol_data: List, parent_name: str,
543
- library_path: Path, library_name: str) -> Optional[List]:
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, 'r', encoding='utf-8') as f:
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 = [item for item in merged if not (
586
- isinstance(item, list) and len(item) >= 2 and
587
- item[0] == sexpdata.Symbol('extends')
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('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('property')]:
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('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('pin'):
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('at'):
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('length'):
667
+ elif item[0] == sexpdata.Symbol("length"):
660
668
  length = float(item[1])
661
- elif item[0] == sexpdata.Symbol('name'):
669
+ elif item[0] == sexpdata.Symbol("name"):
662
670
  name = str(item[1]).strip('"')
663
- elif item[0] == sexpdata.Symbol('number'):
671
+ elif item[0] == sexpdata.Symbol("number"):
664
672
  number = str(item[1]).strip('"')
665
673
 
666
674
  # Map pin type