main
Rithesh03 Update 4: improved README (V1 overview, images, LED order), Version 2 proposal files b874429 13d ago
"""Create the Version 2 LED footprint: v2/hardware/rithesh_v2_fp.pretty/LED_XL-2020RGBC-2812B.kicad_mod

Drawn from the XINGLIGHT XL-2020RGBC-WS2812B datasheet, page 11 (LCSC C5349955):
- Body 2.0 x 2.0 mm, 0.65 mm tall.
- "Recommended Soldering Pattern": four 0.85 (across) x 0.75 (down) mm pads, 0.5 mm apart across
  and 0.5 mm apart down -> pad centres +/-0.675 x +/-0.625 mm.
- Pins (top view): DI top-left, GND top-right, VDD bottom-left, DO bottom-right.
  Pin table: 1 DO, 2 GND, 3 DI, 4 VDD. The round mark on the top of the LED sits at the DO corner (pin 1);
  the bottom-view triangle points to the same side.

NOTE: the pin order is different from the Version 1 LED (XL-5050: 1 VDD, 2 DO, 3 GND, 4 DI).

Usage: python3 make_v2_footprint.py
"""
import os

V2HW = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LIB = os.path.join(V2HW, "rithesh_v2_fp.pretty")

PAD_W, PAD_H = 0.85, 0.75       # pad size, across x down (datasheet recommended pattern)
GAP_X, GAP_Y = 0.5, 0.5         # gaps between pads
PX = GAP_X / 2 + PAD_W / 2      # 0.675: pad centre, across
PY = GAP_Y / 2 + PAD_H / 2      # 0.625: pad centre, down
BODY = 1.0                      # half of the 2.0 mm body
SILK_W = 0.15                   # JLCPCB minimum silkscreen line width
SILK_Y = PY + PAD_H / 2 + 0.25  # 1.25: silkscreen lines above/below the pads (0.175 mm clear of pads)
SILK_X = PX + PAD_W / 2 + 0.15  # 1.25: line length
CY_X = PX + PAD_W / 2 + 0.25    # courtyard: pads + 0.25 mm -> 1.35
CY_Y = BODY + 0.25              # body + 0.25 mm        -> 1.25

# (number, name, x, y) in KiCad coordinates (y down), top view
PADS = [("1", "DO", PX, PY), ("2", "GND", PX, -PY), ("3", "DI", -PX, -PY), ("4", "VDD", -PX, PY)]


def line(x1, y1, x2, y2, layer, w):
    return (f'  (fp_line (start {x1:.3f} {y1:.3f}) (end {x2:.3f} {y2:.3f}) '
            f'(stroke (width {w}) (type solid)) (layer "{layer}"))\n')


def led_footprint():
    s = ('(footprint "LED_XL-2020RGBC-2812B" (version 20221018) (generator rithesh_gen)\n'
         '  (layer "F.Cu")\n'
         '  (descr "XINGLIGHT XL-2020RGBC-WS2812B addressable RGB LED, 2.0x2.0mm (LCSC C5349955). Manufacturer '
         'recommended soldering pattern (datasheet p.11): 4 pads 0.85x0.75mm, 0.5mm apart across and down '
         '(centres +/-0.675 x +/-0.625mm). Pin 1 DO (bottom-right, marked corner), 2 GND, 3 DI, 4 VDD.")\n'
         '  (tags "LED RGB addressable WS2812 2020 XL-2020RGBC-2812B")\n'
         '  (attr smd)\n'
         '  (fp_text reference "REF**" (at 0 -2.0) (layer "F.SilkS")\n'
         '    (effects (font (size 0.8 0.8) (thickness 0.15))))\n'
         '  (fp_text value "XL-2020RGBC-2812B" (at 0 2.1) (layer "F.Fab")\n'
         '    (effects (font (size 0.5 0.5) (thickness 0.08))))\n'
         '  (fp_text user "${REFERENCE}" (at 0 0) (layer "F.Fab")\n'
         '    (effects (font (size 0.4 0.4) (thickness 0.06))))\n')
    b = BODY
    # Fab: 2.0 x 2.0 body, corner chamfer at pin 1 (DO, bottom-right) and the round top mark
    for (x1, y1, x2, y2) in [(-b, -b, b, -b), (b, -b, b, b - 0.3), (b, b - 0.3, b - 0.3, b),
                             (b - 0.3, b, -b, b), (-b, b, -b, -b)]:
        s += line(x1, y1, x2, y2, "F.Fab", 0.05)
    s += ('  (fp_circle (center 0.5 0.7) (end 0.64 0.7) (stroke (width 0.05) (type solid)) '
          '(fill none) (layer "F.Fab"))\n')
    # Silkscreen: lines above and below the pads, pin-1 dot outside the DO corner
    s += line(-SILK_X, -SILK_Y, SILK_X, -SILK_Y, "F.SilkS", SILK_W)
    s += line(-SILK_X, SILK_Y, SILK_X, SILK_Y, "F.SilkS", SILK_W)
    s += ('  (fp_circle (center 1.6 1.25) (end 1.75 1.25) (stroke (width 0.1) (type solid)) '
          '(fill solid) (layer "F.SilkS"))\n')
    for (x1, y1, x2, y2) in [(-CY_X, -CY_Y, CY_X, -CY_Y), (CY_X, -CY_Y, CY_X, CY_Y),
                             (CY_X, CY_Y, -CY_X, CY_Y), (-CY_X, CY_Y, -CY_X, -CY_Y)]:
        s += line(x1, y1, x2, y2, "F.CrtYd", 0.05)
    for num, name, x, y in PADS:
        s += (f'  (pad "{num}" smd rect (at {x:.3f} {y:.3f}) (size {PAD_W} {PAD_H}) '
              f'(layers "F.Cu" "F.Paste" "F.Mask") (pinfunction "{name}"))\n')
    return s + ")\n"


def main():
    os.makedirs(LIB, exist_ok=True)
    path = os.path.join(LIB, "LED_XL-2020RGBC-2812B.kicad_mod")
    with open(path, "w") as f:
        f.write(led_footprint())
    # self-checks against the datasheet numbers
    xs = sorted({round(x, 3) for _, _, x, _ in PADS})
    ys = sorted({round(y, 3) for _, _, _, y in PADS})
    assert abs((xs[1] - xs[0]) - PAD_W - GAP_X) < 1e-9 and abs((ys[1] - ys[0]) - PAD_H - GAP_Y) < 1e-9
    assert SILK_Y - SILK_W / 2 - (PY + PAD_H / 2) >= 0.15            # silkscreen >= 0.15 mm from pads
    print("wrote", path)
    print(f"pads {PAD_W} x {PAD_H} mm, centres +/-{PX:.3f} x +/-{PY:.3f} mm, "
          f"gaps {GAP_X} / {GAP_Y} mm, pattern {2 * PX + PAD_W:.2f} x {2 * PY + PAD_H:.2f} mm, "
          f"courtyard {2 * CY_X:.2f} x {2 * CY_Y:.2f} mm")
    for num, name, x, y in PADS:
        print(f"  pad {num} {name:3} at ({x:+.3f}, {y:+.3f})")


if __name__ == "__main__":
    main()