# Option 2: Dynamic Library Loading Plan
## Executive Summary
Replace the template-based schematic workflow with dynamic symbol loading from KiCad's installed symbol libraries. This would eliminate the 13-component limitation and provide access to ALL KiCad symbols (~10,000+ symbols from standard libraries).
**Current Status (Option 1):**
- ✅ Template-based approach working
- ✅ 13 component types supported
- ❌ Limited symbol variety
- ❌ Requires manual template updates for new types
**Proposed (Option 2):**
- 🎯 Dynamic loading from `.kicad_sym` library files
- 🎯 Access to ~10,000+ KiCad symbols
- 🎯 No template maintenance required
- 🎯 User can specify any library/symbol combination
---
## Problem Analysis
### kicad-skip Library Limitation
**Core Issue:** kicad-skip **cannot create symbols from scratch**. It can only:
1. Clone existing symbols from a loaded schematic
2. Modify properties of cloned symbols
**Current Workaround:** Pre-load template symbols in schematic file
**Proposed Solution:** Load symbols from KiCad's `.kicad_sym` library files, inject them into the schematic's `lib_symbols` section, then clone from there.
---
## KiCad Symbol Library Architecture
### Symbol Library File Format (`.kicad_sym`)
KiCad symbol libraries are S-expression files containing symbol definitions:
```lisp
(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor)
(symbol "Device:R"
(pin_numbers hide)
(pin_names (offset 0))
(in_bom yes)
(on_board yes)
(property "Reference" "R" ...)
(property "Value" "R" ...)
;; Graphics definitions
(symbol "R_0_1" ...)
(symbol "R_1_1"
(pin passive line ...)
)
)
(symbol "Device:C" ...)
(symbol "Device:L" ...)
;; ... thousands more
)
```
### Standard KiCad Library Locations
**Linux:**
- System libraries: `/usr/share/kicad/symbols/`
- User libraries: `~/.local/share/kicad/8.0/symbols/` or `~/.config/kicad/8.0/symbols/`
**Windows:**
- System libraries: `C:\Program Files\KiCad\9.0\share\kicad\symbols\`
- User libraries: `%APPDATA%\kicad\8.0\symbols\`
**macOS:**
- System libraries: `/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/`
- User libraries: `~/Library/Preferences/kicad/8.0/symbols/`
### Standard Library Files
Common libraries (each containing 50-500 symbols):
- `Device.kicad_sym` - Passives (R, C, L, D, LED, Crystal, etc.)
- `Connector.kicad_sym` - Connectors (headers, USB, etc.)
- `Connector_Generic.kicad_sym` - Generic connectors
- `Transistor_BJT.kicad_sym` - Bipolar transistors
- `Transistor_FET.kicad_sym` - MOSFETs
- `Amplifier_Operational.kicad_sym` - Op-amps
- `Regulator_Linear.kicad_sym` - Voltage regulators
- `MCU_*.kicad_sym` - Microcontrollers
- `Interface_*.kicad_sym` - Interface ICs
- ... 100+ more libraries
---
## Implementation Strategy
### Phase 1: Library Discovery & Indexing
**Goal:** Build an index of all available symbols and their locations
**Implementation:**
```python
class SymbolLibraryManager:
def __init__(self):
self.library_paths = []
self.symbol_index = {} # {"Device:R": "/path/to/Device.kicad_sym", ...}
def discover_libraries(self):
"""Find all KiCad symbol libraries on the system"""
search_paths = [
"/usr/share/kicad/symbols/",
os.path.expanduser("~/.local/share/kicad/8.0/symbols/"),
os.path.expanduser("~/.config/kicad/8.0/symbols/"),
]
for search_path in search_paths:
if os.path.exists(search_path):
for lib_file in os.listdir(search_path):
if lib_file.endswith('.kicad_sym'):
self.library_paths.append(os.path.join(search_path, lib_file))
def index_symbols(self):
"""Parse all libraries and build symbol index"""
for lib_path in self.library_paths:
lib_name = os.path.basename(lib_path).replace('.kicad_sym', '')
symbols = self._parse_library(lib_path)
for symbol_name in symbols:
full_name = f"{lib_name}:{symbol_name}"
self.symbol_index[full_name] = {
'library': lib_name,
'library_path': lib_path,
'symbol_name': symbol_name
}
def _parse_library(self, lib_path):
"""Parse .kicad_sym file and extract symbol names"""
# Use sexpdata (already a dependency of kicad-skip)
import sexpdata
with open(lib_path, 'r') as f:
data = sexpdata.load(f)
symbols = []
for item in data[2:]: # Skip header
if isinstance(item, list) and item[0] == Symbol('symbol'):
symbol_name = item[1] # e.g., "Device:R"
# Extract just the symbol part after ':'
if ':' in symbol_name:
symbol_name = symbol_name.split(':')[1]
symbols.append(symbol_name)
return symbols
```
### Phase 2: Dynamic Symbol Injection
**Goal:** Load symbol definition from library file and inject into schematic
**Challenge:** kicad-skip works with loaded schematics, but we need to dynamically add symbols to the `lib_symbols` section.
**Solution:** Modify the schematic's S-expression data directly before loading with kicad-skip:
```python
def inject_symbol_into_schematic(schematic_path, library_path, symbol_name):
"""
1. Read schematic S-expression
2. Read library S-expression
3. Extract symbol definition from library
4. Inject into schematic's lib_symbols section
5. Save modified schematic
6. Reload with kicad-skip
"""
import sexpdata
# Load schematic
with open(schematic_path, 'r') as f:
sch_data = sexpdata.load(f)
# Load library
with open(library_path, 'r') as f:
lib_data = sexpdata.load(f)
# Find symbol definition in library
symbol_def = None
for item in lib_data[2:]:
if isinstance(item, list) and item[0] == Symbol('symbol'):
if symbol_name in str(item[1]):
symbol_def = item
break
if not symbol_def:
raise ValueError(f"Symbol {symbol_name} not found in {library_path}")
# Find lib_symbols section in schematic
lib_symbols_index = None
for i, item in enumerate(sch_data):
if isinstance(item, list) and item[0] == Symbol('lib_symbols'):
lib_symbols_index = i
break
# Inject symbol definition
if lib_symbols_index:
sch_data[lib_symbols_index].append(symbol_def)
# Save modified schematic
with open(schematic_path, 'w') as f:
sexpdata.dump(sch_data, f)
# Reload with kicad-skip
return Schematic(schematic_path)
```
### Phase 3: Template Instance Creation
**Goal:** Create offscreen template instances that can be cloned
**After injection:** Symbol definition is in `lib_symbols`, but we need an instance to clone from:
```python
def create_template_instance(schematic, library_name, symbol_name):
"""
Create an offscreen template instance that can be cloned
Similar to our current _TEMPLATE_R approach
"""
# This requires directly manipulating the S-expression
# Add a symbol instance at offscreen position with special reference
template_ref = f"_TEMPLATE_{library_name}_{symbol_name}"
# Create symbol instance (S-expression)
symbol_instance = [
Symbol('symbol'),
[Symbol('lib_id'), f"{library_name}:{symbol_name}"],
[Symbol('at'), -100, -100 - (len(schematic.symbol) * 10), 0],
[Symbol('unit'), 1],
[Symbol('in_bom'), Symbol('no')],
[Symbol('on_board'), Symbol('no')],
[Symbol('dnp'), Symbol('yes')],
[Symbol('uuid'), str(uuid.uuid4())],
[Symbol('property'), "Reference", template_ref, ...],
# ... more properties
]
# Inject into schematic and reload
# ... (similar to inject_symbol_into_schematic)
return template_ref
```
### Phase 4: User-Facing API
**Goal:** Simple interface for users to add any KiCad symbol
**New MCP Tool: `add_schematic_component_dynamic`**
```python
def add_schematic_component_dynamic(params):
"""
Add component by library:symbol notation
Example:
{
"library": "Device",
"symbol": "R",
"reference": "R1",
"value": "10k",
"x": 100,
"y": 100
}
OR using full notation:
{
"lib_symbol": "Device:R", # Full notation
"reference": "R1",
...
}
"""
lib_symbol = params.get('lib_symbol') or f"{params['library']}:{params['symbol']}"
# 1. Check if symbol is already in schematic's lib_symbols
# 2. If not, inject it from library file
# 3. Create template instance if needed
# 4. Clone template and set properties
return {"success": True, "reference": params['reference']}
```
---
## Advantages Over Template Approach
### ✅ Unlimited Symbol Access
- Access to ~10,000+ standard KiCad symbols
- Support for custom user libraries
- Support for 3rd-party libraries (JLCPCB, Espressif, etc.)
### ✅ No Maintenance Required
- Template doesn't need updates for new component types
- Automatically supports new KiCad library additions
- Works with custom symbol libraries
### ✅ Better User Experience
```
User: "Add an STM32F103C8T6 microcontroller at position 100,100"
AI: *Searches symbol index*
*Finds MCU_ST_STM32F1:STM32F103C8Tx*
*Loads from library*
*Injects into schematic*
*Places component*
✓ Done!
```
### ✅ Flexible Symbol Search
```python
# Find all resistors
symbols = lib_manager.search_symbols(query="resistor")
# Returns: ["Device:R", "Device:R_Small", "Device:R_Network", ...]
# Find all STM32 MCUs
symbols = lib_manager.search_symbols(query="STM32", library="MCU_ST_STM32F1")
```
---
## Challenges & Mitigations
### Challenge 1: S-expression Manipulation Complexity
**Problem:** Directly manipulating S-expression data is error-prone
**Mitigation:**
- Use `sexpdata` library (already a dependency)
- Create helper functions for common operations
- Add comprehensive validation and error handling
- Extensive testing with various symbol types
### Challenge 2: Performance
**Problem:** Loading/reloading schematics after injection could be slow
**Mitigation:**
- **Cache loaded symbols**: Once injected, symbol stays in schematic
- **Batch injection**: Inject multiple symbols at once
- **Lazy loading**: Only inject symbols when first used
### Challenge 3: Symbol Compatibility
**Problem:** Some symbols may have complex pin configurations or multiple units
**Mitigation:**
- Start with simple 2-pin passives (R, C, L)
- Gradually add support for multi-pin ICs
- Handle multi-unit symbols (gates, OpAmp sections) explicitly
- Document supported symbol types
### Challenge 4: Library Version Compatibility
**Problem:** KiCad symbol format may change between versions
**Mitigation:**
- Parse KiCad version from library files
- Version-specific handling if needed
- Fallback to template approach for unsupported formats
---
## Implementation Phases
### Phase A: Proof of Concept (1-2 weeks)
- [ ] Create `SymbolLibraryManager` class
- [ ] Implement library discovery (Linux paths only)
- [ ] Implement symbol indexing
- [ ] Test with Device.kicad_sym (R, C, L)
- [ ] Implement basic S-expression injection
- [ ] Test end-to-end with simple components
### Phase B: Core Functionality (2-3 weeks)
- [ ] Cross-platform library discovery (Windows, macOS)
- [ ] Symbol search functionality
- [ ] Template instance creation automation
- [ ] Multi-pin component support
- [ ] Error handling and validation
- [ ] Unit tests for all operations
### Phase C: MCP Integration (1 week)
- [ ] Create `add_schematic_component_dynamic` tool
- [ ] Update `search_symbols` to use library index
- [ ] Add `list_available_symbols` tool
- [ ] Add `list_symbol_libraries` tool
- [ ] Documentation and examples
### Phase D: Advanced Features (2-3 weeks)
- [ ] Multi-unit symbol support (e.g., quad OpAmps)
- [ ] Custom library registration
- [ ] Symbol caching and optimization
- [ ] 3rd-party library support (JLCPCB, etc.)
- [ ] Symbol preview generation
---
## Migration Strategy
### Backward Compatibility
Keep template-based approach as fallback:
```python
def add_schematic_component(params):
"""Smart component addition with fallback"""
# Try dynamic loading first
try:
if 'library' in params or 'lib_symbol' in params:
return add_schematic_component_dynamic(params)
except Exception as e:
logger.warning(f"Dynamic loading failed: {e}, falling back to template")
# Fallback to template-based
return add_schematic_component_template(params)
```
### Gradual Rollout
1. **Week 1-2:** Implement basic dynamic loading
2. **Week 3-4:** Test with power users, gather feedback
3. **Week 5-6:** Make dynamic loading the default
4. **Week 7+:** Deprecate template-only approach (keep as fallback)
---
## Success Criteria
### Must Have
- [ ] Load symbols from Device.kicad_sym (passives)
- [ ] Support R, C, L, D, LED (5 core types)
- [ ] Cross-platform library discovery
- [ ] Proper error handling
### Should Have
- [ ] Support for all Device.kicad_sym symbols (~50 symbols)
- [ ] Support for Connector.kicad_sym symbols
- [ ] Symbol search by name/keyword
- [ ] Performance: < 1 second per symbol injection
### Nice to Have
- [ ] Support for all standard libraries (~10,000 symbols)
- [ ] Multi-unit symbol support
- [ ] Custom library registration
- [ ] Symbol preview/documentation
---
## Risk Assessment
| Risk | Probability | Impact | Mitigation |
|------|-------------|--------|------------|
| S-expression parsing complexity | High | High | Use proven `sexpdata` library, extensive testing |
| Performance degradation | Medium | Medium | Implement caching, lazy loading |
| KiCad version incompatibility | Low | High | Version detection, format validation |
| Template fallback breaks | Low | Medium | Maintain template approach in parallel |
| User confusion | Medium | Low | Clear documentation, gradual rollout |
---
## Conclusion
Dynamic library loading is **feasible and highly beneficial** for the schematic workflow. While the template-based approach (Option 1) provides immediate value with 13 component types, Option 2 would:
1. **Eliminate the 13-component limitation**
2. **Provide access to 10,000+ KiCad symbols**
3. **Remove manual template maintenance**
4. **Enable true "natural language PCB design"**
**Recommendation:**
- ✅ **Keep Option 1 (expanded template) for immediate use**
- ✅ **Implement Option 2 (dynamic loading) over 6-8 weeks**
- ✅ **Maintain template fallback for compatibility**
This gives users immediate value while we build the robust long-term solution.
---
## References
- [KiCad File Formats Documentation](https://dev-docs.kicad.org/en/file-formats/)
- [kicad-skip GitHub](https://github.com/mvnmgrx/kicad-skip)
- [sexpdata Python Library](https://github.com/jd-boyd/sexpdata)
- [KiCad Symbol Library Format Spec](https://dev-docs.kicad.org/en/file-formats/sexpr-intro/)