#!/usr/bin/env python3 """ Baby Mobile — Combined Panel v4 PANEL LAYOUT: ┌───────────────────────────────────────────────┐ │ CARRIER BOARD (44×26mm) │ │ ┌───────USB-C──┐ │ │ │ █████████████ │ │ │ │ │ [U2 IS25LP128F] [C3][R2] │ │ │ XIAO BLE │ ●1 │ │ │ nRF52840 │ [R1][C5] │ │ │ │ │ │ │ D0 D10│ [U1 TPS61220] │ │ │ D1 D9│ ●1 [L1][C1][C2] │ │ │ D2 D8│ │ │ │ D3 D7│ ┌────────────────┐ │ │ │ D4 3V3│ │ J1 FPC-20 0.5mm│ │ │ │ D5 GND│ └────────────────┘ │ │ │ D6 5V│ │ │ └───────────────┘ (M2) (M2) │ ├ ─ ─ ─ ─ ─ SNAP ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │ ┌──────┐ ┃ ┌──────────┐ ┃ ┌──────────┐ │ │ │SOP-8 │ ┃ │SOP-16 N │ ┃ │SOP-16 W │ │ │ C│ │C┃C│ │C┃C│ │C │ │ A│[FPC] │A┃A│ [FPC] │A┃A│ [FPC] │A │ │ S│ │S┃S│ │S┃S│ │S │ │ T│ │T┃T│ │T┃T│ │T │ │ └──────┘ ┃ └──────────┘ ┃ └──────────┘ │ └───────────┸──────────────┸─────────────────────┘ C=castellated edges ┃=v-score snap lines Each SOP adapter: - Long edges (top/bottom): castellated half-holes = SOP pads - Short edges: v-score snap lines - Center top: FPC 0.5mm connector (20-pin for SOP-16, 10-pin for SOP-8) - All SOP pins netted to corresponding FPC pins """ import uuid, os, json def uid(): return str(uuid.uuid4()) # ============================================================ # KiCad drawing primitives # ============================================================ def gr_line(x1, y1, x2, y2, layer="Edge.Cuts", w=0.1): return f' (gr_line (start {x1:.3f} {y1:.3f}) (end {x2:.3f} {y2:.3f}) (layer "{layer}") (width {w}) (uuid "{uid()}"))' def gr_rect(x1, y1, x2, y2, layer="F.SilkS", w=0.15): return f' (gr_rect (start {x1:.3f} {y1:.3f}) (end {x2:.3f} {y2:.3f}) (layer "{layer}") (width {w}) (uuid "{uid()}"))' def gr_text(s, x, y, sz=0.8, layer="F.SilkS", mirror=False): th = max(0.08, sz * 0.13) mir = " (justify mirror)" if mirror else "" return f' (gr_text "{s}" (at {x:.3f} {y:.3f}) (layer "{layer}") (uuid "{uid()}") (effects (font (size {sz} {sz}) (thickness {th:.3f})){mir}))' def gr_circle(x, y, r=0.3, layer="F.SilkS", fill="solid"): return f' (gr_circle (center {x:.3f} {y:.3f}) (end {x+r:.3f} {y:.3f}) (layer "{layer}") (width 0.1) (fill {fill}) (uuid "{uid()}"))' def fp_hdr(name, x, y, layer="F.Cu"): return f""" (footprint "panel:{name}" (layer "{layer}") (at {x:.3f} {y:.3f}) (uuid "{uid()}") (fp_text reference "{name}" (at 0 0) (layer "{layer[0]}.Fab") (uuid "{uid()}") (effects (font (size 0.5 0.5) (thickness 0.08)) hide)) (fp_text value "" (at 0 0) (layer "{layer[0]}.Fab") (uuid "{uid()}") (effects (font (size 0.5 0.5) (thickness 0.08)) hide))""" def pad_smd(name, x, y, w, h, net, nname, layer="F.Cu"): layers = f'"{layer}" "{layer[0]}.Paste" "{layer[0]}.Mask"' return f' (pad "{name}" smd rect (at {x:.3f} {y:.3f}) (size {w:.3f} {h:.3f}) (layers {layers}) (net {net} "{nname}") (uuid "{uid()}"))' def pad_thru(name, x, y, pd, dd, net, nname): return f' (pad "{name}" thru_hole circle (at {x:.3f} {y:.3f}) (size {pd} {pd}) (drill {dd}) (layers "*.Cu" "*.Mask") (net {net} "{nname}") (uuid "{uid()}"))' def pad_cast(name, x, y, net, nname): """Castellated half-hole on board edge.""" return f' (pad "{name}" thru_hole circle (at {x:.3f} {y:.3f}) (size 1.0 1.0) (drill 0.6) (layers "*.Cu" "*.Mask") (net {net} "{nname}") (uuid "{uid()}"))' # ============================================================ # XIAO nRF52840 Dimensions (from Seeed docs) # ============================================================ # PCB: 21.0mm x 17.5mm # USB-C: centered on one 17.5mm short edge, ~9mm wide, protrudes ~2.5mm # Pin headers: 2x7, 2.54mm pitch, rows 17.78mm apart (center-center) # Pin 1 (D0) at top-left when USB faces left # Reset button on USB side, LED on opposite side XIAO_W = 21.0 XIAO_H = 17.5 XIAO_ROW_SPAN = 17.78 # center to center of pin rows XIAO_USB_W = 8.9 XIAO_USB_H = 3.2 XIAO_USB_PROTRUDE = 1.5 # how far USB sticks past PCB edge # ============================================================ # Main generator # ============================================================ def generate(): # Panel dimensions CARRIER_W = 44.0 CARRIER_H = 26.0 ADAPTER_STRIP_H = 14.0 PANEL_W = 48.0 PANEL_H = CARRIER_H + ADAPTER_STRIP_H + 1.0 # 1mm v-score gap ox, oy = 100.0, 100.0 # KiCad origin pcb = [] # ---- File header ---- pcb.append(f"""(kicad_pcb (version 20221018) (generator "panel_v4") (general (thickness 1.6)) (paper "A4") (title_block (title "Baby Mobile Combined Panel v4") (date "2026-03-11") (rev "4.0") (comment 1 "Carrier: XIAO nRF52840 + IS25LP128F + TPS61220 + FPC-20") (comment 2 "Adapters: SOP-8, SOP-16N, SOP-16W — castellated, FPC connected") (comment 3 "Order: JLCPCB, 2-layer, castellated holes=yes, v-score=yes") ) (layers (0 "F.Cu" signal) (31 "B.Cu" signal) (34 "B.Paste" user) (35 "F.Paste" user) (36 "B.SilkS" user "B.Silkscreen") (37 "F.SilkS" user "F.Silkscreen") (38 "B.Mask" user "B.Mask") (39 "F.Mask" user "F.Mask") (44 "Edge.Cuts" user) (48 "B.Fab" user "B.Fab") (49 "F.Fab" user "F.Fab") ) (setup (pad_to_mask_clearance 0.05) (pcbplotparams (layerselection 0x00010fc_ffffffff) (outputdirectory "gerbers/")) ) (net 0 "") (net 1 "GND") (net 2 "VBAT") (net 3 "3V3") (net 4 "SPI_MOSI") (net 5 "SPI_MISO") (net 6 "SPI_SCK") (net 7 "FLASH_CS") (net 8 "BOOST_SW") (net 9 "AUDIO_FILT") """) # 16 signal nets for SOP pins / FPC for i in range(1, 17): pcb.append(f' (net {i+9} "S{i}")') # Total nets: 0-25 # ================================================================ # PANEL OUTLINE # ================================================================ # Outer rectangle pcb.append(gr_line(ox, oy, ox+PANEL_W, oy)) pcb.append(gr_line(ox+PANEL_W, oy, ox+PANEL_W, oy+PANEL_H)) pcb.append(gr_line(ox+PANEL_W, oy+PANEL_H, ox, oy+PANEL_H)) pcb.append(gr_line(ox, oy+PANEL_H, ox, oy)) # V-score: carrier / adapter strip vs_y = oy + CARRIER_H + 0.5 pcb.append(gr_line(ox, vs_y, ox+PANEL_W, vs_y)) pcb.append(gr_text("▼ SNAP ▼", ox + PANEL_W/2, vs_y - 0.3, 0.5)) # V-score: between adapters adapt_base = vs_y + 0.5 # SOP-8: 10mm wide, SOP-16N: 16mm wide, SOP-16W: 16mm wide a8_w = 12.0 a16n_w = 16.0 a16w_w = 16.0 gap = 0.0 # v-score is zero-width vs_x1 = ox + a8_w + 1.0 vs_x2 = vs_x1 + a16n_w pcb.append(gr_line(vs_x1, adapt_base, vs_x1, oy + PANEL_H)) pcb.append(gr_line(vs_x2, adapt_base, vs_x2, oy + PANEL_H)) # ================================================================ # CARRIER: XIAO SOCKET with accurate outline # ================================================================ # Place XIAO with USB-C facing left edge of carrier xiao_cx = ox + 3 + XIAO_W/2 # center of XIAO xiao_cy = oy + CARRIER_H/2 # Board outline rectangle xb_l = xiao_cx - XIAO_W/2 # left edge of XIAO PCB xb_r = xiao_cx + XIAO_W/2 # right edge xb_t = xiao_cy - XIAO_H/2 # top xb_b = xiao_cy + XIAO_H/2 # bottom pcb.append(gr_rect(xb_l, xb_t, xb_r, xb_b, "F.SilkS", 0.2)) # USB-C rectangle protruding from left edge usb_l = xb_l - XIAO_USB_PROTRUDE usb_t = xiao_cy - XIAO_USB_W/2 usb_b = xiao_cy + XIAO_USB_W/2 pcb.append(gr_rect(usb_l, usb_t, xb_l, usb_b, "F.SilkS", 0.2)) # Fill USB area pcb.append(gr_rect(usb_l + 0.3, usb_t + 0.3, xb_l - 0.1, usb_b - 0.3, "F.SilkS", 0.15)) pcb.append(gr_text("USB-C", usb_l + 2, xiao_cy, 0.5)) pcb.append(gr_text("XIAO nRF52840", xiao_cx + 1, xiao_cy, 0.8)) # Reset button indicator (top-left corner of XIAO near USB) pcb.append(gr_text("RST", xb_l + 2, xb_t + 1.5, 0.4)) pcb.append(gr_circle(xb_l + 1, xb_t + 1.5, 0.5, "F.SilkS", "none")) # Pin labels left_labels = ["D0","D1","D2","D3","D4","D5","D6"] right_labels = ["D10","D9","D8","D7","3V3","GND","5V"] # Left row nets: SIG_1..7 → S1..S7 (from toy via FPC) left_nets = [(10,"S1"),(11,"S2"),(12,"S3"),(13,"S4"),(14,"S5"),(15,"S6"),(16,"S7")] # Right row: D10→S8(audio), D9→SCK, D8→MOSI, D7→MISO, 3V3, GND, 5V(NC) right_nets = [(17,"S8"),(6,"SPI_SCK"),(4,"SPI_MOSI"),(5,"SPI_MISO"),(3,"3V3"),(1,"GND"),(0,"")] # Left header pcb.append(fp_hdr("J2_XIAO_L", xiao_cx - XIAO_ROW_SPAN/2, xiao_cy)) for i in range(7): y = -7.62 + i * 2.54 net_id, net_name = left_nets[i] pcb.append(pad_thru(left_labels[i], 0, y, 1.7, 1.0, net_id, net_name)) pcb.append(" )") for i, lbl in enumerate(left_labels): pcb.append(gr_text(lbl, xiao_cx - XIAO_ROW_SPAN/2 - 2.5, xiao_cy - 7.62 + i*2.54, 0.45)) # Right header pcb.append(fp_hdr("J3_XIAO_R", xiao_cx + XIAO_ROW_SPAN/2, xiao_cy)) for i in range(7): y = -7.62 + i * 2.54 net_id, net_name = right_nets[i] if net_id > 0: pcb.append(pad_thru(right_labels[i], 0, y, 1.7, 1.0, net_id, net_name)) else: pcb.append(f' (pad "{right_labels[i]}" thru_hole circle (at 0 {y:.2f}) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask") (uuid "{uid()}"))') pcb.append(" )") for i, lbl in enumerate(right_labels): pcb.append(gr_text(lbl, xiao_cx + XIAO_ROW_SPAN/2 + 2.8, xiao_cy - 7.62 + i*2.54, 0.45)) # ================================================================ # CARRIER: IS25LP128F (SOIC-8) # ================================================================ fl_cx = ox + 32 fl_cy = oy + 5.5 soic_span = 5.4 # SOIC-8 body outline on silk pcb.append(gr_rect(fl_cx - 2.5, fl_cy - soic_span/2 - 0.5, fl_cx + 2.5, fl_cy + soic_span/2 + 0.5)) # Pin 1 dot pcb.append(gr_circle(fl_cx - 1.5, fl_cy + soic_span/2 - 0.5, 0.25)) # Notch on pin 1 end pcb.append(gr_circle(fl_cx, fl_cy + soic_span/2 + 0.5, 0.4, "F.SilkS", "none")) pcb.append(gr_text("U2", fl_cx, fl_cy - soic_span/2 - 1.5, 0.5)) pcb.append(gr_text("IS25LP128F", fl_cx, fl_cy + soic_span/2 + 2, 0.4)) # Pin numbers on silk for i, num in enumerate([1,2,3,4]): pcb.append(gr_text(str(num), fl_cx - 1.905 + i*1.27, fl_cy + soic_span/2 + 1.2, 0.3)) for i, num in enumerate([8,7,6,5]): pcb.append(gr_text(str(num), fl_cx - 1.905 + i*1.27, fl_cy - soic_span/2 - 1.2, 0.3)) flash_pins = [ (1, -1.905, soic_span/2, 7, "FLASH_CS"), (2, -0.635, soic_span/2, 5, "SPI_MISO"), (3, 0.635, soic_span/2, 3, "3V3"), # ~WP tie high (4, 1.905, soic_span/2, 1, "GND"), (5, 1.905, -soic_span/2, 4, "SPI_MOSI"), (6, 0.635, -soic_span/2, 6, "SPI_SCK"), (7, -0.635, -soic_span/2, 3, "3V3"), # ~HOLD tie high (8, -1.905, -soic_span/2, 3, "3V3"), # VCC ] pcb.append(fp_hdr("U2_Flash", fl_cx, fl_cy)) for pin, x, y, net, nn in flash_pins: pcb.append(pad_smd(str(pin), x, y, 0.6, 1.5, net, nn)) pcb.append(" )") # Flash decoupling C3 pcb.append(fp_hdr("C3", fl_cx + 5.5, fl_cy - 1)) pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 3, "3V3")) pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND")) pcb.append(" )") pcb.append(gr_text("C3", fl_cx + 5.5, fl_cy - 2.3, 0.4)) pcb.append(gr_text("100n", fl_cx + 5.5, fl_cy + 0.3, 0.35)) # Flash CS pullup R2 pcb.append(fp_hdr("R2", fl_cx + 5.5, fl_cy + 2)) pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 7, "FLASH_CS")) pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 3, "3V3")) pcb.append(" )") pcb.append(gr_text("R2", fl_cx + 5.5, fl_cy + 0.8, 0.4)) pcb.append(gr_text("10K", fl_cx + 5.5, fl_cy + 3.3, 0.35)) # ================================================================ # CARRIER: Audio RC filter (R1 + C5) # ================================================================ rc_x = ox + 32 rc_y = oy + 14 pcb.append(fp_hdr("R1", rc_x, rc_y)) pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 17, "S8")) # from XIAO D10 pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 9, "AUDIO_FILT")) pcb.append(" )") pcb.append(gr_text("R1 4.7K", rc_x, rc_y - 1.3, 0.4)) pcb.append(fp_hdr("C5", rc_x, rc_y + 3)) pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 9, "AUDIO_FILT")) pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND")) pcb.append(" )") pcb.append(gr_text("C5 10n", rc_x, rc_y + 1.7, 0.4)) # ================================================================ # CARRIER: TPS61220 Boost Converter # ================================================================ bx = ox + 32 by = oy + 21 # SC-70-5 outline pcb.append(gr_rect(bx - 1.2, by - 1.1, bx + 1.2, by + 1.1)) pcb.append(gr_circle(bx - 0.8, by + 0.6, 0.2)) # pin 1 dot pcb.append(gr_text("U1", bx, by - 2, 0.5)) pcb.append(gr_text("TPS61220", bx, by + 2, 0.4)) pcb.append(gr_text("●1", bx - 1.5, by + 0.7, 0.35)) pcb.append(fp_hdr("U1_Boost", bx, by)) pcb.append(pad_smd("1", -0.65, 0.7, 0.4, 0.55, 2, "VBAT")) # VIN pcb.append(pad_smd("2", 0, 0.7, 0.4, 0.55, 1, "GND")) # GND pcb.append(pad_smd("3", 0.65, 0.7, 0.4, 0.55, 2, "VBAT")) # EN=VIN pcb.append(pad_smd("4", 0.65, -0.7, 0.4, 0.55, 3, "3V3")) # VOUT pcb.append(pad_smd("5", -0.65, -0.7, 0.4, 0.55, 8, "BOOST_SW")) # L pcb.append(" )") # L1 (0805) pcb.append(fp_hdr("L1", bx - 4, by)) pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 8, "BOOST_SW")) pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 2, "VBAT")) pcb.append(" )") pcb.append(gr_text("L1 4.7u", bx - 4, by - 1.3, 0.4)) # C1 input (0805) pcb.append(fp_hdr("C1", bx - 4, by + 3)) pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 2, "VBAT")) pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND")) pcb.append(" )") pcb.append(gr_text("C1 10u", bx - 4, by + 1.7, 0.4)) # C2 output (0805) pcb.append(fp_hdr("C2", bx + 4, by)) pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 3, "3V3")) pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND")) pcb.append(" )") pcb.append(gr_text("C2 10u", bx + 4, by - 1.3, 0.4)) # ================================================================ # CARRIER: FPC-20 Connector (0.5mm pitch, bottom-contact) # ================================================================ fpc_cx = ox + 38 fpc_cy = oy + 14 fpc_pitch = 0.5 n_fpc = 20 # FPC connector outline fpc_w = (n_fpc - 1) * fpc_pitch + 2.0 pcb.append(gr_rect(fpc_cx - fpc_w/2, fpc_cy - 2, fpc_cx + fpc_w/2, fpc_cy + 2)) pcb.append(gr_text("J1 FPC-20", fpc_cx, fpc_cy - 3, 0.5)) pcb.append(gr_text("0.5mm TO TOY", fpc_cx, fpc_cy + 3, 0.35)) pcb.append(gr_circle(fpc_cx - (n_fpc-1)*fpc_pitch/2 - 0.8, fpc_cy, 0.2)) # pin 1 # FPC pin assignment: # 1=VBAT 2=GND 3=S1 4=S2 ... 10=S8 11-16=S9-S14(spare) 17-20=spare/GND fpc_nets = [ (1, 2, "VBAT"), (2, 1, "GND"), (3, 10, "S1"), (4, 11, "S2"), (5, 12, "S3"), (6, 13, "S4"), (7, 14, "S5"), (8, 15, "S6"), (9, 16, "S7"), (10, 17, "S8"), (11, 18, "S9"), (12, 19, "S10"), (13, 20, "S11"), (14, 21, "S12"), (15, 22, "S13"), (16, 23, "S14"), (17, 24, "S15"), (18, 25, "S16"), (19, 1, "GND"), (20, 1, "GND"), ] pcb.append(fp_hdr("J1_FPC20", fpc_cx, fpc_cy)) for pin, net, nn in fpc_nets: x = -((n_fpc - 1) * fpc_pitch / 2) + (pin - 1) * fpc_pitch pcb.append(pad_smd(str(pin), x, 0, 0.3, 1.2, net, nn)) # Anchor/lock pads pcb.append(pad_smd("S1", -(fpc_w/2 - 0.5), 2.5, 1.5, 1.0, 0, "")) pcb.append(pad_smd("S2", (fpc_w/2 - 0.5), 2.5, 1.5, 1.0, 0, "")) pcb.append(" )") # ================================================================ # CARRIER: Mounting holes # ================================================================ for mx, my in [(ox + 2, oy + CARRIER_H - 2), (ox + CARRIER_W - 3, oy + CARRIER_H - 2)]: pcb.append(fp_hdr("MH", mx, my)) pcb.append(f' (pad "" thru_hole circle (at 0 0) (size 3.5 3.5) (drill 2.2) (layers "*.Cu" "*.Mask") (uuid "{uid()}"))') pcb.append(" )") # Carrier back silk pcb.append(gr_text("XIAO: D0-D6→SIG1-7 D7=MISO D8=MOSI D9=SCK D10=AUDIO(S8)", ox + CARRIER_W/2, oy + CARRIER_H/2, 0.4, "B.SilkS")) pcb.append(gr_text("FPC: 1=VBAT 2=GND 3-10=S1-S8 19-20=GND", ox + CARRIER_W/2, oy + CARRIER_H/2 + 2, 0.4, "B.SilkS")) # ================================================================ # ADAPTERS: Castellated SOP breakouts with FPC connectors # ================================================================ SOP_PITCH = 1.27 def make_adapter(name, board_cx, board_cy, pins_per_side, row_span, n_fpc_pins): """ Adapter board with: - Castellated half-holes on TOP and BOTTOM long edges (SOP pads) - FPC connector centered on F.Cu - Board edges through SOP pad centers """ total_pins = pins_per_side * 2 row_len = (pins_per_side - 1) * SOP_PITCH bw = row_len + 3.0 # board width (along pin rows) bh = row_span # board height = exactly SOP row spacing # Board outline — edges at castellation centers bl = board_cx - bw/2 br = board_cx + bw/2 bt = board_cy - bh/2 # top edge (pins N+1..2N) bb = board_cy + bh/2 # bottom edge (pins 1..N) # Don't draw left/right edges if they coincide with panel v-score # (they'll be part of the panel outline or v-score already) # Draw top and bottom edges pcb.append(gr_line(bl, bt, br, bt)) pcb.append(gr_line(br, bt, br, bb)) pcb.append(gr_line(br, bb, bl, bb)) pcb.append(gr_line(bl, bb, bl, bt)) # Label pcb.append(gr_text(name, board_cx, board_cy, 0.6)) # Castellated pads footprint pcb.append(fp_hdr(f"{name}_cast", board_cx, board_cy, "B.Cu")) for i in range(pins_per_side): pin = i + 1 x = -row_len/2 + i * SOP_PITCH net_id = pin + 9 # S1=net10, S2=net11, etc. # Bottom edge: pins 1..N pcb.append(pad_cast(str(pin), x, bh/2, net_id, f"S{pin}")) # Top edge: pins 2N..N+1 (reversed, SOP convention) top_pin = total_pins - i top_net = top_pin + 9 pcb.append(pad_cast(str(top_pin), x, -bh/2, top_net, f"S{top_pin}")) pcb.append(" )") # Pin labels on front silk for i in range(pins_per_side): x = board_cx - row_len/2 + i * SOP_PITCH pcb.append(gr_text(str(i+1), x, bb + 0.7, 0.3)) pcb.append(gr_text(str(total_pins - i), x, bt - 0.7, 0.3)) # Pin 1 dot pcb.append(gr_circle(board_cx - row_len/2 - 0.5, bb + 0.5, 0.2)) pcb.append(gr_text("●1", board_cx - row_len/2 - 0.5, bb + 1.3, 0.3)) # Pin labels on back silk (mirror, for soldering reference) pcb.append(gr_text(f"{name} ●1→", board_cx, board_cy, 0.5, "B.SilkS", mirror=True)) # FPC connector centered fpc_pitch_a = 0.5 pcb.append(fp_hdr(f"{name}_FPC", board_cx, board_cy)) for j in range(n_fpc_pins): x = -((n_fpc_pins - 1) * fpc_pitch_a / 2) + j * fpc_pitch_a # Map FPC pins to SOP pin nets if j < total_pins: sop_pin = j + 1 net_id = sop_pin + 9 nn = f"S{sop_pin}" elif j == n_fpc_pins - 2: net_id = 1; nn = "GND" # second-to-last = GND elif j == n_fpc_pins - 1: net_id = 2; nn = "VBAT" # last = VBAT else: net_id = 0; nn = "" pcb.append(pad_smd(f"F{j+1}", x, 0, 0.3, 1.0, net_id, nn)) # Anchor pads aw = (n_fpc_pins - 1) * fpc_pitch_a / 2 + 1.5 pcb.append(pad_smd("SA", -aw, 1.5, 1.2, 0.8, 0, "")) pcb.append(pad_smd("SB", aw, 1.5, 1.2, 0.8, 0, "")) pcb.append(" )") pcb.append(gr_text("FPC", board_cx, board_cy - 1.8, 0.35)) pcb.append(gr_circle(board_cx - (n_fpc_pins-1)*fpc_pitch_a/2 - 0.6, board_cy, 0.15)) # Place adapters in the strip strip_cy = vs_y + 0.5 + ADAPTER_STRIP_H / 2 # SOP-8: 4+4 pins, 3.9mm span, 10-pin FPC (8 signals + GND + VBAT) make_adapter("SOP-8", ox + a8_w/2 + 0.5, strip_cy, 4, 3.9, 10) # SOP-16 narrow: 8+8 pins, 6.0mm span, 20-pin FPC (16 signals + GND + VBAT + 2 spare) make_adapter("SOP-16N", vs_x1 + a16n_w/2, strip_cy, 8, 6.0, 20) # SOP-16 wide: 8+8 pins, 10.3mm span, 20-pin FPC make_adapter("SOP-16W", vs_x2 + a16w_w/2, strip_cy, 8, 10.3, 20) # ================================================================ # Panel title # ================================================================ pcb.append(gr_text("Baby Mobile v4 — JLCPCB Panel", ox + PANEL_W/2, oy - 2, 1.0)) pcb.append(gr_text("Castellated Holes=YES V-Score=YES 2-Layer 1.6mm", ox + PANEL_W/2, oy + PANEL_H + 2, 0.5)) pcb.append(")") return "\n".join(pcb) if __name__ == "__main__": outdir = os.path.dirname(os.path.abspath(__file__)) pcb_text = generate() pcb_path = os.path.join(outdir, "baby-mobile-panel.kicad_pcb") with open(pcb_path, "w") as f: f.write(pcb_text) print(f"Written: {pcb_path} ({len(pcb_text)} bytes)") proj = json.dumps({ "meta": {"filename": "baby-mobile-panel.kicad_pro", "version": 1}, "board": {"design_settings": {"rules": {"min_track_width": 0.15}}}, "net_settings": {"classes": [{"name":"Default","clearance":0.15,"track_width":0.25}]}, }, indent=2) with open(os.path.join(outdir, "baby-mobile-panel.kicad_pro"), "w") as f: f.write(proj) print("\nPanel:") print(" Carrier (44×26mm): XIAO(accurate outline+USB-C), IS25LP128F(pin1 dot+notch), TPS61220(pin1 dot), FPC-20") print(" SOP-8 adapter: castellation on long edges, 10-pin FPC center") print(" SOP-16N adapter: castellation on long edges, 20-pin FPC center") print(" SOP-16W adapter: castellation on long edges, 20-pin FPC center") print(" V-score between carrier and adapter strip, and between each adapter") print(" All polarized parts have ●1 markers")