Common Recipes and Patterns

This guide shows solutions to common tasks with kicad-sch-api.

Table of Contents

  1. Basic Circuit Patterns

  2. Component Management

  3. Wiring and Connectivity

  4. Circuit Analysis

  5. Connectivity Analysis ⭐ NEW

  6. Hierarchy Management ⭐ NEW

  7. Batch Operations

  8. Advanced Patterns


Basic Circuit Patterns

Recipe 1: Voltage Divider

import kicad_sch_api as ksa

def create_voltage_divider(sch, name, r1_val, r2_val, position, v_in_label="VIN", v_out_label="VOUT"):
    """Create a voltage divider at the specified position.

    Args:
        sch: Schematic object
        name: Unique identifier for this divider
        r1_val: Top resistor value (string, e.g., "10k")
        r2_val: Bottom resistor value
        position: (x, y) tuple for placement
        v_in_label: Input voltage label
        v_out_label: Output voltage label

    Returns:
        Tuple of (r1_component, r2_component)
    """
    x, y = position

    # Add resistors
    r1 = sch.components.add(
        "Device:R",
        f"R_{name}_1",
        r1_val,
        (x, y)
    )

    r2 = sch.components.add(
        "Device:R",
        f"R_{name}_2",
        r2_val,
        (x, y + 20)  # 20mm below first resistor
    )

    # Connect resistors
    sch.add_wire_between_pins(f"R_{name}_1", "2", f"R_{name}_2", "1")

    # Add connection points and labels
    sch.add_wire_to_pin((x, y - 10), f"R_{name}_1", "1")
    sch.add_label(v_in_label, (x, y - 10))

    sch.add_wire_to_pin((x + 10, y + 10), f"R_{name}_1", "2")
    sch.add_label(v_out_label, (x + 10, y + 10))

    sch.add_wire_to_pin((x, y + 30), f"R_{name}_2", "2")
    sch.add_label("GND", (x, y + 30))

    return r1, r2

# Use it
sch = ksa.create_schematic("Voltage Dividers")
create_voltage_divider(sch, "3V3", "10k", "5k", (100, 100))
create_voltage_divider(sch, "1V8", "22k", "10k", (150, 100))
sch.save("dividers.kicad_sch")

Recipe 2: RC Low-Pass Filter

def create_rc_filter(sch, name, r_val, c_val, position):
    """Create an RC low-pass filter.

    Cutoff frequency: f_c = 1 / (2 * Ο€ * R * C)
    """
    x, y = position

    # Resistor
    r = sch.components.add("Device:R", f"R_{name}", r_val, (x, y))

    # Capacitor
    c = sch.components.add("Device:C", f"C_{name}", c_val, (x + 20, y + 10))

    # Connect R output to C input
    sch.add_wire_between_pins(f"R_{name}", "2", f"C_{name}", "1")

    # Input
    sch.add_wire_to_pin((x - 10, y), f"R_{name}", "1")
    sch.add_label("INPUT", (x - 10, y))

    # Output (between R and C)
    sch.add_label("OUTPUT", (x + 10, y))

    # Ground the capacitor
    sch.add_wire_to_pin((x + 20, y + 20), f"C_{name}", "2")
    sch.add_label("GND", (x + 20, y + 25))

    return r, c

# Calculate values for specific cutoff frequency
import math

def rc_values_for_frequency(target_freq_hz):
    """Calculate R and C values for target frequency."""
    # Use standard E12 values
    c_val = 100e-9  # 100nF
    r_val = 1 / (2 * math.pi * target_freq_hz * c_val)
    # Round to nearest E12 value
    return f"{int(r_val)}", f"{int(c_val * 1e9)}nF"

# Create filter for 1kHz cutoff
sch = ksa.create_schematic("RC Filter")
r_val, c_val = rc_values_for_frequency(1000)
create_rc_filter(sch, "1kHz", r_val, c_val, (100, 100))
sch.save("rc_filter_1khz.kicad_sch")

Recipe 3: LED with Current-Limiting Resistor

def create_led_circuit(sch, name, led_color, supply_voltage, led_forward_voltage, current_ma, position):
    """Create LED circuit with calculated current-limiting resistor.

    Args:
        supply_voltage: Supply voltage (e.g., 5.0 for 5V)
        led_forward_voltage: LED forward voltage (e.g., 2.0 for red LED)
        current_ma: Desired current in milliamps (e.g., 20)
    """
    x, y = position

    # Calculate resistor value: R = (V_supply - V_led) / I
    v_drop = supply_voltage - led_forward_voltage
    r_ohms = v_drop / (current_ma / 1000.0)

    # Round to nearest standard value
    r_val = f"{int(r_ohms)}"

    # Add components
    r = sch.components.add("Device:R", f"R_{name}", r_val, (x, y))
    led = sch.components.add("Device:LED", f"D_{name}", led_color, (x, y + 20))

    # Connect
    sch.add_wire_between_pins(f"R_{name}", "2", f"D_{name}", "1")

    # Power connection
    sch.add_wire_to_pin((x, y - 10), f"R_{name}", "1")
    sch.add_label(f"V{int(supply_voltage)}V", (x, y - 10))

    # Ground
    sch.add_wire_to_pin((x, y + 30), f"D_{name}", "2")
    sch.add_label("GND", (x, y + 30))

    print(f"LED {name}: {r_val}Ξ© resistor for {current_ma}mA at {supply_voltage}V")

    return r, led

# Create multiple LED circuits
sch = ksa.create_schematic("LED Indicators")
create_led_circuit(sch, "RED", "RED", 5.0, 2.0, 20, (100, 100))
create_led_circuit(sch, "GREEN", "GREEN", 5.0, 2.2, 20, (150, 100))
create_led_circuit(sch, "BLUE", "BLUE", 5.0, 3.2, 20, (200, 100))
sch.save("led_indicators.kicad_sch")

Component Management

Recipe 4: Find and Update Components

# Load existing schematic
sch = ksa.load_schematic("design.kicad_sch")

# Find all resistors
resistors = sch.components.filter(lib_id="Device:R")
print(f"Found {len(resistors)} resistors")

# Update specific resistors
for r in resistors:
    if r.reference.startswith("R_PULLUP"):
        r.value = "10k"  # Standardize pullups to 10k
        r.set_property("Tolerance", "5%")

# Find components by value
high_value_resistors = [
    r for r in resistors
    if "k" in r.value and float(r.value.replace("k", "")) > 100
]

# Find components in specific area
components_in_area = sch.components.in_area(
    x1=100, y1=100,
    x2=200, y2=200
)

# Find components near a point
components_near_ic = sch.components.near_point(
    point=(150, 150),
    radius=30  # 30mm radius
)

Recipe 5: Bulk Property Updates

# Add manufacturer part numbers to all resistors
resistor_mpns = {
    "10k": "RC0603FR-0710KL",
    "100k": "RC0603FR-07100KL",
    # ...
}

resistors = sch.components.filter(lib_id="Device:R")
for r in resistors:
    if r.value in resistor_mpns:
        r.set_property("MPN", resistor_mpns[r.value])
        r.set_property("Manufacturer", "Yageo")

# Bulk update all capacitors
sch.components.bulk_update(
    criteria={'lib_id': 'Device:C'},
    updates={
        'properties': {
            'Voltage': '50V',
            'Tolerance': '10%'
        }
    }
)

# Update all SMD footprints to 0805
for comp in sch.components:
    if "0603" in (comp.footprint or ""):
        comp.footprint = comp.footprint.replace("0603", "0805")

Recipe 6: Component Validation

def validate_design(sch):
    """Check design for common issues."""
    issues = []

    # Check for missing footprints
    for comp in sch.components:
        if not comp.footprint:
            issues.append(f"{comp.reference}: Missing footprint")

    # Check for missing MPNs (for production)
    for comp in sch.components:
        if "MPN" not in comp.properties:
            issues.append(f"{comp.reference}: Missing MPN property")

    # Check resistor power ratings
    for r in sch.components.filter(lib_id="Device:R"):
        if "Power" not in r.properties:
            issues.append(f"{r.reference}: Missing power rating")

    # Check for duplicate references (shouldn't happen, but check anyway)
    refs = [c.reference for c in sch.components]
    duplicates = [ref for ref in refs if refs.count(ref) > 1]
    if duplicates:
        issues.append(f"Duplicate references: {set(duplicates)}")

    return issues

# Validate and report
issues = validate_design(sch)
if issues:
    print("Design issues found:")
    for issue in issues:
        print(f"  - {issue}")
else:
    print("Design validation passed!")

Wiring and Connectivity

Recipe 7: Auto-Route Between Components

# Connect multiple components in a chain
components = ["R1", "C1", "R2", "C2", "R3"]

for i in range(len(components) - 1):
    current = components[i]
    next_comp = components[i + 1]

    # Connect output of current to input of next
    sch.add_wire_between_pins(current, "2", next_comp, "1")

# Connect multiple components to common rail
resistors = ["R1", "R2", "R3", "R4"]
for r in resistors:
    # Connect all resistor pin 1's to VCC
    sch.add_wire_to_pin((50, 50), r, "1")
    sch.add_label("VCC", (50, 50))

    # Connect all resistor pin 2's to different signal lines
    pin_pos = sch.get_component_pin_position(r, "2")
    if pin_pos:
        sch.add_label(f"SIG_{r}", (pin_pos.x + 10, pin_pos.y))

Recipe 8: Create Multi-Point Wires

# Create L-shaped wire (Manhattan routing)
sch.wires.add(
    points=[
        (100, 100),  # Start
        (100, 120),  # Down 20mm
        (150, 120),  # Right 50mm
        (150, 140)   # Down 20mm to end
    ]
)

# Create connection with junction
junction = sch.junctions.add(position=(125, 120))

# Wires connecting to junction
sch.wires.add(start=(100, 100), end=(125, 120))  # To junction
sch.wires.add(start=(125, 120), end=(150, 120))  # From junction
sch.wires.add(start=(125, 120), end=(125, 150))  # Branch from junction

Recipe 9: Label Management

# Add labels for nets
sch.add_label("VCC", position=(100, 50))
sch.add_label("GND", position=(100, 150))
sch.add_label("SDA", position=(150, 100))
sch.add_label("SCL", position=(150, 120))

# Find all labels with specific text
vcc_labels = sch.labels.find_by_text("VCC")
print(f"Found {len(vcc_labels)} VCC labels")

# Rename a net (update all labels)
old_net = "VIN"
new_net = "V_INPUT"
for label in sch.labels.find_by_text(old_net):
    label.text = new_net

Circuit Analysis

Recipe 10: Generate Bill of Materials

def generate_bom(sch):
    """Generate bill of materials from schematic."""
    bom = {}

    for comp in sch.components:
        # Create BOM key (lib_id + value + footprint)
        key = (comp.lib_id, comp.value, comp.footprint or "N/A")

        if key not in bom:
            bom[key] = {
                'lib_id': comp.lib_id,
                'value': comp.value,
                'footprint': comp.footprint,
                'quantity': 0,
                'references': [],
                'mpn': comp.get_property("MPN", "")
            }

        bom[key]['quantity'] += 1
        bom[key]['references'].append(comp.reference)

    return list(bom.values())

# Generate and print BOM
sch = ksa.load_schematic("product.kicad_sch")
bom = generate_bom(sch)

print("Bill of Materials:")
print(f"{'Qty':<5} {'References':<20} {'Value':<10} {'Part Number'}")
print("-" * 70)
for item in sorted(bom, key=lambda x: x['lib_id']):
    refs = ", ".join(sorted(item['references']))
    print(f"{item['quantity']:<5} {refs:<20} {item['value']:<10} {item['mpn']}")

Recipe 11: Find Unconnected Pins

def find_unconnected_pins(sch):
    """Find component pins that aren't connected to anything."""
    unconnected = []

    for comp in sch.components:
        for pin in comp.pins:
            pin_pos = sch.get_component_pin_position(comp.reference, pin.number)
            if not pin_pos:
                continue

            # Check if any wire connects to this pin
            connected = False
            for wire in sch.wires:
                for point in wire.points:
                    if point.distance_to(pin_pos) < 0.1:  # 0.1mm tolerance
                        connected = True
                        break

            if not connected:
                unconnected.append((comp.reference, pin.number, pin.name))

    return unconnected

# Check for unconnected pins
unconnected = find_unconnected_pins(sch)
if unconnected:
    print("Warning: Unconnected pins found:")
    for ref, pin_num, pin_name in unconnected:
        print(f"  {ref} pin {pin_num} ({pin_name})")

Recipe 12: Component Statistics

def get_component_statistics(sch):
    """Get statistics about components in schematic."""
    stats = {
        'total_components': len(sch.components),
        'by_type': {},
        'by_library': {},
        'total_value': 0
    }

    for comp in sch.components:
        # Count by type
        comp_type = comp.lib_id.split(":")[-1]
        stats['by_type'][comp_type] = stats['by_type'].get(comp_type, 0) + 1

        # Count by library
        lib = comp.lib_id.split(":")[0]
        stats['by_library'][lib] = stats['by_library'].get(lib, 0) + 1

    return stats

# Print statistics
stats = get_component_statistics(sch)
print(f"Total components: {stats['total_components']}")
print("\nBy type:")
for comp_type, count in sorted(stats['by_type'].items()):
    print(f"  {comp_type}: {count}")
print("\nBy library:")
for lib, count in sorted(stats['by_library'].items()):
    print(f"  {lib}: {count}")

Connectivity Analysis

NEW in v0.5.0 - Electrical connectivity patterns.

Recipe 13: Check if Components are Connected

def check_connection(sch, ref1, pin1, ref2, pin2):
    """Check if two component pins are electrically connected."""
    if sch.are_pins_connected(ref1, pin1, ref2, pin2):
        print(f"{ref1}.{pin1} is connected to {ref2}.{pin2}")

        # Get the net they're on
        net = sch.get_net_for_pin(ref1, pin1)
        if net:
            print(f"  Net name: {net.name}")
            print(f"  Total pins on net: {len(net.pins)}")
    else:
        print(f"{ref1}.{pin1} is NOT connected to {ref2}.{pin2}")

# Example usage
check_connection(sch, "R1", "2", "R2", "1")

Recipe 14: Find All Components on the Same Net

def get_components_on_net(sch, component_ref, pin_number):
    """Get all components connected to a specific pin."""
    net = sch.get_net_for_pin(component_ref, pin_number)
    if not net:
        return []

    # Get unique component references (excluding power symbols)
    components = set()
    for pin in net.pins:
        if not pin.reference.startswith("#PWR"):
            components.add(pin.reference)

    return sorted(components)

# Find all components on VCC net
vcc_components = get_components_on_net(sch, "U1", "VCC")
print(f"Components powered by VCC: {', '.join(vcc_components)}")

Recipe 15: Trace Power Rail Distribution

def trace_power_rail(sch, power_net_name):
    """Trace all components connected to a power rail."""
    power_consumers = []

    for component in sch.components:
        # Skip power symbols themselves
        if component.reference.startswith("#PWR"):
            continue

        # Check each pin
        for pin_num, pin_pos in sch.list_component_pins(component.reference):
            net = sch.get_net_for_pin(component.reference, pin_num)
            if net and net.name == power_net_name:
                power_consumers.append({
                    'component': component.reference,
                    'pin': pin_num,
                    'value': component.value
                })

    return power_consumers

# Trace VCC distribution
vcc_consumers = trace_power_rail(sch, "VCC")
print(f"Components using VCC:")
for consumer in vcc_consumers:
    print(f"  {consumer['component']}.{consumer['pin']} ({consumer['value']})")

Recipe 16: Validate Circuit Connectivity

def validate_circuit_connectivity(sch, required_connections):
    """Validate that required connections exist."""
    errors = []

    for conn in required_connections:
        ref1, pin1, ref2, pin2 = conn
        if not sch.are_pins_connected(ref1, pin1, ref2, pin2):
            errors.append(f"Missing connection: {ref1}.{pin1} β†’ {ref2}.{pin2}")

    return errors

# Define required connections
required = [
    ("R1", "2", "LED1", "1"),
    ("LED1", "2", "GND", "1"),
    ("U1", "VCC", "C1", "1"),
]

errors = validate_circuit_connectivity(sch, required)
if errors:
    print("Connectivity errors found:")
    for error in errors:
        print(f"  ❌ {error}")
else:
    print("βœ… All required connections verified!")

Recipe 17: Find Floating Nets

def find_floating_nets(sch):
    """Find nets with only one connection (potential errors)."""
    floating = []

    # Build set of all nets
    all_nets = set()
    for component in sch.components:
        for pin_num, pin_pos in sch.list_component_pins(component.reference):
            net = sch.get_net_for_pin(component.reference, pin_num)
            if net:
                all_nets.add(net.name)

    # Check each net
    for net_name in all_nets:
        # Find first pin on this net
        for component in sch.components:
            for pin_num, pin_pos in sch.list_component_pins(component.reference):
                net = sch.get_net_for_pin(component.reference, pin_num)
                if net and net.name == net_name:
                    if len(net.pins) == 1:
                        floating.append({
                            'net': net_name,
                            'pin': f"{component.reference}.{pin_num}"
                        })
                    break
            if floating and floating[-1]['net'] == net_name:
                break

    return floating

# Find potential errors
floating_nets = find_floating_nets(sch)
if floating_nets:
    print("⚠️  Floating nets found:")
    for item in floating_nets:
        print(f"  {item['net']}: Only connected to {item['pin']}")

Hierarchy Management

NEW in v0.5.0 - Multi-sheet schematic patterns.

Recipe 18: Validate Hierarchical Design

from pathlib import Path

def validate_hierarchy(sch, schematic_path):
    """Validate all hierarchical sheet connections."""
    # Build hierarchy tree
    tree = sch.hierarchy.build_hierarchy_tree(sch, Path(schematic_path))

    # Check for reused sheets
    reused = sch.hierarchy.find_reused_sheets()
    if reused:
        print(f"βœ… Found {len(reused)} reused sheets:")
        for filename, instances in reused.items():
            print(f"  {filename}: used {len(instances)} times")

    # Validate sheet pins
    connections = sch.hierarchy.validate_sheet_pins()
    errors = sch.hierarchy.get_validation_errors()

    if errors:
        print("\n❌ Validation errors:")
        for error in errors:
            print(f"  {error['pin_name']}: {error['error']}")
        return False
    else:
        print("\nβœ… All sheet connections valid!")
        return True

# Validate design
validate_hierarchy(sch, "my_project.kicad_sch")

Recipe 19: Flatten Hierarchical Design for Analysis

def flatten_and_analyze(sch, schematic_path):
    """Flatten hierarchy and analyze full design."""
    # Build tree first
    tree = sch.hierarchy.build_hierarchy_tree(sch, Path(schematic_path))

    # Flatten with prefixed references
    flattened = sch.hierarchy.flatten_hierarchy(prefix_references=True)

    print(f"Flattened design statistics:")
    print(f"  Total components: {len(flattened['components'])}")
    print(f"  Total wires: {len(flattened['wires'])}")
    print(f"  Total labels: {len(flattened['labels'])}")

    # Show hierarchy map
    print(f"\nComponent locations:")
    for ref, path in flattened['hierarchy_map'].items():
        sheet_name = "Root" if path == "/" else path.split("/")[-2]
        print(f"  {ref}: {sheet_name}")

    return flattened

# Flatten and analyze
flattened = flatten_and_analyze(sch, "my_project.kicad_sch")

Recipe 20: Trace Signal Through Hierarchy

def trace_signal_through_sheets(sch, signal_name, schematic_path):
    """Trace a signal's path through hierarchical sheets."""
    # Build tree
    tree = sch.hierarchy.build_hierarchy_tree(sch, Path(schematic_path))

    # Trace signal
    paths = sch.hierarchy.trace_signal_path(signal_name)

    print(f"Signal '{signal_name}' trace:")
    for i, path in enumerate(paths, 1):
        print(f"\n  Path {i}:")
        print(f"    Start: {path.start_path}")
        print(f"    End: {path.end_path}")
        print(f"    Sheet crossings: {path.sheet_crossings}")

        if path.connections:
            print(f"    Connection points:")
            for conn in path.connections:
                print(f"      - {conn}")

    return paths

# Trace VCC through design
paths = trace_signal_through_sheets(sch, "VCC", "my_project.kicad_sch")

Recipe 21: Visualize Hierarchy with Stats

def show_hierarchy_statistics(sch, schematic_path):
    """Show hierarchy tree with component counts."""
    # Build tree
    tree = sch.hierarchy.build_hierarchy_tree(sch, Path(schematic_path))

    # Get visualization
    viz = sch.hierarchy.visualize_hierarchy(include_stats=True)
    print(viz)

    # Additional statistics
    def count_tree_nodes(node):
        """Recursively count nodes."""
        count = 1
        for child in node.children:
            count += count_tree_nodes(child)
        return count

    total_sheets = count_tree_nodes(tree)
    reused = sch.hierarchy.find_reused_sheets()

    print(f"\nHierarchy Statistics:")
    print(f"  Total sheets: {total_sheets}")
    print(f"  Reused sheets: {len(reused)}")
    print(f"  Max depth: {max(node.get_depth() for node in get_all_nodes(tree))}")

    return tree

def get_all_nodes(node):
    """Get all nodes in tree."""
    nodes = [node]
    for child in node.children:
        nodes.extend(get_all_nodes(child))
    return nodes

# Show hierarchy
tree = show_hierarchy_statistics(sch, "my_project.kicad_sch")

Recipe 22: Generate Hierarchy Report

def generate_hierarchy_report(sch, schematic_path, output_file):
    """Generate detailed hierarchy report."""
    tree = sch.hierarchy.build_hierarchy_tree(sch, Path(schematic_path))

    with open(output_file, 'w') as f:
        f.write("# Hierarchical Design Report\n\n")

        # Tree visualization
        f.write("## Hierarchy Tree\n\n")
        f.write("```\n")
        f.write(sch.hierarchy.visualize_hierarchy(include_stats=True))
        f.write("\n```\n\n")

        # Reused sheets
        reused = sch.hierarchy.find_reused_sheets()
        if reused:
            f.write("## Reused Sheets\n\n")
            for filename, instances in reused.items():
                f.write(f"### {filename} ({len(instances)} instances)\n\n")
                for inst in instances:
                    f.write(f"- Path: `{inst.path}`\n")
                    f.write(f"  Pins: {len(inst.sheet_pins)}\n")
                f.write("\n")

        # Validation errors
        errors = sch.hierarchy.get_validation_errors()
        if errors:
            f.write("## Validation Errors\n\n")
            for error in errors:
                f.write(f"- **{error['pin_name']}**: {error['error']}\n")
        else:
            f.write("## Validation\n\nβœ… All connections validated successfully.\n")

    print(f"Report generated: {output_file}")

# Generate report
generate_hierarchy_report(sch, "my_project.kicad_sch", "hierarchy_report.md")

Batch Operations

Recipe 13: Generate Test Circuits

def generate_pin_test_circuit(ic_part, pin_number):
    """Generate test circuit for a specific IC pin."""
    sch = ksa.create_schematic(f"Test_{ic_part}_Pin{pin_number}")

    # Add IC
    ic = sch.components.add(
        f"IC:{ic_part}",
        "U1",
        ic_part,
        (100, 100)
    )

    # Add test resistor to the pin
    test_r = sch.components.add(
        "Device:R",
        f"R_TEST_{pin_number}",
        "10k",
        (150, 100 + pin_number * 10)
    )

    # Connect
    sch.add_wire_between_pins("U1", str(pin_number), f"R_TEST_{pin_number}", "1")

    # Add measurement point
    sch.add_label(f"TEST_PIN_{pin_number}", (160, 100 + pin_number * 10))

    return sch

# Generate test circuits for all 64 pins
ic_part = "STM32F103"
for pin in range(1, 65):
    sch = generate_pin_test_circuit(ic_part, pin)
    sch.save(f"test_{ic_part}_pin{pin:02d}.kicad_sch")

Recipe 14: Parameter Sweep

def generate_filter_sweep():
    """Generate filters with different cutoff frequencies."""
    frequencies = [100, 500, 1000, 5000, 10000]  # Hz

    for freq in frequencies:
        sch = ksa.create_schematic(f"Filter_{freq}Hz")

        # Calculate R and C for this frequency
        C = 100e-9  # Fixed 100nF
        R = 1 / (2 * 3.14159 * freq * C)

        create_rc_filter(sch, f"{freq}Hz", f"{int(R)}", "100nF", (100, 100))

        sch.save(f"filter_{freq}hz.kicad_sch")
        print(f"Generated filter for {freq}Hz")

generate_filter_sweep()

Recipe 15: Design Variants

def create_design_variant(base_sch_path, variant_name, modifications):
    """Create a design variant with specific modifications."""
    # Load base design
    sch = ksa.load_schematic(base_sch_path)

    # Apply modifications
    for mod in modifications:
        if mod['type'] == 'change_value':
            comp = sch.components.get(mod['reference'])
            if comp:
                comp.value = mod['new_value']

        elif mod['type'] == 'add_component':
            sch.components.add(
                mod['lib_id'],
                mod['reference'],
                mod['value'],
                mod['position']
            )

        elif mod['type'] == 'remove_component':
            sch.components.remove(mod['reference'])

    # Save variant
    sch.save(f"{variant_name}.kicad_sch")

# Create variants
modifications_v2 = [
    {'type': 'change_value', 'reference': 'R1', 'new_value': '22k'},
    {'type': 'add_component', 'lib_id': 'Device:C', 'reference': 'C10',
     'value': '10uF', 'position': (150, 150)}
]

create_design_variant("base_design.kicad_sch", "design_v2", modifications_v2)

Advanced Patterns

Recipe 16: Template System

class CircuitTemplate:
    """Base class for circuit templates."""

    def generate(self, sch, position, **params):
        """Generate circuit at position with parameters."""
        raise NotImplementedError

class VoltageRegulatorTemplate(CircuitTemplate):
    """Linear voltage regulator template."""

    def generate(self, sch, position, v_in, v_out, current_ma):
        x, y = position

        # Add regulator IC
        reg = sch.components.add(
            "Regulator_Linear:LM317_TO220",
            f"U_REG_{v_out}V",
            "LM317",
            (x, y)
        )

        # Calculate resistor values
        r1_val = "240"  # Standard value
        r2_val = str(int((v_out - 1.25) * 240 / 1.25))

        # Add resistors
        r1 = sch.components.add("Device:R", f"R1_{v_out}V", r1_val, (x + 20, y))
        r2 = sch.components.add("Device:R", f"R2_{v_out}V", r2_val, (x + 20, y + 10))

        # Add capacitors
        c_in = sch.components.add("Device:C", f"C_IN_{v_out}V", "100nF", (x - 10, y))
        c_out = sch.components.add("Device:C", f"C_OUT_{v_out}V", "10uF", (x + 30, y))

        # Wire it up...
        return reg

# Use templates
templates = {
    'regulator': VoltageRegulatorTemplate(),
}

sch = ksa.create_schematic("Power Supply")
templates['regulator'].generate(sch, (100, 100), v_in=12, v_out=5, current_ma=500)
sch.save("power_supply.kicad_sch")

Recipe 17: Configuration-Driven Generation

import json

def generate_from_config(config_path):
    """Generate schematic from JSON configuration."""
    with open(config_path) as f:
        config = json.load(f)

    sch = ksa.create_schematic(config['name'])

    # Process components
    for comp_cfg in config['components']:
        sch.components.add(
            comp_cfg['lib_id'],
            comp_cfg['reference'],
            comp_cfg['value'],
            tuple(comp_cfg['position'])
        )

        # Set properties
        comp = sch.components.get(comp_cfg['reference'])
        for key, value in comp_cfg.get('properties', {}).items():
            comp.set_property(key, value)

    # Process connections
    for conn in config['connections']:
        if conn['type'] == 'pin_to_pin':
            sch.add_wire_between_pins(
                conn['from_component'],
                conn['from_pin'],
                conn['to_component'],
                conn['to_pin']
            )

    sch.save(config['output_file'])

# Example config.json:
# {
#   "name": "LED Circuit",
#   "components": [
#     {
#       "lib_id": "Device:R",
#       "reference": "R1",
#       "value": "330",
#       "position": [100, 100]
#     }
#   ],
#   "connections": [
#     {
#       "type": "pin_to_pin",
#       "from_component": "R1",
#       "from_pin": "2",
#       "to_component": "D1",
#       "to_pin": "1"
#     }
#   ]
# }

generate_from_config("circuit_config.json")

More Examples

Check the examples/ directory for complete, working examples:

  • basic_usage.py - Simple circuits

  • advanced_usage.py - Complex operations

  • pin_to_pin_wiring_demo.py - Wiring examples

Contributing Recipes

Have a useful recipe? Please contribute!

  1. Fork the repository

  2. Add your recipe to this file

  3. Include a working example

  4. Submit a pull request


Happy circuit generation! πŸš€