kicad-sch-api 0.0.2__py3-none-any.whl → 0.1.0__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.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- kicad_sch_api/core/components.py +63 -0
- kicad_sch_api/core/formatter.py +56 -11
- kicad_sch_api/core/ic_manager.py +187 -0
- kicad_sch_api/core/junctions.py +206 -0
- kicad_sch_api/core/parser.py +606 -26
- kicad_sch_api/core/schematic.py +739 -8
- kicad_sch_api/core/types.py +102 -7
- kicad_sch_api/core/wires.py +248 -0
- kicad_sch_api/library/cache.py +321 -10
- kicad_sch_api/utils/validation.py +1 -1
- {kicad_sch_api-0.0.2.dist-info → kicad_sch_api-0.1.0.dist-info}/METADATA +13 -17
- kicad_sch_api-0.1.0.dist-info/RECORD +21 -0
- kicad_sch_api-0.0.2.dist-info/RECORD +0 -18
- {kicad_sch_api-0.0.2.dist-info → kicad_sch_api-0.1.0.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.0.2.dist-info → kicad_sch_api-0.1.0.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.0.2.dist-info → kicad_sch_api-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.0.2.dist-info → kicad_sch_api-0.1.0.dist-info}/top_level.txt +0 -0
kicad_sch_api/library/cache.py
CHANGED
|
@@ -14,6 +14,8 @@ from dataclasses import dataclass, field
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
|
16
16
|
|
|
17
|
+
import sexpdata
|
|
18
|
+
|
|
17
19
|
from ..core.types import PinShape, PinType, Point, SchematicPin
|
|
18
20
|
from ..utils.validation import ValidationError
|
|
19
21
|
|
|
@@ -37,6 +39,12 @@ class SymbolDefinition:
|
|
|
37
39
|
power_symbol: bool = False
|
|
38
40
|
graphic_elements: List[Dict[str, Any]] = field(default_factory=list)
|
|
39
41
|
|
|
42
|
+
# Raw KiCAD data for exact format preservation
|
|
43
|
+
raw_kicad_data: Any = None
|
|
44
|
+
|
|
45
|
+
# Symbol inheritance
|
|
46
|
+
extends: Optional[str] = None # Parent symbol name if this symbol extends another
|
|
47
|
+
|
|
40
48
|
# Performance metrics
|
|
41
49
|
load_time: float = 0.0
|
|
42
50
|
access_count: int = 0
|
|
@@ -254,15 +262,19 @@ class SymbolLibraryCache:
|
|
|
254
262
|
Returns:
|
|
255
263
|
Symbol definition if found, None otherwise
|
|
256
264
|
"""
|
|
265
|
+
logger.debug(f"🔧 CACHE: Requesting symbol: {lib_id}")
|
|
266
|
+
|
|
257
267
|
# Check cache first
|
|
258
268
|
if lib_id in self._symbols:
|
|
259
269
|
self._cache_hits += 1
|
|
260
270
|
symbol = self._symbols[lib_id]
|
|
261
271
|
symbol.access_count += 1
|
|
262
272
|
symbol.last_accessed = time.time()
|
|
273
|
+
logger.debug(f"🔧 CACHE: Cache hit for {lib_id}")
|
|
263
274
|
return symbol
|
|
264
275
|
|
|
265
276
|
# Cache miss - try to load symbol
|
|
277
|
+
logger.debug(f"🔧 CACHE: Cache miss for {lib_id}, loading...")
|
|
266
278
|
self._cache_misses += 1
|
|
267
279
|
return self._load_symbol(lib_id)
|
|
268
280
|
|
|
@@ -344,17 +356,22 @@ class SymbolLibraryCache:
|
|
|
344
356
|
|
|
345
357
|
def _load_symbol(self, lib_id: str) -> Optional[SymbolDefinition]:
|
|
346
358
|
"""Load a single symbol from its library."""
|
|
359
|
+
logger.debug(f"🔧 LOAD: Loading symbol {lib_id}")
|
|
360
|
+
|
|
347
361
|
if ":" not in lib_id:
|
|
348
|
-
logger.warning(f"Invalid lib_id format: {lib_id}")
|
|
362
|
+
logger.warning(f"🔧 LOAD: Invalid lib_id format: {lib_id}")
|
|
349
363
|
return None
|
|
350
364
|
|
|
351
365
|
library_name, symbol_name = lib_id.split(":", 1)
|
|
366
|
+
logger.debug(f"🔧 LOAD: Library: {library_name}, Symbol: {symbol_name}")
|
|
352
367
|
|
|
353
368
|
if library_name not in self._library_index:
|
|
354
|
-
logger.warning(f"Library not found: {library_name}")
|
|
369
|
+
logger.warning(f"🔧 LOAD: Library not found: {library_name}")
|
|
370
|
+
logger.debug(f"🔧 LOAD: Available libraries: {list(self._library_index.keys())}")
|
|
355
371
|
return None
|
|
356
372
|
|
|
357
373
|
library_path = self._library_index[library_name]
|
|
374
|
+
logger.debug(f"🔧 LOAD: Library path: {library_path}")
|
|
358
375
|
return self._load_symbol_from_library(library_path, lib_id)
|
|
359
376
|
|
|
360
377
|
def _load_symbol_from_library(
|
|
@@ -364,22 +381,32 @@ class SymbolLibraryCache:
|
|
|
364
381
|
start_time = time.time()
|
|
365
382
|
|
|
366
383
|
try:
|
|
367
|
-
# This is a simplified version - in reality, you'd parse the .kicad_sym file
|
|
368
|
-
# For now, create a basic symbol definition
|
|
369
384
|
library_name, symbol_name = lib_id.split(":", 1)
|
|
370
385
|
|
|
371
|
-
#
|
|
372
|
-
|
|
386
|
+
# Parse the .kicad_sym file to find the symbol
|
|
387
|
+
symbol_data = self._parse_kicad_symbol_file(library_path, lib_id)
|
|
388
|
+
if not symbol_data:
|
|
389
|
+
logger.warning(f"Symbol {symbol_name} not found in {library_path}")
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
# Create SymbolDefinition from parsed data
|
|
373
393
|
symbol = SymbolDefinition(
|
|
374
394
|
lib_id=lib_id,
|
|
375
395
|
name=symbol_name,
|
|
376
396
|
library=library_name,
|
|
377
|
-
reference_prefix=
|
|
378
|
-
description=
|
|
379
|
-
|
|
397
|
+
reference_prefix=symbol_data.get("reference_prefix", "U"),
|
|
398
|
+
description=symbol_data.get("description", ""),
|
|
399
|
+
keywords=symbol_data.get("keywords", ""),
|
|
400
|
+
datasheet=symbol_data.get("datasheet", "~"),
|
|
401
|
+
pins=symbol_data.get("pins", []),
|
|
402
|
+
extends=symbol_data.get("extends"), # Store extends information
|
|
380
403
|
load_time=time.time() - start_time,
|
|
381
404
|
)
|
|
382
405
|
|
|
406
|
+
# Store the raw symbol data for later use in schematic generation
|
|
407
|
+
symbol.raw_kicad_data = symbol_data.get("raw_data", {})
|
|
408
|
+
logger.debug(f"🔧 CREATED: SymbolDefinition for {lib_id}, extends: {symbol.extends}")
|
|
409
|
+
|
|
383
410
|
self._symbols[lib_id] = symbol
|
|
384
411
|
self._symbol_index[symbol_name] = lib_id
|
|
385
412
|
self._total_load_time += symbol.load_time
|
|
@@ -391,6 +418,285 @@ class SymbolLibraryCache:
|
|
|
391
418
|
logger.error(f"Error loading symbol {lib_id} from {library_path}: {e}")
|
|
392
419
|
return None
|
|
393
420
|
|
|
421
|
+
def _parse_kicad_symbol_file(self, library_path: Path, lib_id: str) -> Optional[Dict[str, Any]]:
|
|
422
|
+
"""Parse a KiCAD .kicad_sym file to extract a specific symbol."""
|
|
423
|
+
try:
|
|
424
|
+
# Extract symbol name from lib_id
|
|
425
|
+
library_name, symbol_name = lib_id.split(":", 1)
|
|
426
|
+
|
|
427
|
+
with open(library_path, 'r', encoding='utf-8') as f:
|
|
428
|
+
content = f.read()
|
|
429
|
+
|
|
430
|
+
# Parse the S-expression with symbol preservation
|
|
431
|
+
parsed = sexpdata.loads(content, true=None, false=None, nil=None)
|
|
432
|
+
logger.debug(f"🔧 PARSE: Parsed library file with {len(parsed)} top-level items")
|
|
433
|
+
|
|
434
|
+
# Find the symbol we're looking for
|
|
435
|
+
symbol_data = self._find_symbol_in_parsed_data(parsed, symbol_name)
|
|
436
|
+
if not symbol_data:
|
|
437
|
+
logger.debug(f"🔧 PARSE: Symbol {symbol_name} not found in {library_path}")
|
|
438
|
+
return None
|
|
439
|
+
|
|
440
|
+
logger.debug(f"🔧 PARSE: Found symbol {symbol_name} in library")
|
|
441
|
+
|
|
442
|
+
# Extract the library name and symbol name for resolution
|
|
443
|
+
library_name, symbol_name = lib_id.split(":", 1)
|
|
444
|
+
|
|
445
|
+
# Check if this symbol extends another symbol
|
|
446
|
+
extends_symbol = self._check_extends_directive(symbol_data)
|
|
447
|
+
logger.debug(f"🔧 CACHE: Symbol {lib_id} extends: {extends_symbol}")
|
|
448
|
+
|
|
449
|
+
# If this symbol extends another, we need to resolve it
|
|
450
|
+
if extends_symbol:
|
|
451
|
+
resolved_symbol_data = self._resolve_extends_relationship(
|
|
452
|
+
symbol_data, extends_symbol, library_path, library_name
|
|
453
|
+
)
|
|
454
|
+
if resolved_symbol_data:
|
|
455
|
+
symbol_data = resolved_symbol_data
|
|
456
|
+
extends_symbol = None # Clear extends after resolution
|
|
457
|
+
logger.debug(f"🔧 CACHE: Resolved extends for {lib_id}")
|
|
458
|
+
|
|
459
|
+
# Extract symbol information
|
|
460
|
+
result = {
|
|
461
|
+
"raw_data": symbol_data, # Store the raw parsed data
|
|
462
|
+
"reference_prefix": "U", # Default
|
|
463
|
+
"description": "",
|
|
464
|
+
"keywords": "",
|
|
465
|
+
"datasheet": "~",
|
|
466
|
+
"pins": [],
|
|
467
|
+
"extends": extends_symbol # Should be None after resolution
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
# Extract properties from the symbol
|
|
471
|
+
for item in symbol_data[1:]:
|
|
472
|
+
if isinstance(item, list) and len(item) > 0:
|
|
473
|
+
if item[0] == sexpdata.Symbol('property'):
|
|
474
|
+
prop_name = item[1]
|
|
475
|
+
prop_value = item[2]
|
|
476
|
+
|
|
477
|
+
logger.debug(f"🔧 Processing property: {prop_name} = {prop_value}")
|
|
478
|
+
if prop_name == sexpdata.Symbol('Reference'):
|
|
479
|
+
result["reference_prefix"] = str(prop_value)
|
|
480
|
+
logger.debug(f"🔧 Set reference_prefix: {str(prop_value)}")
|
|
481
|
+
elif prop_name == sexpdata.Symbol('Description'):
|
|
482
|
+
result["Description"] = str(prop_value) # Keep original case
|
|
483
|
+
logger.debug(f"🔧 Set Description: {str(prop_value)}")
|
|
484
|
+
elif prop_name == sexpdata.Symbol('ki_keywords'):
|
|
485
|
+
result["keywords"] = str(prop_value)
|
|
486
|
+
elif prop_name == sexpdata.Symbol('Datasheet'):
|
|
487
|
+
result["Datasheet"] = str(prop_value) # Keep original case
|
|
488
|
+
logger.debug(f"🔧 Set Datasheet: {str(prop_value)}")
|
|
489
|
+
|
|
490
|
+
# Extract pins (this is simplified - pins are in symbol sub-definitions)
|
|
491
|
+
# For now, we'll extract pins from the actual symbol structure
|
|
492
|
+
result["pins"] = self._extract_pins_from_symbol(symbol_data)
|
|
493
|
+
|
|
494
|
+
return result
|
|
495
|
+
|
|
496
|
+
except Exception as e:
|
|
497
|
+
logger.error(f"Error parsing {library_path}: {e}")
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
def _find_symbol_in_parsed_data(self, parsed_data: List, symbol_name: str) -> Optional[List]:
|
|
501
|
+
"""Find a specific symbol in parsed KiCAD library data."""
|
|
502
|
+
logger.debug(f"🔧 FIND: Looking for symbol '{symbol_name}' in parsed data")
|
|
503
|
+
|
|
504
|
+
if not isinstance(parsed_data, list):
|
|
505
|
+
logger.debug(f"🔧 FIND: Parsed data is not a list: {type(parsed_data)}")
|
|
506
|
+
return None
|
|
507
|
+
|
|
508
|
+
# First, log all available symbols for debugging
|
|
509
|
+
available_symbols = []
|
|
510
|
+
for item in parsed_data:
|
|
511
|
+
if isinstance(item, list) and len(item) >= 2:
|
|
512
|
+
if item[0] == sexpdata.Symbol('symbol'):
|
|
513
|
+
available_symbols.append(str(item[1]).strip('"'))
|
|
514
|
+
|
|
515
|
+
logger.debug(f"🔧 FIND: Available symbols in library: {available_symbols[:10]}...") # Show first 10
|
|
516
|
+
|
|
517
|
+
# Search through the parsed data for the symbol
|
|
518
|
+
for item in parsed_data:
|
|
519
|
+
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):
|
|
523
|
+
logger.debug(f"🔧 FIND: Found symbol '{symbol_name}'")
|
|
524
|
+
return item
|
|
525
|
+
|
|
526
|
+
logger.debug(f"🔧 FIND: Symbol '{symbol_name}' not found in library")
|
|
527
|
+
return None
|
|
528
|
+
|
|
529
|
+
def _check_extends_directive(self, symbol_data: List) -> Optional[str]:
|
|
530
|
+
"""Check if symbol has extends directive and return parent symbol name."""
|
|
531
|
+
if not isinstance(symbol_data, list):
|
|
532
|
+
return None
|
|
533
|
+
|
|
534
|
+
for item in symbol_data[1:]:
|
|
535
|
+
if isinstance(item, list) and len(item) >= 2:
|
|
536
|
+
if item[0] == sexpdata.Symbol('extends'):
|
|
537
|
+
parent_name = str(item[1]).strip('"')
|
|
538
|
+
logger.debug(f"Found extends directive: {parent_name}")
|
|
539
|
+
return parent_name
|
|
540
|
+
return None
|
|
541
|
+
|
|
542
|
+
def _resolve_extends_relationship(self, child_symbol_data: List, parent_name: str,
|
|
543
|
+
library_path: Path, library_name: str) -> Optional[List]:
|
|
544
|
+
"""Resolve extends relationship by merging parent symbol into child."""
|
|
545
|
+
logger.debug(f"🔧 RESOLVE: Resolving extends {parent_name} for child symbol")
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
# Load the parent symbol from the same library
|
|
549
|
+
with open(library_path, 'r', encoding='utf-8') as f:
|
|
550
|
+
content = f.read()
|
|
551
|
+
|
|
552
|
+
parsed = sexpdata.loads(content, true=None, false=None, nil=None)
|
|
553
|
+
parent_symbol_data = self._find_symbol_in_parsed_data(parsed, parent_name)
|
|
554
|
+
|
|
555
|
+
if not parent_symbol_data:
|
|
556
|
+
logger.warning(f"🔧 RESOLVE: Parent symbol {parent_name} not found in library")
|
|
557
|
+
return None
|
|
558
|
+
|
|
559
|
+
logger.debug(f"🔧 RESOLVE: Found parent symbol {parent_name}")
|
|
560
|
+
|
|
561
|
+
# Merge parent into child (adapt from circuit-synth logic)
|
|
562
|
+
merged_symbol = self._merge_parent_into_child(child_symbol_data, parent_symbol_data)
|
|
563
|
+
logger.debug(f"🔧 RESOLVE: Merged parent into child symbol")
|
|
564
|
+
|
|
565
|
+
return merged_symbol
|
|
566
|
+
|
|
567
|
+
except Exception as e:
|
|
568
|
+
logger.error(f"🔧 RESOLVE: Error resolving extends: {e}")
|
|
569
|
+
return None
|
|
570
|
+
|
|
571
|
+
def _merge_parent_into_child(self, child_data: List, parent_data: List) -> List:
|
|
572
|
+
"""Merge parent symbol graphics and pins into child symbol."""
|
|
573
|
+
import copy
|
|
574
|
+
|
|
575
|
+
# Get child and parent symbol names for unit renaming
|
|
576
|
+
child_name = str(child_data[1]).strip('"') if len(child_data) > 1 else "Child"
|
|
577
|
+
parent_name = str(parent_data[1]).strip('"') if len(parent_data) > 1 else "Parent"
|
|
578
|
+
|
|
579
|
+
logger.debug(f"🔧 MERGE: Merging {parent_name} into {child_name}")
|
|
580
|
+
|
|
581
|
+
# Start with child symbol structure
|
|
582
|
+
merged = copy.deepcopy(child_data)
|
|
583
|
+
|
|
584
|
+
# 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
|
+
# Copy all graphics and unit definitions from parent
|
|
591
|
+
for item in parent_data[1:]:
|
|
592
|
+
if isinstance(item, list) and len(item) > 0:
|
|
593
|
+
# Copy symbol unit definitions (contain graphics and pins)
|
|
594
|
+
if item[0] == sexpdata.Symbol('symbol'):
|
|
595
|
+
# Rename unit from parent name to child name
|
|
596
|
+
unit_item = copy.deepcopy(item)
|
|
597
|
+
if len(unit_item) > 1:
|
|
598
|
+
old_unit_name = str(unit_item[1]).strip('"')
|
|
599
|
+
# Replace parent name with child name in unit name
|
|
600
|
+
new_unit_name = old_unit_name.replace(parent_name, child_name)
|
|
601
|
+
unit_item[1] = new_unit_name
|
|
602
|
+
logger.debug(f"🔧 MERGE: Renamed unit {old_unit_name} -> {new_unit_name}")
|
|
603
|
+
merged.append(unit_item)
|
|
604
|
+
# Copy other non-property elements (child properties override parent)
|
|
605
|
+
elif item[0] not in [sexpdata.Symbol('property')]:
|
|
606
|
+
merged.append(copy.deepcopy(item))
|
|
607
|
+
|
|
608
|
+
logger.debug(f"🔧 MERGE: Merged symbol has {len(merged)} elements")
|
|
609
|
+
return merged
|
|
610
|
+
|
|
611
|
+
def _extract_pins_from_symbol(self, symbol_data: List) -> List[SchematicPin]:
|
|
612
|
+
"""Extract pins from symbol data."""
|
|
613
|
+
pins = []
|
|
614
|
+
|
|
615
|
+
# Look for symbol sub-definitions like "R_1_1" that contain pins
|
|
616
|
+
for item in symbol_data[1:]:
|
|
617
|
+
if isinstance(item, list) and len(item) > 0:
|
|
618
|
+
if item[0] == sexpdata.Symbol('symbol'):
|
|
619
|
+
# This is a symbol unit definition, look for pins
|
|
620
|
+
pins.extend(self._extract_pins_from_unit(item))
|
|
621
|
+
|
|
622
|
+
return pins
|
|
623
|
+
|
|
624
|
+
def _extract_pins_from_unit(self, unit_data: List) -> List[SchematicPin]:
|
|
625
|
+
"""Extract pins from a symbol unit definition."""
|
|
626
|
+
pins = []
|
|
627
|
+
|
|
628
|
+
for item in unit_data[1:]:
|
|
629
|
+
if isinstance(item, list) and len(item) > 0:
|
|
630
|
+
if item[0] == sexpdata.Symbol('pin'):
|
|
631
|
+
pin = self._parse_pin_definition(item)
|
|
632
|
+
if pin:
|
|
633
|
+
pins.append(pin)
|
|
634
|
+
|
|
635
|
+
return pins
|
|
636
|
+
|
|
637
|
+
def _parse_pin_definition(self, pin_data: List) -> Optional[SchematicPin]:
|
|
638
|
+
"""Parse a pin definition from KiCAD format."""
|
|
639
|
+
try:
|
|
640
|
+
# pin_data format: (pin passive line (at 0 3.81 270) (length 1.27) ...)
|
|
641
|
+
pin_type_str = str(pin_data[1]) if len(pin_data) > 1 else "passive"
|
|
642
|
+
pin_shape_str = str(pin_data[2]) if len(pin_data) > 2 else "line"
|
|
643
|
+
|
|
644
|
+
position = Point(0, 0)
|
|
645
|
+
length = 2.54
|
|
646
|
+
rotation = 0
|
|
647
|
+
name = "~"
|
|
648
|
+
number = "1"
|
|
649
|
+
|
|
650
|
+
# Parse pin attributes
|
|
651
|
+
for item in pin_data[3:]:
|
|
652
|
+
if isinstance(item, list) and len(item) > 0:
|
|
653
|
+
if item[0] == sexpdata.Symbol('at'):
|
|
654
|
+
# (at x y rotation)
|
|
655
|
+
if len(item) >= 3:
|
|
656
|
+
position = Point(float(item[1]), float(item[2]))
|
|
657
|
+
if len(item) >= 4:
|
|
658
|
+
rotation = float(item[3])
|
|
659
|
+
elif item[0] == sexpdata.Symbol('length'):
|
|
660
|
+
length = float(item[1])
|
|
661
|
+
elif item[0] == sexpdata.Symbol('name'):
|
|
662
|
+
name = str(item[1]).strip('"')
|
|
663
|
+
elif item[0] == sexpdata.Symbol('number'):
|
|
664
|
+
number = str(item[1]).strip('"')
|
|
665
|
+
|
|
666
|
+
# Map pin type
|
|
667
|
+
pin_type = PinType.PASSIVE
|
|
668
|
+
if pin_type_str == "input":
|
|
669
|
+
pin_type = PinType.INPUT
|
|
670
|
+
elif pin_type_str == "output":
|
|
671
|
+
pin_type = PinType.OUTPUT
|
|
672
|
+
elif pin_type_str == "bidirectional":
|
|
673
|
+
pin_type = PinType.BIDIRECTIONAL
|
|
674
|
+
elif pin_type_str == "power_in":
|
|
675
|
+
pin_type = PinType.POWER_IN
|
|
676
|
+
elif pin_type_str == "power_out":
|
|
677
|
+
pin_type = PinType.POWER_OUT
|
|
678
|
+
|
|
679
|
+
# Map pin shape
|
|
680
|
+
pin_shape = PinShape.LINE
|
|
681
|
+
if pin_shape_str == "inverted":
|
|
682
|
+
pin_shape = PinShape.INVERTED
|
|
683
|
+
elif pin_shape_str == "clock":
|
|
684
|
+
pin_shape = PinShape.CLOCK
|
|
685
|
+
|
|
686
|
+
return SchematicPin(
|
|
687
|
+
number=number,
|
|
688
|
+
name=name,
|
|
689
|
+
position=position,
|
|
690
|
+
pin_type=pin_type,
|
|
691
|
+
pin_shape=pin_shape,
|
|
692
|
+
length=length,
|
|
693
|
+
rotation=rotation,
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
except Exception as e:
|
|
697
|
+
logger.error(f"Error parsing pin definition: {e}")
|
|
698
|
+
return None
|
|
699
|
+
|
|
394
700
|
def _load_library(self, library_path: Path) -> bool:
|
|
395
701
|
"""Load all symbols from a library file."""
|
|
396
702
|
library_name = library_path.stem
|
|
@@ -471,6 +777,8 @@ class SymbolLibraryCache:
|
|
|
471
777
|
Path("/usr/share/kicad/symbols"),
|
|
472
778
|
Path("/usr/local/share/kicad/symbols"),
|
|
473
779
|
Path.home() / ".local/share/kicad/symbols",
|
|
780
|
+
# macOS KiCAD.app bundle path
|
|
781
|
+
Path("/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols"),
|
|
474
782
|
]
|
|
475
783
|
)
|
|
476
784
|
|
|
@@ -482,7 +790,10 @@ class SymbolLibraryCache:
|
|
|
482
790
|
]
|
|
483
791
|
)
|
|
484
792
|
|
|
485
|
-
|
|
793
|
+
logger.debug(f"Potential library search paths: {search_paths}")
|
|
794
|
+
existing_paths = [path for path in search_paths if path.exists()]
|
|
795
|
+
logger.debug(f"Existing library search paths: {existing_paths}")
|
|
796
|
+
return existing_paths
|
|
486
797
|
|
|
487
798
|
def _load_persistent_index(self):
|
|
488
799
|
"""Load persistent symbol index from disk."""
|
|
@@ -87,7 +87,7 @@ class SchematicValidator:
|
|
|
87
87
|
"""
|
|
88
88
|
self.strict = strict
|
|
89
89
|
self.issues = []
|
|
90
|
-
self._valid_reference_pattern = re.compile(r"^(#[A-Z]+[0-9]+|[A-Z]+[0-9]*)$")
|
|
90
|
+
self._valid_reference_pattern = re.compile(r"^(#[A-Z]+[0-9]+|[A-Z]+[0-9]*[A-Z]?)$")
|
|
91
91
|
self._valid_lib_id_pattern = re.compile(r"^[^:]+:[^:]+$")
|
|
92
92
|
|
|
93
93
|
def validate_schematic_data(self, schematic_data: Dict[str, Any]) -> List[ValidationIssue]:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kicad-sch-api
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Professional KiCAD schematic manipulation library with exact format preservation and AI agent integration
|
|
5
5
|
Author-email: Circuit-Synth <info@circuit-synth.com>
|
|
6
6
|
Maintainer-email: Circuit-Synth <info@circuit-synth.com>
|
|
7
|
-
License: MIT
|
|
7
|
+
License-Expression: MIT
|
|
8
8
|
Project-URL: Homepage, https://github.com/circuit-synth/kicad-sch-api
|
|
9
9
|
Project-URL: Documentation, https://circuit-synth.github.io/kicad-sch-api/
|
|
10
10
|
Project-URL: Repository, https://github.com/circuit-synth/kicad-sch-api.git
|
|
@@ -15,7 +15,6 @@ Classifier: Development Status :: 4 - Beta
|
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
18
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
19
18
|
Classifier: Programming Language :: Python :: 3
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.8
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -59,13 +58,13 @@ Dynamic: license-file
|
|
|
59
58
|
|
|
60
59
|
## 🆚 vs. Existing Solutions
|
|
61
60
|
|
|
62
|
-
| Feature | kicad-sch-api |
|
|
63
|
-
|
|
64
|
-
| **Schematic Support** | ✅ Full |
|
|
61
|
+
| Feature | kicad-sch-api | Other Solutions | KiCAD Official API |
|
|
62
|
+
|---------|---------------|-----------------|-------------------|
|
|
63
|
+
| **Schematic Support** | ✅ Full | ⚠️ Varies | ❌ PCB Only |
|
|
65
64
|
| **Format Preservation** | ✅ Exact | ❌ Basic | N/A |
|
|
66
65
|
| **Performance** | ✅ Optimized | ⚠️ Basic | N/A |
|
|
67
|
-
| **Library Management** | ✅ Advanced | ⚠️
|
|
68
|
-
| **Runtime Dependencies** | ❌ None |
|
|
66
|
+
| **Library Management** | ✅ Advanced | ⚠️ Limited | N/A |
|
|
67
|
+
| **Runtime Dependencies** | ❌ None | ⚠️ Varies | ✅ KiCAD Required |
|
|
69
68
|
|
|
70
69
|
## 📦 Installation
|
|
71
70
|
|
|
@@ -89,22 +88,19 @@ npm run build
|
|
|
89
88
|
```python
|
|
90
89
|
import kicad_sch_api as ksa
|
|
91
90
|
|
|
92
|
-
#
|
|
93
|
-
sch = ksa.
|
|
91
|
+
# Create new schematic
|
|
92
|
+
sch = ksa.create_schematic('My Circuit')
|
|
94
93
|
|
|
95
94
|
# Add components
|
|
96
|
-
resistor = sch.components.add('Device:R',
|
|
97
|
-
capacitor = sch.components.add('Device:C',
|
|
95
|
+
resistor = sch.components.add('Device:R', reference='R1', value='10k', position=(100, 100))
|
|
96
|
+
capacitor = sch.components.add('Device:C', reference='C1', value='0.1uF', position=(150, 100))
|
|
98
97
|
|
|
99
98
|
# Update properties
|
|
100
99
|
resistor.footprint = 'Resistor_SMD:R_0603_1608Metric'
|
|
101
100
|
resistor.set_property('MPN', 'RC0603FR-0710KL')
|
|
102
101
|
|
|
103
|
-
# Connect components
|
|
104
|
-
sch.add_wire(resistor.get_pin_position('2'), capacitor.get_pin_position('1'))
|
|
105
|
-
|
|
106
102
|
# Save with exact format preservation
|
|
107
|
-
sch.save()
|
|
103
|
+
sch.save('my_circuit.kicad_sch')
|
|
108
104
|
```
|
|
109
105
|
|
|
110
106
|
### Advanced Operations
|
|
@@ -204,7 +200,7 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
204
200
|
## 🔗 Related Projects
|
|
205
201
|
|
|
206
202
|
- **[circuit-synth](https://github.com/circuit-synth/circuit-synth)**: Comprehensive circuit design automation
|
|
207
|
-
- **[
|
|
203
|
+
- **[sexpdata](https://github.com/jd-boyd/sexpdata)**: S-expression parsing library
|
|
208
204
|
|
|
209
205
|
---
|
|
210
206
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
kicad_sch_api/__init__.py,sha256=mogTeOic6O-WWOfpoRuBzPicVNha-gbdMrhJX4ir5PY,2821
|
|
2
|
+
kicad_sch_api/py.typed,sha256=e4ldqxwpY7pNDG1olbvj4HSKr8sZ9vxgA_2ek8xXn-Q,70
|
|
3
|
+
kicad_sch_api/core/__init__.py,sha256=ur_KeYBlGKl-e1hLpLdxAhGV2A-PCCGkcqd0r6KSeBA,566
|
|
4
|
+
kicad_sch_api/core/components.py,sha256=TiQrl7jdDTnuCWKdrlQC8-3b9PU58KoxTTnPX6VyZZk,24759
|
|
5
|
+
kicad_sch_api/core/formatter.py,sha256=WhJPII8-hd4ou3_4AYa4UB8704cEE8rLshhBhVHnY7I,14173
|
|
6
|
+
kicad_sch_api/core/ic_manager.py,sha256=5pw0FtUBXeF6afbiYej7kQe6ga32N4bNTScEHWYGq7A,7298
|
|
7
|
+
kicad_sch_api/core/junctions.py,sha256=p99iv8pBb-3oktVsTeHgjl7QkkZa9EeN32nSy4g23o4,6251
|
|
8
|
+
kicad_sch_api/core/parser.py,sha256=xVWzRwGPklROWe9_VoauVeoFsCZjKQGyo6AsSg0bqgU,44174
|
|
9
|
+
kicad_sch_api/core/schematic.py,sha256=JxOTHbASxcOVEFK2Ms1s7q7uERaKVQb0Ojomfyip99I,45176
|
|
10
|
+
kicad_sch_api/core/types.py,sha256=sn6_OnOKnNjc_6oXpASEfWI6f7oHsMyJaHk8kc3QFMk,12823
|
|
11
|
+
kicad_sch_api/core/wires.py,sha256=kvANBg79KxOd5TKZNpHbjMyouZbGwRKLPNz4ePvvHgk,8207
|
|
12
|
+
kicad_sch_api/library/__init__.py,sha256=NG9UTdcpn25Bl9tPsYs9ED7bvpaVPVdtLMbnxkQkOnU,250
|
|
13
|
+
kicad_sch_api/library/cache.py,sha256=NPJT-apWH_JfVG1aXJ0gDnfGu8es_2tYiyZr-chcTxY,33511
|
|
14
|
+
kicad_sch_api/utils/__init__.py,sha256=1V_yGgI7jro6MUc4Pviux_WIeJ1wmiYFID186SZwWLQ,277
|
|
15
|
+
kicad_sch_api/utils/validation.py,sha256=XlWGRZJb3cOPYpU9sLQQgC_NASwbi6W-LCN7PzUmaPY,15626
|
|
16
|
+
kicad_sch_api-0.1.0.dist-info/licenses/LICENSE,sha256=Em65Nvte1G9MHc0rHqtYuGkCPcshD588itTa358J6gs,1070
|
|
17
|
+
kicad_sch_api-0.1.0.dist-info/METADATA,sha256=LIRlM5Iev8fS3Elb0zynJXiZIwmIqmUqcaYmnKN8WdA,6674
|
|
18
|
+
kicad_sch_api-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
kicad_sch_api-0.1.0.dist-info/entry_points.txt,sha256=VWKsFi2Jv7G_tmio3cNVhhIBfv_OZFaKa-T_ED84lc8,57
|
|
20
|
+
kicad_sch_api-0.1.0.dist-info/top_level.txt,sha256=n0ex4gOJ1b_fARowcGqRzyOGZcHRhc5LZa6_vVgGxcI,14
|
|
21
|
+
kicad_sch_api-0.1.0.dist-info/RECORD,,
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
kicad_sch_api/__init__.py,sha256=mogTeOic6O-WWOfpoRuBzPicVNha-gbdMrhJX4ir5PY,2821
|
|
2
|
-
kicad_sch_api/py.typed,sha256=e4ldqxwpY7pNDG1olbvj4HSKr8sZ9vxgA_2ek8xXn-Q,70
|
|
3
|
-
kicad_sch_api/core/__init__.py,sha256=ur_KeYBlGKl-e1hLpLdxAhGV2A-PCCGkcqd0r6KSeBA,566
|
|
4
|
-
kicad_sch_api/core/components.py,sha256=P_-qZPNpA8LAT0M3dLHFF6XSFyNAnSlcKjUf6aREdUo,22425
|
|
5
|
-
kicad_sch_api/core/formatter.py,sha256=MVF3Hhc5ZVPyVDYnGcxb88q0x0UTr2DQa45gppiFqNk,11953
|
|
6
|
-
kicad_sch_api/core/parser.py,sha256=Ao1j8JPWHGBkznUi9k-RO4_sLcCrkE_5ialSxZAd0cQ,17943
|
|
7
|
-
kicad_sch_api/core/schematic.py,sha256=O76nZvj4qffHkFrMJV5Z35xU95efPW-_mtAD8Nni7ao,15553
|
|
8
|
-
kicad_sch_api/core/types.py,sha256=VyzloTl4RbjMKj0TKu5rEZ-rtxtiT8nvQw8L6xawEvs,9980
|
|
9
|
-
kicad_sch_api/library/__init__.py,sha256=NG9UTdcpn25Bl9tPsYs9ED7bvpaVPVdtLMbnxkQkOnU,250
|
|
10
|
-
kicad_sch_api/library/cache.py,sha256=_JtzEGgO7ViIKF4W2zVrvmHQBIiosp9hOr9pG06Tw6I,18917
|
|
11
|
-
kicad_sch_api/utils/__init__.py,sha256=1V_yGgI7jro6MUc4Pviux_WIeJ1wmiYFID186SZwWLQ,277
|
|
12
|
-
kicad_sch_api/utils/validation.py,sha256=7QZBtPHKu24SA6Bhkj7M-rxs76AVQZWhISDfsJYfV_0,15620
|
|
13
|
-
kicad_sch_api-0.0.2.dist-info/licenses/LICENSE,sha256=Em65Nvte1G9MHc0rHqtYuGkCPcshD588itTa358J6gs,1070
|
|
14
|
-
kicad_sch_api-0.0.2.dist-info/METADATA,sha256=0wa5C_1GHCirtAdlUTh2Eqqa1AreZkeTts6WkQaNjd8,6810
|
|
15
|
-
kicad_sch_api-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
-
kicad_sch_api-0.0.2.dist-info/entry_points.txt,sha256=VWKsFi2Jv7G_tmio3cNVhhIBfv_OZFaKa-T_ED84lc8,57
|
|
17
|
-
kicad_sch_api-0.0.2.dist-info/top_level.txt,sha256=n0ex4gOJ1b_fARowcGqRzyOGZcHRhc5LZa6_vVgGxcI,14
|
|
18
|
-
kicad_sch_api-0.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|