Files
2026-10-08 14:36:28 +02:00

201 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""Plan d'implantation vu de dessus : côté composants + côté soudures (miroir)."""
import runpy, sys
OUT = sys.argv[1]
sys.argv = ["x", "/dev/null"]
g = runpy.run_path(sys.path[0] + "/gen_fzz.py", run_name="gen")
COLS, ROWS = g["COLS"], g["ROWS"]
NU = g["NU"]
leds, res_anode, res_base, trans = g["leds"], g["res_anode"], g["res_base"], g["trans"]
board_wires, ard_jobs = g["board_wires"], g["ard_jobs"]
PX = 16 # pas d'affichage
MARGE_X, MARGE_Y = 44, 120
PANNEAU = COLS * PX + 2 * MARGE_X
LARG = 2 * PANNEAU + 40
HAUT = MARGE_Y + ROWS * PX + 270
out = []
A = out.append
def xy(c, r, panneau):
if panneau == 1: # côté soudures : plaque retournée gauche/droite
c = COLS - 1 - c
return (panneau * (PANNEAU + 40) + MARGE_X + c * PX + PX / 2, MARGE_Y + r * PX + PX / 2)
def txt(x, y, s, size=11, anchor="middle", fill="#333", weight="normal", rot=None):
tr = f' transform="rotate({rot} {x:.1f} {y:.1f})"' if rot else ""
A(f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" text-anchor="{anchor}" '
f'fill="{fill}" font-weight="{weight}"{tr}>{s}</text>')
A(f'<svg xmlns="http://www.w3.org/2000/svg" width="{LARG}" height="{HAUT}" '
f'viewBox="0 0 {LARG} {HAUT}" font-family="Helvetica, Arial, sans-serif">')
A('<title>Implantation de la matrice 3x3 sur plaque à pastilles</title>')
A(f'<rect width="{LARG}" height="{HAUT}" fill="#ffffff"/>')
for pan, titre, sous in ((0, "Côté composants (vu de dessus)",
"on pose les composants et les fils isolés"),
(1, "Côté soudures (plaque retournée)",
"on soude et on relie avec du fil nu. Attention : image en miroir !")):
x0 = pan * (PANNEAU + 40) + MARGE_X
txt(x0, 34, titre, 18, "start", "#222", "bold")
txt(x0, 54, sous, 13, "start", "#666")
# plaque
A(f'<rect x="{x0 - 8}" y="{MARGE_Y - 8}" width="{COLS * PX + 16}" height="{ROWS * PX + 16}" '
f'rx="6" fill="#e3c58f" stroke="#b8975c"/>')
# numéros de colonnes et de lignes (comptés à partir de 1)
for c in range(COLS):
x, _ = xy(c, 0, pan)
if (c + 1) % 5 == 0 or c == 0:
txt(x, MARGE_Y + ROWS * PX + 22, str(c + 1), 10, fill="#8a6d3b")
for r in range(ROWS):
_, y = xy(0, r, pan)
if (r + 1) % 5 == 0 or r == 0:
txt(x0 - 14, y + 4, str(r + 1), 10, fill="#8a6d3b")
# pastilles
for c in range(COLS):
for r in range(ROWS):
x, y = xy(c, r, pan)
A(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="4.6" fill="#c9a063"/>'
f'<circle cx="{x:.1f}" cy="{y:.1f}" r="1.8" fill="#fff8ec"/>')
# ---------------------------------------------------------------- côté composants
def contour_led(inst, pan, plein):
hs = list(inst["holes"].values())
xs = [xy(c, r, pan)[0] for c, r in hs]
cx, cy = sum(xs) / 4, xy(*hs[0], pan)[1]
if plein:
A(f'<circle cx="{cx:.1f}" cy="{cy:.1f}" r="{PX * 1.0:.1f}" fill="#ffffff" '
f'fill-opacity="0.85" stroke="#555" stroke-width="1.5"/>')
for (c, r), lettre, coul in zip(hs, "RKGB", ("#cc1414", "#222", "#2a9d3a", "#2f6fc4")):
x, y = xy(c, r, pan)
A(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="2.6" fill="{coul}"/>')
txt(x, y - 6, lettre, 9, fill=coul, weight="bold")
else:
A(f'<circle cx="{cx:.1f}" cy="{cy:.1f}" r="{PX * 1.0:.1f}" fill="none" '
f'stroke="#9a7b4a" stroke-dasharray="3 3"/>')
def contour_res(inst, pan, plein, valeur):
(c1, r1), (c2, r2) = inst["holes"].values()
(xa, ya), (xb, yb) = xy(c1, r1, pan), xy(c2, r2, pan)
if plein:
A(f'<line x1="{xa:.1f}" y1="{ya:.1f}" x2="{xb:.1f}" y2="{yb:.1f}" stroke="#8c8c8c" '
f'stroke-width="2"/>')
vertical = abs(xa - xb) < 1
L = 0.55 * (abs(ya - yb) if vertical else abs(xa - xb))
mx, my = (xa + xb) / 2, (ya + yb) / 2
w, h = (9, L) if vertical else (L, 9)
style = ('fill="#e8d3a8" stroke="#a88a52"' if plein
else 'fill="none" stroke="#9a7b4a" stroke-dasharray="3 3"')
A(f'<rect x="{mx - w / 2:.1f}" y="{my - h / 2:.1f}" width="{w:.1f}" height="{h:.1f}" '
f'rx="3" {style}/>')
if plein:
txt(mx + (0 if vertical else 0), my + (3 if not vertical else 3), valeur, 8,
fill="#5a4310", weight="bold", rot=(-90 if vertical else None))
def contour_npn(inst, pan, plein, nom):
hs = inst["holes"]
xc, y = xy(*hs["connector2"], pan)
xe, _ = xy(*hs["connector0"], pan)
cx = (xc + xe) / 2
R = PX * 1.25
# demi-cercle, face plate vers le bas de la plaque
d = f'M{cx - R:.1f} {y + 5:.1f} A{R:.1f} {R:.1f} 0 0 1 {cx + R:.1f} {y + 5:.1f} Z'
if plein:
A(f'<path d="{d}" fill="#2b2b2b" fill-opacity="0.9"/>')
for con, lettre in (("connector2", "C"), ("connector1", "B"), ("connector0", "E")):
x, yy = xy(*hs[con], pan)
A(f'<circle cx="{x:.1f}" cy="{yy:.1f}" r="2.6" fill="#ddd"/>')
txt(x, yy - 6, lettre, 9, fill="#fff", weight="bold")
txt(cx, y - R + 2, nom, 10, fill="#222", weight="bold")
else:
A(f'<path d="{d}" fill="none" stroke="#9a7b4a" stroke-dasharray="3 3"/>')
def composants(pan, plein):
for k, inst in res_anode.items():
contour_res(inst, pan, plein, inst["props"]["resistance"].replace("Ω", ""))
for r, inst in res_base.items():
contour_res(inst, pan, plein, "1k")
for inst in leds.values():
contour_led(inst, pan, plein)
for r, inst in trans.items():
contour_npn(inst, pan, plein, f"T{r}")
# fils isolés (côté composants)
for a, b, coul in board_wires:
if coul == NU:
continue
(xa, ya), (xb, yb) = xy(*a, 0), xy(*b, 0)
A(f'<line x1="{xa:.1f}" y1="{ya:.1f}" x2="{xb:.1f}" y2="{yb:.1f}" stroke="{coul}" '
f'stroke-width="5" stroke-linecap="round"/>')
composants(0, True)
# arrivées des fils de l'Arduino
for pin, (c, r), coul in ard_jobs:
x, y = xy(c, r, 0)
A(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="5.5" fill="none" stroke="{coul}" stroke-width="3"/>')
if r <= 2: # entrées en haut de la plaque : étiquette verticale au-dessus
A(f'<line x1="{x:.1f}" y1="{y - 6:.1f}" x2="{x:.1f}" y2="{MARGE_Y - 26:.1f}" '
f'stroke="{coul}" stroke-width="2"/>')
txt(x + 4, MARGE_Y - 30, pin, 11, "start", coul if coul != "#ffe24d" else "#b39b00",
"bold", rot=-90)
else:
txt(x + 9, y + 4, pin, 11, "start", "#222", "bold")
# ---------------------------------------------------------------- côté soudures
composants(1, False)
for a, b, coul in board_wires:
(xa, ya), (xb, yb) = xy(*a, 1), xy(*b, 1)
if coul == NU:
A(f'<line x1="{xa:.1f}" y1="{ya:.1f}" x2="{xb:.1f}" y2="{yb:.1f}" stroke="#7d7d7d" '
f'stroke-width="4" stroke-linecap="round"/>')
soudures = set()
for inst in list(leds.values()) + list(res_anode.values()) + list(res_base.values()) + list(trans.values()):
soudures.update(inst["holes"].values())
for a, b, coul in board_wires:
soudures.update((a, b))
for _, h, _ in ard_jobs:
soudures.add(h)
for c, r in soudures:
x, y = xy(c, r, 1)
A(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="4.4" fill="#bfbfbf" stroke="#6f6f6f"/>')
for a, b, coul in board_wires: # bouts des fils isolés qui traversent
if coul != NU:
for h in (a, b):
x, y = xy(*h, 1)
A(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="2.2" fill="{coul}"/>')
for pin, h, coul in ard_jobs:
x, y = xy(*h, 1)
A(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="2.2" fill="{coul}"/>')
# ---------------------------------------------------------------- légende
ly = MARGE_Y + ROWS * PX + 56
txt(MARGE_X, ly, "Légende", 15, "start", "#222", "bold")
items = [
('<circle cx="{x}" cy="{y}" r="9" fill="#fff" stroke="#555" stroke-width="1.5"/>',
"LED RGB vue de dessus : pattes R, K (cathode, la plus longue), G, B"),
('<rect x="{xm}" y="{ym}" width="22" height="9" rx="3" fill="#e8d3a8" stroke="#a88a52"/>',
"Résistance (valeur en ohms : 220, 150, 1k = 1000)"),
('<path d="M{xl} {y4} A11 11 0 0 1 {xr} {y4} Z" fill="#2b2b2b"/>',
"Transistor BC337, face plate vers le bas : pattes C, B, E de gauche à droite"),
('<line x1="{xl}" y1="{y}" x2="{xr}" y2="{y}" stroke="#404040" stroke-width="5" stroke-linecap="round"/>',
"Fil isolé, côté composants (cathodes des lignes)"),
('<line x1="{xl}" y1="{y}" x2="{xr}" y2="{y}" stroke="#7d7d7d" stroke-width="4" stroke-linecap="round"/>',
"Fil nu (ou patte repliée), côté soudures"),
('<circle cx="{x}" cy="{y}" r="5.5" fill="none" stroke="#cc1414" stroke-width="3"/>',
"Arrivée d'un fil vers l'Arduino (D2 à D13, GND)"),
]
for i, (forme, texte) in enumerate(items):
col, lig = i % 2, i // 2
bx = MARGE_X + col * (LARG / 2)
by = ly + 30 + lig * 30
A(forme.format(x=bx + 11, y=by - 4, xm=bx, ym=by - 8, xl=bx, xr=bx + 22, y4=by + 2))
txt(bx + 34, by, texte, 12, "start", "#333")
txt(MARGE_X, ly + 140, "Pas entre LED : 6 trous = 15,24 mm. Les numéros de colonnes et de "
"lignes sont ceux du côté composants ; côté soudures, gauche et droite sont inversées.",
12, "start", "#666")
txt(MARGE_X, ly + 160, "Correspondance avec le code : colonne c de la matrice = LED Lr Cc ; "
"ligne r = transistor Tr (broche D11 + r).", 12, "start", "#666")
A("</svg>")
with open(OUT, "w") as f:
f.write("\n".join(out))