""" Crystal oscillator with load capacitors pattern. Places a crystal at a specified position with load capacitors on each side, ground symbols on the caps, and optional wires to IC pins. """ import logging from typing import Any from mckicad.patterns._geometry import add_power_symbol_to_pin, snap_to_grid logger = logging.getLogger(__name__) def place_crystal_with_caps( sch: Any, xtal_value: str, cap_value: str, x: float, y: float, ic_ref: str | None = None, ic_xin_pin: str | None = None, ic_xout_pin: str | None = None, ground_net: str = "GND", xtal_lib_id: str = "Device:Crystal", cap_lib_id: str = "Device:C", ) -> dict[str, Any]: """Place a crystal oscillator with load capacitors. Creates the classic crystal + 2x load cap circuit: - Crystal at center - Load cap on each side (XIN and XOUT) - GND on each cap's second pin - Optional wires to IC oscillator pins Does NOT call ``sch.save()`` — caller manages persistence. Args: sch: A kicad-sch-api SchematicDocument. xtal_value: Crystal frequency string (e.g. "16MHz", "32.768kHz"). cap_value: Load capacitor value (e.g. "22pF", "15pF"). x: Center X position for the crystal. y: Center Y position for the crystal. ic_ref: Optional IC reference for wiring (e.g. "U1"). ic_xin_pin: XIN pin number on the IC. ic_xout_pin: XOUT pin number on the IC. ground_net: Ground rail name. xtal_lib_id: Crystal symbol library ID. cap_lib_id: Capacitor symbol library ID. Returns: Dictionary with crystal and cap references, wire IDs, and power symbol details. """ cap_offset = 10.16 # Horizontal offset for load caps from crystal # Auto-generate references max_y_num = 0 max_c_num = 0 for comp in sch.components: ref = getattr(comp, "reference", "") if ref.startswith("Y") and ref[1:].isdigit(): max_y_num = max(max_y_num, int(ref[1:])) elif ref.startswith("C") and ref[1:].isdigit(): max_c_num = max(max_c_num, int(ref[1:])) xtal_ref = f"Y{max_y_num + 1}" cap_xin_ref = f"C{max_c_num + 1}" cap_xout_ref = f"C{max_c_num + 2}" # Place crystal at center xtal_x = snap_to_grid(x) xtal_y = snap_to_grid(y) sch.components.add( lib_id=xtal_lib_id, reference=xtal_ref, value=xtal_value, position=(xtal_x, xtal_y), ) # Place load caps on each side cap_xin_x = snap_to_grid(x - cap_offset) cap_xout_x = snap_to_grid(x + cap_offset) cap_y = snap_to_grid(y + 7.62) # Below the crystal sch.components.add( lib_id=cap_lib_id, reference=cap_xin_ref, value=cap_value, position=(cap_xin_x, cap_y), ) sch.components.add( lib_id=cap_lib_id, reference=cap_xout_ref, value=cap_value, position=(cap_xout_x, cap_y), ) # GND on each cap's pin 2 gnd_symbols: list[dict[str, Any]] = [] for cap_ref in (cap_xin_ref, cap_xout_ref): pin2_pos = sch.get_component_pin_position(cap_ref, "2") if pin2_pos: gnd_result = add_power_symbol_to_pin( sch=sch, pin_position=(pin2_pos.x, pin2_pos.y), net=ground_net, ) gnd_symbols.append(gnd_result) # Wire crystal pins to cap pins internal_wires: list[str] = [] # Crystal pin 1 -> XIN cap pin 1 xtal_pin1 = sch.get_component_pin_position(xtal_ref, "1") cap_xin_pin1 = sch.get_component_pin_position(cap_xin_ref, "1") if xtal_pin1 and cap_xin_pin1: wid = sch.add_wire( start=(xtal_pin1.x, xtal_pin1.y), end=(cap_xin_pin1.x, cap_xin_pin1.y), ) internal_wires.append(str(wid)) # Crystal pin 2 -> XOUT cap pin 1 xtal_pin2 = sch.get_component_pin_position(xtal_ref, "2") cap_xout_pin1 = sch.get_component_pin_position(cap_xout_ref, "1") if xtal_pin2 and cap_xout_pin1: wid = sch.add_wire( start=(xtal_pin2.x, xtal_pin2.y), end=(cap_xout_pin1.x, cap_xout_pin1.y), ) internal_wires.append(str(wid)) # Optional wires to IC pins ic_wires: list[str] = [] if ic_ref and ic_xin_pin: ic_xin_pos = sch.get_component_pin_position(ic_ref, ic_xin_pin) if ic_xin_pos and xtal_pin1: wid = sch.add_wire( start=(xtal_pin1.x, xtal_pin1.y), end=(ic_xin_pos.x, ic_xin_pos.y), ) ic_wires.append(str(wid)) if ic_ref and ic_xout_pin: ic_xout_pos = sch.get_component_pin_position(ic_ref, ic_xout_pin) if ic_xout_pos and xtal_pin2: wid = sch.add_wire( start=(xtal_pin2.x, xtal_pin2.y), end=(ic_xout_pos.x, ic_xout_pos.y), ) ic_wires.append(str(wid)) logger.info( "Placed crystal %s (%s) with caps %s, %s (%s) at (%.1f, %.1f)", xtal_ref, xtal_value, cap_xin_ref, cap_xout_ref, cap_value, x, y, ) return { "crystal_ref": xtal_ref, "xtal_value": xtal_value, "cap_xin_ref": cap_xin_ref, "cap_xout_ref": cap_xout_ref, "cap_value": cap_value, "gnd_symbols": gnd_symbols, "internal_wires": internal_wires, "ic_wires": ic_wires, "ground_net": ground_net, }