Files
KiCAD-MCP-Server/docs/DYNAMIC_LIBRARY_LOADING_PLAN.md
KiCAD MCP Bot 4a543313eb feat: Expand schematic component support from 3 to 13 types + plan dynamic loading
Issue #26 Follow-up: The original fix only supported 3 component types (R, C, LED).
This expands the template-based approach to 13 types while planning for unlimited access.

**Expanded Template (Option 1 - Immediate Solution):**
- Added 10 new component types to template
- Passives: R, C, L, Crystal (4 types)
- Semiconductors: D, LED, Q_NPN, Q_NMOS (4 types)
- ICs: OpAmp, Voltage Regulator (2 types)
- Connectors: Conn_2pin, Conn_4pin (2 types)
- Misc: Switch/Button (1 type)

**Components Now Supported:**
1. Resistor (R)
2. Capacitor (C)
3. Inductor (L)
4. Crystal (Y)
5. Diode (D)
6. LED
7. NPN Transistor (Q_NPN)
8. N-Channel MOSFET (Q_NMOS)
9. Op-Amp (U)
10. Voltage Regulator (U_REG)
11. 2-pin Connector (J2)
12. 4-pin Connector (J4)
13. Push Button/Switch (SW)

**Implementation:**
- Created template_with_symbols_expanded.kicad_sch with all 13 types
- Updated TEMPLATE_MAP with comprehensive type mappings
- Updated project.py to use expanded template by default
- All tests passing (13/13 components added successfully)

**Future Plan (Option 2 - Dynamic Library Loading):**
- Documented comprehensive plan for accessing ALL KiCad symbols (~10,000+)
- Dynamic loading from .kicad_sym library files
- S-expression injection approach
- 6-8 week implementation timeline
- Maintains template fallback for compatibility

**Files Added:**
- python/templates/template_with_symbols_expanded.kicad_sch
- docs/DYNAMIC_LIBRARY_LOADING_PLAN.md

**Files Modified:**
- python/commands/component_schematic.py (expanded TEMPLATE_MAP)
- python/commands/project.py (use expanded template)

This provides immediate value (13 types vs 3) while we plan the long-term
solution of unlimited symbol access through dynamic library loading.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-10 09:54:51 -05:00

15 KiB

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:

(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:

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:

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:

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

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!
# 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:

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